<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>随心笔记 Blog</title>
        <link>https://tanzhuo.xyz/</link>
        <description>随心笔记 Blog</description>
        <lastBuildDate>Sun, 16 Aug 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-Hans</language>
        <item>
            <title><![CDATA[AgentMux:远程服务器上 AI 编码 agent 的控制平面]]></title>
            <link>https://tanzhuo.xyz/2026/08/16/agentmux</link>
            <guid>https://tanzhuo.xyz/2026/08/16/agentmux</guid>
            <pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[开源了一个桌面应用 AgentMux,用来管理跑在远程服务器上的 AI 编码 agent:所有服务器、项目、agent 收在一棵树里,会话跑在服务器的 tmux 中,关掉应用只是 detach。Go + Wails 3 + React,MIT 协议。]]></description>
            <content:encoded><![CDATA[<p>开源了 <a href="https://github.com/tan-zhuo/AgentMux" target="_blank" rel="noopener noreferrer" class="">AgentMux</a>:一个桌面应用,用来管理跑在远程服务器上的 AI 编码 agent。Go + Wails 3 + React,MIT 协议。</p>
<!-- -->
<p>我的编码 agent 后来基本都挪到远程服务器上跑了 —— 一个 agent 一跑几十分钟,本地机器要开会、要休眠、要换网络,不太合适。麻烦在于管理:每次都得开终端、SSH、想一下这个项目在哪个目录、把 agent 起起来;机器和项目一多就开始记不住谁在跑什么,而连接一断,前台起的东西也跟着没了。</p>
<p>AgentMux 就是干这个的:所有服务器、项目和 agent 收在同一棵树里,按运行状态着色,谁在跑、跑到哪一步扫一眼就知道。</p>
<p>关键的一点是它启动的东西全在服务器的 tmux 会话里,桌面端不持有任何进程。所以关掉标签页只是 detach,关应用、断网、合盖都不影响 agent 继续干活,回来时终端还在原来的位置,滚动历史也在。</p>
<p>其它大致能做这些:</p>
<ul>
<li class="">真终端,xterm.js 直连 agent 所在的 pane,可以中途接手打字,不用先打断它</li>
<li class="">一条指令发给多个 agent,每个单独回一张送达回执</li>
<li class="">SFTP 文件浏览器,双击文本文件用 Monaco 打开,保存是原子的、保留权限位,被别人改过会提示</li>
<li class="">主机指标:CPU(分项与每核)、内存、磁盘、网卡、温度,有 NVIDIA 卡就把利用率和显存一起读出来</li>
<li class="">安装面板:探测这台机器缺哪些 agent CLI(Claude Code、Codex、Gemini CLI、OpenCode、Aider、Cursor CLI),一键装,装的过程也在 tmux 里</li>
</ul>
<p>密码和私钥口令落盘前用 AES-256-GCM 加密,主密钥放系统钥匙串,前端只知道某项凭据设了没有。它不代理 agent 的模型流量,也不碰 API key。</p>
<p>想试的话,<a href="https://github.com/tan-zhuo/AgentMux/releases" target="_blank" rel="noopener noreferrer" class="">release 页</a>有 macOS、Windows、Linux 的包(没做签名,首次打开要多点一下)。从源码构建需要 Go 1.25+ 和 Node 20+,远端有 <code>tmux</code> 和一个 SSH 账号就行:</p>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">git</span><span class="token plain"> clone https://github.com/tan-zhuo/AgentMux.git</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">cd</span><span class="token plain"> AgentMux</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">cd</span><span class="token plain"> frontend </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&amp;&amp;</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">npm</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&amp;&amp;</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">npm</span><span class="token plain"> run build </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">cd</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">..</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">go build </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-o</span><span class="token plain"> agentmux </span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">./agentmux</span><br></div></code></pre></div></div>
<p>还没做的也不少:原设计里的调度 AI 只搭好了管道,标签页内不能分屏,文件传输一次一个,指标历史只活在打开着的面板里。编辑器是 Monaco 不是 VS Code,跑不了插件,真需要的话在服务器上跑 <code>code-server</code>、让 AgentMux 用 tmux 守着它更合适。</p>
<p>代码在 <a href="https://github.com/tan-zhuo/AgentMux" target="_blank" rel="noopener noreferrer" class="">github.com/tan-zhuo/AgentMux</a>,欢迎试用和提 issue。</p>]]></content:encoded>
            <category>AI</category>
            <category>Go</category>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[Earth:浏览器里的 3D 地球,一路看到可观测宇宙]]></title>
            <link>https://tanzhuo.xyz/2026/08/13/earth</link>
            <guid>https://tanzhuo.xyz/2026/08/13/earth</guid>
            <pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[开源了一个新项目 Earth:类似 Google Earth 的纯前端 3D 地球,点击国家看国旗、人口、GDP、政体,带 GDP 柱状图、贸易航线、时间旅行模式,还能沿宇宙尺度阶梯从地球看到可观测宇宙。]]></description>
            <content:encoded><![CDATA[<p>开源了一个新项目 <a href="https://github.com/tan-zhuo/earth" target="_blank" rel="noopener noreferrer" class="">Earth</a>:类似 Google Earth 的纯前端 3D 地球。拖动旋转、滚轮缩放,点击任何一个国家,飞过去看它的国旗、人口、GDP、政体;顶栏还能沿宇宙尺度阶梯往外走——月球、太阳系、银河系,直到可观测宇宙。在线体验:<a href="https://earth.kim/" target="_blank" rel="noopener noreferrer" class="">earth.kim</a>。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么做这个">为什么做这个<a href="https://tanzhuo.xyz/2026/08/13/earth#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%81%9A%E8%BF%99%E4%B8%AA" class="hash-link" aria-label="为什么做这个的直接链接" title="为什么做这个的直接链接" translate="no">​</a></h2>
<p>想要的东西很简单:打开浏览器就能转的地球,点一个国家就能看到它的现状——不装客户端,不注册 token,不接付费 API。市面上专业的方案(比如 Cesium)对这个需求来说太重了,于是用轻量的方案自己拼了一个。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="里面有什么">里面有什么<a href="https://tanzhuo.xyz/2026/08/13/earth#%E9%87%8C%E9%9D%A2%E6%9C%89%E4%BB%80%E4%B9%88" class="hash-link" aria-label="里面有什么的直接链接" title="里面有什么的直接链接" translate="no">​</a></h2>
<p><strong>国家信息</strong>:点击国家,镜头按国家面积自适应高度平滑飞过去,详情面板给出国旗、双语名称与官方全称、首都、地区、面积、人口、语言、货币、GDP(名义 + 人均)、发展程度和政治体制,还内嵌了维基百科的历史摘要,中英文随语言切换。</p>
<p><strong>数据图层</strong>:GDP 柱状图层把所有国家的最新 GDP 以柱高立在地图上(sqrt 归一化,颜色按世界银行收入分组);国旗图层把国旗按面积贴在各国位置;航线图层画出全球主要海运贸易通道——按枢纽港分段的动画弧线,线宽和颜色表示运量等级。都可以单独开关。</p>
<p><strong>全球排行榜</strong>:GDP、出口、进口(World Bank 实时数据),加上石油、天然气产量的静态快照。点榜单行直接飞到那个国家。</p>
<p><strong>时间旅行模式</strong>:从 7.5 亿年前的罗迪尼亚超大陆到现代,16 个地质时代的古地理 3D 演变,支持时间轴拖动和自动播放(古地理图 © C.R. Scotese PALEOMAP Project),可以看着大陆漂移、拼合、再裂开。</p>
<p><strong>宇宙尺度阶梯</strong>:顶栏在五级尺度间切换,每级配双语资料卡——地球(10⁷ m)、月球(10⁸ m,月面贴图 + 阿波罗/嫦娥/月球号 10 个历史着陆点)、太阳系(10¹³ m,八大行星沿轨道公转)、银河系(10²¹ m,5 万粒子的程序化棒旋星系,标注太阳系位置与人马座 A*)、可观测宇宙(10²⁷ m,星系团与纤维结构的宇宙网点云)。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据从哪来">数据从哪来<a href="https://tanzhuo.xyz/2026/08/13/earth#%E6%95%B0%E6%8D%AE%E4%BB%8E%E5%93%AA%E6%9D%A5" class="hash-link" aria-label="数据从哪来的直接链接" title="数据从哪来的直接链接" translate="no">​</a></h2>
<p>全部免费、无密钥。国家基础信息来自 <a href="https://github.com/mledoze/countries" target="_blank" rel="noopener noreferrer" class="">mledoze/countries</a>(REST Countries 的上游数据集),<strong>构建时</strong>静态化打包,运行时零请求;人口、GDP、进出口走 World Bank Open Data API,点击国家时按需拉取,localStorage 缓存 30 天;国旗来自 flagcdn.com;国家边界用 Natural Earth 110m,随应用自托管;地球贴图是 three-globe 附带的 NASA 贴图;行星与月球贴图来自 Solar System Scope(CC BY 4.0)。</p>
<p>一个小坑:REST Countries v3.1 API 已在 2026 年弃用(新版要注册),所以项目不在运行时依赖它,改为构建时静态化上游数据集,更新数据只需重跑一个脚本。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="技术栈">技术栈<a href="https://tanzhuo.xyz/2026/08/13/earth#%E6%8A%80%E6%9C%AF%E6%A0%88" class="hash-link" aria-label="技术栈的直接链接" title="技术栈的直接链接" translate="no">​</a></h2>
<p>React 18 + TypeScript + Vite,3D 部分用 Three.js(globe.gl 封装)——比 Cesium 轻量数个量级,不需要 Ion token,国家多边形、飞行动画、悬停拾取开箱即用。样式 Tailwind CSS v4,状态 Zustand,国际化 react-i18next(自动检测浏览器语言 + localStorage 记忆)。纯前端,可静态部署,桌面端右侧面板、移动端底部抽屉。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="后续">后续<a href="https://tanzhuo.xyz/2026/08/13/earth#%E5%90%8E%E7%BB%AD" class="hash-link" aria-label="后续的直接链接" title="后续的直接链接" translate="no">​</a></h2>
<p>计划中的还有国家搜索、主要出口商品与资源模块、多国对比和图表可视化。代码在 <a href="https://github.com/tan-zhuo/earth" target="_blank" rel="noopener noreferrer" class="">github.com/tan-zhuo/earth</a>,欢迎试玩和提 issue。</p>]]></content:encoded>
            <category>前端</category>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[AI Room:走进一个正在真实运算的神经网络]]></title>
            <link>https://tanzhuo.xyz/2026/08/06/ai-room</link>
            <guid>https://tanzhuo.xyz/2026/08/06/ai-room</guid>
            <pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[开源了一个新项目 AI Room:在浏览器里搭出的神经网络 3D 空间,MLP、CNN、迷你 Transformer 全部真实训练、真实前向传播,每个节点都能点开对账。]]></description>
            <content:encoded><![CDATA[<p>开源了一个新项目 <a href="https://github.com/tan-zhuo/ai-room" target="_blank" rel="noopener noreferrer" class="">AI Room</a>:把神经网络搬进一个 3D 空间,让你走进去逐节点观察它的运算。在线体验:<a href="https://airoom.run/" target="_blank" rel="noopener noreferrer" class="">airoom.run</a>。</p>
<p>它和常见的"神经网络科普动画"有一个本质区别:<strong>这不是动画,是真实计算</strong>。页面加载时,每个网络都用固定随机种子在浏览器里真实训练;你看到的每一个数值——激活值、注意力权重、softmax 概率——都是前向传播的真实结果。点开任何节点,可以手动核对它的算术。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么做这个">为什么做这个<a href="https://tanzhuo.xyz/2026/08/06/ai-room#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%81%9A%E8%BF%99%E4%B8%AA" class="hash-link" aria-label="为什么做这个的直接链接" title="为什么做这个的直接链接" translate="no">​</a></h2>
<p>看过不少讲神经网络的图和视频,大多停留在"示意"层面:箭头、方块、渐变色,数字是编的,流程是摆拍的。我一直想要一个能"对账"的版本——每个神经元的输入、权重、偏置、激活函数摆在面前,Σ 加出来的数就是屏幕上亮着的数。想验证它没骗你,就自己按计算器。</p>
<p>于是有了 AI Room。纯前端,没有后端,没有运行时网络请求,所有训练和推理都发生在你的浏览器里。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="里面有什么">里面有什么<a href="https://tanzhuo.xyz/2026/08/06/ai-room#%E9%87%8C%E9%9D%A2%E6%9C%89%E4%BB%80%E4%B9%88" class="hash-link" aria-label="里面有什么的直接链接" title="里面有什么的直接链接" translate="no">​</a></h2>
<p><strong>MLP(多层感知机)</strong>:对三类高斯簇做分类。数据流粒子沿着带权重的连接逐层流动,动画顺序就是计算顺序。</p>
<p><strong>CNN(卷积网络)</strong>:识别图案(竖线/横线/对角线/圆环)。感受野滑窗严格按计算顺序在输入上滑动;卷积核有两种模式——手工的 Sobel 风格边缘检测核(可解释,只训练全连接头),或者从随机噪声端到端训练出来的核,反向传播是手写的。还有画板模式:自己在输入网格上画图案,看整个网络一笔一笔地实时分类。S/M/L 三档规模,切换时现场重新训练。</p>
<p><strong>迷你 Transformer(字符级)</strong>:结构完整的 Transformer 块——分词器 → 嵌入 → 正弦位置编码 → 多头因果注意力(2 头,含输出投影)→ 残差 + LayerNorm → 前馈 → 残差 + LayerNorm → 输出 softmax。前向和反向传播(包括 LayerNorm 和残差的梯度)都是手写实现,加载时在一个小语料上训练约 2.5 秒。两个头的 8×8 注意力矩阵逐行点亮;点开 Add &amp; Norm 格子能看到 μ、σ、γ、β 的完整算式。按下「连续生成」就是真正的自回归解码:采样出的字符拼回上下文、窗口滑动、整条流水线重跑,文字一个字一个字流出来——和真实 LLM 的写字方式一致。温度滑块(0.2–1.4)实时控制采样分布。</p>
<p><strong>语言识别(AI 应用)</strong>:输入任意文字,文本被转成 8 个可解释的统计特征(拉丁字母占比、CJK 占比、假名占比等),一个训练好的 MLP 判断它是中文、English 还是日本語。这也是"计算是真的"的一个直接证明:你的输入、它的数字、它的预测,一条链看得清清楚楚。</p>
<p>每个层的标题都可以点开讲解:它在做什么、网络为什么需要它、一个大白话类比——中英日三语,讲解时该层在 3D 里高亮,其余变暗。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="它没有假装自己是-gpt">它没有假装自己是 GPT<a href="https://tanzhuo.xyz/2026/08/06/ai-room#%E5%AE%83%E6%B2%A1%E6%9C%89%E5%81%87%E8%A3%85%E8%87%AA%E5%B7%B1%E6%98%AF-gpt" class="hash-link" aria-label="它没有假装自己是 GPT的直接链接" title="它没有假装自己是 GPT的直接链接" translate="no">​</a></h2>
<p>README 里专门列了一节「与生产级模型的差异」:只有一个 Transformer 块、2 个头、d=12、字符级分词;训练是逐样本的朴素 SGD;CNN 只有一段卷积+池化;语言识别刻意用了手工统计特征而不是现代 embedding。展示的数学是真的,规模不是。我觉得诚实列出简化项,比假装"这就是 ChatGPT 的原理"更有价值。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="技术栈">技术栈<a href="https://tanzhuo.xyz/2026/08/06/ai-room#%E6%8A%80%E6%9C%AF%E6%A0%88" class="hash-link" aria-label="技术栈的直接链接" title="技术栈的直接链接" translate="no">​</a></h2>
<p>Vite + React + TypeScript + React Three Fiber + Drei + Zustand。<code>npm run sanity</code> 会把每个网络在每档规模下训练一遍并验证准确率。</p>
<p>代码在 <a href="https://github.com/tan-zhuo/ai-room" target="_blank" rel="noopener noreferrer" class="">github.com/tan-zhuo/ai-room</a>,欢迎试玩和提 issue。</p>]]></content:encoded>
            <category>AI</category>
            <category>前端</category>
        </item>
        <item>
            <title><![CDATA[从容器云到智能云]]></title>
            <link>https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji</link>
            <guid>https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji</guid>
            <pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[单个智能体已经不够用了。本文提出在 Kubernetes 之上构建专为 AI 设计的控制平面——Kubernetes AI OS 的六层架构与演进路径。]]></description>
            <content:encoded><![CDATA[<p>AI 智能体（Agent）系统发展到今天，单个智能体已经不够用了。我们需要的是<strong>多智能体协作</strong>、<strong>可观测执行轨迹</strong>、<strong>安全副作用治理</strong>，以及在分布式环境下的可靠调度。</p>
<!-- -->
<p>OpenClaw 在 gateway、tools、sessions 和本地多智能体路由上表现出色，但面对大规模协作、跨节点调度和操作者可见性时仍有短板。而类似 golutra 的编排与 trace 能力，又提供了宝贵的补充。</p>
<p>基于 OpenClaw 的 gateway runtime，我提出一个演进架构：<strong>将 Kubernetes 作为坚实底座，在其之上构建一个专为 AI 设计的控制平面</strong>——我们称之为 <strong>Kubernetes AI OS</strong>。它不替代 Kubernetes，而是将其视为基础设施层，新增智能体运行时、执行调度、能力发现、副作用治理和跨节点协同等能力，做一个真正面向操作者的执行系统。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2026-04-Gemini_Generated_Image_xonbpixonbpixonb-c7b4a8c8648cceb82c3292ec7ce19fe8.webp" width="1600" height="873" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么需要这个架构">为什么需要这个架构？<a href="https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E8%BF%99%E4%B8%AA%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="为什么需要这个架构？的直接链接" title="为什么需要这个架构？的直接链接" translate="no">​</a></h2>
<p>OpenClaw 目前在 channels、tools、sessions 和本地多智能体路由上很强，但在大规模协作、执行轨迹审计、跨节点放置上还有明显短板。</p>
<p>我们希望融合：</p>
<ul>
<li class="">OpenClaw 的南向集成和 agent runtime</li>
<li class="">类 golutra 的执行轨迹与可视化能力</li>
<li class="">Kubernetes 的生命周期、调度和故障恢复能力</li>
</ul>
<p>核心理念只有一句话：<strong>执行必须可见、可检查、可归因</strong>。没有清晰的执行账本，多智能体系统很容易变成黑盒。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="设计目标">设计目标<a href="https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#%E8%AE%BE%E8%AE%A1%E7%9B%AE%E6%A0%87" class="hash-link" aria-label="设计目标的直接链接" title="设计目标的直接链接" translate="no">​</a></h2>
<ul>
<li class="">支持单节点内多智能体清晰协作</li>
<li class="">支持跨节点的工作流级调度</li>
<li class="">节点可自动加入、隔离、恢复</li>
<li class="">用小模型辅助角色推荐，但高风险决策必须走策略面</li>
<li class="">所有有意义的动作都要进统一执行账本</li>
<li class="">清晰分离控制面、执行面、可观测面和策略面</li>
</ul>
<p><strong>不做的事</strong>：不替换 Kubernetes 原生调度，不允许智能体无限制自由聊天，不让模型直接决定提权或拓扑变更等高危操作。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六层架构概览">六层架构概览<a href="https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#%E5%85%AD%E5%B1%82%E6%9E%B6%E6%9E%84%E6%A6%82%E8%A7%88" class="hash-link" aria-label="六层架构概览的直接链接" title="六层架构概览的直接链接" translate="no">​</a></h2>
<p>系统分为六层，责任边界清晰：</p>
<ul>
<li class=""><strong>Southbound Gateway Layer</strong>：沿用 OpenClaw 的 channel、tool、session 接入能力</li>
<li class=""><strong>Execution Kernel</strong>：核心执行层，管理 Run → Flow → Step → SideEffect 全生命周期</li>
<li class=""><strong>Cluster Mesh</strong>：负责节点注册、能力发布、step 调度和安全隔离（不鼓励节点间自由聊天）</li>
<li class=""><strong>Observability Plane</strong>：统一执行账本，提供 runs、flows、steps、side effects 的可视化视图</li>
<li class=""><strong>Cognition Plane</strong>：用小模型做 workload 识别、角色推荐、状态总结</li>
<li class=""><strong>Policy Plane</strong>：负责安全审批、secret 作用域、side-effect 治理和信任检查</li>
</ul>
<p>控制面决定“什么该跑、在哪跑、按什么策略跑”；执行面负责真正执行并产生 trace。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="单节点多智能体运行时">单节点多智能体运行时<a href="https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#%E5%8D%95%E8%8A%82%E7%82%B9%E5%A4%9A%E6%99%BA%E8%83%BD%E4%BD%93%E8%BF%90%E8%A1%8C%E6%97%B6" class="hash-link" aria-label="单节点多智能体运行时的直接链接" title="单节点多智能体运行时的直接链接" translate="no">​</a></h2>
<p>即使只有一个节点，也要支持多角色协同运行。推荐基础角色包括：</p>
<ul>
<li class="">Planner（规划）</li>
<li class="">Executor（执行）</li>
<li class="">Reviewer（审查）</li>
<li class="">Watcher（监控）</li>
<li class="">Specialist（领域专家）</li>
</ul>
<p>所有动作先上报本地 ledger，再由控制面聚合，确保执行轨迹清晰可见。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="跨节点调度基于-step-而非聊天">跨节点调度：基于 Step 而非聊天<a href="https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#%E8%B7%A8%E8%8A%82%E7%82%B9%E8%B0%83%E5%BA%A6%E5%9F%BA%E4%BA%8E-step-%E8%80%8C%E9%9D%9E%E8%81%8A%E5%A4%A9" class="hash-link" aria-label="跨节点调度：基于 Step 而非聊天的直接链接" title="跨节点调度：基于 Step 而非聊天的直接链接" translate="no">​</a></h2>
<p>跨节点协作的关键是<strong>以 Step 为单位调度</strong>，而不是让节点间随意转发消息。这样才能保证所有权清晰、可取消、可审计。</p>
<p>调度器会综合考虑所需工具、模型可用性、硬件资源、locality、信任等级和 tenant 亲和性等因素。</p>
<p>节点状态包括 joining、ready、degraded、quarantined、draining 等，支持自动隔离高风险节点。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心原语execution-ledger执行账本">核心原语：Execution Ledger（执行账本）<a href="https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#%E6%A0%B8%E5%BF%83%E5%8E%9F%E8%AF%ADexecution-ledger%E6%89%A7%E8%A1%8C%E8%B4%A6%E6%9C%AC" class="hash-link" aria-label="核心原语：Execution Ledger（执行账本）的直接链接" title="核心原语：Execution Ledger（执行账本）的直接链接" translate="no">​</a></h2>
<p>这是整个系统最重要的一环。所有事件（run、flow、step、side effect、policy 等）都绑定到 runId，形成统一可查询的账本。</p>
<p>操作者应该能轻松回答：</p>
<ul>
<li class="">是哪个 agent 在执行？</li>
<li class="">发生了哪些副作用？</li>
<li class="">当前卡在哪一步？</li>
<li class="">这个失败是否可以安全重试？</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="与-kubernetes-的关系">与 Kubernetes 的关系<a href="https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#%E4%B8%8E-kubernetes-%E7%9A%84%E5%85%B3%E7%B3%BB" class="hash-link" aria-label="与 Kubernetes 的关系的直接链接" title="与 Kubernetes 的关系的直接链接" translate="no">​</a></h2>
<p>Kubernetes 继续负责 Pod 调度、liveness、service discovery、存储和 RBAC 等基础设施能力。</p>
<p>AI OS 层则通过 CustomResource（CRD）来表达 HiveRun、HiveFlow、HiveStepLease、HiveNode 等对象，用 Operator 实现调谐逻辑。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="演进路径建议">演进路径建议<a href="https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#%E6%BC%94%E8%BF%9B%E8%B7%AF%E5%BE%84%E5%BB%BA%E8%AE%AE" class="hash-link" aria-label="演进路径建议的直接链接" title="演进路径建议的直接链接" translate="no">​</a></h2>
<p>推荐从小步开始：</p>
<ol>
<li class=""><strong>Phase 1</strong>：单节点补齐 run ledger、flow graph 和 side-effect 可视化</li>
<li class=""><strong>Phase 2</strong>：实现单集群 step scheduler 和节点管理</li>
<li class=""><strong>Phase 3</strong>：加入轻量角色推荐</li>
<li class=""><strong>Phase 4</strong>：实现自适应集群行为</li>
</ol>
<p>第一目标不是追求完全自治，而是先做一个<strong>单节点、具备一流可观测能力的 AI 运行时</strong>。先让执行可见，再让副作用可治理，最后再做跨节点调度。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Kubernetes AI OS 的目标，是让 Kubernetes 真正成为 AI 时代的操作系统底座：在保持安全和可控的前提下，充分发挥多智能体的协作能力。它不重造轮子，而是把 OpenClaw 的南向接入、类 golutra 的执行轨迹与 Kubernetes 的调度恢复能力拼在一起。整个设计只围绕一条主线——执行必须可见、可检查、可归因。落地时不必一步到位，从单节点的执行账本做起，逐步走向跨节点调度与自适应集群。</p>]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[openclaw架构设计]]></title>
            <link>https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji</link>
            <guid>https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji</guid>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[OpenClaw 将大模型的推理能力与本地执行环境彻底分离。本文从整体架构、运行逻辑、安全机制三个维度解析这款开源 AI 代理工具。]]></description>
            <content:encoded><![CDATA[<p>多数AI应用仍停留在“聊天交互”的表层——用户输入问题，模型返回文本，却无法真正触达本地设备、执行实际任务。OpenClaw（前身为Clawdbot/Moltbot/Molty）打破了这一局限：它将大模型的“推理能力”与本地执行环境彻底分离，让AI从“对话助手”升级为“可自主行动的代理”。本文从整体架构、运行逻辑、设计亮点三个维度，解析这款由Peter Steinberger开发的TypeScript/Node.js开源工具。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="整体架构设计中心辐射式分层解耦的操作系统级框架">整体架构设计：中心辐射式+分层解耦的“操作系统级”框架<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E4%B8%AD%E5%BF%83%E8%BE%90%E5%B0%84%E5%BC%8F%E5%88%86%E5%B1%82%E8%A7%A3%E8%80%A6%E7%9A%84%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%BA%A7%E6%A1%86%E6%9E%B6" class="hash-link" aria-label="整体架构设计：中心辐射式+分层解耦的“操作系统级”框架的直接链接" title="整体架构设计：中心辐射式+分层解耦的“操作系统级”框架的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2026-04-Gemini_Generated_Image_iy2svaiy2svaiy2s-1-f3d52510266b418bdf06cabe1bb30c7a.webp" width="1408" height="768" class="img_ev3q"></p>
<p>OpenClaw的核心设计哲学是“把AI当成基础设施问题，而非提示工程问题”——模型仅负责推理，系统则承担状态管理、执行控制、安全保障和多渠道协同。其架构采用<strong>中心辐射式（Hub-and-Spoke）<strong>与</strong>分层解耦</strong>结合的方式，既保证了核心逻辑的集中可控，又兼顾了组件的灵活性。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="中心辐射式gateway作为唯一控制平面">中心辐射式：Gateway作为唯一控制平面<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E4%B8%AD%E5%BF%83%E8%BE%90%E5%B0%84%E5%BC%8Fgateway%E4%BD%9C%E4%B8%BA%E5%94%AF%E4%B8%80%E6%8E%A7%E5%88%B6%E5%B9%B3%E9%9D%A2" class="hash-link" aria-label="中心辐射式：Gateway作为唯一控制平面的直接链接" title="中心辐射式：Gateway作为唯一控制平面的直接链接" translate="no">​</a></h3>
<p>OpenClaw以**Gateway（网关）**为中心，所有组件（客户端、渠道适配器、Agent执行器等）都围绕Gateway辐射连接。Gateway是系统的“交通枢纽”，默认绑定本地回环地址（127.0.0.1:18789），仅允许本地设备访问，从根源保障数据隐私。它的核心职责包括：</p>
<ul>
<li class=""><strong>统一认证与会话隔离</strong>：不同用户或设备的会话独立管理，避免数据交叉污染；</li>
<li class=""><strong>Lane Queue（车道式串行队列）</strong>：每个会话默认串行执行任务，彻底解决多任务并发导致的状态冲突和日志交织问题（可手动开启并行模式）；</li>
<li class=""><strong>消息路由与调度</strong>：将标准化后的消息精准转发到对应Agent；</li>
<li class=""><strong>流式交互支持</strong>：实时推送模型输出和“正在输入”状态，模拟自然对话体验。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="分层架构从外到内的能力拆解">分层架构：从外到内的能力拆解<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E5%88%86%E5%B1%82%E6%9E%B6%E6%9E%84%E4%BB%8E%E5%A4%96%E5%88%B0%E5%86%85%E7%9A%84%E8%83%BD%E5%8A%9B%E6%8B%86%E8%A7%A3" class="hash-link" aria-label="分层架构：从外到内的能力拆解的直接链接" title="分层架构：从外到内的能力拆解的直接链接" translate="no">​</a></h3>
<p>OpenClaw的分层架构从用户交互到基础设施层层递进，每一层职责明确、可独立扩展。</p>
<p>1）<strong>客户端层：多入口的交互界面</strong></p>
<p>用户无需依赖专用App，可通过以下方式与OpenClaw交互：</p>
<ul>
<li class="">命令行工具（CLI）：适合技术用户快速调试；</li>
<li class="">Web UI：可视化操作面板；</li>
<li class="">macOS菜单栏App、iOS/Android Node：移动设备端的轻量入口；</li>
<li class="">第三方聊天平台：支持WhatsApp、Telegram、Discord等20+常用App，用户可直接用熟悉的工具下达指令。</li>
</ul>
<p>2）<strong>接入协调层：Gateway的“中枢功能”</strong></p>
<p>如前所述，Gateway是接入协调的核心，负责将异构输入转化为系统可识别的格式，并管理会话生命周期。</p>
<p>3）<strong>渠道抽象层：突破平台协议壁垒</strong></p>
<p>不同聊天平台的消息协议差异巨大（如WhatsApp的Baileys协议、Telegram的MTProto协议），<strong>Channel Adapters（渠道适配器）</strong> 扮演了“翻译官”的角色：它将各平台的消息（文本、图片、语音等）统一转化为OpenClaw的内部标准格式，并处理附件下载、缓存和访问控制（如“仅允许指定联系人发送指令”）。</p>
<p>4）<strong>核心逻辑层：Agent的“思考与执行引擎”</strong></p>
<p>核心逻辑层由<strong>Agent Runner</strong>和<strong>Memory System</strong>组成，是AI代理“做事”的关键：</p>
<ul>
<li class=""><strong>Agent Runner</strong>：每个渠道或群组可映射到独立的Agent实例（支持多Agent协作），通过RPC调用Pi Agent Runtime完成“接收指令→调用模型→执行工具→生成结果”的完整循环；</li>
<li class=""><strong>Memory System</strong>：负责上下文管理，包括会话历史、用户长期记忆、工具能力描述等，为模型提供决策依据。</li>
</ul>
<p>5）<strong>基础设施层：本地执行的“地基”</strong></p>
<p>基础设施层提供系统运行的基础能力：</p>
<ul>
<li class="">本地持久化：用Markdown和.jsonl格式存储所有数据（避免依赖云端数据库）；</li>
<li class="">WebSocket流式通信：保障客户端与Gateway的实时连接；</li>
<li class="">沙箱执行：限制工具的系统权限，防止恶意操作；</li>
<li class="">定时任务（Cron）与Webhook：支持主动触发任务（如每日晨间报告）。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心组件关系各模块如何协同工作">核心组件关系：各模块如何协同工作？<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E6%A0%B8%E5%BF%83%E7%BB%84%E4%BB%B6%E5%85%B3%E7%B3%BB%E5%90%84%E6%A8%A1%E5%9D%97%E5%A6%82%E4%BD%95%E5%8D%8F%E5%90%8C%E5%B7%A5%E4%BD%9C" class="hash-link" aria-label="核心组件关系：各模块如何协同工作？的直接链接" title="核心组件关系：各模块如何协同工作？的直接链接" translate="no">​</a></h3>
<p>为更清晰地理解架构，我们可以将核心组件的关系简化为：</p>
<ol>
<li class=""><strong>Gateway</strong>作为“调度中心”，接收来自客户端或渠道适配器的消息，分配给对应的Agent；</li>
<li class=""><strong>Agent Runner</strong>根据Gateway的指令，调用模型生成决策，并执行工具（如读写文件、操作浏览器）；</li>
<li class=""><strong>Channel Adapters</strong>负责“翻译”第三方平台的消息，让用户无需切换工具即可交互；</li>
<li class=""><strong>Skills（技能插件）</strong>：存放在<code>~/.openclaw/workspace/skills</code>目录下，Agent可自主发现、安装并调用（支持ClawHub注册表分享技能）；</li>
<li class=""><strong>Nodes（设备端执行器）</strong>：在macOS、iOS等设备上运行，通过WebSocket暴露本地硬件能力（如语音输入、摄像头、屏幕录制）；</li>
<li class=""><strong>Canvas</strong>：独立端口（18793）提供的可视化工作空间，Agent可生成HTML/A2UI界面（如任务进度条、数据仪表盘）。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="运行逻辑agent-loop与主动调度的做事机制">运行逻辑：Agent Loop与主动调度的“做事”机制<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E8%BF%90%E8%A1%8C%E9%80%BB%E8%BE%91agent-loop%E4%B8%8E%E4%B8%BB%E5%8A%A8%E8%B0%83%E5%BA%A6%E7%9A%84%E5%81%9A%E4%BA%8B%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="运行逻辑：Agent Loop与主动调度的“做事”机制的直接链接" title="运行逻辑：Agent Loop与主动调度的“做事”机制的直接链接" translate="no">​</a></h2>
<p>OpenClaw的核心价值是“让AI真正做事”，这依赖于<strong>可循环的Agent Loop（代理循环）<strong>和</strong>主动调度机制</strong>——前者处理用户触发的任务，后者实现无人值守的自主运行。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="典型消息处理流程从输入到输出的全链路">典型消息处理流程：从输入到输出的全链路<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E5%85%B8%E5%9E%8B%E6%B6%88%E6%81%AF%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B%E4%BB%8E%E8%BE%93%E5%85%A5%E5%88%B0%E8%BE%93%E5%87%BA%E7%9A%84%E5%85%A8%E9%93%BE%E8%B7%AF" class="hash-link" aria-label="典型消息处理流程：从输入到输出的全链路的直接链接" title="典型消息处理流程：从输入到输出的全链路的直接链接" translate="no">​</a></h3>
<p>一条用户指令（如“整理桌面最近3天的文件并生成清单”）的处理流程如下。</p>
<p>1）<strong>消息进入与标准化</strong></p>
<p>用户通过Telegram发送指令→Channel Adapter将Telegram消息转化为系统标准格式（包含文本内容、发送者信息、上下文）→将标准化消息丢入Gateway的对应Session Lane Queue（串行执行，避免冲突）。</p>
<p>2）<strong>路由与Agent激活</strong></p>
<p>Gateway根据配置（如“main会话绑定默认Agent”“非main会话需@触发”），将消息路由到目标Agent。例如，用户在家庭群组中@OpenClaw，Gateway会激活“家庭助手”Agent。</p>
<p>3）<strong>上下文组装：让Agent“记得”关键信息</strong></p>
<p>Memory System会自动组装以下内容，作为模型的输入上下文：</p>
<ul>
<li class="">当前会话的历史消息（避免Agent“失忆”）；</li>
<li class="">用户长期记忆（如“用户喜欢用Markdown整理文件”“桌面路径是~/Desktop”）；</li>
<li class="">系统提示：包括AGENTS.md（Agent的行为基线，如“优先使用本地工具”）、SOUL.md（性格设定，如“简洁高效”）、TOOLS.md（工具的能力描述，如“file.read可读取本地文件”）；</li>
<li class="">已安装的Skills（如“文件分类技能”）。</li>
</ul>
<p>若上下文过长，系统会自动压缩总结，保证模型输入在上下文窗口内。</p>
<p>4）<strong>模型调用与决策生成</strong></p>
<p>Agent Runner调用配置的LLM（支持Claude、GPT或本地模型如Llama 3），模型基于上下文判断：是否需要执行工具？</p>
<ul>
<li class="">若不需要工具：直接生成自然语言回复（如“已整理完成，清单已保存到桌面”）；</li>
<li class="">若需要工具：生成工具调用指令（如<code>{"name": "file.list", "parameters": {"path": "~/Desktop", "days": 3}}</code>）。</li>
</ul>
<p>5）<strong>工具执行：多轮循环直到任务完成</strong></p>
<p>工具执行采用<strong>循环机制</strong>：</p>
<ol>
<li class="">Agent Runner在沙箱中执行工具（main会话默认拥有主机权限，非main会话可强制Docker沙箱隔离）；</li>
<li class="">工具返回执行结果（如“找到5个文件：report.pdf、photo.jpg...”）；</li>
<li class="">结果被写回上下文，Agent Runner再次调用模型，判断是否需要进一步操作（如“是否需要将清单转为Markdown？”）；</li>
<li class="">重复上述步骤，直到模型认为任务完成。</li>
</ol>
<p>6）<strong>输出与持久化</strong></p>
<p>最终结果通过Gateway流式推送给原渠道（如Telegram），同时所有交互记录、工具执行日志、记忆更新都会写入本地的.jsonl和Markdown文件，确保数据可追溯且不依赖云端。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="主动运行机制ai的自主意识来源">主动运行机制：AI的“自主意识”来源<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E4%B8%BB%E5%8A%A8%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%88%B6ai%E7%9A%84%E8%87%AA%E4%B8%BB%E6%84%8F%E8%AF%86%E6%9D%A5%E6%BA%90" class="hash-link" aria-label="主动运行机制：AI的“自主意识”来源的直接链接" title="主动运行机制：AI的“自主意识”来源的直接链接" translate="no">​</a></h3>
<p>传统聊天机器人需要用户主动触发，但OpenClaw支持<strong>主动运行</strong>，让AI无需用户指令即可完成任务。其实现方式包括以下三种。</p>
<p>1）<strong>定时任务（Heartbeat Scheduler/Cron）</strong></p>
<p>用户可配置周期性任务，如“每天早上8点整理昨日邮件并生成摘要”“每周日备份桌面文件到移动硬盘”。Gateway会按时唤醒Agent执行任务，结果自动发送到指定渠道（如用户的WhatsApp）。</p>
<p>2）<strong>外部事件触发（Webhook/Pub/Sub）</strong></p>
<p>OpenClaw支持通过Webhook接收外部事件，如Gmail新邮件、GitHub Issue创建、智能家居设备触发等。例如，当用户收到重要工作邮件时，OpenClaw可自动提取关键信息并提醒用户。</p>
<p>3）<strong>多Agent协作</strong></p>
<p>Agent可通过内置工具（如<code>sessions_list</code>查看所有会话、<code>sessions_send</code>向其他Agent发送消息）实现协作。例如，“日程管理Agent”发现用户明天有会议，可通知“文件整理Agent”提前准备会议资料。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="启动与运行简单几步即可部署">启动与运行：简单几步即可部署<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E5%90%AF%E5%8A%A8%E4%B8%8E%E8%BF%90%E8%A1%8C%E7%AE%80%E5%8D%95%E5%87%A0%E6%AD%A5%E5%8D%B3%E5%8F%AF%E9%83%A8%E7%BD%B2" class="hash-link" aria-label="启动与运行：简单几步即可部署的直接链接" title="启动与运行：简单几步即可部署的直接链接" translate="no">​</a></h3>
<p>OpenClaw的部署流程非常轻量化，适合普通用户快速上手：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 全局安装最新版本</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">npm</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-g</span><span class="token plain"> openclaw@latest</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 初始化：引导配置聊天渠道、LLM模型、工作区路径</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">openclaw onboard</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 常驻运行：将Gateway注册为系统守护进程，实现24/7运行</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">openclaw onboard --install-daemon</span><br></div></code></pre></div></div>
<p>完成后，通过CLI、Web UI或聊天App连接Gateway即可开始使用。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="关键设计亮点与安全机制本地优先的隐私保障">关键设计亮点与安全机制：本地优先的隐私保障<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E5%85%B3%E9%94%AE%E8%AE%BE%E8%AE%A1%E4%BA%AE%E7%82%B9%E4%B8%8E%E5%AE%89%E5%85%A8%E6%9C%BA%E5%88%B6%E6%9C%AC%E5%9C%B0%E4%BC%98%E5%85%88%E7%9A%84%E9%9A%90%E7%A7%81%E4%BF%9D%E9%9A%9C" class="hash-link" aria-label="关键设计亮点与安全机制：本地优先的隐私保障的直接链接" title="关键设计亮点与安全机制：本地优先的隐私保障的直接链接" translate="no">​</a></h2>
<p>OpenClaw的设计围绕“用户控制权”和“隐私安全”展开，以下是其核心亮点。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="本地优先数据永远在你手中">本地优先：数据永远在你手中<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E6%9C%AC%E5%9C%B0%E4%BC%98%E5%85%88%E6%95%B0%E6%8D%AE%E6%B0%B8%E8%BF%9C%E5%9C%A8%E4%BD%A0%E6%89%8B%E4%B8%AD" class="hash-link" aria-label="本地优先：数据永远在你手中的直接链接" title="本地优先：数据永远在你手中的直接链接" translate="no">​</a></h3>
<p>所有数据（会话历史、记忆、工具执行日志）都存储在本地设备，Gateway默认仅监听127.0.0.1（本地回环地址），不会向云端传输任何数据。即使断网，OpenClaw仍可正常执行本地任务（如文件操作、本地模型调用）。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="安全隔离多重防护避免风险">安全隔离：多重防护避免风险<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E5%AE%89%E5%85%A8%E9%9A%94%E7%A6%BB%E5%A4%9A%E9%87%8D%E9%98%B2%E6%8A%A4%E9%81%BF%E5%85%8D%E9%A3%8E%E9%99%A9" class="hash-link" aria-label="安全隔离：多重防护避免风险的直接链接" title="安全隔离：多重防护避免风险的直接链接" translate="no">​</a></h3>
<p>OpenClaw通过多层机制保障系统安全：</p>
<ul>
<li class=""><strong>私信配对审批</strong>：第三方聊天平台的私信指令需用户手动审批（如Telegram用户发送<code>/pair</code>后，OpenClaw会发送验证码，用户验证后才能交互）；</li>
<li class=""><strong>沙箱执行</strong>：非main会话的工具执行默认在Docker沙箱中，限制对主机系统的访问；</li>
<li class=""><strong>工具黑名单</strong>：用户可配置禁止Agent调用某些危险工具（如<code>system.exec</code>执行系统命令）；</li>
<li class=""><strong>显式控制</strong>：支持通过<code>/think high</code>（提高模型思考深度）、<code>/verbose on</code>（显示工具执行细节）等命令微调Agent行为。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="可扩展性插件化架构支持自定义">可扩展性：插件化架构支持自定义<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E5%8F%AF%E6%89%A9%E5%B1%95%E6%80%A7%E6%8F%92%E4%BB%B6%E5%8C%96%E6%9E%B6%E6%9E%84%E6%94%AF%E6%8C%81%E8%87%AA%E5%AE%9A%E4%B9%89" class="hash-link" aria-label="可扩展性：插件化架构支持自定义的直接链接" title="可扩展性：插件化架构支持自定义的直接链接" translate="no">​</a></h3>
<p>OpenClaw的所有核心组件都支持热插拔：</p>
<ul>
<li class=""><strong>渠道扩展</strong>：可自行开发新的Channel Adapter（如微信、Signal）；</li>
<li class=""><strong>模型扩展</strong>：支持接入任意LLM（只需实现标准接口）；</li>
<li class=""><strong>技能扩展</strong>：用户可编写自定义Skills（如“自动同步Obsidian笔记”“生成PPT大纲”），Agent会自主阅读Skill的契约文件并调用；</li>
<li class=""><strong>内存扩展</strong>：可替换Memory System为向量数据库（如Pinecone），提升长期记忆的检索效率。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="记忆系统四层结构让agent越用越聪明">记忆系统：四层结构让Agent“越用越聪明”<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E8%AE%B0%E5%BF%86%E7%B3%BB%E7%BB%9F%E5%9B%9B%E5%B1%82%E7%BB%93%E6%9E%84%E8%AE%A9agent%E8%B6%8A%E7%94%A8%E8%B6%8A%E8%81%AA%E6%98%8E" class="hash-link" aria-label="记忆系统：四层结构让Agent“越用越聪明”的直接链接" title="记忆系统：四层结构让Agent“越用越聪明”的直接链接" translate="no">​</a></h3>
<p>OpenClaw的记忆系统分为四层，确保Agent能高效利用历史信息：</p>
<ol>
<li class=""><strong>SOUL层</strong>：Agent的性格和价值观（如“优先保护用户隐私”“简洁回答问题”）；</li>
<li class=""><strong>TOOLS层</strong>：工具的能力描述和使用方法；</li>
<li class=""><strong>USER向量记忆</strong>：用户的长期偏好（如“喜欢咖啡胜过茶”“常用的文件路径”），采用向量存储实现快速检索；</li>
<li class=""><strong>Session短期记忆</strong>：当前会话的历史消息，自动压缩避免冗余。</li>
</ol>
<p>系统会定期对记忆进行“压缩（compaction）”，保留关键信息，删除重复内容，让Agent的“记忆”更高效。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/04/02/openclaw-jia-gou-she-ji#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>OpenClaw用一套“操作系统级”的架构回答了“AI如何真正做事”这个问题：Gateway作为唯一控制平面统一调度，分层设计让渠道、模型、技能都可以独立替换。Agent Loop的多轮工具循环与定时任务、Webhook等主动机制结合，使AI既能响应指令，也能无人值守地运行。而本地优先的存储策略、沙箱隔离与配对审批，则在能力与安全之间划出了清晰的边界。对想把大模型接入真实工作流的人来说，这套设计有不少值得借鉴的地方。</p>]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[Cursor 的发展]]></title>
            <link>https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan</link>
            <guid>https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Cursor 的发展脉络：从传统 IDE 和代码补全工具讲起，梳理这款 AI 原生编辑器的诞生背景、核心技术设计、增长情况与面临的挑战。]]></description>
            <content:encoded><![CDATA[<p>Cursor 大概是这两年增长最快的开发工具之一。这篇文章梳理它的发展脉络：从传统 IDE 讲起，看看这款 AI 原生编辑器到底做了什么、又面临哪些问题。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ide-编程工具的背景">IDE 编程工具的背景<a href="https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan#ide-%E7%BC%96%E7%A8%8B%E5%B7%A5%E5%85%B7%E7%9A%84%E8%83%8C%E6%99%AF" class="hash-link" aria-label="IDE 编程工具的背景的直接链接" title="IDE 编程工具的背景的直接链接" translate="no">​</a></h2>
<p>在很长一段时间里，软件开发主要依赖传统 IDE，例如 Visual Studio Code、IntelliJ IDEA、Eclipse。这些 IDE 的核心能力是代码编辑、语法高亮、自动补全和调试工具，但真正的代码逻辑仍然需要 <strong>程序员自己编写</strong>。</p>
<p>直到 <strong>大语言模型（LLM）出现后</strong>，软件开发开始进入 <strong>AI 编程时代</strong>。最早的一批 AI 编程工具包括 GitHub Copilot 和 Tabnine，它们主要解决 <strong>代码补全问题</strong>。但 Cursor 的目标并不是简单补全，而是：</p>
<blockquote>
<p><strong>构建一个 AI 原生（AI-Native）的开发环境。</strong></p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cursor-的诞生">Cursor 的诞生<a href="https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan#cursor-%E7%9A%84%E8%AF%9E%E7%94%9F" class="hash-link" aria-label="Cursor 的诞生的直接链接" title="Cursor 的诞生的直接链接" translate="no">​</a></h2>
<p>Cursor 是一个 <strong>AI 原生代码编辑器（AI-Native IDE）</strong>，由 <strong>Anysphere</strong> 开发。公司成立于 <strong>2022 年</strong>，创始人是四位 MIT 毕业生：</p>
<ul>
<li class="">Michael Truell</li>
<li class="">Aman Sanger</li>
<li class="">Sualeh Asif</li>
<li class="">Arvid Lunnemark</li>
</ul>
<p>Cursor 在 <strong>2023 年正式发布</strong>。技术上它是：</p>
<blockquote>
<p><strong>基于 VS Code Fork 的 AI IDE</strong></p>
</blockquote>
<p>但在 VS Code 的基础上加入了大量 AI 功能，例如 AI 代码生成、AI Debug、AI 重构、AI 查询代码库。Cursor 的目标非常明确：</p>
<blockquote>
<p><strong>让开发者通过自然语言直接写代码。</strong></p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cursor-的核心技术设计">Cursor 的核心技术设计<a href="https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan#cursor-%E7%9A%84%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="Cursor 的核心技术设计的直接链接" title="Cursor 的核心技术设计的直接链接" translate="no">​</a></h2>
<p>Cursor 的设计思想主要包括三个核心点。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1ai-代码生成">1）AI 代码生成<a href="https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan#1ai-%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90" class="hash-link" aria-label="1）AI 代码生成的直接链接" title="1）AI 代码生成的直接链接" translate="no">​</a></h3>
<p>Cursor 通过大语言模型实现代码生成。开发者可以直接输入"实现一个 Redis 分布式锁"，Cursor 会自动生成代码、注释和测试代码。这和传统 IDE 的 <strong>代码补全完全不同</strong>：</p>
<ul>
<li class="">传统 IDE：开发者写代码，IDE 辅助补全</li>
<li class="">Cursor：开发者描述需求，AI 生成代码</li>
</ul>
<p>这种模式被称为 <strong>Vibe Coding</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2codebase-理解能力">2）Codebase 理解能力<a href="https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan#2codebase-%E7%90%86%E8%A7%A3%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="2）Codebase 理解能力的直接链接" title="2）Codebase 理解能力的直接链接" translate="no">​</a></h3>
<p>Cursor 的一个核心能力是 <strong>理解整个代码仓库</strong>。它会建立代码向量索引、文件关系和依赖关系，然后开发者可以直接问"这个项目的用户登录逻辑在哪里？"，Cursor 就能定位相关代码。这类似于 RAG + Code Embedding 的做法。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3ai-代码修改能力">3）AI 代码修改能力<a href="https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan#3ai-%E4%BB%A3%E7%A0%81%E4%BF%AE%E6%94%B9%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="3）AI 代码修改能力的直接链接" title="3）AI 代码修改能力的直接链接" translate="no">​</a></h3>
<p>Cursor 支持一种叫 <strong>Smart Rewrite</strong> 的能力。例如"把这个模块改成异步"，Cursor 可以自动修改多个文件、自动调整函数、自动更新调用关系。这在传统 IDE 中几乎是不可能的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cursor-的快速增长">Cursor 的快速增长<a href="https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan#cursor-%E7%9A%84%E5%BF%AB%E9%80%9F%E5%A2%9E%E9%95%BF" class="hash-link" aria-label="Cursor 的快速增长的直接链接" title="Cursor 的快速增长的直接链接" translate="no">​</a></h2>
<p>Cursor 的增长速度非常惊人。发布后不到两年：</p>
<ul>
<li class="">ARR 超过 <strong>3 亿美元</strong></li>
<li class="">融资超过 <strong>10 亿美元</strong></li>
<li class="">公司估值接近 <strong>300 亿美元</strong></li>
</ul>
<p>Cursor 甚至成为 <strong>增长最快的 AI SaaS 产品之一</strong>。很多公司开始使用 Cursor，例如 Nvidia、Stripe、Spotify——Nvidia 有 <strong>3 万多工程师使用 Cursor 开发代码</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cursor-的技术挑战">Cursor 的技术挑战<a href="https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan#cursor-%E7%9A%84%E6%8A%80%E6%9C%AF%E6%8C%91%E6%88%98" class="hash-link" aria-label="Cursor 的技术挑战的直接链接" title="Cursor 的技术挑战的直接链接" translate="no">​</a></h2>
<p>虽然 Cursor 非常火，但学术研究也指出了一些问题：AI 编程助手会提高开发速度，但可能 <strong>增加代码复杂度和警告数量</strong>。</p>
<p>例如一篇研究论文指出：</p>
<blockquote>
<p>使用 AI 编程助手后，项目开发速度会短期提升，但代码复杂度也会上升。</p>
</blockquote>
<p>另一项研究发现：</p>
<blockquote>
<p>AI 代码助手容易受到上下文错误代码影响，从而生成缺陷代码。</p>
</blockquote>
<p>这说明 AI 编程仍然需要 <strong>开发者进行审核和控制</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cursor-的未来方向">Cursor 的未来方向<a href="https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan#cursor-%E7%9A%84%E6%9C%AA%E6%9D%A5%E6%96%B9%E5%90%91" class="hash-link" aria-label="Cursor 的未来方向的直接链接" title="Cursor 的未来方向的直接链接" translate="no">​</a></h2>
<p>Cursor 的愿景非常激进：未来的软件开发可能变成"自然语言 → 软件系统"。开发流程可能变成：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">需求</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">↓</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">AI 生成代码</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">↓</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">AI 生成测试</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">↓</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">AI 自动部署</span><br></div></code></pre></div></div>
<p>也就是说，<strong>软件开发可能从"写代码"变成"描述需求"</strong>。Cursor 官方甚至提出一个愿景：</p>
<blockquote>
<p>有一天开发者只需要写 50 行伪代码，就能生成 2000 行完整代码。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/15/cursor-de-fa-zhan#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Cursor 用 Fork VS Code 的方式快速拿到了成熟的编辑器底座，再把 LLM 能力做进代码生成、仓库理解和跨文件修改，踩中了 AI 编程爆发的时间窗口。惊人的增长数字说明这条路走通了，但研究也提醒我们：AI 生成的代码会带来复杂度上升等新问题。工具再强，最终对代码质量负责的仍然是开发者。这一点在可预见的未来大概不会改变。</p>]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[PolarDB 存算分离]]></title>
            <link>https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou</link>
            <guid>https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从架构角度分析 PolarDB 的存算分离设计：计算与存储解耦如何解决扩展性、存储成本与高可用问题，并与 AWS Aurora、传统 MySQL 架构做对比。]]></description>
            <content:encoded><![CDATA[<p>随着业务规模扩大，传统关系型数据库架构逐渐暴露出扩展能力不足、存储成本高、读写压力集中等瓶颈。云厂商为此设计了一种新的架构模式：<strong>存算分离（Storage-Compute Decoupling）</strong>。本文从架构角度分析 PolarDB 的存算分离设计，并与 AWS Aurora 以及传统 MySQL 架构进行对比。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="背景">背景<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="背景的直接链接" title="背景的直接链接" translate="no">​</a></h2>
<p>PolarDB 是阿里云推出的一款云原生数据库，其核心设计理念之一就是 <strong>计算层与存储层解耦</strong>。这种架构使数据库具备更强的弹性扩展能力和更高的资源利用率。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2026-04-Gemini_Generated_Image_lr15jglr15jglr15-aaa1df73e131dae3dab907117c43cd3a.webp" width="1408" height="768" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="传统-mysql-架构的问题">传统 MySQL 架构的问题<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#%E4%BC%A0%E7%BB%9F-mysql-%E6%9E%B6%E6%9E%84%E7%9A%84%E9%97%AE%E9%A2%98" class="hash-link" aria-label="传统 MySQL 架构的问题的直接链接" title="传统 MySQL 架构的问题的直接链接" translate="no">​</a></h2>
<p>在传统 MySQL 架构中，数据库通常运行在单个服务器上，计算和存储都在同一台机器：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">MySQL Server</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"> ├── CPU</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"> ├── Memory</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"> └── Local Disk      # 数据与计算绑定在同一台机器</span><br></div></code></pre></div></div>
<p>这种架构在早期互联网时代已经足够，但随着业务规模扩大，会出现几个明显问题。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-存储扩展困难">1) 存储扩展困难<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#1-%E5%AD%98%E5%82%A8%E6%89%A9%E5%B1%95%E5%9B%B0%E9%9A%BE" class="hash-link" aria-label="1) 存储扩展困难的直接链接" title="1) 存储扩展困难的直接链接" translate="no">​</a></h3>
<p>数据库数据通常存储在本地磁盘中，当数据量增长时，只能升级磁盘或更换更大的机器，扩展能力有限。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-读扩展成本高">2) 读扩展成本高<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#2-%E8%AF%BB%E6%89%A9%E5%B1%95%E6%88%90%E6%9C%AC%E9%AB%98" class="hash-link" aria-label="2) 读扩展成本高的直接链接" title="2) 读扩展成本高的直接链接" translate="no">​</a></h3>
<p>MySQL 常见的扩展方式是 <strong>主从复制</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Master</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"> ├── Slave1      # 每个从库都持有一份完整数据副本</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"> ├── Slave2</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"> └── Slave3</span><br></div></code></pre></div></div>
<p>每个只读节点都需要一份完整的数据副本。随着读节点增加，存储成本会快速增长，数据同步压力也随之上升。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-高可用复杂">3) 高可用复杂<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#3-%E9%AB%98%E5%8F%AF%E7%94%A8%E5%A4%8D%E6%9D%82" class="hash-link" aria-label="3) 高可用复杂的直接链接" title="3) 高可用复杂的直接链接" translate="no">​</a></h3>
<p>如果主节点故障，需要手动或自动进行主从切换，期间可能存在复制延迟，数据一致性也需要额外处理。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="polardb-的存算分离架构">PolarDB 的存算分离架构<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#polardb-%E7%9A%84%E5%AD%98%E7%AE%97%E5%88%86%E7%A6%BB%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="PolarDB 的存算分离架构的直接链接" title="PolarDB 的存算分离架构的直接链接" translate="no">​</a></h2>
<p>PolarDB 的核心设计是：<strong>计算节点与存储节点完全解耦</strong>。架构大致如下：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          +--------------------+</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          |    Compute Node    |</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          | (MySQL / PG / ORA) |   # 计算层：SQL 解析、执行、事务</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          +---------+----------+</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                    |</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          +---------+----------+</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          | Distributed Storage|   # 存储层：数据、日志、副本</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          |     (PolarStore)   |</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          +--------------------+</span><br></div></code></pre></div></div>
<p>计算节点负责 SQL 解析、查询执行和事务处理；存储层负责数据存储、日志管理和副本复制。所有计算节点共享同一份分布式存储，这种架构被称为 <strong>Shared Storage Architecture</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="存算分离解决了什么问题">存算分离解决了什么问题<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#%E5%AD%98%E7%AE%97%E5%88%86%E7%A6%BB%E8%A7%A3%E5%86%B3%E4%BA%86%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98" class="hash-link" aria-label="存算分离解决了什么问题的直接链接" title="存算分离解决了什么问题的直接链接" translate="no">​</a></h2>
<p>这种设计主要解决了传统数据库的几个核心问题。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-弹性扩展能力">1) 弹性扩展能力<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#1-%E5%BC%B9%E6%80%A7%E6%89%A9%E5%B1%95%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="1) 弹性扩展能力的直接链接" title="1) 弹性扩展能力的直接链接" translate="no">​</a></h3>
<p>传统数据库增加计算能力必须升级服务器，而在 PolarDB 中可以直接增加计算节点：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Storage Layer</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   ↑</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   │</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Compute1      # 新增计算节点无需复制数据</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Compute2</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Compute3</span><br></div></code></pre></div></div>
<p>所有节点共享同一份数据，因此可以实现读扩展、分布式计算和秒级扩容。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-存储成本降低">2) 存储成本降低<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#2-%E5%AD%98%E5%82%A8%E6%88%90%E6%9C%AC%E9%99%8D%E4%BD%8E" class="hash-link" aria-label="2) 存储成本降低的直接链接" title="2) 存储成本降低的直接链接" translate="no">​</a></h3>
<p>传统 MySQL 的读节点需要完整数据副本，而在存算分离架构下，多个计算节点共享同一存储系统，不需要复制完整数据，存储成本显著降低。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-高可用能力更强">3) 高可用能力更强<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#3-%E9%AB%98%E5%8F%AF%E7%94%A8%E8%83%BD%E5%8A%9B%E6%9B%B4%E5%BC%BA" class="hash-link" aria-label="3) 高可用能力更强的直接链接" title="3) 高可用能力更强的直接链接" translate="no">​</a></h3>
<p>PolarDB 的存储层通常使用分布式副本机制：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Storage Node</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"> ├── Replica1      # 多副本保障数据持久化</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"> ├── Replica2</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"> └── Replica3</span><br></div></code></pre></div></div>
<p>当某个节点故障时，系统可以极快地进行恢复。这种设计可以实现自动故障恢复、高可用架构和数据持久化保障。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-极致性能">4) 极致性能<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#4-%E6%9E%81%E8%87%B4%E6%80%A7%E8%83%BD" class="hash-link" aria-label="4) 极致性能的直接链接" title="4) 极致性能的直接链接" translate="no">​</a></h3>
<p>PolarDB 存储层通常采用分布式日志结构、并行 I/O 和 SSD 云存储，可以大幅提升数据库性能。主要瓶颈从存储层转移到了内部网络 I/O 层。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="polardb-的劣势">PolarDB 的劣势<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#polardb-%E7%9A%84%E5%8A%A3%E5%8A%BF" class="hash-link" aria-label="PolarDB 的劣势的直接链接" title="PolarDB 的劣势的直接链接" translate="no">​</a></h2>
<p>虽然存算分离架构有很多优势，但也存在一些挑战。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-网络延迟">1) 网络延迟<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#1-%E7%BD%91%E7%BB%9C%E5%BB%B6%E8%BF%9F" class="hash-link" aria-label="1) 网络延迟的直接链接" title="1) 网络延迟的直接链接" translate="no">​</a></h3>
<p>计算层与存储层分离意味着数据访问需要通过网络，相比本地磁盘会增加一定延迟。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-架构复杂度高">2) 架构复杂度高<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#2-%E6%9E%B6%E6%9E%84%E5%A4%8D%E6%9D%82%E5%BA%A6%E9%AB%98" class="hash-link" aria-label="2) 架构复杂度高的直接链接" title="2) 架构复杂度高的直接链接" translate="no">​</a></h3>
<p>存算分离架构需要设计分布式存储系统、网络协议和数据一致性机制，实现难度远高于传统数据库。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-对云基础设施依赖强">3) 对云基础设施依赖强<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#3-%E5%AF%B9%E4%BA%91%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD%E4%BE%9D%E8%B5%96%E5%BC%BA" class="hash-link" aria-label="3) 对云基础设施依赖强的直接链接" title="3) 对云基础设施依赖强的直接链接" translate="no">​</a></h3>
<p>这种架构高度依赖高性能网络、分布式存储和云平台调度能力，因此通常只适用于云环境。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="polardb-vs-aws-aurora">PolarDB vs AWS Aurora<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#polardb-vs-aws-aurora" class="hash-link" aria-label="PolarDB vs AWS Aurora的直接链接" title="PolarDB vs AWS Aurora的直接链接" translate="no">​</a></h2>
<p>PolarDB 和 Aurora 的整体设计理念非常接近，二者都是典型的 <strong>云原生数据库架构</strong>，都采用了 <strong>存算分离（Compute + Storage Decoupling）</strong> 的设计。</p>
<p>Aurora 将计算节点与一个分布式存储集群分离，多个计算节点共享同一存储系统。架构示意：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          Writer Node</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">               |</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       +-------+-------+</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       |               |</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   Reader1         Reader2         # 读节点共享底层存储</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       |               |</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   +-----------------------+</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   |   Distributed Storage |</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   +-----------------------+</span><br></div></code></pre></div></div>
<p>Aurora 的分布式存储通常跨多个可用区部署，以提升可用性。但在具体实现上，两者仍然存在一些重要差异。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-存储架构设计差异">1) 存储架构设计差异<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#1-%E5%AD%98%E5%82%A8%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E5%B7%AE%E5%BC%82" class="hash-link" aria-label="1) 存储架构设计差异的直接链接" title="1) 存储架构设计差异的直接链接" translate="no">​</a></h3>
<p>Aurora 的核心设计是 <strong>日志驱动存储（Log Structured Storage）</strong>。在 Aurora 中，计算节点并不会直接写入数据库页，而是将 <strong>redo log</strong> 发送到存储节点，存储层根据这些日志来重建数据页。简化后的流程如下：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Client</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">↓</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Aurora Compute Node</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">↓</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Redo Log                    # 只传输日志，不传输完整数据页</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">↓</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Distributed Storage Nodes</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">↓</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Storage Node 重建数据页</span><br></div></code></pre></div></div>
<p>Aurora 的存储层通常由 <strong>6 个副本组成</strong>，分布在 <strong>3 个可用区（AZ）</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">AZ1 : Storage Node A / Storage Node B</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">AZ2 : Storage Node C / Storage Node D</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">AZ3 : Storage Node E / Storage Node F</span><br></div></code></pre></div></div>
<p>写入时只需要 <strong>4/6 quorum</strong> 即可提交事务。这种设计带来了几个优势：</p>
<ul>
<li class="">写入只需要发送日志，网络传输量更小</li>
<li class="">不需要完整复制数据库页</li>
<li class="">故障恢复速度更快</li>
</ul>
<p>而 PolarDB 的存储实现方式略有不同。PolarDB 采用的是 <strong>共享分布式存储（Shared Storage）</strong> 架构，计算节点可以直接访问底层的 <strong>PolarStore / PolarFS 分布式文件系统</strong>，数据库页仍然以页的形式存储。简化结构如下：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Client</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">↓</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Compute Node (MySQL / PG / Oracle)</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">↓</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">PolarFS / PolarStore        # 分布式文件系统，页式存储</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">↓</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Distributed Storage Nodes</span><br></div></code></pre></div></div>
<p>这种设计更接近传统数据库的存储方式，因此数据页结构保持兼容，对数据库内核改动相对较小。但在写入路径上，网络传输的数据量可能比 Aurora 的日志方式更大。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-读节点架构">2) 读节点架构<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#2-%E8%AF%BB%E8%8A%82%E7%82%B9%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="2) 读节点架构的直接链接" title="2) 读节点架构的直接链接" translate="no">​</a></h3>
<p>Aurora 的读节点主要是 <strong>Reader Instance</strong>，读节点通常只负责读取数据，写操作集中在 <strong>Writer Node</strong>。PolarDB 则支持一种 <strong>Reader Node + 主节点共享存储</strong> 的模式，多个节点可以直接访问共享存储，从而实现更灵活的读扩展。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-生态与兼容性策略">3) 生态与兼容性策略<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#3-%E7%94%9F%E6%80%81%E4%B8%8E%E5%85%BC%E5%AE%B9%E6%80%A7%E7%AD%96%E7%95%A5" class="hash-link" aria-label="3) 生态与兼容性策略的直接链接" title="3) 生态与兼容性策略的直接链接" translate="no">​</a></h3>
<p>Aurora 是 AWS 深度改造后的数据库引擎，虽然兼容 MySQL / PostgreSQL，但底层实现已经发生了较大变化。PolarDB 则更加注重 <strong>原生数据库兼容性</strong>：</p>
<ul>
<li class="">PolarDB MySQL</li>
<li class="">PolarDB PostgreSQL</li>
<li class="">PolarDB Oracle</li>
</ul>
<p>因此很多应用迁移到 PolarDB 时，修改成本会更低。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/15/polardb-du-xie-fen-chi-jia-gou#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>存算分离的本质，是把数据库的计算能力和存储能力拆成两个可以独立扩展的层。PolarDB 通过共享分布式存储换来了秒级扩容、低成本读扩展和更强的高可用能力，代价则是网络延迟和更高的架构复杂度。与 Aurora 相比，两者理念相近，但 PolarDB 走的是共享文件系统路线，更贴近传统数据库的存储形态，兼容性更好；Aurora 则用日志驱动存储换取了更小的写入传输量。理解这两条路线的取舍，对评估云数据库选型很有帮助。</p>]]></content:encoded>
            <category>DB</category>
            <category>技术</category>
            <category>Linux</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[团队管理经验]]></title>
            <link>https://tanzhuo.xyz/2026/03/15/tuan-dui-guan-li-jing-yan</link>
            <guid>https://tanzhuo.xyz/2026/03/15/tuan-dui-guan-li-jing-yan</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[带技术团队几年的一些朴素经验：先把事情想清楚、分工明确、放手让团队成长、用流程减少问题，以及为什么团队氛围和长期视角同样重要。]]></description>
            <content:encoded><![CDATA[<p>很多人以为技术团队管理是一门很复杂的学问，但我自己带团队之后慢慢发现，其实很多事情没有想象中那么复杂。技术团队管理，说到底不是"管人"，而是 <strong>让团队能够稳定地把事情做好</strong>。</p>
<!-- -->
<p>刚开始带团队的时候，我也走过一些弯路。最早的时候，我觉得只要自己技术能力强，很多事情亲自做就能解决。但后来慢慢发现，如果团队规模变大，靠一个人是撑不住的。技术负责人真正需要做的事情，其实是让团队整体能力提升，而不是自己变成团队里最忙的人。这些年做技术管理，我逐渐总结出一些比较简单但很重要的经验。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先把事情想清楚再让团队去做">先把事情想清楚，再让团队去做<a href="https://tanzhuo.xyz/2026/03/15/tuan-dui-guan-li-jing-yan#%E5%85%88%E6%8A%8A%E4%BA%8B%E6%83%85%E6%83%B3%E6%B8%85%E6%A5%9A%E5%86%8D%E8%AE%A9%E5%9B%A2%E9%98%9F%E5%8E%BB%E5%81%9A" class="hash-link" aria-label="先把事情想清楚，再让团队去做的直接链接" title="先把事情想清楚，再让团队去做的直接链接" translate="no">​</a></h2>
<p>很多技术团队效率低，其实不是因为工程师能力不行，而是事情本身没有想清楚。以前我也遇到过这种情况：需求来了，大家马上开始开发，结果开发到一半才发现设计不合理，又要返工。</p>
<p>后来我慢慢养成一个习惯，在项目开始之前一定要把 <strong>设计思路和整体方案想清楚</strong>。例如一个系统要做什么功能、系统大概怎么拆分、核心模块怎么设计、哪些地方可能成为瓶颈。如果这些事情没有想清楚，团队越努力，可能走得越偏。</p>
<p>所以现在我通常会先做一件事情：<strong>把问题想明白，再安排团队做事情</strong>。这样不仅效率更高，也能减少很多不必要的返工。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="团队分工一定要清晰">团队分工一定要清晰<a href="https://tanzhuo.xyz/2026/03/15/tuan-dui-guan-li-jing-yan#%E5%9B%A2%E9%98%9F%E5%88%86%E5%B7%A5%E4%B8%80%E5%AE%9A%E8%A6%81%E6%B8%85%E6%99%B0" class="hash-link" aria-label="团队分工一定要清晰的直接链接" title="团队分工一定要清晰的直接链接" translate="no">​</a></h2>
<p>技术团队里一个很常见的问题，就是责任不清。很多系统看起来大家都在做，但真正出了问题的时候，却没人知道应该找谁。</p>
<p>后来我慢慢意识到，一个团队如果想运转顺畅，必须要有清晰的责任划分。例如一个系统一定要有负责人，这个人不一定什么代码都写，但他必须清楚系统架构、核心逻辑以及系统运行情况。</p>
<p>我通常会给每个核心系统安排一个 Owner，例如某个服务、某个模块或者某个基础组件。这样当系统出现问题时，团队可以很快找到负责人，也能避免大家互相推诿。责任清晰之后，团队效率会明显提升。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="技术负责人不应该什么都自己做">技术负责人不应该什么都自己做<a href="https://tanzhuo.xyz/2026/03/15/tuan-dui-guan-li-jing-yan#%E6%8A%80%E6%9C%AF%E8%B4%9F%E8%B4%A3%E4%BA%BA%E4%B8%8D%E5%BA%94%E8%AF%A5%E4%BB%80%E4%B9%88%E9%83%BD%E8%87%AA%E5%B7%B1%E5%81%9A" class="hash-link" aria-label="技术负责人不应该什么都自己做的直接链接" title="技术负责人不应该什么都自己做的直接链接" translate="no">​</a></h2>
<p>刚开始带团队的时候，我经常会忍不住自己去写很多代码。因为自己写代码确实比较快，也比较放心。但时间久了就会发现，这种方式其实不利于团队成长。如果技术负责人把所有关键事情都自己做完，团队成员就很难成长，长期来看会让团队越来越依赖某一个人。</p>
<p>后来我慢慢调整了自己的方式。现在我更倾向于 <strong>让团队成员参与设计和决策</strong>，而不是只负责执行。比如系统设计讨论的时候，我会让大家一起参与，而不是我一个人决定。这样做虽然一开始可能会慢一点，但长期来看，团队能力会越来越强。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="流程不是为了约束而是为了提高效率">流程不是为了约束，而是为了提高效率<a href="https://tanzhuo.xyz/2026/03/15/tuan-dui-guan-li-jing-yan#%E6%B5%81%E7%A8%8B%E4%B8%8D%E6%98%AF%E4%B8%BA%E4%BA%86%E7%BA%A6%E6%9D%9F%E8%80%8C%E6%98%AF%E4%B8%BA%E4%BA%86%E6%8F%90%E9%AB%98%E6%95%88%E7%8E%87" class="hash-link" aria-label="流程不是为了约束，而是为了提高效率的直接链接" title="流程不是为了约束，而是为了提高效率的直接链接" translate="no">​</a></h2>
<p>很多工程师一听到流程，就觉得是增加工作量。但实际上，一个成熟的团队一定需要基本流程。例如在我们团队里，通常会有几个固定环节：</p>
<ol>
<li class="">需求评审</li>
<li class="">技术设计</li>
<li class="">代码评审</li>
<li class="">测试验证</li>
<li class="">上线发布</li>
</ol>
<p>这些流程的目的不是增加复杂度，而是减少问题。例如代码评审可以提前发现潜在 bug，技术设计可以避免架构问题，测试流程可以减少线上事故。当流程稳定之后，团队合作会顺畅很多。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="技术负责人需要关注长期问题">技术负责人需要关注长期问题<a href="https://tanzhuo.xyz/2026/03/15/tuan-dui-guan-li-jing-yan#%E6%8A%80%E6%9C%AF%E8%B4%9F%E8%B4%A3%E4%BA%BA%E9%9C%80%E8%A6%81%E5%85%B3%E6%B3%A8%E9%95%BF%E6%9C%9F%E9%97%AE%E9%A2%98" class="hash-link" aria-label="技术负责人需要关注长期问题的直接链接" title="技术负责人需要关注长期问题的直接链接" translate="no">​</a></h2>
<p>很多时候团队每天都在处理需求，很容易只关注眼前的事情。但如果长期不关注技术债务，系统会越来越难维护。所以我通常会留出一部分时间去思考系统架构，例如：</p>
<ul>
<li class="">系统是否存在性能瓶颈</li>
<li class="">架构是否需要调整</li>
<li class="">是否需要引入新的技术组件</li>
</ul>
<p>这些事情短期内可能看不到效果，但长期来看会对系统稳定性产生很大影响。技术负责人不仅要解决今天的问题，还要考虑 <strong>系统未来的发展方向</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="团队氛围其实很重要">团队氛围其实很重要<a href="https://tanzhuo.xyz/2026/03/15/tuan-dui-guan-li-jing-yan#%E5%9B%A2%E9%98%9F%E6%B0%9B%E5%9B%B4%E5%85%B6%E5%AE%9E%E5%BE%88%E9%87%8D%E8%A6%81" class="hash-link" aria-label="团队氛围其实很重要的直接链接" title="团队氛围其实很重要的直接链接" translate="no">​</a></h2>
<p>技术团队的效率不仅和技术能力有关，也和团队氛围有关。如果团队成员之间缺乏信任，很多事情都会变得困难。</p>
<p>我自己比较重视的一点是，让团队成员可以自由讨论技术问题。很多时候不同的人会有不同的思路，这种讨论往往能产生更好的解决方案。另外我也不太喜欢那种过于严格的管理方式，技术团队更适合一种相对开放的环境，让大家专注于把事情做好。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="技术管理其实也是一种成长">技术管理其实也是一种成长<a href="https://tanzhuo.xyz/2026/03/15/tuan-dui-guan-li-jing-yan#%E6%8A%80%E6%9C%AF%E7%AE%A1%E7%90%86%E5%85%B6%E5%AE%9E%E4%B9%9F%E6%98%AF%E4%B8%80%E7%A7%8D%E6%88%90%E9%95%BF" class="hash-link" aria-label="技术管理其实也是一种成长的直接链接" title="技术管理其实也是一种成长的直接链接" translate="no">​</a></h2>
<p>从工程师到技术负责人，其实是一个很大的转变。以前只需要关注代码，现在需要关注团队、项目和系统整体。刚开始的时候可能会不习惯，但慢慢会发现，技术管理其实也是一种新的挑战。它不仅需要技术能力，也需要思考能力和沟通能力。</p>
<p>对我来说，带团队最大的收获就是看到团队成员不断成长，同时系统也在不断进步。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/15/tuan-dui-guan-li-jing-yan#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这些年做技术团队管理，我最大的体会其实很简单：技术团队管理不是复杂的管理学，而是让一群工程师能够 <strong>一起把事情做好</strong>。目标清晰，团队才不会白费力气；分工明确，出了问题才有人兜底；流程稳定，协作才不靠运气。再加上团队之间保持信任，很多事情其实都会自然变得顺畅。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[微服务架构设计]]></title>
            <link>https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji</link>
            <guid>https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从单体架构的瓶颈谈起，梳理微服务的核心设计原则、整体架构组成与关键设计问题，也聊聊它的扩展能力、落地挑战与适用边界。]]></description>
            <content:encoded><![CDATA[<p>单体应用膨胀到一定程度，拆分几乎是绕不开的话题。这篇文章梳理微服务架构的由来、核心设计原则和整体架构组成，也聊聊它带来的新代价。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么会出现微服务架构">为什么会出现微服务架构<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BC%9A%E5%87%BA%E7%8E%B0%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="为什么会出现微服务架构的直接链接" title="为什么会出现微服务架构的直接链接" translate="no">​</a></h2>
<p>在早期的软件系统中，大多数应用都采用 <strong>单体架构（Monolithic Architecture）</strong>。所有功能模块都运行在同一个应用进程中，例如用户系统、订单系统、支付系统、库存系统等，都被打包在一个工程中统一部署。这种架构在系统规模较小时非常简单直接，开发效率也较高。</p>
<p>但随着业务不断增长，单体架构的问题逐渐显现出来。系统代码越来越庞大，一个项目可能包含几十万甚至上百万行代码，任何一次修改都需要重新构建和部署整个系统。同时，不同功能模块之间耦合严重，一个模块出现问题很可能影响整个系统运行。当访问量增长时，系统也无法只对某个热点模块进行扩展，只能整体扩容，资源利用效率很低。</p>
<p>在这种背景下，微服务架构逐渐成为大型系统的主流设计方式。微服务的核心思想是 <strong>将一个庞大的系统拆分为多个独立服务，每个服务负责一个明确的业务能力，并可以独立开发、部署和扩展</strong>。这样不仅可以降低系统复杂度，还可以提升系统的可维护性和扩展能力。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="微服务架构的核心设计思想">微服务架构的核心设计思想<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E7%9A%84%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E6%80%9D%E6%83%B3" class="hash-link" aria-label="微服务架构的核心设计思想的直接链接" title="微服务架构的核心设计思想的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2026-04-Gemini_Generated_Image_qjw17xqjw17xqjw1-40a2fd0d4db35c47974e2ed1357238bd.webp" width="1408" height="768" class="img_ev3q"></p>
<p>微服务架构并不仅仅是把系统拆分成很多小服务，更重要的是围绕 <strong>业务能力（Business Capability）进行系统设计</strong>。每个服务应该具备清晰的职责边界，并且能够独立运行。</p>
<p>在微服务设计中，通常会遵循几个核心原则：</p>
<ul>
<li class=""><strong>单一职责</strong>：每个微服务只负责一个核心业务领域，例如用户服务、订单服务、库存服务等。这样可以避免服务职责混乱，也方便团队分工协作。</li>
<li class=""><strong>服务自治</strong>：每个微服务都拥有自己的数据库和数据模型，避免多个服务共享数据库。这样可以减少服务之间的强依赖，使系统更加松耦合。</li>
<li class=""><strong>独立部署</strong>：每个服务可以独立发布和升级，而不需要影响其他服务。这一点对于大型团队尤为重要，可以大幅提高研发效率。</li>
<li class=""><strong>接口驱动通信</strong>：服务之间通过 API 或消息系统进行通信，而不是直接调用数据库或共享代码库。</li>
</ul>
<p>这些原则共同构成了微服务架构的基础。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="微服务系统的整体架构设计">微服务系统的整体架构设计<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E7%9A%84%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="微服务系统的整体架构设计的直接链接" title="微服务系统的整体架构设计的直接链接" translate="no">​</a></h2>
<p>在实际系统中，一个完整的微服务架构通常由多个基础组件构成，而不仅仅是简单的服务拆分。</p>
<p>系统最外层通常是 <strong>API Gateway（网关层）</strong>。网关作为系统统一入口，负责处理客户端请求，并提供认证、限流、日志记录、路由转发等功能。通过网关，可以屏蔽后端服务的复杂性，使客户端只需要面对一个统一接口。</p>
<p>网关之后是 <strong>业务服务层</strong>。这一层由多个微服务组成，例如用户服务、订单服务、商品服务、支付服务等。每个服务负责独立业务能力，并通过 API 或 RPC 进行交互。</p>
<p>为了保证服务之间能够互相发现和通信，系统通常会引入 <strong>服务注册与发现系统</strong>，例如常见的 etcd、Nacos、Consul 等组件。服务启动时会注册到注册中心，其他服务可以通过注册中心获取目标服务地址。</p>
<p>在高并发系统中，通常还会引入 <strong>消息队列（MQ）</strong> 来实现异步通信，例如 Kafka、RabbitMQ 或 RocketMQ。消息队列可以用于削峰填谷、解耦服务以及构建事件驱动架构。</p>
<p>数据层方面，每个微服务一般会拥有自己的数据库，例如 MySQL、PostgreSQL 或 NoSQL 数据库。为了提升性能，还会使用 Redis 作为缓存层，减少数据库压力。</p>
<p>最终整个系统通常会运行在 <strong>容器平台（例如 Kubernetes）</strong> 上，通过容器编排实现服务的自动部署、扩容以及故障恢复。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="微服务架构中的关键设计问题">微服务架构中的关键设计问题<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E4%B8%AD%E7%9A%84%E5%85%B3%E9%94%AE%E8%AE%BE%E8%AE%A1%E9%97%AE%E9%A2%98" class="hash-link" aria-label="微服务架构中的关键设计问题的直接链接" title="微服务架构中的关键设计问题的直接链接" translate="no">​</a></h2>
<p>虽然微服务架构带来了很多优势，但也会引入新的复杂性。在系统设计时，需要重点关注几个关键问题。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-服务拆分粒度">1) 服务拆分粒度<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#1-%E6%9C%8D%E5%8A%A1%E6%8B%86%E5%88%86%E7%B2%92%E5%BA%A6" class="hash-link" aria-label="1) 服务拆分粒度的直接链接" title="1) 服务拆分粒度的直接链接" translate="no">​</a></h3>
<p>如果服务拆分过粗，系统仍然会变成大型单体；如果拆分过细，则会导致服务数量过多，增加系统复杂度。通常建议以业务领域为边界进行拆分，而不是按照技术模块拆分。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-服务通信方式">2) 服务通信方式<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#2-%E6%9C%8D%E5%8A%A1%E9%80%9A%E4%BF%A1%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="2) 服务通信方式的直接链接" title="2) 服务通信方式的直接链接" translate="no">​</a></h3>
<p>同步调用通常使用 HTTP 或 gRPC，异步通信则通过消息队列实现。在高并发系统中，大量同步调用可能会导致服务之间形成复杂调用链，因此需要合理设计通信模式。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-分布式事务">3) 分布式事务<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#3-%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1" class="hash-link" aria-label="3) 分布式事务的直接链接" title="3) 分布式事务的直接链接" translate="no">​</a></h3>
<p>在单体系统中，数据库事务可以保证数据一致性；但在微服务架构中，每个服务拥有独立数据库，传统事务机制无法直接使用。因此通常会采用 <strong>最终一致性（Eventual Consistency）</strong>、Saga 模式或分布式事务框架来解决数据一致性问题。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-系统稳定性">4) 系统稳定性<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#4-%E7%B3%BB%E7%BB%9F%E7%A8%B3%E5%AE%9A%E6%80%A7" class="hash-link" aria-label="4) 系统稳定性的直接链接" title="4) 系统稳定性的直接链接" translate="no">​</a></h3>
<p>在微服务架构中，服务数量增加意味着故障点也随之增加，因此需要引入限流、熔断、降级等机制，例如通过服务治理框架或 Service Mesh 来实现流量控制。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="微服务架构的扩展能力">微服务架构的扩展能力<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E7%9A%84%E6%89%A9%E5%B1%95%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="微服务架构的扩展能力的直接链接" title="微服务架构的扩展能力的直接链接" translate="no">​</a></h2>
<p>微服务架构最大的优势之一就是 <strong>弹性扩展能力</strong>。</p>
<p>在传统单体架构中，如果某个功能模块成为系统瓶颈，例如订单服务压力过大，就必须整体扩容整个系统。而在微服务架构中，可以只对订单服务进行扩展，例如增加多个实例进行负载均衡。</p>
<p>在云原生环境中，Kubernetes 可以根据系统负载自动扩展服务实例数量：流量高峰期自动扩容服务节点，流量下降后再自动缩容，从而提高资源利用率。这种按需扩展能力，使得微服务架构非常适合互联网高并发场景。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="微服务架构的挑战">微服务架构的挑战<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E7%9A%84%E6%8C%91%E6%88%98" class="hash-link" aria-label="微服务架构的挑战的直接链接" title="微服务架构的挑战的直接链接" translate="no">​</a></h2>
<p>虽然微服务架构解决了很多问题，但它并不是万能的。相比单体架构，微服务系统会更加复杂：</p>
<ul>
<li class=""><strong>运维复杂度增加</strong>：系统中可能存在几十甚至上百个服务，每个服务都有自己的部署流程和监控体系，因此需要完善的 DevOps 平台支持。</li>
<li class=""><strong>服务治理问题</strong>：随着服务数量增加，服务调用关系会变得非常复杂，需要借助服务治理系统进行管理。</li>
<li class=""><strong>分布式系统固有问题</strong>：例如网络延迟、数据一致性、服务依赖等。这些问题在单体系统中几乎不存在，但在微服务系统中却需要重点解决。</li>
</ul>
<p>因此在系统规模较小时，单体架构往往更加简单高效，而微服务架构更适合 <strong>中大型系统</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-jia-gou-she-ji#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>微服务架构的本质，是通过 <strong>服务拆分和自治</strong> 来降低系统复杂度，并提升系统的可扩展性与可维护性。它使系统能够根据业务增长进行灵活扩展，并支持团队并行开发，从而更好地支撑大型互联网系统的发展。</p>
<p>但微服务并不是简单的技术升级，它需要配套的基础设施，例如服务注册中心、API 网关、消息系统、监控系统以及容器平台等。只有在完整技术体系支持下，微服务架构才能真正发挥价值。</p>
<p>在实际系统设计中，架构师需要根据业务规模、团队能力以及系统复杂度进行综合判断，选择最适合的架构模式，而不是盲目追求微服务化。真正优秀的架构设计，并不是技术最复杂的方案，而是 <strong>能够在复杂度与收益之间取得平衡的方案</strong>。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[一级二级缓存设计]]></title>
            <link>https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun</link>
            <guid>https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[介绍一级（本地）缓存与二级（Redis）缓存的分层架构：访问流程、两级缓存各自解决的问题、一致性与容量控制，以及常见的缓存更新策略。]]></description>
            <content:encoded><![CDATA[<p>数据库扛不住时，大家的第一反应是加 Redis；当 Redis 也开始吃紧，就轮到多级缓存出场了。这篇文章聊聊最常见的一级 + 二级缓存设计。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么需要多级缓存">为什么需要多级缓存<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E5%A4%9A%E7%BA%A7%E7%BC%93%E5%AD%98" class="hash-link" aria-label="为什么需要多级缓存的直接链接" title="为什么需要多级缓存的直接链接" translate="no">​</a></h2>
<p>在互联网系统中，随着业务规模不断增长，数据库往往会成为系统的性能瓶颈。大量请求如果直接访问数据库，不仅会带来高延迟，还会导致数据库压力过大，甚至出现连接耗尽、查询变慢等问题。因此，几乎所有高并发系统都会引入 <strong>缓存（Cache）</strong> 来提升系统性能。</p>
<p>最常见的缓存方式是使用 Redis 作为统一缓存层。客户端请求首先访问 Redis，如果缓存命中，就直接返回数据；如果缓存未命中，再访问数据库并将结果写入缓存。这种方式已经能够显著降低数据库压力。</p>
<p>但随着系统规模继续扩大，仅仅依赖 Redis 仍然可能出现新的问题。例如在高 QPS 场景下，大量请求同时访问 Redis，会产生网络开销和 Redis CPU 压力。同时，某些热点数据可能会被频繁读取，每次都经过网络访问 Redis，也会带来额外延迟。</p>
<p>为了解决这些问题，很多大型系统会引入 <strong>多级缓存架构（Multi-Level Cache）</strong>，其中最常见的一种模式就是 <strong>一级缓存 + 二级缓存设计</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一级二级缓存的基本架构">一级二级缓存的基本架构<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E4%B8%80%E7%BA%A7%E4%BA%8C%E7%BA%A7%E7%BC%93%E5%AD%98%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="一级二级缓存的基本架构的直接链接" title="一级二级缓存的基本架构的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2026-04-Gemini_Generated_Image_a0c1wqa0c1wqa0c1-f8f48454a1f04bb95342ba3fc5447ebd.webp" width="1408" height="768" class="img_ev3q"></p>
<p>一级二级缓存本质上是一种 <strong>缓存分层架构</strong>，通常系统会设计两层缓存。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="一级缓存l1-cache">一级缓存（L1 Cache）<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E4%B8%80%E7%BA%A7%E7%BC%93%E5%AD%98l1-cache" class="hash-link" aria-label="一级缓存（L1 Cache）的直接链接" title="一级缓存（L1 Cache）的直接链接" translate="no">​</a></h3>
<p>一级缓存通常位于 <strong>应用进程内部</strong>，也称为 <strong>本地缓存（Local Cache）</strong>。例如：</p>
<ul>
<li class="">Go：bigcache / ristretto</li>
<li class="">Java：Caffeine / Guava Cache</li>
<li class="">Node：LRU Cache</li>
</ul>
<p>一级缓存的特点是：</p>
<ul>
<li class="">访问速度极快（内存访问）</li>
<li class="">不需要网络通信</li>
<li class="">每个服务实例都有自己的缓存</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="二级缓存l2-cache">二级缓存（L2 Cache）<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E4%BA%8C%E7%BA%A7%E7%BC%93%E5%AD%98l2-cache" class="hash-link" aria-label="二级缓存（L2 Cache）的直接链接" title="二级缓存（L2 Cache）的直接链接" translate="no">​</a></h3>
<p>二级缓存通常是 <strong>分布式缓存系统</strong>，最常见的是 Redis 或 Memcached。所有服务实例共享同一个缓存系统。</p>
<p>二级缓存的特点是：</p>
<ul>
<li class="">多节点共享</li>
<li class="">数据一致性更好</li>
<li class="">容量更大</li>
<li class="">支持分布式部署</li>
</ul>
<p>整个系统的访问流程通常是：客户端请求 → 服务进程 → 查询一级缓存 → 未命中则查询二级缓存 → 再未命中则查询数据库。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一级二级缓存访问流程">一级二级缓存访问流程<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E4%B8%80%E7%BA%A7%E4%BA%8C%E7%BA%A7%E7%BC%93%E5%AD%98%E8%AE%BF%E9%97%AE%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="一级二级缓存访问流程的直接链接" title="一级二级缓存访问流程的直接链接" translate="no">​</a></h2>
<p>在实际系统中，一次请求的典型访问流程如下：</p>
<p>首先，请求进入应用服务，系统会优先查询 <strong>一级缓存</strong>。如果数据存在，直接返回结果，这种情况延迟最低，通常只需要微秒级时间。</p>
<p>如果一级缓存未命中，则继续查询 <strong>二级缓存（Redis）</strong>。如果 Redis 中存在数据，则返回结果，并将该数据写入一级缓存，以便后续请求可以直接命中本地缓存。</p>
<p>如果 Redis 中也没有数据，则说明缓存未命中，此时系统需要访问数据库。数据库返回结果后，系统会同时写入 Redis 和一级缓存，从而建立新的缓存数据。</p>
<p>整个流程可以理解为：</p>
<p>L1 Cache → L2 Cache → Database</p>
<p>通过这种方式，大部分请求都可以在缓存层被拦截，从而避免频繁访问数据库。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么要设计两级缓存">为什么要设计两级缓存<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E8%AE%BE%E8%AE%A1%E4%B8%A4%E7%BA%A7%E7%BC%93%E5%AD%98" class="hash-link" aria-label="为什么要设计两级缓存的直接链接" title="为什么要设计两级缓存的直接链接" translate="no">​</a></h2>
<p>一级缓存和二级缓存各自解决不同的问题，两者结合可以大幅提升系统性能。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="降低-redis-压力">降低 Redis 压力<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E9%99%8D%E4%BD%8E-redis-%E5%8E%8B%E5%8A%9B" class="hash-link" aria-label="降低 Redis 压力的直接链接" title="降低 Redis 压力的直接链接" translate="no">​</a></h3>
<p>在高并发系统中，如果所有请求都访问 Redis，Redis 可能成为新的瓶颈。通过一级缓存，大量热点数据可以直接在应用内存中返回，从而减少 Redis 请求数量。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="减少网络开销">减少网络开销<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E5%87%8F%E5%B0%91%E7%BD%91%E7%BB%9C%E5%BC%80%E9%94%80" class="hash-link" aria-label="减少网络开销的直接链接" title="减少网络开销的直接链接" translate="no">​</a></h3>
<p>访问 Redis 需要网络通信，即使延迟只有 1ms，在高 QPS 场景下也会产生明显开销。而一级缓存是进程内访问，速度远远快于网络调用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="提升系统吞吐量">提升系统吞吐量<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E6%8F%90%E5%8D%87%E7%B3%BB%E7%BB%9F%E5%90%9E%E5%90%90%E9%87%8F" class="hash-link" aria-label="提升系统吞吐量的直接链接" title="提升系统吞吐量的直接链接" translate="no">​</a></h3>
<p>通过一级缓存，系统可以处理更多请求，而不会增加 Redis 或数据库负载。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="提升热点数据访问效率">提升热点数据访问效率<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E6%8F%90%E5%8D%87%E7%83%AD%E7%82%B9%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE%E6%95%88%E7%8E%87" class="hash-link" aria-label="提升热点数据访问效率的直接链接" title="提升热点数据访问效率的直接链接" translate="no">​</a></h3>
<p>在很多业务场景中，少量热点数据会被频繁访问，例如热门商品、用户信息、配置数据、推荐结果等。一级缓存可以让这些热点数据直接在本地命中。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一级缓存设计需要注意的问题">一级缓存设计需要注意的问题<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E4%B8%80%E7%BA%A7%E7%BC%93%E5%AD%98%E8%AE%BE%E8%AE%A1%E9%9C%80%E8%A6%81%E6%B3%A8%E6%84%8F%E7%9A%84%E9%97%AE%E9%A2%98" class="hash-link" aria-label="一级缓存设计需要注意的问题的直接链接" title="一级缓存设计需要注意的问题的直接链接" translate="no">​</a></h2>
<p>虽然一级缓存性能很好，但它也会带来一些新的问题。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-数据一致性">1) 数据一致性<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#1-%E6%95%B0%E6%8D%AE%E4%B8%80%E8%87%B4%E6%80%A7" class="hash-link" aria-label="1) 数据一致性的直接链接" title="1) 数据一致性的直接链接" translate="no">​</a></h3>
<p>由于一级缓存存在于每个服务实例中，不同实例的缓存数据可能不同步。当数据更新时，如果没有正确的缓存失效机制，可能会导致读取到旧数据。为了解决这个问题，通常会采用 <strong>缓存失效策略</strong>，例如在数据更新时，通过消息队列或发布订阅机制通知所有服务实例清除本地缓存。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-缓存容量控制">2) 缓存容量控制<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#2-%E7%BC%93%E5%AD%98%E5%AE%B9%E9%87%8F%E6%8E%A7%E5%88%B6" class="hash-link" aria-label="2) 缓存容量控制的直接链接" title="2) 缓存容量控制的直接链接" translate="no">​</a></h3>
<p>一级缓存存在于应用内存中，如果缓存数据过多，可能会占用大量内存，影响系统稳定性。因此通常会使用 LRU 或 LFU 等策略限制缓存大小。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-缓存穿透和缓存击穿">3) 缓存穿透和缓存击穿<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#3-%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F%E5%92%8C%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF" class="hash-link" aria-label="3) 缓存穿透和缓存击穿的直接链接" title="3) 缓存穿透和缓存击穿的直接链接" translate="no">​</a></h3>
<p>当大量请求同时访问不存在的数据，可能会直接冲击数据库。通常可以通过布隆过滤器或互斥锁机制进行防护。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="缓存更新策略设计">缓存更新策略设计<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E7%BC%93%E5%AD%98%E6%9B%B4%E6%96%B0%E7%AD%96%E7%95%A5%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="缓存更新策略设计的直接链接" title="缓存更新策略设计的直接链接" translate="no">​</a></h2>
<p>在多级缓存系统中，缓存更新策略非常关键。常见策略包括：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-cache-aside旁路缓存">1) Cache Aside（旁路缓存）<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#1-cache-aside%E6%97%81%E8%B7%AF%E7%BC%93%E5%AD%98" class="hash-link" aria-label="1) Cache Aside（旁路缓存）的直接链接" title="1) Cache Aside（旁路缓存）的直接链接" translate="no">​</a></h3>
<p>应用程序先查询缓存，如果未命中再查询数据库，然后写入缓存。这是最常见的一种模式。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-write-through写穿透">2) Write Through（写穿透）<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#2-write-through%E5%86%99%E7%A9%BF%E9%80%8F" class="hash-link" aria-label="2) Write Through（写穿透）的直接链接" title="2) Write Through（写穿透）的直接链接" translate="no">​</a></h3>
<p>应用在写入数据库的同时也更新缓存，保证缓存和数据库数据同步。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-write-back写回缓存">3) Write Back（写回缓存）<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#3-write-back%E5%86%99%E5%9B%9E%E7%BC%93%E5%AD%98" class="hash-link" aria-label="3) Write Back（写回缓存）的直接链接" title="3) Write Back（写回缓存）的直接链接" translate="no">​</a></h3>
<p>应用只写缓存，由缓存系统异步写入数据库。这种方式性能更高，但实现复杂。</p>
<p>在互联网系统中，大多数业务都会采用 <strong>Cache Aside 模式</strong>，因为它实现简单且稳定。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一级二级缓存架构适用场景">一级二级缓存架构适用场景<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E4%B8%80%E7%BA%A7%E4%BA%8C%E7%BA%A7%E7%BC%93%E5%AD%98%E6%9E%B6%E6%9E%84%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="一级二级缓存架构适用场景的直接链接" title="一级二级缓存架构适用场景的直接链接" translate="no">​</a></h2>
<p>多级缓存架构通常适用于以下场景：</p>
<ul>
<li class=""><strong>高并发读取系统</strong>：例如商品详情页、用户信息查询、配置读取等。</li>
<li class=""><strong>热点数据访问频繁</strong>：例如排行榜、推荐列表等。</li>
<li class=""><strong>数据库压力较大</strong>：通过缓存层可以显著减少数据库查询次数。</li>
</ul>
<p>对于一些实时一致性要求非常高的系统，例如金融交易系统，通常不会使用多级缓存，而是直接访问数据库。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>一级二级缓存的核心思路是分层拦截：本地缓存吃掉最热的流量，Redis 承接实例间共享的数据，数据库只处理真正的未命中。这种架构能显著降低访问延迟和后端压力，但代价是引入了多副本一致性问题，需要配合失效通知、容量控制和穿透防护一起设计。是否值得上多级缓存，取决于系统的读写比例和一致性要求——对一致性敏感的场景，简单的方案反而更安全。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[ETCD 探索]]></title>
            <link>https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun</link>
            <guid>https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从分布式协调的实际需求出发，介绍 etcd 的定位与 Raft 强一致原理，记录一次集群搭建与 etcdctl 验证过程，并对比它与 Redis 在协调场景下的取舍。]]></description>
            <content:encoded><![CDATA[<p>写这篇文章的起因，是我在整理 Kubernetes 相关知识时，反复撞见 etcd 这个名字：集群状态存在它里面，服务发现依赖它，连不少配置中心的底层也是它。与其把它当黑盒，不如单独拿出来搭一遍、理一遍。在分布式系统中，经常会遇到这样的问题：</p>
<!-- -->
<ul>
<li class="">服务节点需要共享配置</li>
<li class="">系统需要做服务发现</li>
<li class="">分布式锁需要一个协调中心</li>
<li class="">集群需要一个一致性的状态存储</li>
</ul>
<p>这些问题，本质上都需要一个 <strong>可靠的分布式协调系统</strong>。</p>
<p>单机时代这些需求可以用一个数据库表甚至一个文件解决，但节点一多，问题就变成了：多个副本之间如何对"当前状态"达成一致？谁说了算？某个节点挂了之后数据还可信吗？这正是分布式协调系统要回答的问题。</p>
<p>而在现代云原生体系中，最常用的组件就是 <strong>etcd</strong>。</p>
<p>例如：</p>
<ul>
<li class="">Kubernetes</li>
<li class="">CoreDNS</li>
<li class="">service mesh</li>
<li class="">分布式配置中心</li>
</ul>
<p>这些系统的底层都依赖 etcd。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="etcd-是什么">etcd 是什么<a href="https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun#etcd-%E6%98%AF%E4%BB%80%E4%B9%88" class="hash-link" aria-label="etcd 是什么的直接链接" title="etcd 是什么的直接链接" translate="no">​</a></h2>
<p>简单来说：</p>
<p><strong>etcd 就是一个高可靠的分布式 Key-Value 数据库。</strong></p>
<p>但它和普通数据库最大的区别是：</p>
<p><strong>它是为"分布式协调"而设计的。</strong></p>
<p>这个定位决定了它的取舍：它不追求海量数据和极限吞吐，而是追求"存进去的每一条数据都可信"。所以它适合存元数据、配置、状态这类小而关键的数据，而不适合当业务数据库用。</p>
<p>它的主要特点有：</p>
<ul>
<li class="">强一致（Strong Consistency）</li>
<li class="">支持分布式集群</li>
<li class="">提供 Watch 监听机制</li>
<li class="">支持事务</li>
<li class="">提供租约（Lease）机制</li>
</ul>
<p>这几个特性组合起来很有意思：</p>
<ol>
<li class="">
<p>Watch 让客户端可以订阅某个 key 或前缀的变化，配置一改，所有节点实时感知，不用轮询。</p>
</li>
<li class="">
<p>Lease 相当于带 TTL 的"心跳凭证"，key 可以绑定在租约上，客户端一旦停止续约，key 自动删除——服务注册与故障剔除就是靠它实现的。</p>
</li>
<li class="">
<p>事务（Txn）提供 compare-and-swap 语义，"如果这个 key 不存在就写入"可以原子完成，这是分布式锁的基础。</p>
</li>
</ol>
<p>很多分布式系统都会用 etcd 做：</p>
<ul>
<li class="">服务注册中心</li>
<li class="">配置中心</li>
<li class="">分布式锁</li>
<li class="">Leader 选举</li>
</ul>
<p>例如 Kubernetes 就把整个 <strong>集群状态</strong> 存在 etcd 中。kube-apiserver 是唯一直接读写 etcd 的组件，其他控制器都通过 apiserver 的 Watch 机制间接感知状态变化。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="强一致性raft-共识算法">强一致性（Raft 共识算法）<a href="https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun#%E5%BC%BA%E4%B8%80%E8%87%B4%E6%80%A7raft-%E5%85%B1%E8%AF%86%E7%AE%97%E6%B3%95" class="hash-link" aria-label="强一致性（Raft 共识算法）的直接链接" title="强一致性（Raft 共识算法）的直接链接" translate="no">​</a></h2>
<p>etcd 内部使用的是 <strong>Raft 共识算法</strong>。</p>
<p>Raft 的核心思想是：</p>
<p>整个集群中会选举出一个 <strong>Leader 节点</strong>。</p>
<p>所有写请求必须经过 Leader。</p>
<p>流程大致是：</p>
<p>客户端 → Leader → 同步到多数节点 → 提交成功</p>
<p>只有当：</p>
<p><strong>超过一半节点确认写入</strong></p>
<p>数据才算真正提交。</p>
<p>这里可以再拆细一点。Raft 中每个节点处于三种角色之一：Leader、Follower、Candidate。正常运行时只有一个 Leader，它周期性地向所有 Follower 发送心跳；如果某个 Follower 在超时时间内收不到心跳，它会把自己变成 Candidate 发起选举，拿到多数票的节点成为新 Leader。</p>
<p>写入走的是日志复制（Log Replication）：Leader 先把写操作追加到自己的日志，再并行发给所有 Follower；当<strong>多数派</strong>（majority，即超过半数）节点都持久化了这条日志，Leader 才把它标记为已提交，然后应答客户端。</p>
<p>"多数派"这个设计是强一致的关键：任何两个多数派集合必然有交集，所以即使 Leader 崩溃，新选出的 Leader 也一定包含所有已提交的数据，不会丢写。</p>
<p>这也解释了为什么 etcd 集群节点数推荐是奇数（3、5、7）：</p>
<ul>
<li class="">3 节点允许挂 1 个，5 节点允许挂 2 个</li>
<li class="">4 节点同样只允许挂 1 个（多数派需要 3 个），容错能力和 3 节点一样，反而多了一份同步开销</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="etcd-集群搭建">etcd 集群搭建<a href="https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun#etcd-%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA" class="hash-link" aria-label="etcd 集群搭建的直接链接" title="etcd 集群搭建的直接链接" translate="no">​</a></h2>
<p>部署环境：</p>
<p>操作系统：</p>
<p>Debian 11.5.0</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-下载-etcd">1 下载 etcd<a href="https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun#1-%E4%B8%8B%E8%BD%BD-etcd" class="hash-link" aria-label="1 下载 etcd的直接链接" title="1 下载 etcd的直接链接" translate="no">​</a></h3>
<p>从 github 下载 etcd 二进制文件：</p>
<p><a href="https://github.com/etcd-io/etcd/releases" target="_blank" rel="noopener noreferrer" class="">https://github.com/etcd-io/etcd/releases</a></p>
<p>release 页面按平台提供压缩包，选对应架构（如 linux-amd64）下载即可。etcd 是单个静态二进制，没有额外的运行时依赖，这也是它部署简单的原因之一。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-安装-etcd">2 安装 etcd<a href="https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun#2-%E5%AE%89%E8%A3%85-etcd" class="hash-link" aria-label="2 安装 etcd的直接链接" title="2 安装 etcd的直接链接" translate="no">​</a></h3>
<p>解压之后，将以下文件放入：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">/usr/local/bin</span><br></div></code></pre></div></div>
<p>主要是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">etcd</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">etcdctl</span><br></div></code></pre></div></div>
<p>两个文件分工明确：<code>etcd</code> 是服务端进程，<code>etcdctl</code> 是命令行客户端，后面所有的查询、写入、维护操作都通过它完成。放进 <code>/usr/local/bin</code> 是为了让它们进入 PATH，任意目录下都能直接调用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-设置执行权限">3 设置执行权限<a href="https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun#3-%E8%AE%BE%E7%BD%AE%E6%89%A7%E8%A1%8C%E6%9D%83%E9%99%90" class="hash-link" aria-label="3 设置执行权限的直接链接" title="3 设置执行权限的直接链接" translate="no">​</a></h3>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 解压出来的文件可能没有可执行位，手动补上</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">chmod</span><span class="token plain"> +x /usr/local/bin/etcd</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">chmod</span><span class="token plain"> +x /usr/local/bin/etcdctl</span><br></div></code></pre></div></div>
<p>到这一步，单个节点的二进制就绪。集群模式下，每个节点都重复上述安装步骤，再通过启动参数声明各自的名字、监听地址和初始集群成员列表；节点间通过 2380 端口互相通信，客户端通过 2379 端口访问。我这套环境启用了 TLS，证书统一放在 <code>/opt/etcd/ssl</code> 下，所以下面的验证命令都要带上证书参数。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-验证集群">4 验证集群<a href="https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun#4-%E9%AA%8C%E8%AF%81%E9%9B%86%E7%BE%A4" class="hash-link" aria-label="4 验证集群的直接链接" title="4 验证集群的直接链接" translate="no">​</a></h3>
<p>可以使用 etcdctl 查看数据：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 列出集群中所有 key（只看 key 不看 value）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --cacert/--cert/--key：TLS 双向认证所需的 CA 证书与客户端证书</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --endpoints：把三个节点都写上，客户端会自动负载与故障转移</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token assign-left variable" style="color:hsl(207, 82%, 66%)">ETCDCTL_API</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">3</span><span class="token plain"> etcdctl </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--cacert</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">/opt/etcd/ssl/ca.pem </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--cert</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">/opt/etcd/ssl/server.pem </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--key</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">/opt/etcd/ssl/server-key.pem </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--endpoints</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token string" style="color:hsl(95, 38%, 62%)">"https://172.24.93.151:2379,https://172.24.93.149:2379,https://172.24.93.150:2379"</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">get / </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--prefix</span><span class="token plain"> --keys-only</span><br></div></code></pre></div></div>
<p>命令能返回结果，说明客户端到集群的链路、证书、多数派状态都是通的。<code>--prefix</code> 表示按前缀匹配，配合 <code>/</code> 相当于遍历全部 key。</p>
<p>删除 key：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 按前缀删除，空前缀 "" 会匹配所有 key —— 生产环境慎用</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token assign-left variable" style="color:hsl(207, 82%, 66%)">ETCDCTL_API</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">3</span><span class="token plain"> etcdctl </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--cacert</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">/opt/etcd/ssl/ca.pem </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--cert</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">/opt/etcd/ssl/server.pem </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--key</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">/opt/etcd/ssl/server-key.pem </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--endpoints</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token string" style="color:hsl(95, 38%, 62%)">"https://172.24.93.151:2379,https://172.24.93.149:2379,https://172.24.93.150:2379"</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">del </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--prefix</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">""</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p><code>del --prefix ""</code> 是全量删除。如果这套 etcd 正在给 Kubernetes 当后端，这条命令等于清空整个集群状态，执行前务必确认目标环境。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="etcd-为什么比-redis-更适合做分布式协调">etcd 为什么比 Redis 更适合做分布式协调<a href="https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun#etcd-%E4%B8%BA%E4%BB%80%E4%B9%88%E6%AF%94-redis-%E6%9B%B4%E9%80%82%E5%90%88%E5%81%9A%E5%88%86%E5%B8%83%E5%BC%8F%E5%8D%8F%E8%B0%83" class="hash-link" aria-label="etcd 为什么比 Redis 更适合做分布式协调的直接链接" title="etcd 为什么比 Redis 更适合做分布式协调的直接链接" translate="no">​</a></h2>
<p>很多人会问：</p>
<p>Redis 不是也可以做分布式锁吗？</p>
<p>确实可以。</p>
<p>但 Redis 是 <strong>AP 系统</strong>，而 etcd 是 <strong>CP 系统</strong>。</p>
<p>简单理解就是：</p>
<p>Redis 更注重 <strong>可用性与性能</strong>。</p>
<p>etcd 更注重 <strong>一致性与可靠性</strong>。</p>
<p>具体到锁的场景，差异体现在故障时的行为：Redis 主从复制是异步的，主节点写入锁之后还没同步就宕机，从节点提升为主后锁就"丢"了，两个客户端可能同时持有锁；Redlock 方案试图缓解这个问题，但社区对其安全性一直有争议。而 etcd 的写入必须经过多数派确认才算成功，Leader 切换不会丢已提交的数据，锁的语义在故障场景下依然成立。</p>
<p>代价当然是性能：一次写入要走一轮多数派同步和磁盘落盘，延迟和吞吐都远不如 Redis。所以结论不是谁更好，而是分场景——缓存、计数、允许偶发失效的轻量锁用 Redis；配置、选主、必须严格互斥的锁用 etcd。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>搭建和使用过程中有几点值得记录：</p>
<ol>
<li class="">
<p><code>ETCDCTL_API=3</code> 这个环境变量别漏。老版本 etcdctl 默认走 v2 API，v2 和 v3 的数据完全隔离，用错 API 版本会出现"明明写了却查不到"的假象。较新的版本已默认 v3，但在脚本里显式声明总没错。</p>
</li>
<li class="">
<p><code>--endpoints</code> 建议写全所有节点。只写一个节点时，该节点一旦故障，客户端就直接失联；写全之后客户端能自动切换到健康节点。</p>
</li>
<li class="">
<p>证书路径和 SAN 要匹配。启用 TLS 后，证书里的 IP/域名必须覆盖 endpoints 里实际使用的地址，否则握手阶段就会被拒。</p>
</li>
<li class="">
<p>节点数保持奇数。前面 Raft 部分已经解释过原因：偶数节点不增加容错能力，只增加同步成本。</p>
</li>
<li class="">
<p>etcd 对磁盘延迟敏感。Raft 日志每次提交都要 fsync，磁盘慢会直接拖高写延迟甚至触发 Leader 选举抖动，条件允许尽量用 SSD。</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/14/da-jian-etcdji-qun#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>etcd 本质上是"用性能换一致性"的一个典型设计：Raft 多数派提交保证了任何时刻读到的已提交数据都可信，Watch、Lease、事务这些机制则把"可信的状态存储"进一步变成了服务发现、选主、分布式锁的通用底座。搭建本身不复杂——两个二进制加一组启动参数——真正需要花心思的是 TLS 证书、节点规划和对它适用边界的判断。把它和 Redis 的分工想清楚，各用在各自擅长的场景，比争论谁强更有意义。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Flink 理解]]></title>
            <link>https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian</link>
            <guid>https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从批处理到流式计算的演进讲起,梳理 Flink 解决的实时计算、状态管理、容错与事件时间问题,以及 Stream First、State Driven、Exactly Once 这几个核心设计思想。]]></description>
            <content:encoded><![CDATA[<p>在大数据系统的发展过程中,一直存在一个核心问题:<strong>数据越来越多,但处理速度越来越慢。</strong> 这篇文章从批处理讲起,梳理 Flink 为什么出现、到底解决了什么问题。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="从批处理到流计算">从批处理到流计算<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#%E4%BB%8E%E6%89%B9%E5%A4%84%E7%90%86%E5%88%B0%E6%B5%81%E8%AE%A1%E7%AE%97" class="hash-link" aria-label="从批处理到流计算的直接链接" title="从批处理到流计算的直接链接" translate="no">​</a></h2>
<p>传统的数据处理方式,大多数是<strong>离线处理</strong>:每天晚上跑一次任务,统计用户行为、计算广告数据、生成报表、分析业务指标。这种方式叫 <strong>Batch Processing(批处理)</strong>,典型工具例如:</p>
<ul>
<li class="">Hadoop MapReduce</li>
<li class="">Hive</li>
<li class="">Spark</li>
</ul>
<p>这些系统适合处理<strong>海量历史数据</strong>。但是随着互联网的发展,很多业务开始需要<strong>实时数据处理</strong>,例如:</p>
<ul>
<li class="">实时风控</li>
<li class="">实时推荐</li>
<li class="">实时监控</li>
<li class="">实时广告竞价</li>
<li class="">实时日志分析</li>
</ul>
<p>这些场景有一个共同特点:<strong>数据必须“边产生边处理”</strong>,不能等到第二天。于是就出现了一个新的计算模式:<strong>Stream Processing(流式计算)</strong>。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2026-04-Gemini_Generated_Image_xvg9q2xvg9q2xvg9-d0ff4be7b9e2ef01d55dbbf821b03ac4.webp" width="1600" height="873" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么需要-flink">为什么需要 Flink<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81-flink" class="hash-link" aria-label="为什么需要 Flink的直接链接" title="为什么需要 Flink的直接链接" translate="no">​</a></h2>
<p>早期实时计算系统主要依赖 Storm 和 Spark Streaming,但这些系统都有一些问题。</p>
<p>Storm 虽然是流计算系统,但开发复杂、容错能力一般、状态管理困难。Spark Streaming 虽然稳定,但它的本质是<strong>微批处理(Micro Batch)</strong>,也就是说,它并不是真正的流计算。例如:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">每1秒处理一批数据</span><br></div></code></pre></div></div>
<p>这种模式在很多场景下仍然不够实时。于是 Flink 出现了,它的目标很明确:<strong>构建一个真正的流计算系统。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink-解决了什么问题">Flink 解决了什么问题<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#flink-%E8%A7%A3%E5%86%B3%E4%BA%86%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98" class="hash-link" aria-label="Flink 解决了什么问题的直接链接" title="Flink 解决了什么问题的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-实时数据处理">1) 实时数据处理<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#1-%E5%AE%9E%E6%97%B6%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86" class="hash-link" aria-label="1) 实时数据处理的直接链接" title="1) 实时数据处理的直接链接" translate="no">​</a></h3>
<p>Flink 可以实现真正的<strong>毫秒级实时计算</strong>。以用户点击商品为例:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">点击 → Kafka → Flink → 实时推荐</span><br></div></code></pre></div></div>
<p>整个过程可能只需要<strong>几十毫秒</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-状态管理">2) 状态管理<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#2-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86" class="hash-link" aria-label="2) 状态管理的直接链接" title="2) 状态管理的直接链接" translate="no">​</a></h3>
<p>在流计算中,一个非常重要的问题是<strong>状态(State)</strong>。例如统计用户点击次数:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">用户A 点击 1次</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">用户A 点击 2次</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">用户A 点击 3次</span><br></div></code></pre></div></div>
<p>系统需要记住之前的数据。Flink 内置了状态管理机制(Keyed State、Operator State),并且支持本地状态和 RocksDB 状态存储,这样可以处理<strong>超大规模状态数据</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-容错">3) 容错<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#3-%E5%AE%B9%E9%94%99" class="hash-link" aria-label="3) 容错的直接链接" title="3) 容错的直接链接" translate="no">​</a></h3>
<p>在分布式系统中,节点宕机是常态。Flink 通过 <strong>Checkpoint 机制</strong>来保证数据安全:系统会定期保存计算状态,如果节点宕机,可以从 Checkpoint 恢复计算。这种机制保证了 <strong>Exactly Once(精准一次处理)</strong>——每条数据只会被处理一次。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-事件时间">4) 事件时间<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#4-%E4%BA%8B%E4%BB%B6%E6%97%B6%E9%97%B4" class="hash-link" aria-label="4) 事件时间的直接链接" title="4) 事件时间的直接链接" translate="no">​</a></h3>
<p>在流计算中,还有一个非常难的问题:<strong>事件时间(Event Time)</strong>。日志数据可能会延迟到达,如果只按系统时间计算,就会出现统计错误。</p>
<p>Flink 引入了 <strong>Watermark(水位线)机制</strong>。Watermark 可以帮助系统判断哪些数据已经“基本到齐”,这样就可以正确处理延迟数据、窗口计算和时间聚合。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink-的核心设计思想">Flink 的核心设计思想<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#flink-%E7%9A%84%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E6%80%9D%E6%83%B3" class="hash-link" aria-label="Flink 的核心设计思想的直接链接" title="Flink 的核心设计思想的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-stream-first">1) Stream First<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#1-stream-first" class="hash-link" aria-label="1) Stream First的直接链接" title="1) Stream First的直接链接" translate="no">​</a></h3>
<p>Flink 的核心理念是<strong>一切都是流</strong>,批处理只是<strong>有限数据流</strong>。因此,Flink 的 Batch 和 Stream 使用同一套引擎。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-state-driven">2) State Driven<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#2-state-driven" class="hash-link" aria-label="2) State Driven的直接链接" title="2) State Driven的直接链接" translate="no">​</a></h3>
<p>Flink 是一个<strong>状态驱动的计算系统</strong>,所有计算都围绕状态展开。用户行为统计、实时聚合、实时监控,这些都依赖状态管理。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-exactly-once">3) Exactly Once<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#3-exactly-once" class="hash-link" aria-label="3) Exactly Once的直接链接" title="3) Exactly Once的直接链接" translate="no">​</a></h3>
<p>Flink 非常强调<strong>数据处理的正确性</strong>。通过 Checkpoint、分布式快照和两阶段提交,Flink 可以实现 <strong>Exactly Once 语义</strong>,这是很多实时系统非常关键的能力。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink-在实际系统中的应用">Flink 在实际系统中的应用<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#flink-%E5%9C%A8%E5%AE%9E%E9%99%85%E7%B3%BB%E7%BB%9F%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8" class="hash-link" aria-label="Flink 在实际系统中的应用的直接链接" title="Flink 在实际系统中的应用的直接链接" translate="no">​</a></h2>
<p>Flink 在很多互联网公司都有大量应用,典型场景包括:实时推荐系统、用户行为分析、广告点击统计、实时风控系统、日志实时分析。一条常见的数据链路是:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">用户行为 → Kafka → Flink → ClickHouse</span><br></div></code></pre></div></div>
<p>Flink 在中间负责数据清洗、实时计算和实时聚合。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/14/flink-ji-qun-da-jian#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Flink 的出现,本质上是为了做“真正的流计算”这件事。它用同一套引擎统一了批和流,用内置状态管理支撑大规模有状态计算,用 Checkpoint 机制换来 Exactly Once 的正确性,再用 Watermark 处理乱序和延迟数据。理解了这几点,也就理解了 Flink 和早期实时计算系统的根本差别。后面再去看具体的 API 和集群部署,会顺畅很多。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[Netty 深入学习]]></title>
            <link>https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi</link>
            <guid>https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从 BIO 的困境讲起，梳理 Java NIO 的 Channel、Buffer、Selector 三大组件与 Reactor 线程模型，理解 Netty 高性能网络通信的底层设计思想。]]></description>
            <content:encoded><![CDATA[<p>在分布式系统、微服务架构中，网络通信是最基础也是最重要的一部分。<br>
很多高性能框架（如 Dubbo、gRPC、RocketMQ、Elasticsearch 等）底层都依赖 <strong>Netty</strong> 来完成网络通信。</p>
<!-- -->
<p>我最初接触 Netty 时，直接去看它的 API 和示例代码，结果越看越糊涂：为什么要有 EventLoop？为什么连接和读写要分开两组线程？后来才意识到，这些设计不是凭空出现的，而是为了解决传统网络编程的固有问题。绕过这段演进史去学 Netty，等于背答案不看题目。</p>
<p>所以这篇笔记不急着写代码，先把 Netty 背后的 <strong>NIO 网络模型设计思想</strong> 理清楚。理解了这条主线，Netty 的各种组件就都能对号入座了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="传统网络编程的问题">传统网络编程的问题<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#%E4%BC%A0%E7%BB%9F%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E7%9A%84%E9%97%AE%E9%A2%98" class="hash-link" aria-label="传统网络编程的问题的直接链接" title="传统网络编程的问题的直接链接" translate="no">​</a></h2>
<p>在早期 Java 网络编程中，大多数程序使用的是 <strong>BIO（Blocking IO）</strong> 模型。</p>
<p>BIO 的"阻塞"体现在两个地方：<code>accept()</code> 等待新连接时会阻塞，<code>read()</code> 等待数据到达时也会阻塞。一个线程一旦阻塞在某个连接的读写上，就什么别的事都干不了。</p>
<p>例如：</p>
<p>服务器每接入一个客户端连接，就创建一个线程。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">一个连接 = 一个线程</span><br></div></code></pre></div></div>
<p>这个模型的好处是编程简单直观——每个线程只管自己那一个连接，从头读到尾就行。连接数少的时候完全够用。</p>
<p>但如果连接很多，比如：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">1万连接 = 1万个线程</span><br></div></code></pre></div></div>
<p>这会带来几个严重问题：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="线程资源消耗巨大">线程资源消耗巨大<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#%E7%BA%BF%E7%A8%8B%E8%B5%84%E6%BA%90%E6%B6%88%E8%80%97%E5%B7%A8%E5%A4%A7" class="hash-link" aria-label="线程资源消耗巨大的直接链接" title="线程资源消耗巨大的直接链接" translate="no">​</a></h3>
<p>线程本身需要内存和调度成本。每个 Java 线程都要占用独立的栈内存，连接数上万时，光是线程栈就能吃掉大量内存，还没算上操作系统维护线程结构的开销。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="线程上下文切换开销大">线程上下文切换开销大<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#%E7%BA%BF%E7%A8%8B%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2%E5%BC%80%E9%94%80%E5%A4%A7" class="hash-link" aria-label="线程上下文切换开销大的直接链接" title="线程上下文切换开销大的直接链接" translate="no">​</a></h3>
<p>CPU需要频繁在不同线程之间切换。每次切换都要保存和恢复寄存器、刷新缓存，线程数远超 CPU 核心数时，相当一部分 CPU 时间花在了切换本身，而不是处理业务。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="系统扩展性差">系统扩展性差<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#%E7%B3%BB%E7%BB%9F%E6%89%A9%E5%B1%95%E6%80%A7%E5%B7%AE" class="hash-link" aria-label="系统扩展性差的直接链接" title="系统扩展性差的直接链接" translate="no">​</a></h3>
<p>连接数量一多，系统就容易崩溃。更尴尬的是，大量连接其实是空闲的（比如长连接场景下多数客户端并不总在发数据），却各自占着一个线程干等——资源浪费在了"等待"上。</p>
<p>因此，传统 BIO 并不适合 <strong>高并发网络服务</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="nio-的核心设计思想">NIO 的核心设计思想<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#nio-%E7%9A%84%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E6%80%9D%E6%83%B3" class="hash-link" aria-label="NIO 的核心设计思想的直接链接" title="NIO 的核心设计思想的直接链接" translate="no">​</a></h2>
<p>为了解决 BIO 的问题，Java 提出了 <strong>NIO（Non-Blocking IO）</strong>。</p>
<p>NIO 的核心思想非常简单：</p>
<blockquote>
<p><strong>用少量线程，管理大量连接</strong></p>
</blockquote>
<p>思路的转变在于：既然大部分连接大部分时间都是空闲的，那就不要让线程守着连接干等，而是把"哪个连接有数据了"这件事交给操作系统去监视，线程只在真正有事件发生时才出手处理。这就是 <strong>IO 多路复用</strong> 的思想，在 Linux 上对应的就是 select/poll/epoll 这类系统调用。</p>
<p>它依赖三个核心组件：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Channel</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Buffer</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Selector</span><br></div></code></pre></div></div>
<p>这三者构成了 NIO 的核心架构。下面逐个来看。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="channel通道">Channel（通道）<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#channel%E9%80%9A%E9%81%93" class="hash-link" aria-label="Channel（通道）的直接链接" title="Channel（通道）的直接链接" translate="no">​</a></h2>
<p>Channel 可以理解为：</p>
<blockquote>
<p><strong>数据传输的管道</strong></p>
</blockquote>
<p>和传统 IO 不同的是：</p>
<p>传统 IO：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">输入流 / 输出流</span><br></div></code></pre></div></div>
<p>而 Channel：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">是双向的</span><br></div></code></pre></div></div>
<p>既可以读，也可以写。Channel 本质就是 <strong>网络连接的抽象</strong>。</p>
<p>在 Java NIO 中，常用的实现有 <code>ServerSocketChannel</code>（监听端口、接收连接）和 <code>SocketChannel</code>（代表一条具体的 TCP 连接）。Channel 可以被设置为非阻塞模式——这是它能被 Selector 统一管理的前提：读不到数据时立即返回，而不是卡住线程。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="buffer缓冲区">Buffer（缓冲区）<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#buffer%E7%BC%93%E5%86%B2%E5%8C%BA" class="hash-link" aria-label="Buffer（缓冲区）的直接链接" title="Buffer（缓冲区）的直接链接" translate="no">​</a></h2>
<p>在 NIO 中，所有数据都必须先进入 <strong>Buffer</strong>。</p>
<p>可以理解为：</p>
<blockquote>
<p><strong>数据的临时存储区域</strong></p>
</blockquote>
<p>数据流程：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">网络 -&gt; Buffer -&gt; 程序</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">程序 -&gt; Buffer -&gt; 网络</span><br></div></code></pre></div></div>
<p>Buffer 本质是一块带状态的内存区域，内部靠 position、limit、capacity 几个指针来记录"写到哪了、能读到哪"。读写模式的切换要靠 <code>flip()</code> 这类方法完成，这也是 NIO 原生 API 出了名容易写错的地方之一。Netty 后来自己实现了 ByteBuf，很大程度上就是为了摆脱这套别扭的操作。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="selector选择器">Selector（选择器）<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#selector%E9%80%89%E6%8B%A9%E5%99%A8" class="hash-link" aria-label="Selector（选择器）的直接链接" title="Selector（选择器）的直接链接" translate="no">​</a></h2>
<p>Selector 是 <strong>NIO 最核心的组件</strong>。</p>
<p>它的作用是：</p>
<blockquote>
<p><strong>用一个线程管理多个 Channel</strong></p>
</blockquote>
<p>也就是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">一个线程</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">监听多个连接</span><br></div></code></pre></div></div>
<p>工作方式类似：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">事件轮询</span><br></div></code></pre></div></div>
<p>具体来说，每个 Channel 注册到 Selector 时会声明自己关心的事件类型：新连接到达（ACCEPT）、数据可读（READ）、可以写出（WRITE）等。线程调用 <code>select()</code> 阻塞等待，一旦有任意 Channel 就绪，就返回就绪的集合，线程挨个处理即可。</p>
<p>流程：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Selector</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   |</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">监听多个 Channel</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   |</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">哪个 Channel 有事件</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   |</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">处理哪个</span><br></div></code></pre></div></div>
<p>这样一来，线程的时间全部花在"处理就绪事件"上，而不是"等待某个连接"上——这正是 NIO 相比 BIO 的根本差别。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reactor-线程模型">Reactor 线程模型<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#reactor-%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="Reactor 线程模型的直接链接" title="Reactor 线程模型的直接链接" translate="no">​</a></h2>
<p>Reactor 线程模型是一种 <strong>高并发网络服务器常用的设计模式</strong>。它的核心思想是：<strong>用少量线程，通过事件驱动的方式去处理大量网络连接</strong>。线程不再为每一个连接单独创建，而是通过监听网络事件（连接、读、写等），当某个连接有数据到达时再去处理它。这样就避免了大量线程带来的资源消耗和上下文切换问题，大大提升了服务器的并发处理能力。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2026-03-6f2341a1-2edd-42d2-b66a-a174f49001c4-1d85f6faeca7214b64186dc69cd8562e.webp" width="1536" height="1024" class="img_ev3q"></p>
<p>Reactor 模型通常包含几个关键角色：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1事件监听reactor">1、事件监听（Reactor）<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#1%E4%BA%8B%E4%BB%B6%E7%9B%91%E5%90%ACreactor" class="hash-link" aria-label="1、事件监听（Reactor）的直接链接" title="1、事件监听（Reactor）的直接链接" translate="no">​</a></h3>
<p>负责监听网络事件，例如新的连接到来、数据可读、数据可写等。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2连接接入acceptor">2、连接接入（Acceptor）<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#2%E8%BF%9E%E6%8E%A5%E6%8E%A5%E5%85%A5acceptor" class="hash-link" aria-label="2、连接接入（Acceptor）的直接链接" title="2、连接接入（Acceptor）的直接链接" translate="no">​</a></h3>
<p>当有新的客户端连接时，负责接收连接并注册到后续的处理线程中。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3事件分发dispatcher">3、事件分发（Dispatcher）<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#3%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91dispatcher" class="hash-link" aria-label="3、事件分发（Dispatcher）的直接链接" title="3、事件分发（Dispatcher）的直接链接" translate="no">​</a></h3>
<p>将不同的网络事件分发给对应的处理逻辑。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4业务处理handler">4、业务处理（Handler）<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#4%E4%B8%9A%E5%8A%A1%E5%A4%84%E7%90%86handler" class="hash-link" aria-label="4、业务处理（Handler）的直接链接" title="4、业务处理（Handler）的直接链接" translate="no">​</a></h3>
<p>真正执行业务逻辑，比如解析协议、处理请求、返回结果。</p>
<p>Reactor 模型本身也有几种演进形态：单 Reactor 单线程（一个线程包揽监听和处理）、单 Reactor 多线程（监听单线程、处理交给线程池）、主从 Reactor（连接接入和读写处理分别由不同的 Reactor 线程组负责）。连接量越大，越需要把接入和读写拆开，避免相互拖累。</p>
<p>在 Netty 中，Reactor 模型通常体现为 <strong>BossGroup + WorkerGroup 的线程结构</strong>，对应的正是主从 Reactor 形态：</p>
<ul>
<li class=""><strong>Boss 线程</strong>：负责接收客户端连接</li>
<li class=""><strong>Worker 线程</strong>：负责处理网络读写和业务逻辑</li>
</ul>
<p>通过这种设计，Netty 可以用 <strong>少量线程处理成千上万的连接</strong>，这也是它能够实现高性能网络通信的核心原因。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>学习和使用这套模型时，有几点值得提前知道：</p>
<p>1）<strong>不要在 Worker 线程里做耗时操作</strong>。Reactor 模型的前提是事件处理足够快，一个 Worker 线程通常服务多个连接，如果 Handler 里做了慢查询、同步远程调用，这个线程上的其他连接会被一起拖住。耗时业务应该丢给独立的业务线程池。</p>
<p>2）<strong>直接用原生 NIO API 很容易写错</strong>。Buffer 的读写切换、Selector 的事件处理细节都有不少坑，这也是实际项目几乎都选择 Netty 而不是裸写 NIO 的原因——它把这些复杂性封装掉了。</p>
<p>3）<strong>NIO 快在"省线程"，不在单次 IO</strong>。对于连接数很少的场景，BIO 未必更差；NIO/Reactor 的优势是在大量连接并存时体现出来的。技术选型要看场景。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/14/netty-shen-ru-xue-xi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>回顾这条演进主线：</p>
<ul>
<li class="">BIO 的问题是"一连接一线程"，线程被空闲连接白白占用；</li>
<li class="">NIO 用 Channel + Buffer + Selector 实现 IO 多路复用，让少量线程只处理就绪事件；</li>
<li class="">Reactor 模型在此之上定义了事件监听、分发、处理的职责划分；</li>
<li class="">Netty 的 BossGroup + WorkerGroup 就是主从 Reactor 的工程化实现。</li>
</ul>
<p>理解了这条线，再去看 Netty 的 EventLoop、Pipeline、ByteBuf 这些具体组件，就不会觉得它们是凭空冒出来的设计了。后续再单独展开 Netty 的核心组件与实战用法。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Redis分布式锁]]></title>
            <link>https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo</link>
            <guid>https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从本地锁到分布式锁：用 Redis 原子命令实现加锁与安全释放，聊聊锁过期、WatchDog 续期、主从切换下的一致性风险，以及与 etcd/ZooKeeper 的取舍。]]></description>
            <content:encoded><![CDATA[<p>很多人在刚接触分布式系统的时候，都会遇到一个问题：<br>
<strong>多个服务实例同时处理同一件事情，如何避免数据被重复处理？</strong></p>
<!-- -->
<p>例如：</p>
<ul>
<li class="">用户抢优惠券</li>
<li class="">定时任务执行</li>
<li class="">库存扣减</li>
<li class="">订单状态更新</li>
</ul>
<p>如果系统只有一个进程，其实很简单，用 <strong>本地锁（mutex）</strong> 就能解决。但在微服务架构或者集群部署之后，问题就变了：系统可能有 10 个服务实例、100 个 Worker，甚至多个数据中心。这时候本地锁就完全失效了，因为不同进程之间根本不知道彼此的锁状态。</p>
<p>于是就出现了 <strong>分布式锁（Distributed Lock）</strong> 这个概念。它的目标很简单：</p>
<blockquote>
<p>在分布式环境下，保证某一时刻只有一个节点能执行某段逻辑。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么-redis-可以做分布式锁">为什么 Redis 可以做分布式锁？<a href="https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo#%E4%B8%BA%E4%BB%80%E4%B9%88-redis-%E5%8F%AF%E4%BB%A5%E5%81%9A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" class="hash-link" aria-label="为什么 Redis 可以做分布式锁？的直接链接" title="为什么 Redis 可以做分布式锁？的直接链接" translate="no">​</a></h2>
<p>实现分布式锁的时候，很多人第一反应是数据库，例如：</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 借助数据库行锁实现互斥</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">select</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">.</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">.</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">.</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">for</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">update</span><br></div></code></pre></div></div>
<p>但数据库锁的问题是：性能差、锁粒度大、并发高时压力很大。于是大家开始寻找一个更适合做锁的系统，Redis 就非常合适，因为它天然具备三个优势。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1单线程模型">1）单线程模型<a href="https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo#1%E5%8D%95%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="1）单线程模型的直接链接" title="1）单线程模型的直接链接" translate="no">​</a></h3>
<p>Redis 核心命令是单线程执行的，同一时间只有一个命令在执行。所以像下面这种操作：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># NX：key 不存在才写入，天然互斥</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">SET key value NX</span><br></div></code></pre></div></div>
<p>是 <strong>绝对原子操作</strong>，不会出现竞争条件。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2内存操作速度极快">2）内存操作，速度极快<a href="https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo#2%E5%86%85%E5%AD%98%E6%93%8D%E4%BD%9C%E9%80%9F%E5%BA%A6%E6%9E%81%E5%BF%AB" class="hash-link" aria-label="2）内存操作，速度极快的直接链接" title="2）内存操作，速度极快的直接链接" translate="no">​</a></h3>
<p>Redis 所有数据都在内存中，一次锁操作通常只需要一次 SET、一次 DEL，延迟通常在 <strong>几十微秒级别</strong>，远远比数据库快。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3支持自动过期">3）支持自动过期<a href="https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo#3%E6%94%AF%E6%8C%81%E8%87%AA%E5%8A%A8%E8%BF%87%E6%9C%9F" class="hash-link" aria-label="3）支持自动过期的直接链接" title="3）支持自动过期的直接链接" translate="no">​</a></h3>
<p>Redis key 可以设置 TTL，例如：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># NX：不存在才创建；EX 10：10 秒自动过期</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">SET lock:order </span><span class="token number" style="color:hsl(29, 54%, 61%)">123</span><span class="token plain"> NX EX </span><span class="token number" style="color:hsl(29, 54%, 61%)">10</span><br></div></code></pre></div></div>
<p>这样即使服务崩溃，锁也不会永远卡住。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-锁的释放">Redis 锁的释放<a href="https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo#redis-%E9%94%81%E7%9A%84%E9%87%8A%E6%94%BE" class="hash-link" aria-label="Redis 锁的释放的直接链接" title="Redis 锁的释放的直接链接" translate="no">​</a></h2>
<p>释放锁时不能直接 DEL，因为可能发生这种情况：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">线程A获取锁</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">线程A执行超时</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">锁过期</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">线程B获取锁</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">线程A执行完删除锁</span><br></div></code></pre></div></div>
<p>这时候线程 A 会误删线程 B 的锁。正确做法是 <strong>只删除自己持有的锁</strong>，通常通过 Lua 脚本实现：</p>
<div class="language-lua codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-lua codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">-- 先比对锁的值（唯一标识），匹配才删除，保证只释放自己的锁</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">if redis.call("GET",KEYS[1]) == ARGV[1] then</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    return redis.call("DEL",KEYS[1])</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">else</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    return 0</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">end</span><br></div></code></pre></div></div>
<p>Lua 脚本在 Redis 中整体原子执行，GET 和 DEL 之间不会被其他命令插入，这样才能保证安全释放。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-分布式锁的几个经典问题">Redis 分布式锁的几个经典问题<a href="https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo#redis-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E5%87%A0%E4%B8%AA%E7%BB%8F%E5%85%B8%E9%97%AE%E9%A2%98" class="hash-link" aria-label="Redis 分布式锁的几个经典问题的直接链接" title="Redis 分布式锁的几个经典问题的直接链接" translate="no">​</a></h2>
<p>虽然 Redis 锁很简单，但在真实系统中会遇到很多坑，最常见的是下面两个。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1锁过期问题">1）锁过期问题<a href="https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo#1%E9%94%81%E8%BF%87%E6%9C%9F%E9%97%AE%E9%A2%98" class="hash-link" aria-label="1）锁过期问题的直接链接" title="1）锁过期问题的直接链接" translate="no">​</a></h3>
<p>如果业务执行时间超过 TTL，锁就会提前释放，其他节点就可能同时进入。解决办法是 <strong>自动续期（WatchDog）</strong>。例如：</p>
<ul>
<li class="">锁的 TTL 设为 10 秒</li>
<li class="">在剩余有效期进入最后 30% 时自动续期一次，并加入随机抖动，避免大量续期请求在同一时刻集中打到 Redis</li>
</ul>
<p>只要任务还在执行，锁就不会过期。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2redis-单点问题">2）Redis 单点问题<a href="https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo#2redis-%E5%8D%95%E7%82%B9%E9%97%AE%E9%A2%98" class="hash-link" aria-label="2）Redis 单点问题的直接链接" title="2）Redis 单点问题的直接链接" translate="no">​</a></h3>
<p>如果 Redis 挂掉，所有锁都会失效。常见解决方案是：</p>
<ul>
<li class="">Redis Sentinel</li>
<li class="">Redis Cluster</li>
<li class="">RedLock 算法</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-ap-设计与-etcdzookeeper-cp-架构的区别">Redis AP 设计与 etcd、ZooKeeper CP 架构的区别<a href="https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo#redis-ap-%E8%AE%BE%E8%AE%A1%E4%B8%8E-etcdzookeeper-cp-%E6%9E%B6%E6%9E%84%E7%9A%84%E5%8C%BA%E5%88%AB" class="hash-link" aria-label="Redis AP 设计与 etcd、ZooKeeper CP 架构的区别的直接链接" title="Redis AP 设计与 etcd、ZooKeeper CP 架构的区别的直接链接" translate="no">​</a></h2>
<p>Redis 本身是一个偏 <strong>AP（可用性优先）</strong> 的系统：在网络分区或者节点故障的时候，Redis 会优先保证服务可用，而不是保证数据绝对一致。这就可能带来一个风险——极端情况下，<strong>可能同时出现两个客户端都认为自己拿到了锁</strong>。</p>
<p>例如：</p>
<p>客户端 A 在主节点获取了锁<br>
主节点还没来得及同步到从节点<br>
主节点突然宕机<br>
从节点被提升为新的主节点</p>
<p>这时候新的主节点并不知道 A 已经加过锁，客户端 B 就可能再次获取锁，出现两个客户端同时持有锁的情况。这就是 Redis 分布式锁在理论上的一致性风险。</p>
<p>相比之下，像 <strong>etcd 或 ZooKeeper</strong> 这样的系统是基于 <strong>CP 模型（强一致）</strong> 设计的，内部通过 <strong>Raft 或 ZAB 协议</strong> 保证多数节点确认之后才会提交数据。因此一旦锁被获取成功，整个集群都会达成一致，不会出现多个客户端同时拿到锁的情况。</p>
<p>所以在工程实践中通常有一个经验：<strong>业务级锁（例如库存、任务控制）</strong> 用 Redis 分布式锁通常已经足够；但 <strong>强一致要求很高的场景</strong>——金融交易、全局调度系统、分布式协调服务——很多系统会选择 etcd / ZooKeeper 这种强一致的锁实现。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/14/redisfen-bu-shi-tan-suo#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Redis 分布式锁的核心思想非常简单：<strong>利用 Redis 原子操作实现互斥</strong>。它包含三个关键点：原子加锁、安全释放、自动过期或续期。在实际工程中，一套成熟的 Redis 锁通常还会加上重试机制、WatchDog 自动续期、Lua 安全释放和锁唯一标识。同时也要清楚它偏 AP 的定位——业务级互斥够用，强一致场景则考虑 etcd / ZooKeeper。这些设计组合在一起，才能真正成为一个稳定可靠的分布式锁系统。</p>]]></content:encoded>
            <category>Redis</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[算法：基础知识]]></title>
            <link>https://tanzhuo.xyz/2026/03/14/suan-fa-ji-chu-zhi-shi</link>
            <guid>https://tanzhuo.xyz/2026/03/14/suan-fa-ji-chu-zhi-shi</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从「算法是什么」讲起：算法与数据结构的关系、时间复杂度的意义,以及学算法时应该关注建模思路而不是刷题数量。]]></description>
            <content:encoded><![CDATA[<p>刚开始写代码的时候,我一直觉得算法是竞赛选手才需要的东西,平时写业务用不上。后来发现不是这样:接口慢、列表卡、数据量一大就超时,这些日常问题背后往往就是算法问题。这篇先把最基础的概念理一遍。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="什么是算法">什么是算法？<a href="https://tanzhuo.xyz/2026/03/14/suan-fa-ji-chu-zhi-shi#%E4%BB%80%E4%B9%88%E6%98%AF%E7%AE%97%E6%B3%95" class="hash-link" aria-label="什么是算法？的直接链接" title="什么是算法？的直接链接" translate="no">​</a></h2>
<p>算法，本质上就是 <strong>解决问题的一套步骤</strong>。</p>
<p>只要是：</p>
<p>输入一组数据<br>
按照一定规则处理<br>
得到结果</p>
<p>这整套处理过程，就是算法。</p>
<p>它不一定要多高深。按字母顺序查字典是算法,菜谱也是算法——都是一套明确的、可重复执行的步骤。写程序时区别只在于:这套步骤要交给计算机执行,所以每一步都必须写得足够精确,不能有含糊的地方。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="算法的理解">算法的理解<a href="https://tanzhuo.xyz/2026/03/14/suan-fa-ji-chu-zhi-shi#%E7%AE%97%E6%B3%95%E7%9A%84%E7%90%86%E8%A7%A3" class="hash-link" aria-label="算法的理解的直接链接" title="算法的理解的直接链接" translate="no">​</a></h2>
<p>为什么程序员需要关心算法？因为同样一个问题，不同的算法效率可能差很多。有的做法可能几秒就能算出来，有的可能要跑好几分钟甚至更久。当数据量变大时，这种差距会越来越明显，所以很多系统性能好不好，其实和算法设计关系很大。</p>
<p>这种差距通常用「时间复杂度」来描述,也就是常见的大 O 记法。它关心的不是某次运行花了几毫秒,而是数据量增长时,运算量以什么速度增长:线性扫描是 O(n),数据翻倍工作量也翻倍;二分查找是 O(log n),数据翻倍只多查一次;而两层嵌套循环的 O(n²),数据翻倍工作量就变成四倍。数据量小的时候大家都很快,一旦到了十万、百万级,增长曲线的差别就是「能用」和「不能用」的差别。</p>
<p>算法通常不会单独存在，它往往和数据结构一起使用。数据结构负责把数据组织好，比如数组、链表、树、哈希表这些；而算法则负责对这些数据进行操作。简单来说，一个负责存数据，一个负责处理数据，两者配合起来，程序才能高效运行。</p>
<p>这两者的选择是互相影响的。同样是「查一个元素在不在」,放在数组里要从头扫到尾,放在哈希表里基本一步就能定位;同样是「频繁在中间插入」,数组要整体挪动元素,链表只需要改指针。所以很多时候,换一个更合适的数据结构,算法自然就快了——问题建模的方式,决定了后面能用什么算法。</p>
<p>在现实系统中，算法其实无处不在。比如搜索引擎要根据算法排序网页，短视频平台要用算法推荐内容，导航软件要用算法计算最短路线，电商平台也会用算法做商品推荐和排序。很多我们每天使用的软件，其实背后都有各种算法在工作。</p>
<p>学习算法并不是为了刷多少题，而是为了培养解决问题的思路。当遇到一个问题时，能快速想到几种解决方式，然后选择效率更高的一种，这才是算法真正的价值。很多经验丰富的工程师，其实都是在不断优化解决问题的方法。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="算法学习应该关注什么">算法学习应该关注什么？<a href="https://tanzhuo.xyz/2026/03/14/suan-fa-ji-chu-zhi-shi#%E7%AE%97%E6%B3%95%E5%AD%A6%E4%B9%A0%E5%BA%94%E8%AF%A5%E5%85%B3%E6%B3%A8%E4%BB%80%E4%B9%88" class="hash-link" aria-label="算法学习应该关注什么？的直接链接" title="算法学习应该关注什么？的直接链接" translate="no">​</a></h2>
<p>很多人学习算法时容易陷入刷题。</p>
<p>但实际上更重要的是理解：</p>
<p>1.问题如何建模</p>
<p>把一个模糊的现实需求,翻译成清晰的输入、输出和约束条件。建模建对了,问题往往就解决了一半;建错了,后面写得再快也是白费。</p>
<p>2.数据结构如何设计</p>
<p>根据访问模式选结构:查得多用哈希表,要有序遍历考虑树,先进先出用队列。结构选对了,代码会自然变简单。</p>
<p>3.时间复杂度如何优化</p>
<p>先估算当前做法在目标数据量下能不能扛住,再考虑要不要优化。不是所有代码都值得优化,但要知道瓶颈在哪。</p>
<p>4.算法思路如何演进</p>
<p>从暴力解法出发,观察哪里做了重复计算,再一步步改进到更优的解法。这个推导过程比直接背最优解有价值得多。</p>
<p><strong>真正的算法能力，不是记住多少题。而是：看到问题，能快速想到解决思路。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2026/03/14/suan-fa-ji-chu-zhi-shi#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class="">不要一上来就追求最优解。先写出能跑通的暴力解法,确认理解了问题,再谈优化。很多时候暴力解在实际数据量下已经够用。</li>
<li class="">复杂度分析要结合数据量。n 只有几百的场景,O(n²) 和 O(n log n) 的差别可以忽略;别为了炫技把简单代码改复杂。</li>
<li class="">刷题只记结论、不推过程,过两周就忘。合上题解自己重新推一遍,才算真的会了。</li>
<li class="">别忽略空间复杂度。用空间换时间是常见手段,但缓存、哈希表也会吃内存,要清楚代价在哪。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/14/suan-fa-ji-chu-zhi-shi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>算法就是解决问题的步骤,数据结构负责组织数据,两者配合决定了程序的效率。衡量算法好坏的核心工具是复杂度分析,它描述的是数据量增长时运算量的增长趋势。学算法的重点不在题量,而在建模、选结构、推导思路这几件事——把它们练熟,遇到新问题时自然知道从哪里下手。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[AI的发展史]]></title>
            <link>https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi</link>
            <guid>https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[AI 不是这几年突然冒出来的，它已经走过 70 多年。从图灵测试、深度学习到大模型时代，这篇文章梳理 AI 的三个发展阶段、当前落地情况与未来方向。]]></description>
            <content:encoded><![CDATA[<p>很多人觉得 AI 好像是这几年突然冒出来的，其实不是。AI 的发展已经走了 <strong>70 多年</strong>，可以简单理解为三个阶段。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第一阶段ai概念诞生19501980">第一阶段：AI概念诞生（1950—1980）<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5ai%E6%A6%82%E5%BF%B5%E8%AF%9E%E7%94%9F19501980" class="hash-link" aria-label="第一阶段：AI概念诞生（1950—1980）的直接链接" title="第一阶段：AI概念诞生（1950—1980）的直接链接" translate="no">​</a></h2>
<p>1950年，英国科学家 <strong>图灵（Alan Turing）</strong> 提出一个问题：</p>
<blockquote>
<p>“机器能不能像人一样思考？”</p>
</blockquote>
<p>于是他提出了一个著名的测试（<strong>图灵测试</strong>）。简单说就是：如果人类和机器聊天，分不出来谁是机器，那机器就算“有智能”。</p>
<p>1956年，美国召开了一次会议，第一次正式提出：</p>
<blockquote>
<p>Artificial Intelligence（人工智能）</p>
</blockquote>
<p>从那时起，AI 成为一个正式研究领域。不过当时的计算机很弱，数据也少，所以 AI 主要停留在理论研究阶段。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第二阶段ai开始有点用19802010">第二阶段：AI开始有点用（1980—2010）<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5ai%E5%BC%80%E5%A7%8B%E6%9C%89%E7%82%B9%E7%94%A819802010" class="hash-link" aria-label="第二阶段：AI开始有点用（1980—2010）的直接链接" title="第二阶段：AI开始有点用（1980—2010）的直接链接" translate="no">​</a></h2>
<p>随着计算机越来越强，一些 AI 技术开始真正落地，比如：</p>
<ul>
<li class="">语音识别</li>
<li class="">机器翻译</li>
<li class="">推荐系统</li>
</ul>
<p>很多互联网产品，其实早就用了 AI，例如：</p>
<ul>
<li class="">淘宝推荐商品</li>
<li class="">Google 搜索排序</li>
<li class="">Netflix 推荐电影</li>
</ul>
<p>但是那个时候的 AI 还比较“笨”，基本只能做 <strong>单一任务</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第三阶段大模型时代2010现在">第三阶段：大模型时代（2010—现在）<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#%E7%AC%AC%E4%B8%89%E9%98%B6%E6%AE%B5%E5%A4%A7%E6%A8%A1%E5%9E%8B%E6%97%B6%E4%BB%A32010%E7%8E%B0%E5%9C%A8" class="hash-link" aria-label="第三阶段：大模型时代（2010—现在）的直接链接" title="第三阶段：大模型时代（2010—现在）的直接链接" translate="no">​</a></h2>
<p>真正让 AI 爆发的，是三个因素：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-算力变强">1) 算力变强<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#1-%E7%AE%97%E5%8A%9B%E5%8F%98%E5%BC%BA" class="hash-link" aria-label="1) 算力变强的直接链接" title="1) 算力变强的直接链接" translate="no">​</a></h3>
<p>GPU 算力暴涨。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-数据变多">2) 数据变多<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#2-%E6%95%B0%E6%8D%AE%E5%8F%98%E5%A4%9A" class="hash-link" aria-label="2) 数据变多的直接链接" title="2) 数据变多的直接链接" translate="no">​</a></h3>
<p>互联网产生海量数据。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-算法突破">3) 算法突破<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#3-%E7%AE%97%E6%B3%95%E7%AA%81%E7%A0%B4" class="hash-link" aria-label="3) 算法突破的直接链接" title="3) 算法突破的直接链接" translate="no">​</a></h3>
<p>特别是 <strong>深度学习 + Transformer 架构</strong>。</p>
<p>2017年，Google 发布一篇论文：</p>
<blockquote>
<p>Attention Is All You Need</p>
</blockquote>
<p>这篇论文直接催生了今天的大模型。于是出现了一批 AI 产品：</p>
<ul>
<li class="">ChatGPT</li>
<li class="">Claude</li>
<li class="">Gemini</li>
<li class="">DeepSeek</li>
</ul>
<p>AI 突然从“工具”变成了一个可以对话、写作、编程、画画的智能助手。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai现在发展到什么水平">AI现在发展到什么水平？<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#ai%E7%8E%B0%E5%9C%A8%E5%8F%91%E5%B1%95%E5%88%B0%E4%BB%80%E4%B9%88%E6%B0%B4%E5%B9%B3" class="hash-link" aria-label="AI现在发展到什么水平？的直接链接" title="AI现在发展到什么水平？的直接链接" translate="no">​</a></h2>
<p>如果放在十年前，很多人可能会觉得 AI 只是个实验室技术，或者只是一些有趣的小工具。但现在情况已经完全不一样了。今天的 AI，已经从“新奇玩具”变成了真正的 <strong>生产力工具</strong>，而且正在越来越多的行业里发挥作用。</p>
<p>最明显的变化发生在 <strong>软件开发领域</strong>。现在很多程序员在日常工作中都会使用 AI 辅助写代码，比如 Cursor、Copilot、ChatGPT、Claude 等工具。AI 不仅可以帮开发者写函数、生成脚本，还能帮助查 bug、解释代码逻辑，甚至可以根据需求快速生成一段完整的程序框架。在很多团队中，引入 AI 之后，开发效率普遍可以提升 <strong>30%—50%</strong>。</p>
<p>在 <strong>内容创作领域</strong>，AI 的应用也非常广泛。现在 AI 已经可以写新闻、写小说、写广告文案、写总结报告，甚至很多自媒体作者都会用 AI 来辅助写文章、整理素材或者生成创意。虽然最终内容往往还需要人工修改，但 AI 已经可以完成很大一部分基础工作，大大提高了创作效率。</p>
<p>在 <strong>企业服务领域</strong>，AI 也开始大量替代一些重复性工作，比如客服。很多电商平台、银行和运营商已经开始使用 AI 客服系统来处理常见问题咨询。相比传统人工客服，AI 可以做到 <strong>24 小时在线响应</strong>，同时处理大量用户请求，大幅降低企业的人力成本。</p>
<p>另外，在 <strong>视觉内容生产方面</strong>，AI 的能力也进步非常快。像 Midjourney、DALL·E 这样的工具已经可以根据文字生成高质量图片，而 Sora、Runway 等视频生成技术也开始逐渐成熟。现在一些短视频、广告设计，甚至影视制作中，都已经开始用 AI 来辅助生成画面、角色或动画内容。</p>
<p>在 <strong>数据分析领域</strong>，AI 同样发挥着越来越重要的作用。很多企业已经在使用 AI 自动分析数据报表、生成 BI 报告，甚至给出业务趋势判断和决策建议。一些公司已经开始部署所谓的“AI 数据分析助手”，帮助管理层更快地理解数据和做出决策。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai现在已经在哪些行业落地">AI现在已经在哪些行业落地？<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#ai%E7%8E%B0%E5%9C%A8%E5%B7%B2%E7%BB%8F%E5%9C%A8%E5%93%AA%E4%BA%9B%E8%A1%8C%E4%B8%9A%E8%90%BD%E5%9C%B0" class="hash-link" aria-label="AI现在已经在哪些行业落地？的直接链接" title="AI现在已经在哪些行业落地？的直接链接" translate="no">​</a></h2>
<p>其实 AI 已经悄悄进入了很多行业。互联网平台里的内容推荐，比如抖音、淘宝、B站，本质上都是 AI 在决定你看到什么内容；金融行业用 AI 做风控、反欺诈和投资分析，很多银行的核心系统里都有 AI 模型在运行；医疗领域里，AI 已经可以辅助医生诊断疾病、识别医学影像，比如 X 光片和 CT，有些场景下甚至比人工识别更快更准确；在制造业，AI 被用在工厂自动化、质量检测和设备预测维护，让工厂越来越智能化；在 IT 行业，AI 也开始进入运维领域，比如自动监控系统、日志分析、自动排障和自动扩容，未来甚至可能出现“AI 运维助手”，帮助工程师管理复杂系统。可以说，AI 已经从实验室技术，变成了很多行业背后的基础工具。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai对人类意味着什么">AI对人类意味着什么？<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#ai%E5%AF%B9%E4%BA%BA%E7%B1%BB%E6%84%8F%E5%91%B3%E7%9D%80%E4%BB%80%E4%B9%88" class="hash-link" aria-label="AI对人类意味着什么？的直接链接" title="AI对人类意味着什么？的直接链接" translate="no">​</a></h2>
<p>很多人担心 AI 会不会取代人类，其实更现实的情况是：<strong>AI 并不是来取代人的，而是来放大人的能力的。</strong></p>
<p>回顾技术的发展史，人类一直在用工具放大自己的能力。蒸汽机让人类拥有了远超自身的体力，计算机让人类拥有了极强的计算能力，互联网让信息传播速度几乎没有距离限制。而 AI 放大的，是人类的 <strong>智力劳动能力</strong>。</p>
<p>很多过去需要一个团队完成的事情，现在一个人加上 AI 就能做到。例如写代码、分析数据、制作内容、做设计、做运营方案等。AI 可以帮人快速生成思路、整理信息、完成大量基础工作，而人类则负责判断方向、做决策和把控结果。</p>
<p>换句话说，未来很多工作的模式可能会变成：</p>
<p><strong>一个人 + AI，就相当于过去的一个小团队。</strong></p>
<p>所以，从更长远的角度来看，AI 真正改变的不是“人会不会失业”，而是 <strong>工作的方式会发生变化</strong>。重复性、标准化的工作会越来越多地被 AI 完成，而人类更多需要做的是创造、判断、决策和创新。</p>
<p>可以说，AI 并不是人类的对手，而更像是一种新的工具。就像当年计算机和互联网改变了世界一样，AI 正在成为新的基础能力，并逐渐融入到每一个行业之中。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="未来ai会如何发展">未来AI会如何发展？<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#%E6%9C%AA%E6%9D%A5ai%E4%BC%9A%E5%A6%82%E4%BD%95%E5%8F%91%E5%B1%95" class="hash-link" aria-label="未来AI会如何发展？的直接链接" title="未来AI会如何发展？的直接链接" translate="no">​</a></h2>
<p>从目前的发展趋势来看，AI 未来大概率会往几个方向发展。首先是从“聊天工具”逐渐变成 <strong>可以自己完成任务的智能体（AI Agent）</strong>，比如自动写代码、自动分析数据、自动运营系统，人只需要给目标，AI 就能一步一步把事情完成。其次，AI 会越来越多地进入现实世界，例如与机器人、自动驾驶、智能工厂结合，让机器不仅会“思考”，还会“行动”。另外，AI 也会逐渐成为一种 <strong>基础能力</strong>，像互联网和电力一样融入各类软件和系统中，未来几乎所有应用都会带有 AI 功能。简单来说，未来的 AI 不会只是一个工具，而更像是人类的“智能助手”和“生产力放大器”，帮助个人和企业用更少的人力完成更多的事情。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai目前的资金投入与成本情况">AI目前的资金投入与成本情况<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#ai%E7%9B%AE%E5%89%8D%E7%9A%84%E8%B5%84%E9%87%91%E6%8A%95%E5%85%A5%E4%B8%8E%E6%88%90%E6%9C%AC%E6%83%85%E5%86%B5" class="hash-link" aria-label="AI目前的资金投入与成本情况的直接链接" title="AI目前的资金投入与成本情况的直接链接" translate="no">​</a></h2>
<p>从全球来看，AI 已经成为资本投入最大的科技领域之一，各国政府、科技公司和投资机构都在持续加码。根据斯坦福发布的《AI Index 2025》报告，<strong>2024年全球企业在AI领域的投资规模达到约2523亿美元</strong>，创历史新高。</p>
<p>其中，美国依然是 AI 投资最强的国家，<strong>2024年美国私营AI投资达到1091亿美元</strong>，大约是中国（约93亿美元）的12倍、英国（45亿美元）的24倍。</p>
<p>在 AI 投资结构中，<strong>生成式AI（如大模型）是增长最快的领域</strong>，2024年全球生成式 AI 私营投资达到 <strong>339亿美元</strong>，同比增长约18.7%。</p>
<p>与此同时，AI 的企业使用率也在快速增长。数据显示，<strong>2024年约78%的企业已经在业务中使用AI技术</strong>，相比前一年的55%明显提升，这意味着 AI 正在从“技术尝试”变成企业的常规工具。</p>
<p>从市场规模来看，AI 产业也处于高速增长阶段。2025年全球 AI 市场规模约 <strong>3910亿美元</strong>，预计到2030年可能增长到 <strong>1.8万亿美元以上</strong>，成为未来增长最快的科技产业之一。</p>
<p>简单来说，现在 AI 的发展背后是 <strong>大量资金、算力和企业投入的共同推动</strong>。科技巨头、投资机构和各国政府都在持续投入资源，希望在未来的 AI 产业竞争中占据领先位置。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/13/aide-fa-zhan-shi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>AI 走过了 70 多年，从图灵的一个设想，到只能做单一任务的工具，再到今天能对话、写代码、生成图像的大模型。这背后是算力、数据和算法三条线的长期积累，而不是某一年的突然爆发。现在它已经渗透进软件开发、内容创作、金融、医疗等大量行业，背后还有持续加码的资金投入。对个人来说，与其担心被取代，不如把它当成放大自己能力的工具，尽早学会和它协作。</p>]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[go-lynx设计思路]]></title>
            <link>https://tanzhuo.xyz/2026/03/13/go-lynxshe-ji-si-lu</link>
            <guid>https://tanzhuo.xyz/2026/03/13/go-lynxshe-ji-si-lu</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[go-lynx 是基于 Kratos 的插件化微服务基础框架：插件热插拔、配置驱动、内置服务治理与 TLS 证书自动轮转，目标是把微服务架构变成搭积木。]]></description>
            <content:encoded><![CDATA[<p>github.com/go-lynx 的设计目的是为了可以快速帮助企业构建微服务体系的基础框架，其中我把整个仓库（包括组织 go-lynx 下 29 个 repo）进行了拆分，分为了 lynx 架构基座，和各种 lynx 插件模块。</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://avatars.githubusercontent.com/u/150900434?s=96&amp;v=4" alt="" class="img_ev3q"></p>
<p><a href="https://github.com/go-lynx" target="_blank" rel="noopener noreferrer" class="">https://github.com/go-lynx</a></p>
<p>亮点就是：<strong>Plug-and-Play</strong>（真正开箱即用），把复杂微服务架构变成“搭积木”。</p>
<p>它不是从 0 造轮子，而是站在巨人肩膀上：</p>
<ul>
<li class="">核心运行时借 <strong>Kratos</strong>（B 站那套开源框架）</li>
<li class="">服务发现/治理用 <strong>Polaris</strong>（腾讯云原生服务网格）、<strong>Nacos（阿里开源）</strong></li>
<li class="">分布式事务用 <strong>Seata、DTM 等</strong></li>
<li class="">然后我自己加了一套 <strong>插件管理系统 + 事件总线 + 控制平面</strong>，实现真正热插拔。</li>
</ul>
<p><strong>对比 Kratos</strong>：Kratos 是“基础框架”，Lynx 是“Kratos 的生产增强版 + 插件体系 + 零配置”。如果你喜欢 Kratos 但嫌配置麻烦、插件不统一，这套就是为这个痛点生的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="脚手架">脚手架<a href="https://tanzhuo.xyz/2026/03/13/go-lynxshe-ji-si-lu#%E8%84%9A%E6%89%8B%E6%9E%B6" class="hash-link" aria-label="脚手架的直接链接" title="脚手架的直接链接" translate="no">​</a></h2>
<p>零配置 + CLI 脚手架，一条命令快速生成完整项目：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 安装 lynx 命令行工具</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">go </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> github.com/go-lynx/lynx/cmd/lynx@latest</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 生成一个完整的微服务项目骨架</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">lynx</span><span class="token plain"> new my-service</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="已实现的插件体系">已实现的插件体系<a href="https://tanzhuo.xyz/2026/03/13/go-lynxshe-ji-si-lu#%E5%B7%B2%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%8F%92%E4%BB%B6%E4%BD%93%E7%B3%BB" class="hash-link" aria-label="已实现的插件体系的直接链接" title="已实现的插件体系的直接链接" translate="no">​</a></h2>
<ul>
<li class="">已有的插件：gRPC、HTTP、Redis、PostgreSQL、Redis 分布式锁、Swagger、Tracer、Seata 等。</li>
<li class="">插件间通过内置 <strong>Event Bus</strong> 通信，实现插件之间的互通与控制。</li>
<li class="">服务注册发现 + 健康检查 + 多版本 + 负载均衡（Polaris）</li>
<li class="">流量治理：限流、熔断、蓝绿/金丝雀、fallback</li>
<li class="">安全：TLS 双向认证、JWT、OAuth2、RBAC/ABAC</li>
<li class="">分布式事务：Seata 全自动</li>
<li class="">可观测性：Prometheus + OpenTelemetry + Zap（JSON 日志）</li>
<li class="">优雅关机、重试、死信队列、恢复管理器</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="配置驱动">配置驱动<a href="https://tanzhuo.xyz/2026/03/13/go-lynxshe-ji-si-lu#%E9%85%8D%E7%BD%AE%E9%A9%B1%E5%8A%A8" class="hash-link" aria-label="配置驱动的直接链接" title="配置驱动的直接链接" translate="no">​</a></h2>
<ul>
<li class="">每个插件有固定 confPrefix（如 <code>lynx.grpc.service</code>、<code>lynx.grpc.client</code>），配置从 Lynx Runtime 的 <code>rt.GetConfig().Value(confPrefix).Scan(...)</code> 加载。</li>
<li class="">支持 Configure(c) 热更新：服务端用 <code>confMu</code> 保护配置指针替换，保证并发安全并可在不重启下更新部分能力。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="lynx-项目示例配置yaml-格式">lynx 项目示例配置（YAML 格式）<a href="https://tanzhuo.xyz/2026/03/13/go-lynxshe-ji-si-lu#lynx-%E9%A1%B9%E7%9B%AE%E7%A4%BA%E4%BE%8B%E9%85%8D%E7%BD%AEyaml-%E6%A0%BC%E5%BC%8F" class="hash-link" aria-label="lynx 项目示例配置（YAML 格式）的直接链接" title="lynx 项目示例配置（YAML 格式）的直接链接" translate="no">​</a></h3>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token key atrule" style="color:hsl(29, 54%, 61%)">lynx</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">polaris</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">namespace</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> default</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain">   </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 服务注册发现</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">http</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">addr</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">":8080"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain">           </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># HTTP 服务监听地址</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">grpc</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">addr</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">":9090"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain">           </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># gRPC 服务监听地址</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># metrics/tracing/logging/tls/rate_limit 全都有现成模板</span><br></div></code></pre></div></div>
<p>整体类似于 Spring Boot，使用什么插件就配置什么内容，插件完全开箱即用。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="插件化架构设计">插件化架构设计<a href="https://tanzhuo.xyz/2026/03/13/go-lynxshe-ji-si-lu#%E6%8F%92%E4%BB%B6%E5%8C%96%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="插件化架构设计的直接链接" title="插件化架构设计的直接链接" translate="no">​</a></h2>
<ul>
<li class="">统一插件接口：插件实现 Lynx 的 <code>plugins.Plugin</code> 接口，通过 BasePlugin 嵌入获得 ID、名称、版本、配置前缀、权重等通用能力。</li>
<li class="">工厂注册：客户端插件在 <code>init()</code> 里通过 <code>factory.GlobalTypedFactory().RegisterPlugin(clientPluginName, "lynx.grpc.client", ...)</code> 注册，由框架按配置前缀加载并管理生命周期。</li>
<li class="">生命周期：标准三阶段——InitializeResources（读配置、校验）、StartupTasks（启动服务/连接）、CleanupTasks（优雅关闭、释放资源）。</li>
<li class="">依赖注入：服务端插件通过 <code>SetDependencies</code> 注入应用名、Logger、证书、控制面等，避免全局单例，便于测试与多实例。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安全模块设计">安全模块设计<a href="https://tanzhuo.xyz/2026/03/13/go-lynxshe-ji-si-lu#%E5%AE%89%E5%85%A8%E6%A8%A1%E5%9D%97%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="安全模块设计的直接链接" title="安全模块设计的直接链接" translate="no">​</a></h2>
<p>为了保证内网微服务之间的通讯安全，Lynx 内置了 TLS 证书自动管理与轮转能力，在服务运行过程中证书更新无需重启，新建立的连接会自动使用最新证书，实现真正的零停机更新。整体设计上通过统一的证书提供者（CertificateProvider）进行证书管理，内网 gRPC 服务端在 TLS 握手时动态获取当前证书，客户端则通过同一机制获取 Root CA 并支持凭证刷新，使证书更新可以自动生效。同时该机制可以对接文件监听、云证书服务或密钥管理系统，实现自动化证书更新与企业级安全通信，既提升了系统安全性，也大幅降低了运维成本。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/13/go-lynxshe-ji-si-lu#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>go-lynx 的核心思路不是重造轮子，而是在 Kratos 之上补齐生产环境需要的那一层：统一的插件体系、配置驱动的开箱即用体验，以及事件总线和控制平面带来的热插拔能力。服务治理、分布式事务、可观测性、TLS 自动轮转这些能力都以插件形式提供，用到什么配什么。如果你在用 Kratos 但被配置和插件的不一致折腾过，可以到 <a href="https://github.com/go-lynx" target="_blank" rel="noopener noreferrer" class="">go-lynx</a> 看看这套设计是否解决了你的痛点。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[OpenClaw 使用记录]]></title>
            <link>https://tanzhuo.xyz/2026/03/13/openclaw</link>
            <guid>https://tanzhuo.xyz/2026/03/13/openclaw</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[OpenClaw 是一个开源的 AI Agent 自动化执行框架，能自动规划任务、调用工具并持续迭代。这篇记录整理了它的核心能力、明显短板与适用场景。]]></description>
            <content:encoded><![CDATA[<p>OpenClaw 是一个 <strong>开源的 AI Agent 自动化执行框架</strong>。
它的核心目标是让 AI 不仅仅停留在“对话”，而是能够 **自动规划任务、调用工具、执行操作并持续迭代完成复杂目标，**最近使用下来总结了一些内容。</p>
<!-- -->
<p>写这篇记录的起因很简单：聊天式的 AI 用久了，会发现它的天花板不在“答得好不好”，而在“答完之后呢”。代码建议给了，命令列出来了，真正去执行、验证、改错的还是人。Agent 框架要解决的就是这最后一公里——把“说”变成“做”。OpenClaw 是我最近实际跑过一段时间的一个，把使用中的观察和问题记下来，方便自己回头看，也给在选型的人一个参考。</p>
<p>简单来说，OpenClaw 可以理解为：</p>
<blockquote>
<p><strong>一个可以自动写代码、执行命令、分析项目并持续迭代任务的 AI 工程助手。</strong></p>
</blockquote>
<p>与普通的 ChatGPT 或 Claude 不同，OpenClaw 的设计目标是：</p>
<ul>
<li class="">让 AI <strong>具备任务执行能力</strong></li>
<li class="">可以 <strong>拆解复杂目标</strong></li>
<li class="">自动 <strong>调用工具和执行命令</strong></li>
<li class="">持续 <strong>迭代直到任务完成</strong></li>
</ul>
<p>因此，它更像是一个 <strong>AI 自动化开发助手（AI Software Engineer）</strong>。</p>
<p><a href="https://openclaw.ai/" target="_blank" rel="noopener noreferrer" class="">OpenClaw — Personal AI Assistant</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="openclaw-的核心能力">OpenClaw 的核心能力<a href="https://tanzhuo.xyz/2026/03/13/openclaw#openclaw-%E7%9A%84%E6%A0%B8%E5%BF%83%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="OpenClaw 的核心能力的直接链接" title="OpenClaw 的核心能力的直接链接" translate="no">​</a></h2>
<p>OpenClaw 的能力主要体现在 <strong>任务规划 + 自动执行 + 工具调用</strong> 三个方面。</p>
<p>这三者其实对应了 Agent 系统的经典循环：模型先根据目标产出计划（规划），再通过工具作用于真实环境（执行），然后把执行结果——命令输出、报错信息、测试结果——重新喂回模型作为观察（反馈），据此修正下一步动作。循环往复，直到模型判断任务完成。理解了这个「规划—执行—观察」的闭环，OpenClaw 的行为就不难预测了。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-任务规划planning">1) 任务规划（Planning）<a href="https://tanzhuo.xyz/2026/03/13/openclaw#1-%E4%BB%BB%E5%8A%A1%E8%A7%84%E5%88%92planning" class="hash-link" aria-label="1) 任务规划（Planning）的直接链接" title="1) 任务规划（Planning）的直接链接" translate="no">​</a></h3>
<p>当用户给 OpenClaw 一个目标，例如：</p>
<blockquote>
<p>“帮我给这个项目增加一个 Redis 缓存”</p>
</blockquote>
<p>OpenClaw 会自动进行任务拆解，例如：</p>
<ol>
<li class="">分析项目结构</li>
<li class="">找到数据库访问代码</li>
<li class="">设计缓存策略</li>
<li class="">修改代码</li>
<li class="">添加测试</li>
<li class="">执行验证</li>
</ol>
<p>这个过程类似一个 <strong>AI 自动生成开发计划</strong>。</p>
<p>值得强调的是，这份计划不是一次性生成后就一路执行到底的。每完成一步，模型都会重新审视剩余步骤——比如分析项目结构后发现用的是 MyBatis 而不是 JPA，后续的改动方案就会跟着调整。计划是活的，这也是 Agent 和「让模型一次性输出脚本」的本质区别。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-自动执行任务">2) 自动执行任务<a href="https://tanzhuo.xyz/2026/03/13/openclaw#2-%E8%87%AA%E5%8A%A8%E6%89%A7%E8%A1%8C%E4%BB%BB%E5%8A%A1" class="hash-link" aria-label="2) 自动执行任务的直接链接" title="2) 自动执行任务的直接链接" translate="no">​</a></h3>
<p>OpenClaw 不只是给建议，而是可以：</p>
<ul>
<li class="">修改代码</li>
<li class="">创建文件</li>
<li class="">运行 Shell 命令</li>
<li class="">安装依赖</li>
<li class="">执行脚本</li>
</ul>
<p>例如：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 拉取目标项目到本地</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">git</span><span class="token plain"> clone project</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 安装项目依赖</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">npm</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 跑一遍测试，验证改动是否破坏了现有功能</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">npm</span><span class="token plain"> run </span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">test</span><br></div></code></pre></div></div>
<p>AI 可以直接执行这些操作。</p>
<p>执行的关键在于结果回传：命令的 stdout、stderr、退出码都会进入下一轮上下文。测试挂了，模型能看到具体是哪个用例、什么报错，然后自己去改——这就是「持续迭代直到任务完成」的具体形态。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-工具调用能力">3) 工具调用能力<a href="https://tanzhuo.xyz/2026/03/13/openclaw#3-%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="3) 工具调用能力的直接链接" title="3) 工具调用能力的直接链接" translate="no">​</a></h3>
<p>OpenClaw 可以通过 Tool 调用不同能力，例如：</p>
<ul>
<li class="">文件系统</li>
<li class="">Shell</li>
<li class="">Git</li>
<li class="">HTTP API</li>
<li class="">编译工具</li>
<li class="">测试框架</li>
</ul>
<p>工具本质上是暴露给模型的一组带描述的接口，模型根据任务自行决定调用哪个、传什么参数。这套机制是可扩展的：把内部系统包一层接口注册进去，Agent 就能操作它。能力边界不取决于框架本身，而取决于你愿意给它接上多少工具——当然，接得越多，权限控制也就越需要谨慎。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="openclaw-的缺陷">OpenClaw 的缺陷<a href="https://tanzhuo.xyz/2026/03/13/openclaw#openclaw-%E7%9A%84%E7%BC%BA%E9%99%B7" class="hash-link" aria-label="OpenClaw 的缺陷的直接链接" title="OpenClaw 的缺陷的直接链接" translate="no">​</a></h2>
<p>虽然 OpenClaw 很强，但目前仍然存在一些明显问题。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-非常依赖模型能力">1) 非常依赖模型能力<a href="https://tanzhuo.xyz/2026/03/13/openclaw#1-%E9%9D%9E%E5%B8%B8%E4%BE%9D%E8%B5%96%E6%A8%A1%E5%9E%8B%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="1) 非常依赖模型能力的直接链接" title="1) 非常依赖模型能力的直接链接" translate="no">​</a></h3>
<p>OpenClaw 的能力 <strong>高度依赖底层模型</strong>。</p>
<p>如果模型能力不够：</p>
<ul>
<li class="">任务规划能力会下降</li>
<li class="">代码质量下降</li>
<li class="">执行容易出错</li>
</ul>
<p>因此很多时候：</p>
<p>Agent能力 ≈ 模型能力</p>
<p>如果模型弱，OpenClaw 也会变得“很傻”。</p>
<p>这个问题在 Agent 场景下会被放大：普通对话里模型答错一句，人看一眼就能纠正；而在多步执行的循环里，第一步的错误规划会被后续每一步继承，错误是累积的。框架层面的提示词工程只能兜住一部分，兜不住模型本身的推理短板。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-token-成本问题">2) Token 成本问题<a href="https://tanzhuo.xyz/2026/03/13/openclaw#2-token-%E6%88%90%E6%9C%AC%E9%97%AE%E9%A2%98" class="hash-link" aria-label="2) Token 成本问题的直接链接" title="2) Token 成本问题的直接链接" translate="no">​</a></h3>
<p>Agent 系统通常需要：</p>
<ul>
<li class="">多轮思考</li>
<li class="">多次调用模型</li>
<li class="">持续上下文</li>
</ul>
<p>对Token的消耗非常高，每次都携带了大量上下文进行模型调用，就算调优也只是减少部分开销，或者替换便宜的模型，但便宜的模型替换又会导致上面的问题，OpenClaw变得完全不可靠，不可控。</p>
<p>原因不难理解：每一轮调用都要带上任务目标、历史操作、文件内容和命令输出，上下文随着迭代轮数近似线性增长，而每轮都是一次完整的模型调用。一个多步任务下来，消耗可能是单次对话的几十倍。成本和能力在这里是一对绕不开的矛盾——省钱换弱模型，第一个问题立刻找上门。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2026/03/13/openclaw#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>结合前面两个缺陷，实际使用中有几点建议：</p>
<ul>
<li class=""><strong>别在生产环境直接跑。</strong> Agent 会真的执行命令，删文件、改配置都是真删真改。放进容器或者隔离的工作目录里跑，权限给到最小。</li>
<li class=""><strong>任务要拆小。</strong> 目标越大，规划越容易跑偏，上下文也膨胀得越快。「加一个缓存」比「重构整个数据层」靠谱得多。</li>
<li class=""><strong>盯着执行过程。</strong> 前面提到执行过程不透明的问题，在没有配套监控平台的情况下，至少要保留完整的操作日志，事后能追溯它到底动了什么。</li>
<li class=""><strong>给成本设上限。</strong> 多轮迭代的 Token 消耗很容易失控，尤其是模型在某个错误上反复重试的时候。设置轮数上限或预算上限，避免一觉醒来账单爆炸。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="总体来说">总体来说<a href="https://tanzhuo.xyz/2026/03/13/openclaw#%E6%80%BB%E4%BD%93%E6%9D%A5%E8%AF%B4" class="hash-link" aria-label="总体来说的直接链接" title="总体来说的直接链接" translate="no">​</a></h2>
<p>OpenClaw 是一个非常有潜力的 <strong>AI Agent 框架</strong>。</p>
<p>优点：</p>
<ul>
<li class="">自动任务规划</li>
<li class="">自动执行能力</li>
<li class="">可扩展的 Agent 架构</li>
</ul>
<p>缺点：</p>
<ul>
<li class="">非常依赖模型</li>
<li class="">Token 成本较高 (模型调用非常高，复杂任务能力完全取决于模型)</li>
<li class="">执行过程不透明 (非常严重，执行过程完全不透明，需要配合控制平台一起监控才行)</li>
</ul>
<p>因此它目前更适合：</p>
<ul>
<li class="">AI 自动化开发</li>
<li class="">DevOps 自动化</li>
<li class="">AI Agent 实验</li>
</ul>
<p>但如果结合：</p>
<ul>
<li class="">强模型</li>
<li class="">可视化系统</li>
<li class="">Agent 管理平台</li>
</ul>
<p>未来可能会成为：</p>
<blockquote>
<p><strong>AI 自动化团队的核心基础设施。</strong></p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/13/openclaw#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Agent 框架的价值在于把模型的输出接到真实环境里闭环执行，OpenClaw 在这条路上做得足够完整：规划、执行、工具调用三件事都有。但它的上限被模型能力锁死，下限被成本和透明度拖累。现阶段我的用法是：小任务、隔离环境、盯着日志跑。等围绕它的监控和管理生态成熟一些，再考虑放到更重要的流程里。</p>]]></content:encoded>
            <category>技术</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[运维历史演进与 K8S 之后趋势]]></title>
            <link>https://tanzhuo.xyz/2026/03/13/ops-evolution</link>
            <guid>https://tanzhuo.xyz/2026/03/13/ops-evolution</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从手工管机器到 IaC、云计算与 Kubernetes 的演进脉络,以及 GitOps、可观测性、Serverless 和 AI 运维这些后续趋势,顺带聊聊运维这个职业的走向。]]></description>
            <content:encoded><![CDATA[<p>过去三十多年,服务器运维其实一直在做一件事:<strong>让人越来越少地去“手动管机器”。</strong> 这篇文章梳理这条演进脉络,也聊聊 Kubernetes 之后运维会往哪走。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="背景">背景<a href="https://tanzhuo.xyz/2026/03/13/ops-evolution#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="背景的直接链接" title="背景的直接链接" translate="no">​</a></h2>
<p>早期的运维非常原始。服务器买回来要自己上架、装系统、配置网络、改配置文件。机器出了问题,就 SSH 登录上去排查,很多时候靠的是经验和记忆。那时候的运维,本质上就是“人盯机器”。</p>
<p>但当服务器数量越来越多,这种方式很快就撑不住了。问题主要有两个:<strong>重复劳动</strong>和<strong>配置混乱</strong>。</p>
<p>重复劳动很好理解,比如同一个服务要部署到几十台机器,每次都要手动执行一堆命令;规模一大,运维每天都在做这些重复的事情。在 SRE 体系里,这类工作有一个专门的名字,叫 <strong>toil</strong>(可以自动化、重复、但长期价值不高的工作)。</p>
<p>另一个问题叫<strong>配置漂移</strong>。今天在一台机器上临时改了个参数,半年后没人记得;不同机器的配置慢慢变得不一样,系统也越来越难维护。</p>
<p>为了解决这些问题,运维开始走向自动化。最早是各种 <strong>Shell 脚本</strong>,后来发展成<strong>配置管理工具</strong>(比如 Puppet、Chef、Ansible)。再往后,一个更重要的理念出现了:<strong>基础设施即代码(IaC)</strong>。</p>
<p>简单来说,就是把服务器配置写成代码,放进 Git 里管理。这样所有变更都有记录,可以回滚,也可以重复执行。运维不再是“去机器上改东西”,而是“改代码”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="虚拟化云计算">虚拟化云计算<a href="https://tanzhuo.xyz/2026/03/13/ops-evolution#%E8%99%9A%E6%8B%9F%E5%8C%96%E4%BA%91%E8%AE%A1%E7%AE%97" class="hash-link" aria-label="虚拟化云计算的直接链接" title="虚拟化云计算的直接链接" translate="no">​</a></h2>
<p>再往后,一个非常关键的变化出现了:<strong>虚拟化和云计算</strong>。</p>
<p>以前买服务器是一件很重的事情,需要采购、上架、布线。虚拟化出现后,一台物理服务器可以跑很多虚拟机,服务器不再是硬件,而变成了一种“资源”。到了云时代,甚至不需要自己买服务器了,直接在控制台点几下就能申请资源。运维的角色,也开始从“管机器的人”变成“调度资源的人”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="容器化技术">容器化技术<a href="https://tanzhuo.xyz/2026/03/13/ops-evolution#%E5%AE%B9%E5%99%A8%E5%8C%96%E6%8A%80%E6%9C%AF" class="hash-link" aria-label="容器化技术的直接链接" title="容器化技术的直接链接" translate="no">​</a></h2>
<p>真正改变软件交付方式的,是 <strong>Docker 和 Kubernetes</strong>。</p>
<p>Docker 解决的是一个老问题:<strong>软件在不同环境运行不一致</strong>。开发机器能跑,测试机器不行,线上环境又不一样。Docker 把应用和运行环境一起打包成一个镜像,就像打包盒饭一样,拿到哪里都能运行。</p>
<p>但如果只有 Docker,还是不够。当容器数量变成几十、几百、几千个时,就需要一个系统去管理这些容器。于是 Kubernetes 出现了。</p>
<p>Kubernetes 的核心思想其实很简单:<strong>你只需要告诉系统“我希望系统是什么样”,系统会不断把现实拉回这个目标状态。</strong></p>
<p>比如你声明:“我要 3 个服务实例”,如果有一个挂掉了,Kubernetes 会自动再拉起一个;如果机器坏了,容器会自动调度到别的机器上。这种机制叫<strong>控制循环(Control Loop)</strong>,也是 Kubernetes 能够自动化管理大规模集群的关键。</p>
<p>所以从更宏观的角度看,运维的发展其实是三次大的跃迁:</p>
<ul>
<li class=""><strong>配置管理 / IaC</strong>:让服务器配置可以版本化管理</li>
<li class=""><strong>虚拟化 / 云计算</strong>:让服务器变成资源</li>
<li class=""><strong>Docker + Kubernetes</strong>:让整个集群可以自动运行</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="kubernetes-之后如何发展">Kubernetes 之后,如何发展<a href="https://tanzhuo.xyz/2026/03/13/ops-evolution#kubernetes-%E4%B9%8B%E5%90%8E%E5%A6%82%E4%BD%95%E5%8F%91%E5%B1%95" class="hash-link" aria-label="Kubernetes 之后,如何发展的直接链接" title="Kubernetes 之后,如何发展的直接链接" translate="no">​</a></h2>
<p>Kubernetes 并不是终点,它只是把“集群管理”这个问题解决得比较好了。在 Kubernetes 之后,运维的发展主要集中在三个方向:<strong>更自动、更可观测、更少服务器心智负担。</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-gitops">1) GitOps<a href="https://tanzhuo.xyz/2026/03/13/ops-evolution#1-gitops" class="hash-link" aria-label="1) GitOps的直接链接" title="1) GitOps的直接链接" translate="no">​</a></h3>
<p>GitOps 的思路很简单:<strong>Git 就是系统的唯一事实来源。</strong></p>
<p>所有配置都写在 Git 里,系统自动把线上环境和 Git 的状态对齐。如果要改配置,不再是去服务器上操作,而是提一个 PR。合并代码之后,系统自动完成部署。</p>
<p>这种模式最大的好处是:变更可审计、回滚简单、流程更清晰。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-可观测性和服务治理">2) 可观测性和服务治理<a href="https://tanzhuo.xyz/2026/03/13/ops-evolution#2-%E5%8F%AF%E8%A7%82%E6%B5%8B%E6%80%A7%E5%92%8C%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86" class="hash-link" aria-label="2) 可观测性和服务治理的直接链接" title="2) 可观测性和服务治理的直接链接" translate="no">​</a></h3>
<p>当系统变成微服务之后,真正复杂的往往不是某个服务,而是服务之间的调用关系。请求可能经过十几个服务,一旦某个环节慢了,整个系统都会受影响。</p>
<p>于是出现了 <strong>Service Mesh、Prometheus、OpenTelemetry</strong> 这一类技术。它们的核心目标是:<strong>让系统运行状态更透明,问题更容易被发现。</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-serverless-和边缘计算">3) Serverless 和边缘计算<a href="https://tanzhuo.xyz/2026/03/13/ops-evolution#3-serverless-%E5%92%8C%E8%BE%B9%E7%BC%98%E8%AE%A1%E7%AE%97" class="hash-link" aria-label="3) Serverless 和边缘计算的直接链接" title="3) Serverless 和边缘计算的直接链接" translate="no">​</a></h3>
<p>Serverless 的理念是:开发者只需要写代码,服务器由平台自动管理。比如一个函数在有请求时才运行,没有请求就不占资源。对很多场景来说,这种模式可以大幅降低运维成本。</p>
<p>与此同时,一些计算也开始往“边缘”移动,比如门店设备、工厂设备、车载系统等。为了适应这些场景,也出现了很多轻量化 Kubernetes(比如 K3s)和云边协同技术。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai-如何改变运维">AI 如何改变运维<a href="https://tanzhuo.xyz/2026/03/13/ops-evolution#ai-%E5%A6%82%E4%BD%95%E6%94%B9%E5%8F%98%E8%BF%90%E7%BB%B4" class="hash-link" aria-label="AI 如何改变运维的直接链接" title="AI 如何改变运维的直接链接" translate="no">​</a></h2>
<p>最近几年,AI 也开始逐渐进入运维领域。</p>
<p>很多传统的监控系统都是基于<strong>固定阈值</strong>的,比如 CPU 超过 80% 报警。但在复杂系统中,很多异常其实很难用阈值描述。于是越来越多的平台开始用<strong>机器学习来做异常检测</strong>。</p>
<p>例如:</p>
<ul>
<li class="">AWS 的 DevOps Guru 会分析指标数据,自动发现异常并给出建议</li>
<li class="">Azure 的智能检测会自动识别性能下降或错误率上升</li>
<li class="">Elastic 的异常检测会先学习系统的正常行为,再识别异常模式</li>
</ul>
<p>还有一些研究和产品,会用深度学习去分析日志,从日志序列中发现异常模式。这些技术的目标其实很简单:<strong>减少告警噪声,让运维更快找到真正的问题。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="运维这个职业何去何从">运维这个职业,何去何从<a href="https://tanzhuo.xyz/2026/03/13/ops-evolution#%E8%BF%90%E7%BB%B4%E8%BF%99%E4%B8%AA%E8%81%8C%E4%B8%9A%E4%BD%95%E5%8E%BB%E4%BD%95%E4%BB%8E" class="hash-link" aria-label="运维这个职业,何去何从的直接链接" title="运维这个职业,何去何从的直接链接" translate="no">​</a></h2>
<p>很多人看到自动化和 AI,会担心运维是不是会消失。但现实情况更像是:<strong>运维的工作方式在改变。</strong></p>
<p>过去运维的价值,很多时候体现在“会不会手动处理问题”,比如登录机器、改配置、重启服务、排查故障。但未来更有价值的能力,其实是这些:</p>
<ul>
<li class="">能把经验<strong>自动化</strong></li>
<li class="">能把流程<strong>平台化</strong></li>
<li class="">能从业务指标理解系统问题</li>
<li class="">能设计<strong>可观测性和稳定性体系</strong></li>
</ul>
<p>在我看来,<strong>未来的运维,不是去修机器的人,而是让系统自己会修自己、会观测自己、会听从人类语言指令自动完成建设的那个人。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2026/03/13/ops-evolution#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>运维这三十多年的演进,核心线索只有一条:把人从重复劳动中解放出来。配置管理和 IaC 让变更可版本化,虚拟化和云计算把服务器变成资源,Docker 与 Kubernetes 让集群可以自动运行。往后看,GitOps、可观测性、Serverless 是确定性比较强的方向,AI 则在逐步接管异常检测和故障定位。运维不会消失,但“手动处理问题”的价值会持续下降,能设计和建设自动化体系的人会越来越值钱。</p>]]></content:encoded>
            <category>Linux</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[Redis 线程模型]]></title>
            <link>https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing</link>
            <guid>https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Redis 常被称为“单线程”，但这个说法只对了一半。从事件循环、IO 多路复用讲到 Redis 6 的 IO 多线程，梳理 Redis 高性能的来源与单线程模型下的使用注意点。]]></description>
            <content:encoded><![CDATA[<p>Redis 以高性能著称，其核心原因之一就是其独特的 <strong>线程模型设计</strong>。很多人听说 Redis 是“单线程”，但实际上 Redis 的线程模型在不同版本中已经发生了演进。理解 Redis 的线程模型，对于理解其高性能原理、以及在高并发场景中的使用方式非常重要。</p>
<!-- -->
<p>之所以想把这个话题单独写一篇，是因为“Redis 是单线程”这句话在面试和日常讨论中出现的频率太高，但大部分人对它的理解停留在字面上：要么以为整个 Redis 进程只有一个线程，要么以为 Redis 6 之后命令执行也变成多线程了。这两种理解都不准确。而这个模型直接影响我们怎么用 Redis——哪些命令不能随便执行、大 Key 为什么危险、Pipeline 为什么有效，根源都在线程模型上。</p>
<p>本文将从 <strong>Redis 单线程设计、事件驱动模型、IO 多路复用以及 Redis 6 之后的多线程改进</strong>几个方面进行介绍。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-为什么选择单线程">Redis 为什么选择单线程<a href="https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing#redis-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9%E5%8D%95%E7%BA%BF%E7%A8%8B" class="hash-link" aria-label="Redis 为什么选择单线程的直接链接" title="Redis 为什么选择单线程的直接链接" translate="no">​</a></h2>
<p>Redis 早期版本（Redis 6 之前）的核心执行模型是 <strong>单线程处理命令</strong>。</p>
<p>也就是说：</p>
<ul>
<li class="">所有客户端请求</li>
<li class="">所有命令执行</li>
<li class="">数据读写</li>
</ul>
<p>都由 <strong>一个主线程完成</strong>。</p>
<p>这个设计带来一个非常重要的性质：<strong>任意时刻只有一条命令在执行</strong>。所以 Redis 的单条命令天然是原子的，不需要加锁，也不存在两条命令交错修改同一个 Key 的问题。像 <code>INCR</code>、<code>SETNX</code> 这类命令能直接当作并发原语来用，就是这个模型的副产品。</p>
<p>但需要注意的是：</p>
<p>Redis 的单线程 <strong>只指命令执行单线程</strong>，并不是整个 Redis 进程只有一个线程。例如：</p>
<ul>
<li class="">RDB 持久化</li>
<li class="">AOF rewrite</li>
<li class="">异步删除</li>
<li class="">BIO 线程</li>
</ul>
<p>这些其实都是后台线程。</p>
<p>以异步删除为例：<code>UNLINK</code> 命令在主线程里只做一件事——把 Key 从字典中摘除，真正释放内存的工作交给后台线程慢慢做。这样即使删除一个很大的对象，主线程也不会被卡住。RDB 持久化则更进一步，直接 fork 出子进程去写快照，利用操作系统的写时复制（copy-on-write）避免阻塞主线程。</p>
<p>Redis 的核心线程只负责：</p>
<ul>
<li class="">网络 IO</li>
<li class="">命令解析</li>
<li class="">命令执行</li>
<li class="">返回结果</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="单线程为什么依然很快">单线程为什么依然很快<a href="https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing#%E5%8D%95%E7%BA%BF%E7%A8%8B%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BE%9D%E7%84%B6%E5%BE%88%E5%BF%AB" class="hash-link" aria-label="单线程为什么依然很快的直接链接" title="单线程为什么依然很快的直接链接" translate="no">​</a></h2>
<p>很多人会疑惑：单线程为什么还能支撑几十万 QPS？</p>
<p>Redis 的高性能主要来自以下几个原因。</p>
<p>内存数据库：Redis 所有数据都在内存中，避免了磁盘 IO。内存速度和磁盘速度不是一个数量级的。对于一条普通的 <code>GET</code> 命令，真正的执行部分只是一次哈希表查找，耗时是微秒甚至纳秒级——瓶颈根本不在 CPU 计算上。</p>
<p>避免线程切换开销：单线程执行命令，避免了加锁，竞争，复杂并发控制。多线程模型里，线程上下文切换、锁的争抢、缓存失效这些开销并不便宜；当每个请求本身只需要极短的处理时间时，这些并发控制的成本反而可能超过收益。Redis 选择单线程，本质上是判断：<strong>命令执行不是瓶颈，网络 IO 才是</strong>，那就把并发问题在 IO 层解决，让执行层保持最简单的形态。</p>
<p>此外，Redis 内部的数据结构也是为这个模型服务的：SDS 字符串、跳表、压缩列表等都针对内存访问做了优化，单个操作的路径非常短。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="io-多路复用">IO 多路复用<a href="https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing#io-%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8" class="hash-link" aria-label="IO 多路复用的直接链接" title="IO 多路复用的直接链接" translate="no">​</a></h2>
<p>Redis 并不是一次只处理一个连接，而是使用 <strong>IO 多路复用模型</strong>。</p>
<p>所谓 IO 多路复用，是指用一个线程同时监视多个 socket，由操作系统告诉你“哪些连接现在有数据可读/可写”，线程只处理就绪的那部分。相比“一个连接一个线程”的传统模型，它不需要为每个连接付出线程的内存和调度成本，单线程就能撑起海量连接。</p>
<p>Redis 使用的 IO 模型包括：</p>
<ul>
<li class="">epoll（Linux）</li>
<li class="">kqueue（MacOS / BSD）</li>
<li class="">select</li>
<li class="">evport</li>
</ul>
<p>Redis 在编译时会根据平台自动选择最优实现，Linux 上就是 epoll。这层封装在源码里叫 ae（A simple Event driven programming library），对上层暴露统一的事件接口。</p>
<p>Redis 会通过事件循环监听所有客户端连接：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">             ┌───────────────┐</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Client 1 ───▶│               │</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Client 2 ───▶│               │</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Client 3 ───▶│  IO Multiplex │</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Client N ───▶│               │</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">             └───────┬───────┘</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                     │</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                     ▼</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">               Redis EventLoop</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                     │</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                     ▼</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">               Command Execute</span><br></div></code></pre></div></div>
<p>处理流程：</p>
<ol>
<li class="">监听多个客户端 socket</li>
<li class="">哪个 socket 有数据就绪</li>
<li class="">读取请求</li>
<li class="">执行命令</li>
<li class="">返回结果</li>
</ol>
<p>事件循环每一轮都会从多路复用接口取出就绪的事件，逐个处理：读事件就解析命令并执行，写事件就把结果发回客户端。因为每个事件的处理都很快，循环转得足够快，客户端感知不到自己在“排队”。</p>
<p>因此 Redis 可以用 <strong>单线程同时处理大量连接</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-6-的多线程改进">Redis 6 的多线程改进<a href="https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing#redis-6-%E7%9A%84%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%94%B9%E8%BF%9B" class="hash-link" aria-label="Redis 6 的多线程改进的直接链接" title="Redis 6 的多线程改进的直接链接" translate="no">​</a></h2>
<p>随着硬件发展，CPU 核数越来越多，Redis 的单线程模型在某些场景下会遇到瓶颈，瓶颈主要在 <strong>网络 IO</strong>。</p>
<p>例如：</p>
<ul>
<li class="">大量客户端连接</li>
<li class="">网络数据收发量巨大</li>
</ul>
<p>命令执行本身是纯内存操作，快得很；但把请求从内核缓冲区读进来、把响应写回去，这些 read/write 系统调用是有实际开销的。当流量大到一定程度，主线程的时间大量花在收发数据上，真正执行命令的时间反而占比很小——这就是单线程模型的天花板。</p>
<p>因此 Redis 6 引入了 <strong>IO 多线程</strong>。</p>
<p>执行流程变为：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Client Request</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       │</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">IO Threads (read)</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       │</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Main Thread Execute Command</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       │</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">IO Threads (write)</span><br></div></code></pre></div></div>
<p>思路很清晰：把读取解析和写回响应这两段“搬运工”的活分给多个 IO 线程并行做，命令执行仍然收敛回主线程串行处理。这样既利用了多核提升网络吞吐，又完整保留了单线程执行的原子性语义——上层业务代码不需要做任何改变。</p>
<p>因此：</p>
<ul>
<li class=""><strong>命令执行仍然是单线程</strong></li>
<li class=""><strong>IO 操作可以并行</strong></li>
</ul>
<p>需要说明的是，IO 多线程默认是关闭的，通过配置开启：</p>
<div class="language-properties codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-properties codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># redis.conf</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># IO 线程数，建议小于 CPU 核数</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">io-threads</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">4</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 默认只有写回用多线程，读取解析也想并行需再打开这个</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">io-threads-do-reads</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">yes</span><br></div></code></pre></div></div>
<p>只有在网络流量确实成为瓶颈的实例上开启才有意义，小流量场景开了反而多一层线程协调的开销。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-单线程的使用建议">Redis 单线程的使用建议<a href="https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing#redis-%E5%8D%95%E7%BA%BF%E7%A8%8B%E7%9A%84%E4%BD%BF%E7%94%A8%E5%BB%BA%E8%AE%AE" class="hash-link" aria-label="Redis 单线程的使用建议的直接链接" title="Redis 单线程的使用建议的直接链接" translate="no">​</a></h2>
<p>由于 Redis 命令执行是单线程，因此需要注意以下几点。一条慢命令会阻塞 <strong>所有</strong> 客户端的请求，这是单线程模型最需要敬畏的地方。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="避免慢命令">避免慢命令<a href="https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing#%E9%81%BF%E5%85%8D%E6%85%A2%E5%91%BD%E4%BB%A4" class="hash-link" aria-label="避免慢命令的直接链接" title="避免慢命令的直接链接" translate="no">​</a></h3>
<p>例如：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 遍历整个键空间，O(N)，期间阻塞所有请求</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">KEYS *</span><br></div></code></pre></div></div>
<p>这类命令会阻塞 Redis。</p>
<p>应该使用：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 基于游标分批迭代，每次只处理一小部分，不会长时间阻塞</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">SCAN</span><br></div></code></pre></div></div>
<p>类似的还有对大集合执行 <code>SMEMBERS</code>、<code>HGETALL</code>、<code>LRANGE 0 -1</code> 等全量读取命令，本质上都是把 O(N) 的工作塞进了单线程里。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="控制大-key">控制大 Key<a href="https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing#%E6%8E%A7%E5%88%B6%E5%A4%A7-key" class="hash-link" aria-label="控制大 Key的直接链接" title="控制大 Key的直接链接" translate="no">​</a></h3>
<p>大 Key 会导致：</p>
<ul>
<li class="">网络阻塞</li>
<li class="">CPU 执行时间变长</li>
</ul>
<p>而且大 Key 的删除和过期同样发生在主线程（除非用 <code>UNLINK</code> 或开启惰性删除），一个几百 MB 的 Key 过期时可能造成明显的请求毛刺。</p>
<p>建议：</p>
<ul>
<li class="">拆分数据</li>
<li class="">使用 hash / set</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用-pipeline">使用 Pipeline<a href="https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing#%E4%BD%BF%E7%94%A8-pipeline" class="hash-link" aria-label="使用 Pipeline的直接链接" title="使用 Pipeline的直接链接" translate="no">​</a></h3>
<p>Pipeline 可以减少网络往返：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">client -&gt; redis -&gt; client</span><br></div></code></pre></div></div>
<p>不用 Pipeline 时，每条命令都要等上一条的响应回来才发下一条，吞吐被网络 RTT 限死；Pipeline 把一批命令攒起来一次发出，一次收回全部响应，把 N 次往返压缩成一次。</p>
<p>提升整体吞吐量。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>结合线程模型，有几个实践中容易忽略的点：</p>
<ol>
<li class="">
<p><strong>Lua 脚本同样阻塞主线程</strong>。脚本执行期间 Redis 不处理任何其他命令，脚本里写循环处理大量数据，效果等同于一条超级慢命令。</p>
</li>
<li class="">
<p><strong>别把 Redis 6 的 IO 多线程理解成“并发执行命令”</strong>。事务、Lua、<code>INCR</code> 的原子性语义完全没变，不需要因此加锁；同样，也别指望开 IO 线程能解决慢命令问题——慢在执行层的，多线程 IO 帮不上忙。</p>
</li>
<li class="">
<p><strong>排查阻塞先看慢日志</strong>。<code>SLOWLOG GET</code> 记录的是命令执行耗时（不含网络），是定位“Redis 偶尔卡一下”的第一入口；配合 <code>latency</code> 相关命令能进一步区分是命令慢、fork 慢还是磁盘慢。</p>
</li>
<li class="">
<p><strong>单实例吃不满多核是正常的</strong>。命令执行只用一个核，如果想利用整台多核机器，常见做法是单机多实例或上 Redis Cluster，而不是等 Redis 变成多线程执行。</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="总结">总结<a href="https://tanzhuo.xyz/2026/03/13/redis-nei-cun-mo-xing#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>Redis 通过 <strong>内存操作、IO 多路复用、事件驱动（Event Loop）以及单线程命令执行</strong> 的架构设计，在避免锁竞争和线程切换开销的同时，实现了极高的并发处理能力。Redis 6 的 IO 多线程只是把网络收发并行化，命令执行仍然串行——这个核心不变，围绕它的使用原则（避免慢命令、控制大 Key、善用 Pipeline）也就始终成立。理解了这一点，很多 Redis 的最佳实践就不再是背出来的条目，而是模型推导出的必然结论。</p>]]></content:encoded>
            <category>技术</category>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[数据结构：二叉树]]></title>
            <link>https://tanzhuo.xyz/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao</link>
            <guid>https://tanzhuo.xyz/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[梳理二叉树的基本概念与常见形态：满二叉树、完全二叉树、二叉搜索树,以及 BST 退化成链表的问题和遍历方式,是学习树形结构的入门笔记。]]></description>
            <content:encoded><![CDATA[<p>链表和数组解决的是"线性"数据的存放问题,但一旦涉及层级关系或者快速查找,线性结构就显得力不从心了。树形结构正是为此而生,而二叉树是其中最基础、也最常被考察的一种——堆、BST、红黑树全都建立在它之上。</p>
<!-- -->
<p><strong>二叉树（Binary Tree）</strong> 是一种常见的数据结构,它由若干节点组成,每个节点最多只有 <strong>两个子节点</strong>：</p>
<ul>
<li class="">左子节点（Left Child）</li>
<li class="">右子节点（Right Child）</li>
</ul>
<p>因此称为 <strong>二叉树</strong></p>
<p>"最多两个"这个限制看似简单,却带来一个重要性质：树的形态可以用递归定义——每棵二叉树都由根节点、左子树、右子树三部分组成,左右子树本身也是二叉树。后面几乎所有关于二叉树的算法（遍历、查找、插入）都是围绕这个递归结构展开的。</p>
<p>每个节点通常包含三个部分：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Node {</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    value   // 节点存储的数据</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    left    // 指向左子节点的引用,没有则为空</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    right   // 指向右子节点的引用,没有则为空</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>也就是说,二叉树在内存里并不要求连续存放,节点之间靠引用（指针）串联,这一点和链表类似,只是每个节点从"一个后继"变成了"最多两个孩子"。</p>
<p>简单结构示例：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        A</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       / \</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      B   C</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">     / \   \</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    D   E   F</span><br></div></code></pre></div></div>
<p>其中：</p>
<ul>
<li class="">A 是 <strong>根节点（Root）</strong></li>
<li class="">B、C 是 A 的 <strong>子节点</strong></li>
<li class="">D、E 是 B 的 <strong>子节点</strong></li>
</ul>
<p>没有子节点的节点（如 D、E、F）称为 <strong>叶子节点（Leaf）</strong>。从根到叶子经过的层数称为树的 <strong>高度</strong>,它直接决定了大多数树上操作的耗时。</p>
<p>按照形态的不同,二叉树有几个常见的特殊类别,下面依次来看。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="满二叉树full-binary-tree">满二叉树（Full Binary Tree）<a href="https://tanzhuo.xyz/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91full-binary-tree" class="hash-link" aria-label="满二叉树（Full Binary Tree）的直接链接" title="满二叉树（Full Binary Tree）的直接链接" translate="no">​</a></h2>
<p>如果一棵树的 <strong>所有节点要么有两个子节点,要么没有子节点</strong>,则称为满二叉树。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        A</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       / \</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      B   C</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">     / \ / \</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    D  E F  G</span><br></div></code></pre></div></div>
<p>特点：</p>
<ul>
<li class="">每个节点要么 <strong>0 个子节点</strong></li>
<li class="">要么 <strong>2 个子节点</strong></li>
</ul>
<p>换句话说,满二叉树里不存在"只有一个孩子"的节点。这种形态最"饱满",在层数相同的情况下能容纳的节点数最多。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="完全二叉树complete-binary-tree">完全二叉树（Complete Binary Tree）<a href="https://tanzhuo.xyz/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91complete-binary-tree" class="hash-link" aria-label="完全二叉树（Complete Binary Tree）的直接链接" title="完全二叉树（Complete Binary Tree）的直接链接" translate="no">​</a></h2>
<p>完全二叉树要求：</p>
<ul>
<li class="">除最后一层外,其余层全部填满</li>
<li class="">最后一层节点从 <strong>左往右连续排列</strong></li>
</ul>
<p>示例：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       / \</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      2   3</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">     / \  /</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    4  5 6</span><br></div></code></pre></div></div>
<p>这种结构非常适合 <strong>数组存储</strong>,因此 <strong>堆（Heap）</strong> 就是完全二叉树。</p>
<p>为什么完全二叉树适合数组存储？因为节点"从上到下、从左到右"编号后中间没有空洞：把根放在下标 0,那么下标为 i 的节点,其左子节点在 <code>2i + 1</code>,右子节点在 <code>2i + 2</code>,父节点在 <code>(i - 1) / 2</code>（向下取整）。不需要存任何指针,靠下标运算就能在父子之间跳转,既省内存又对缓存友好——堆排序和优先级队列正是利用了这一点。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二叉搜索树bst">二叉搜索树（BST）<a href="https://tanzhuo.xyz/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91bst" class="hash-link" aria-label="二叉搜索树（BST）的直接链接" title="二叉搜索树（BST）的直接链接" translate="no">​</a></h2>
<p>前面两种分类关注的是"形状",二叉搜索树关注的则是"节点值的排列规则"。二叉搜索树（Binary Search Tree）满足：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">左子树 &lt; 根节点 &lt; 右子树</span><br></div></code></pre></div></div>
<p>注意这个约束是递归生效的：不只是左右孩子,而是整棵左子树的所有值都小于根,整棵右子树的所有值都大于根。</p>
<p>示例：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        8</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">       / \</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      3   10</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">     / \    \</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    1   6    14</span><br></div></code></pre></div></div>
<p>特点：</p>
<ul>
<li class="">查询效率高</li>
<li class="">平均时间复杂度：<strong>O(log n)</strong></li>
</ul>
<p>查找的过程和二分查找如出一辙：从根开始,目标值比当前节点小就往左走,比它大就往右走,每走一层就排除掉大约一半的候选节点。插入也是同理——沿着查找路径走到空位,把新节点挂上去即可。</p>
<p>但如果树退化成链表：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"> \</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  2</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   \</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    3</span><br></div></code></pre></div></div>
<p>时间复杂度会退化为：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">O(n)</span><br></div></code></pre></div></div>
<p>退化最典型的触发场景,就是按 <strong>有序序列</strong> 依次插入：每个新节点都比之前的大,只能一路挂在右边,树"长歪"成了一条链,查找时每层只能排除一个节点,BST 的优势荡然无存。为了解决这个问题,才有了 AVL 树、红黑树这类 <strong>自平衡二叉搜索树</strong>——它们在插入、删除时通过旋转调整结构,保证树高始终维持在 O(log n) 级别。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="遍历方式">遍历方式<a href="https://tanzhuo.xyz/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#%E9%81%8D%E5%8E%86%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="遍历方式的直接链接" title="遍历方式的直接链接" translate="no">​</a></h2>
<p>访问二叉树所有节点的方式称为遍历,常见的有四种：</p>
<ol>
<li class=""><strong>前序遍历</strong>：根 → 左子树 → 右子树,常用于复制或序列化一棵树</li>
<li class=""><strong>中序遍历</strong>：左子树 → 根 → 右子树,对 BST 而言,中序遍历得到的正是 <strong>升序序列</strong></li>
<li class=""><strong>后序遍历</strong>：左子树 → 右子树 → 根,适合"先处理孩子再处理自己"的场景,比如释放整棵树</li>
<li class=""><strong>层序遍历</strong>：逐层从左到右访问,借助队列实现,也就是树上的广度优先搜索</li>
</ol>
<p>前三种用递归写起来非常自然,这也印证了前面说的——二叉树本身就是一个递归定义的结构。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>结合上面的内容,有几点在实际使用和刷题时容易栽跟头：</p>
<ul>
<li class=""><strong>别默认 BST 是平衡的</strong>。分析复杂度时要区分"平均 O(log n)"和"最坏 O(n)",面试中被追问的往往就是退化场景。</li>
<li class=""><strong>中序遍历验证 BST</strong>。判断一棵树是否为合法 BST,不能只比较节点和它的直接孩子,要保证整棵子树都满足大小关系;用中序遍历检查序列是否严格递增是最不容易出错的写法。</li>
<li class=""><strong>递归深度</strong>。树退化成链表时,递归遍历的调用栈深度也是 O(n),数据量大时可能栈溢出,必要时改用显式栈的迭代写法。</li>
<li class=""><strong>区分"满"与"完全"</strong>。两者定义容易混淆:满二叉树不允许单孩子节点,完全二叉树则要求最后一层左对齐,判断题里经常拿这两个概念互相设坑。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="总结">总结<a href="https://tanzhuo.xyz/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>二叉树是最基础也是最重要的数据结构之一,其核心特点是</p>
<ul>
<li class="">每个节点最多 <strong>两个子节点</strong></li>
<li class="">支持 <strong>高效搜索</strong></li>
<li class="">可以扩展为多种高级数据结构</li>
</ul>
<p>常见变种包括：</p>
<ul>
<li class="">二叉搜索树（BST）</li>
<li class="">AVL 树</li>
<li class="">红黑树</li>
<li class="">堆（Heap）</li>
</ul>
<p>理解二叉树是学习 <strong>算法与数据结构</strong> 的重要基础。掌握了它的递归定义、几种形态的区别以及遍历套路,再去看平衡树和堆,会发现那些"高级"结构不过是在二叉树上叠加了额外的约束而已。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Istio-限流配置]]></title>
            <link>https://tanzhuo.xyz/2025/11/04/istio</link>
            <guid>https://tanzhuo.xyz/2025/11/04/istio</guid>
            <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[在 Istio IngressGateway 上用 Envoy RateLimit + Redis 做全局限流:配置解析、请求判定流程与 domain、descriptor 匹配的常见坑。]]></description>
            <content:encoded><![CDATA[<p>线上接口被刷过一次之后,我把限流从应用层挪到了网关层。这篇笔记整理在 Istio IngressGateway 上基于 Envoy RateLimit + Redis 做全局限流的完整配置,以及几个容易踩的匹配细节。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="背景与问题">背景与问题<a href="https://tanzhuo.xyz/2025/11/04/istio#%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98" class="hash-link" aria-label="背景与问题的直接链接" title="背景与问题的直接链接" translate="no">​</a></h2>
<p>在微服务架构中,系统通常会通过 <strong>Istio IngressGateway</strong> 对外提供统一入口。当流量突然增加(活动流量、爬虫、接口被刷)时,如果没有限流机制,后端可能出现这些问题:</p>
<ul>
<li class="">接口被高频调用,服务 CPU 或数据库压力过大</li>
<li class="">突发流量导致系统雪崩或级联故障</li>
<li class="">核心 API 被恶意刷接口,挤占正常用户的访问</li>
</ul>
<p>在每个服务里各自实现限流当然可行,但规则分散、口径不一,而且请求已经打进了业务进程,资源照样被消耗。更合理的做法是在<strong>网关层统一限流</strong>,请求进入后端之前就完成流量治理。</p>
<p>Istio 的数据面就是 Envoy Proxy,天然具备这个能力:通过 <strong>Envoy RateLimit 过滤器 + 独立的 ratelimit 服务 + Redis</strong>,可以实现跨网关副本的全局限流,对特定路径做访问频率控制。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="原理简述">原理简述<a href="https://tanzhuo.xyz/2025/11/04/istio#%E5%8E%9F%E7%90%86%E7%AE%80%E8%BF%B0" class="hash-link" aria-label="原理简述的直接链接" title="原理简述的直接链接" translate="no">​</a></h2>
<p>整条链路上有三个角色,理解它们的关系,后面的 YAML 就不难读了:</p>
<p>1)<strong>Envoy RateLimit 过滤器</strong>。挂在 IngressGateway 的 HTTP 过滤器链上,每个请求经过时,按路由上配置的 <code>actions</code> 生成一组描述符(descriptor),带着 <code>domain</code> 一起通过 gRPC 询问 ratelimit 服务:这个请求放不放行。</p>
<p>2)<strong>ratelimit 服务</strong>(envoyproxy/ratelimit)。一个独立部署的 gRPC 服务,加载 ConfigMap 里的配额规则,收到描述符后查规则、扣配额,返回 OK 或 OVER_LIMIT。</p>
<p>3)<strong>Redis</strong>。配额计数器的存储。正因为计数在 Redis 里,多个网关副本共享同一份计数,这才叫"全局"限流——本地限流(local rate limit)则是每个 Envoy 实例各算各的。</p>
<p>匹配的关键在于:路由上的 <code>actions</code> 生成的描述符组合,必须与 ConfigMap 里 <code>descriptors</code> 的 key/value 层级<strong>完全对应</strong>,并且两边的 <code>domain</code> 一致,配额才会命中。任何一环对不上,限流就静默失效,请求全部放行。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="什么时候会用到">什么时候会用到<a href="https://tanzhuo.xyz/2025/11/04/istio#%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E4%BC%9A%E7%94%A8%E5%88%B0" class="hash-link" aria-label="什么时候会用到的直接链接" title="什么时候会用到的直接链接" translate="no">​</a></h2>
<p>典型的是这类低频但敏感、被刷的代价很高的接口:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">/api/login</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">/api/send-code</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">/api/payment</span><br></div></code></pre></div></div>
<p>登录接口被刷会触发大量密码校验和风控计算;发验证码接口被刷直接烧短信费;支付接口则关系到下游资源。这些接口本身 QPS 不高,给一个很小的配额就能挡掉绝大部分恶意流量,误伤面也小。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="完整配置">完整配置<a href="https://tanzhuo.xyz/2025/11/04/istio#%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="完整配置的直接链接" title="完整配置的直接链接" translate="no">​</a></h2>
<p>下面是一套可以直接套用的配置,分四部分:Secret 存 Redis 密码、ConfigMap 定义配额、ratelimit 服务本体、两个 EnvoyFilter 分别负责挂过滤器和打描述符。</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># --------------------------------------------</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 0) Redis 密码放 Secret（更安全）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    用 Secret 存放敏感信息（如 Redis 密码），避免明文进入镜像或 Pod 环境变量历史</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --------------------------------------------</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">apiVersion</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">kind</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> Secret</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">redis</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">secret          </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># Secret 名称，后续 Deployment 通过 secretKeyRef 引用</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">namespace</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> istio</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">system               </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 放在与 ratelimit 服务相同的命名空间，便于引用与管理</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">type</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> Opaque</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">stringData</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">REDIS_AUTH</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"password"</span><span class="token plain">       </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 明文由 K8s 负责 base64 编码存储</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --------------------------------------------</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 1) Ratelimit 配额配置（Envoy Ratelimit Server 的运行时配置）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - 该 ConfigMap 被容器以只读方式挂载到 /data/ratelimit/config</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - domain 必须与 Envoy HTTP 过滤器里的 domain 完全一致，否则不会命中</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - descriptors 用来描述限流的“维度组合”（actions 组合后形成的描述符）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --------------------------------------------</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">apiVersion</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">kind</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ConfigMap</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">config</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">namespace</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> istio</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">system</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">data</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">config.yaml</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">|</span><span class="token scalar string" style="color:hsl(95, 38%, 62%)"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">    domain: ingress-ratelimit          # 必须与 RateLimit 过滤器的 domain 一致</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">    descriptors:</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">      - key: header_match              # 顶层 key：与 HTTP_ROUTE 中的 header_value_match 对应（actions 1/2）</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">        value: path-api         # 顶层 value：来自路由上 actions 的 descriptor_value</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">        descriptors:                   # 二级描述符：继续细分 (组合成唯一的限流键)</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">          - key: generic_key           # 与 HTTP_ROUTE 中 generic_key 对应（actions 2/2）</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">            value: istio-limit-v1      # 对应路由里设置的 descriptor_value</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">            rate_limit:</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">              unit: second             # 时间单位：second / minute / hour / day</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">              requests_per_unit: 5     # 配额：单位时间内允许的请求数（此处为 1 秒 1 次）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --------------------------------------------</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 2) Ratelimit Service（连接你现有 Redis 单机）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - 通过 envoyproxy/ratelimit 实现全局滑窗/令牌桶限流的配置服务</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - replicas=1：测试环境单副本；生产建议至少 2-3 副本并配合 Redis 高可用</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - readiness/liveness：通过 6070 端口健康检查</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - 关键环境变量：REDIS_URL / REDIS_AUTH / RUNTIME_ROOT / RUNTIME_SUBDIRECTORY</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --------------------------------------------</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">apiVersion</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">kind</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> Service</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit                         </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 供 Envoy 通过集群名解析 (outbound|8081||ratelimit.istio-system.svc.cluster.local)</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">namespace</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> istio</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">system</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">spec</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">selector</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">app</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain">            </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 与 Deployment 的 labels 匹配</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">ports</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> grpc</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">port</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">8081</span><span class="token plain">                          </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># gRPC 服务端口（Envoy RateLimit 过滤器将连此端口）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">targetPort</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">8081</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">apiVersion</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> apps/v1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">kind</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> Deployment</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">namespace</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> istio</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">system</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">spec</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">replicas</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">1</span><span class="token plain">                              </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 测试设 1，生产建议 ≥3 并做好 HPA/PodDisruptionBudget</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">selector</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">matchLabels</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">app</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">template</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">labels</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">app</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">spec</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">containers</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">image</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> envoyproxy/ratelimit</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">875d418c   </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 指定 commit/tag，保证版本可重现；可考虑升级到官方较新 tag</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">imagePullPolicy</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> IfNotPresent</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">command</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token string" style="color:hsl(95, 38%, 62%)">"/bin/ratelimit"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">            </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 启动入口</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">ports</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">containerPort</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">8080</span><span class="token plain">                </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># HTTP Admin（可选：metrics/调试）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">containerPort</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">8081</span><span class="token plain">                </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># gRPC 主服务端口</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">containerPort</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">6070</span><span class="token plain">                </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 健康检查端口（/healthcheck）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">env</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> REDIS_SOCKET_TYPE</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> tcp                         </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 通过 TCP 连接 Redis</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> REDIS_URL</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"redis://1.1.1.1:6379"</span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 你的 Redis 地址（生产建议内网域名 + 哨兵或集群）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> REDIS_POOL_SIZE</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"20"</span><span class="token plain">                        </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 连接池大小；视并发/副本数调优</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> REDIS_AUTH</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">valueFrom</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">secretKeyRef</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">redis</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">secret   </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 从 Secret 注入密码，避免明文</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">key</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> REDIS_AUTH</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> USE_STATSD</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"false"</span><span class="token plain">                     </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 如有 statsd/Prometheus sidecar 可打开</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> RUNTIME_ROOT</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> /data                       </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 对应挂载点根目录</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> RUNTIME_SUBDIRECTORY</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit                   </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 子目录，最终配置路径 /data/ratelimit/config</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> RUNTIME_IGNOREDOTFILES</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"true"</span><span class="token plain">                      </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 忽略隐藏文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">volumeMounts</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> config</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">mountPath</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> /data/ratelimit/config  </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 与上面 runtime 路径一致，确保装载到 config 子目录</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">readinessProbe</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">httpGet</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">path</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> /healthcheck</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">port</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">6070</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">initialDelaySeconds</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">2</span><span class="token plain">               </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 初始延迟，避免冷启动误判</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">periodSeconds</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">5</span><span class="token plain">                     </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 探测频率</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">livenessProbe</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">httpGet</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">path</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> /healthcheck</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">port</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">6070</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">initialDelaySeconds</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">10</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">periodSeconds</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">10</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">volumes</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> config</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">configMap</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">config               </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 装载上面的 ConfigMap</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --------------------------------------------</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 3) IngressGateway 注入全局 RateLimit 过滤器 + 指向 ratelimit 的 CLUSTER</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - 通过 EnvoyFilter 在 HTTP Router 之前插入全局 ratelimit HTTP 过滤器</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - workloadSelector：限定只作用于网关工作负载（label: istio=ingressgateway）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - rate_limit_service：连到上面定义的 ratelimit gRPC（通过集群名/authority）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - 追加一个 Lua 响应过滤器：当后端返回 429 时，改写为统一 JSON</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --------------------------------------------</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">apiVersion</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> networking.istio.io/v1alpha3</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">kind</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> EnvoyFilter</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> gateway</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">global</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">limit</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">filter</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">namespace</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> istio</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">system</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">spec</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">workloadSelector</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">labels</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">istio</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ingressgateway                 </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 只作用在 IngressGateway 实例</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">configPatches</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 3.1 在 router 之前插入 ratelimit HTTP 过滤器（全局生效）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">applyTo</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> HTTP_FILTER</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">match</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">context</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> GATEWAY</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">listener</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">filterChain</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">filter</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> envoy.filters.network.http_connection_manager    </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># HTTP 连接管理器</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">subFilter</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> envoy.filters.http.router                      </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 在 Router 之前插入</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">patch</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">operation</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> INSERT_BEFORE</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> envoy.filters.http.ratelimit</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">typed_config</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">"@type"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">domain</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ingress</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">ratelimit          </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 必须与 ConfigMap 里的 domain 一致</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">failure_mode_deny</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token boolean important" style="color:hsl(220, 14%, 71%);font-weight:bold">false</span><span class="token plain">           </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 后端 ratelimit 服务异常时是否拒绝请求；false=放行（推荐）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">timeout</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> 10s                       </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 访问 ratelimit 服务的超时</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">rate_limit_service</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">grpc_service</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">envoy_grpc</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">cluster_name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> outbound</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">|</span><span class="token plain">8081</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">|</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">|</span><span class="token plain">ratelimit.istio</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">system.svc.cluster.local  </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># Istio 自动生成的集群名</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">authority</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ratelimit.istio</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">system.svc.cluster.local                     </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># HTTP/2 Host (SNI)，可省略</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">transport_api_version</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> V3         </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 使用 v3 API（推荐）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 3.2 基于端口 1035 的 Listener 再插入 Lua 响应过滤器（可选：仅对该监听端口生效）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">applyTo</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> HTTP_FILTER</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">match</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">context</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> GATEWAY</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">listener</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">portNumber</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">1035</span><span class="token plain">                      </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 仅对 1035 端口的 Listener 生效（你的网关暴露此端口）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">filterChain</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">filter</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> envoy.filters.network.http_connection_manager</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">subFilter</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> envoy.filters.http.router</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">patch</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">operation</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> INSERT_BEFORE</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> envoy.filters.http.lua</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">typed_config</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">"@type"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">inlineCode</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">|</span><span class="token scalar string" style="color:hsl(95, 38%, 62%)"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">              function envoy_on_response(handle)</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">                local s = tonumber(handle:headers():get(":status") or "0")</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">                if s == 429 then</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">                  local body = '{"code":429,"message":"Too many requests. Please try again later."}'</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">                  handle:headers():replace("content-type", "application/json")</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">                  handle:headers():replace("content-length", tostring(#body))</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">                  handle:body(true):setBytes(body)</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">                end</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token scalar string" style="color:hsl(95, 38%, 62%)">              end</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 注意：</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># - 如果你的网关并非监听 1035（或多端口），请确认此过滤器是否应该扩大到所有端口（去掉 portNumber 条件）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">   </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># - 也可用本地速率限制本地拒绝时在本机生成 429，再由该 Lua 统一改写</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --------------------------------------------</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 4) 给 VirtualHost 注入 per-route 限流动作</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - 在指定的 Route（或整个 vhost）下配置 actions，从而生成与 ConfigMap 匹配的 descriptors</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - 这里使用两段 action：</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#        a) header_value_match：当 :path 以 /api 前缀命中时，写入 descriptor_value=path-api</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#        b) generic_key：再追加 descriptor_value=istio-limit-v1</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - 两段组合 =&gt; (key=header_match,value=path-api) + (key=generic_key,value=istio-limit-v1)</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#      正好与 ConfigMap 对应，从而激活“1 秒 1 次”的限流</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --------------------------------------------</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">apiVersion</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> networking.istio.io/v1alpha3</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">kind</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> EnvoyFilter</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> gateway</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">limit</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">descriptor</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">svc</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">namespace</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> istio</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">system</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">spec</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">workloadSelector</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">labels</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">istio</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ingressgateway                       </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 作用范围限定在网关</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">configPatches</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">applyTo</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> HTTP_ROUTE</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">match</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">context</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> GATEWAY</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">routeConfiguration</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">vhost</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> host</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">port       </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 注意此 vhost 名：通常为 "&lt;host&gt;:&lt;port&gt;"</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">patch</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">operation</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> MERGE</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">route</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">rate_limits</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">              </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">actions</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">header_value_match</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">headers</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                      </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">":path"</span><span class="token plain">             </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 匹配伪首部 :path，基于前缀判断</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">prefix_match</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"/api"</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">expect_match</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token boolean important" style="color:hsl(220, 14%, 71%);font-weight:bold">true</span><span class="token plain">            </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 仅当匹配时才添加该描述符</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">descriptor_value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"path-api"</span><span class="token plain">   </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 对应 ConfigMap 顶层 key/value</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">generic_key</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">descriptor_value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"istio-limit-v1"</span><span class="token plain">    </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 对应 ConfigMap 二级 key/value</span><br></div></code></pre></div></div>
<p>几个配置点展开说一下:</p>
<p>1)<strong>两个 EnvoyFilter 缺一不可</strong>。第一个只是把 ratelimit 过滤器挂到过滤器链上、告诉它去哪找 ratelimit 服务;真正决定"哪些请求带什么描述符去问限流"的是第二个——路由上没有 <code>rate_limits.actions</code>,过滤器对请求就什么都不做。</p>
<p>2)<strong><code>failure_mode_deny: false</code></strong>。ratelimit 服务或 Redis 挂掉时选择放行而不是拒绝。限流是保护手段,不该成为新的单点故障;除非接口敏感到宁可不可用也不能被刷,否则建议保持 false。</p>
<p>3)<strong>Lua 过滤器是体验优化</strong>。Envoy 限流命中时默认返回一个裸的 429,前端不好统一处理;这段 Lua 在响应阶段把 429 的 body 改写成统一的 JSON 结构,对客户端更友好。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2025/11/04/istio#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>domain 不一致,限流静默失效</strong>。过滤器里的 <code>domain</code> 和 ConfigMap 里的 <code>domain</code> 必须逐字符相同。对不上时不会报错,ratelimit 服务只是查不到规则,全部返回 OK。</li>
<li class=""><strong>descriptor 层级必须完全对应</strong>。ConfigMap 里 <code>header_match/path-api</code> 嵌套 <code>generic_key/istio-limit-v1</code> 的两层结构,对应路由上两个 action 的先后顺序。少一个 action、value 写错、层级颠倒,都命中不了。</li>
<li class=""><strong>vhost 名不是随便写的</strong>。<code>routeConfiguration.vhost.name</code> 通常是 <code>"&lt;host&gt;:&lt;port&gt;"</code> 的形式,要和 Envoy 实际生成的路由配置里的 vhost 名一致,可以从网关的 config_dump 里确认,写错了 MERGE 不会生效。</li>
<li class=""><strong>注意 EnvoyFilter 匹配的端口</strong>。上面 Lua 过滤器限定了 <code>portNumber: 1035</code>,如果网关监听的是别的端口或多个端口,要相应调整或去掉这个条件,否则 429 改写只对部分流量生效。</li>
<li class=""><strong>ratelimit 服务自身的可用性</strong>。测试环境单副本没问题,生产建议多副本,Redis 也要考虑高可用;<code>REDIS_POOL_SIZE</code> 按并发量和副本数调整。</li>
<li class=""><strong>EnvoyFilter 是较底层的扩展机制</strong>,直接 patch Envoy 配置,对 Envoy 内部 API 有版本耦合。升级 Istio 大版本前,先在测试集群验证这两个 filter 还能正常生效。</li>
</ul>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>排查限流不生效时,顺着链路依次确认:ratelimit 服务日志里有没有收到请求 → domain 是否一致 → 描述符组合是否与 ConfigMap 对齐 → vhost 名是否正确。绝大多数问题出在后三个"对不上"。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2025/11/04/istio#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>网关层全局限流的核心就是三件事:路由上的 <code>actions</code> 负责给请求打描述符,ratelimit 服务负责按 ConfigMap 里的规则查配额,Redis 负责让所有网关副本共享计数。配置本身不复杂,难点集中在 domain 和 descriptor 的匹配上——这些地方错了不报错,只是默默不生效。把匹配链路理清楚,剩下的就是按业务需要调配额了。</p>]]></content:encoded>
            <category>K8S</category>
        </item>
        <item>
            <title><![CDATA[阿里云实时数仓]]></title>
            <link>https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang</link>
            <guid>https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang</guid>
            <pubDate>Wed, 10 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[基于阿里云 Flink、DataHub 与 Hologres 搭建全托管实时数仓的方案：组件选型、处理流程，以及 ODS/DWD/DWS/ADS 数仓分层设计。]]></description>
            <content:encoded><![CDATA[<p>自建大数据集群往往复杂、难维护、部署成本高，借助阿里云现有的产品生态体系可以绕开这些问题。这里记录一套基于阿里云托管产品搭建实时数仓的方案，按需开通对应产品即可满足业务需求。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="面临的痛点">面临的痛点<a href="https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang#%E9%9D%A2%E4%B8%B4%E7%9A%84%E7%97%9B%E7%82%B9" class="hash-link" aria-label="面临的痛点的直接链接" title="面临的痛点的直接链接" translate="no">​</a></h2>
<p>1）底层数据库无法承载海量数据。随着企业发展，10T、100T 乃至 PB、EB 级的数据量既存不下，也无法支撑快速查询响应、数据分析和数据挖掘等工作。</p>
<p>2）实时计算性能存在不足。需要可靠的计算引擎进行毫秒级实时计算，并且数据质量要可靠、可控、可遥测。</p>
<p>3）数据模型调整效率不够快。不能灵活地调整数据模型结构，难以快速支撑业务场景的报表需求。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="应用场景">应用场景<a href="https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang#%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="应用场景的直接链接" title="应用场景的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://yq.aliyun.com/articles/719246?spm=a2c6h.12873639.article-detail.10.35a62292oUX3aq&amp;ref=tanzhuo.xyz" target="_blank" rel="noopener noreferrer" class=""><strong>基于Flink和规则引擎的实时风控解决方案</strong></a></li>
<li class=""><a href="https://yq.aliyun.com/articles/692345?spm=a2c6h.12873639.article-detail.12.35a62292oUX3aq&amp;ref=tanzhuo.xyz" target="_blank" rel="noopener noreferrer" class=""><strong>基于实时计算（Flink）与高斯模型构建实时异常检测系统</strong></a></li>
<li class=""><a href="https://yq.aliyun.com/articles/691497?spm=a2c6h.12873639.article-detail.19.35a62292oUX3aq&amp;ref=tanzhuo.xyz" target="_blank" rel="noopener noreferrer" class="">基于实时计算（Flink）打造一个简单的实时推荐系统</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实时数仓">实时数仓<a href="https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang#%E5%AE%9E%E6%97%B6%E6%95%B0%E4%BB%93" class="hash-link" aria-label="实时数仓的直接链接" title="实时数仓的直接链接" translate="no">​</a></h2>
<p>总体数据开发流程：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2024-03-p301715-260628d991918b4359af348e3a5f2781.webp" width="1162" height="547" class="img_ev3q"></p>
<p>链路概括起来是：数据拉取 -&gt; 数据缓冲 -&gt; 实时计算 -&gt; 下沉落库。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2024-03-p399558-45f9e637bf9658f8bc0a4901192767fb.png" width="798" height="448" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="组件选型">组件选型<a href="https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang#%E7%BB%84%E4%BB%B6%E9%80%89%E5%9E%8B" class="hash-link" aria-label="组件选型的直接链接" title="组件选型的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink">Flink<a href="https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang#flink" class="hash-link" aria-label="Flink的直接链接" title="Flink的直接链接" translate="no">​</a></h3>
<p><a href="https://www.aliyun.com/product/bigdata/sc?spm=a2c4g.11186623.J_4VYgf18xNlTAyFFbOuOQe.115.7cf75c67wmW96D&amp;scm=20140722.S_product%40%40%E4%BA%91%E4%BA%A7%E5%93%81%40%40247054._.ID_product%40%40%E4%BA%91%E4%BA%A7%E5%93%81%40%40247054-RL_flink-LOC_menu%7EUND%7Eproduct-OR_ser-V_3-P0_0&amp;ref=tanzhuo.xyz" target="_blank" rel="noopener noreferrer" class="">阿里云实时计算 Flink 版</a></p>
<p>阿里云全托管的 Flink 实时计算引擎，免去了企业自建 Yarn、Flink 等节点以及繁琐的配置工作。它主要负责处理 DataHub 消息总线中的业务数据，按数据的业务场景把计算结果合理下发到后续链路。依托阿里云生态，元数据管理也更加便利。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="datahub">DataHub<a href="https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang#datahub" class="hash-link" aria-label="DataHub的直接链接" title="DataHub的直接链接" translate="no">​</a></h3>
<p><a href="https://www.aliyun.com/product/bigdata/datahub?spm=5176.28508143.J_4VYgf18xNlTAyFFbOuOQe.48.6ce5154ajrJlWg&amp;scm=20140722.S_product%40%40%E4%BA%91%E4%BA%A7%E5%93%81%40%40813456._.ID_product%40%40%E4%BA%91%E4%BA%A7%E5%93%81%40%40813456-RL_datahub-LOC_menu%7EUND%7Eproduct-OR_ser-V_3-P0_0&amp;ref=tanzhuo.xyz" target="_blank" rel="noopener noreferrer" class="">数据总线 DataHub</a></p>
<p>DataHub 的作用相当于数据缓冲层，为实时数据流提供高性能的存储与流传输。Flink 计算中途产生的部分 DWD、DWS 层临时数据以及下游数据，都经由它做任务转发与存储，起到消息总线的作用。topic 单日读写可达 T 级数据量，数据流转高效、稳定。这个组件同样是全托管的，无需关注节点数量。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hologres">Hologres<a href="https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang#hologres" class="hash-link" aria-label="Hologres的直接链接" title="Hologres的直接链接" translate="no">​</a></h3>
<p><a href="https://www.aliyun.com/product/bigdata/hologram?spm=5176.28348429.J_4VYgf18xNlTAyFFbOuOQe.86.59056284OdWWsx&amp;ref=tanzhuo.xyz" target="_blank" rel="noopener noreferrer" class="">实时数仓Hologres - 大数据交互式分析 – 分析服务一体化</a></p>
<p>Flink 计算完成之后，对应层级的数据输出下沉到 Hologres 数仓中。Hologres 支持行存、列存以及按字段匹配的分区存储，解决海量数据存储问题，并实现了 PB 级数据的亚秒级分析。Hologres 的存储使用阿里自研的 Pangu 分布式文件系统（类似 HDFS），本身架构属于大数据数仓设计体系，整体采用 Storage Disaggregation（存储计算分离）架构，计算节点可以横向扩展，提高并行计算能力，解决海量数据查询的时效问题。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2024-03-p431174-3bb38ec6264307fcee5ef97365184050.png" width="1011" height="595" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="处理流程">处理流程<a href="https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang#%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="处理流程的直接链接" title="处理流程的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2024-01-3-ad3171d5d2d0142e9db892126744680c.webp" width="1530" height="1140" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数仓分层">数仓分层<a href="https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang#%E6%95%B0%E4%BB%93%E5%88%86%E5%B1%82" class="hash-link" aria-label="数仓分层的直接链接" title="数仓分层的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2024-03-v2-27465cd3ad33b8afaa35e914c7f308cb_r-1130bc39ed4a403069f137fd10db67df.png" width="695" height="466" class="img_ev3q"></p>
<p>在大数据领域，ODS、DWD、DWS 和 ADS 是常见的数据分层概念，用于组织和管理数据。它们在数据处理和使用方面各有分工。</p>
<ol>
<li class="">ODS（Operational Data Store，运营数据存储层）：面向操作性业务的数据存储层，通常用于存储源系统的原始数据，保留数据的完整性和详细性。ODS 层的主要目标是支持实时或准实时的操作性需求，例如在线交易处理和实时查询。这一层的数据通常不做大规模加工和计算，而是直接用于操作性业务。</li>
<li class="">DWD（Data Warehouse Detail，数据仓库明细层）：面向数据仓库的明细数据层，存储经过清洗、集成和转换的数据，通常是面向主题的、可查询的数据。DWD 层的数据经过 ETL（抽取、转换和加载）处理，用于支持数据分析、报表和决策支持等任务，具有较高的质量和一致性，通常以事实表和维度表的形式组织。</li>
<li class="">DWS（Data Warehouse Summary，数据仓库汇总层）：存储经过聚合、汇总和预计算的数据，提供更高层次的数据摘要和分析性能。DWS 层的数据通常以汇总表或预计算的指标为主，用于支持复杂的数据分析、数据挖掘和商业智能需求。</li>
<li class="">ADS（Application Data Service，应用数据服务层）：面向应用程序的数据存储层，存储经过进一步加工、计算和优化的数据。ADS 层的数据通常是针对具体应用的数据视图或数据集，经过适配和优化，以提供更高效的数据访问和处理能力。</li>
</ol>
<p>概括来说：ODS 层服务于操作性业务，保留原始数据的完整性；DWD 层提供清洗、集成、转换后的可查询数据；DWS 层做汇总和预计算，换取更高的查询性能；ADS 层则面向具体应用，提供加工优化后的数据。</p>
<p>这种分层设计有几个好处：</p>
<ol>
<li class="">数据处理和管理的分离：把数据分为不同层次，处理和管理的责任也随之分开。DWD 层负责原始数据的提取、清洗和集成，DWS 层负责聚合和汇总，ADS 层提供面向应用的数据服务，数据处理因此模块化、更灵活。</li>
<li class="">数据质量和一致性：DWD 层保留原始数据的完整性和细节，可用于数据质量分析和数据溯源；DWS 层提供聚合汇总后的数据，查询性能更好；ADS 层保证数据在不同应用场景下的一致性和准确性。</li>
<li class="">查询性能和应用开发效率：通过汇总和预计算，DWS 层能加快数据分析和决策过程；ADS 层提供面向应用的数据服务和接口，简化应用开发，提高开发效率。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2024/01/10/a-li-yun-shi-shi-shu-cang#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这套方案的核心思路是把集群运维交给云厂商：DataHub 负责数据缓冲与流转，Flink 负责实时计算，Hologres 承接存储与分析，全链路托管，不用自己维护节点。配合 ODS/DWD/DWS/ADS 的分层设计，各层数据职责清晰，查询性能和报表开发效率都有保障。对于不想投入人力自建大数据集群的团队，这是一条务实的路径。</p>]]></content:encoded>
            <category>Linux</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Grpcs TLS 自签证书]]></title>
            <link>https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s</link>
            <guid>https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s</guid>
            <pubDate>Wed, 08 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[记录用 OpenSSL 为 gRPC 内网通讯生成自签证书的完整流程：根证书、服务证书、SAN 扩展字段与证书内容验证。]]></description>
            <content:encoded><![CDATA[<p>由于微服务通讯需要进行数据加密以保证内网通讯安全。故此记录一下自签证书的生成过程。</p>
<!-- -->
<p>先交代一下背景。gRPC 默认走明文的 HTTP/2，服务间的请求体在内网中是可以被直接抓包读到的。生产环境里即便是内网，也不能假设网络绝对可信——一旦某台机器被攻破，横向嗅探流量的成本非常低。给 gRPC 加上 TLS 是最直接的加固手段。而内网服务没必要（也往往没办法）去公共 CA 申请证书，自建一套根证书、给每个服务签发证书，是更常见的做法。这套流程用 OpenSSL 就能完成，但细节不少，尤其是 Go 侧对证书字段的校验规则，值得完整记录一遍。</p>
<p>以下是在 Linux 或 MacOS 系统上使用 OpenSSL 命令行工具生成自签名证书的步骤。这个过程将创建一个新的根证书（CA），然后使用这个根证书签名一个新的服务器证书。</p>
<p>这里的证书体系是两层结构：根证书自己给自己签名，充当信任锚点；服务证书由根证书签发，客户端只要预先信任根证书，就能验证任何由它签出的服务证书。这也是为什么客户端侧只需要分发 <code>ca.crt</code>，而不需要逐个分发每个服务的证书。</p>
<p>先简明说明整体流程：</p>
<p>根证书：</p>
<p>1、生成根私钥</p>
<p>2、通过根私钥生成根证书</p>
<p>服务证书</p>
<p>1、生成服务私钥</p>
<p>2、通过服务私钥生成服务<strong>CSR</strong></p>
<p>3、通过服务.csr 文件 + 根证书 + 根证书 key 签名出服务证书.crt文件</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="生成根证书的私钥"><strong>生成根证书的私钥</strong><a href="https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s#%E7%94%9F%E6%88%90%E6%A0%B9%E8%AF%81%E4%B9%A6%E7%9A%84%E7%A7%81%E9%92%A5" class="hash-link" aria-label="生成根证书的私钥的直接链接" title="生成根证书的私钥的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 生成 2048 位 RSA 私钥，作为根 CA 的私钥</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">openssl genrsa </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-out</span><span class="token plain"> ca.key </span><span class="token number" style="color:hsl(29, 54%, 61%)">2048</span><br></div></code></pre></div></div>
<p>这个命令将生成一个新的 RSA 私钥，长度为 2048 位。这个私钥将被保存到名为 ca<code>.key</code> 的文件中。</p>
<p>这把私钥是整个信任链的根，谁拿到它就能签发出被所有客户端信任的证书。妥善保管，不要提交到代码仓库。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="生成根证书"><strong>生成根证书</strong><a href="https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s#%E7%94%9F%E6%88%90%E6%A0%B9%E8%AF%81%E4%B9%A6" class="hash-link" aria-label="生成根证书的直接链接" title="生成根证书的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 用根私钥自签一张 X.509 根证书，有效期 365 天</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">openssl req </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-new</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-x509</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-days</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">365</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-key</span><span class="token plain"> ca.key </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-out</span><span class="token plain"> ca.crt</span><br></div></code></pre></div></div>
<p>这个命令将生成一个新的 X.509 根证书，使用 SHA-256 算法进行签名，有效期为 365 天 (可自己调整日期)。这个证书将被保存到名为 ca<code>.crt</code> 的文件中。</p>
<p><code>-x509</code> 参数表示直接输出自签名证书，而不是签名请求，这正是"根证书自己给自己签名"的含义。</p>
<p>在执行这个命令时，OpenSSL 会提示你输入一些信息，这些信息将被包含在证书中。在大多数情况下，你可以直接按 Enter 键接受默认值。</p>
<p>但 <strong>Subject Name 或 Common Name</strong> 需要填写微服务端的名称。</p>
<p>根证书准备好之后，接下来为具体的服务生成证书。每个服务都重复下面三步即可。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="生成服务器证书的私钥"><strong>生成服务器证书的私钥</strong><a href="https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s#%E7%94%9F%E6%88%90%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AF%81%E4%B9%A6%E7%9A%84%E7%A7%81%E9%92%A5" class="hash-link" aria-label="生成服务器证书的私钥的直接链接" title="生成服务器证书的私钥的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 为服务生成 2048 位 RSA 私钥</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">openssl genrsa </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-out</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">service-name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.key </span><span class="token number" style="color:hsl(29, 54%, 61%)">2048</span><br></div></code></pre></div></div>
<p>这个命令与第一个命令类似，生成的私钥将被保存到名为 [service-name]<code>.key</code> 的文件中。[service-name] 为你自己的服务名称。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="生成服务器证书的证书签名请求csr"><strong>生成服务器证书的证书签名请求（CSR）</strong><a href="https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s#%E7%94%9F%E6%88%90%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AF%81%E4%B9%A6%E7%9A%84%E8%AF%81%E4%B9%A6%E7%AD%BE%E5%90%8D%E8%AF%B7%E6%B1%82csr" class="hash-link" aria-label="生成服务器证书的证书签名请求csr的直接链接" title="生成服务器证书的证书签名请求csr的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 基于服务私钥生成证书签名请求（CSR）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">openssl req </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-new</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-key</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">service-name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.key </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-out</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">service-name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.csr</span><br></div></code></pre></div></div>
<p>这个命令将生成一个新的证书签名请求（CSR），这个请求包含了服务器证书的公钥和一些附加信息。这个请求将被保存到名为 <code>server.csr</code> 的文件中。</p>
<p>CSR 本身不是证书，它只是"我是谁、我的公钥是什么"的一份申请材料，需要交给 CA 签名后才变成可用的证书。</p>
<p>同样，在执行这个命令时，OpenSSL 会提示你输入一些信息，这些信息将被包含在 CSR 中。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用根证书签名服务器证书"><strong>使用根证书签名服务器证书</strong><a href="https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s#%E4%BD%BF%E7%94%A8%E6%A0%B9%E8%AF%81%E4%B9%A6%E7%AD%BE%E5%90%8D%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AF%81%E4%B9%A6" class="hash-link" aria-label="使用根证书签名服务器证书的直接链接" title="使用根证书签名服务器证书的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 用根证书 + 根私钥对 CSR 签名，得到服务证书</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">openssl x509 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-req</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-in</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">service-name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.csr </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-CA</span><span class="token plain"> ca.crt </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-CAkey</span><span class="token plain"> ca.key </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-CAcreateserial</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-out</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">service-name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.crt </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-days</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">365</span><br></div></code></pre></div></div>
<p>这个命令将使用根证书对服务器证书进行签名，生成的证书将被保存到名为 <code>server.crt</code> 的文件中。</p>
<p>其中 <code>-CAcreateserial</code> 会生成一个 <code>.srl</code> 序列号文件，用于记录该 CA 已签发证书的序号，后续再签发时会自动递增，无需手动处理。</p>
<p>以上步骤生成的 <code>rootCA.crt</code>、<code>server.crt</code> 和 <code>server.key</code> 就可以用于 GRPC 加密通讯了，配置到对应的服务器端以及客户端。服务端加载自己的证书和私钥，客户端加载根证书用于校验服务端身份。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="部分自定义内容">部分自定义内容<a href="https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s#%E9%83%A8%E5%88%86%E8%87%AA%E5%AE%9A%E4%B9%89%E5%86%85%E5%AE%B9" class="hash-link" aria-label="部分自定义内容的直接链接" title="部分自定义内容的直接链接" translate="no">​</a></h2>
<p>由于在GO更高的版本中，对证书内部分字段的值验证与获取有调整。（Go 1.15版本之后会获取证书alt_names字段信息进行服务名称验证）所以我们需要在生成证书时，指定部分自定义字段信息。</p>
<p>也就是说，只在 Common Name 里写服务名已经不够了：Go 的 TLS 客户端在握手时会拿连接的目标名称去匹配证书里的 SAN（Subject Alternative Name）字段，匹配不上就直接握手失败。所以证书必须显式携带 SAN 扩展。命令行交互不方便填这些扩展字段，改用配置文件的方式。</p>
<p>创建一个 san.cnf 文件内容如下：</p>
<div class="language-properties codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-properties codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">[req]</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">default_bits</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">2048</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">prompt</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">no</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">default_md</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">sha256</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">distinguished_name</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">dn</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 证书主体信息，替代交互式输入</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">[dn]</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">C</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">CN</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">ST</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">Beijing</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">L</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">Beijing</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">O</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">'lynx'</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">OU</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">'lynx'</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">emailAddress</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">'lynx'</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">CN</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">[service-name]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># CSR 阶段使用的扩展：声明 SAN</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">[req_ext]</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">subjectAltName</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">@alt_names</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># SAN 列表，Go 客户端校验的就是这里的 DNS 名称</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">[alt_names]</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">DNS.1</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">[service-name]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 签发证书阶段使用的扩展</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">[v3_ext]</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">authorityKeyIdentifier</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">keyid,issuer:always</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">basicConstraints</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">CA:FALSE</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">keyUsage</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">keyEncipherment,dataEncipherment,digitalSignature</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">extendedKeyUsage</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">serverAuth,clientAuth</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">subjectAltName</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">@alt_names</span><br></div></code></pre></div></div>
<p>配置里 <code>prompt = no</code> 让 OpenSSL 不再交互式提问，直接读取 <code>[dn]</code> 段的内容；<code>extendedKeyUsage</code> 同时声明了 <code>serverAuth</code> 和 <code>clientAuth</code>，意味着这张证书既能用于服务端，也能在双向 TLS 场景下作为客户端证书使用。</p>
<p>然后在上面的生成证书文件的步骤中带上</p>
<p>在生成服务csr文件时添加 -extensions req_ext -config san.cnf 命令</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 生成 CSR 时引用配置文件，带上 req_ext 段声明的 SAN</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">openssl req </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-new</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-key</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">service-name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.key </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-out</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">service-name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.csr </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-extensions</span><span class="token plain"> req_ext </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-config</span><span class="token plain"> san.cnf</span><br></div></code></pre></div></div>
<p>在生成服务证书时，也需要添加 -extensions v3_ext -extfile san.cnf 命令</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 签发时同样要带 v3_ext 扩展，否则 SAN 不会写入最终证书</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">openssl x509 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-req</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-in</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">service-name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.csr </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-CA</span><span class="token plain"> ca.crt </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-CAkey</span><span class="token plain"> ca.key </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-CAcreateserial</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-out</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">service-name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.crt </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-days</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">365</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-extensions</span><span class="token plain"> v3_ext </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-extfile</span><span class="token plain"> san.cnf</span><br></div></code></pre></div></div>
<p>这样生成出来的证书文件就包含了我们指定字段的内容，以及部分证书描述信息。</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p>签发这一步的 <code>-extensions v3_ext -extfile san.cnf</code> 容易被漏掉。<code>openssl x509 -req</code> 默认不会把 CSR 里的扩展字段带进最终证书，如果只在 CSR 阶段声明了 SAN，签出来的证书里仍然没有 SAN。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="证书内容验证">证书内容验证<a href="https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s#%E8%AF%81%E4%B9%A6%E5%86%85%E5%AE%B9%E9%AA%8C%E8%AF%81" class="hash-link" aria-label="证书内容验证的直接链接" title="证书内容验证的直接链接" translate="no">​</a></h2>
<p>可通过命令，查看生成出来的证书是否确实包含字段信息</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 以文本形式打印证书内容，检查 SAN 等扩展字段</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">openssl x509 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-in</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">service-name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.crt </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-text</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-noout</span><br></div></code></pre></div></div>
<p>输出中重点确认 <code>X509v3 Subject Alternative Name</code> 一节是否包含期望的 DNS 名称，以及有效期、签发者是否正确。建议把这一步纳入固定流程，比等到服务上线握手报错再排查省事得多。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）客户端连接时使用的目标名称必须与证书 SAN 中的 DNS 名称一致。如果通过 IP 直连，SAN 里就需要用 <code>IP.1 = ...</code> 的形式声明 IP 条目，DNS 条目是匹配不上的。</p>
<p>2）证书有效期到期后 gRPC 握手会直接失败，而这类故障往往在到期当天才暴露。内网自签证书没人提醒续期，最好记录到期时间并提前更换。</p>
<p>3）<code>ca.key</code> 与各服务的 <code>.key</code> 文件都属于敏感材料，注意文件权限，且不要随镜像或仓库分发；客户端只需要 <code>ca.crt</code>。</p>
<p>4）改动 san.cnf 后需要重新走 CSR 与签发两步，只重签证书而不重新生成 CSR，可能带着旧的主体信息。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/11/08/grpcs-t-l-s#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>整个流程可以浓缩为一条信任链：根私钥生成根证书，服务私钥生成 CSR，根证书对 CSR 签名得到服务证书。在 Go 的 gRPC 场景下，关键点是通过 san.cnf 把 SAN 字段写进证书，并在 CSR 与签发两个阶段都显式指定扩展，最后用 <code>openssl x509 -text</code> 验证一遍。把这几步固化成脚本后，为新服务签发证书就是一分钟的事。</p>]]></content:encoded>
            <category>Go</category>
        </item>
        <item>
            <title><![CDATA[Dinky]]></title>
            <link>https://tanzhuo.xyz/2023/09/08/dinky</link>
            <guid>https://tanzhuo.xyz/2023/09/08/dinky</guid>
            <pubDate>Fri, 08 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Dinky 是一款开源的 Flink 作业管理与开发平台，支持在线编写调试 Flink SQL、整库同步与数据血缘分析。记录一下我对它的了解与使用场景。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tanzhuo.xyz/2023/09/08/dinky#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>做实时计算的同学大多有类似的体会：Flink 本身足够强大，但围绕它的开发和运维体验并不友好。写好的 SQL 要打包提交、调试要翻日志、多个集群各管各的，任务一多就乱。最近在调研实时数仓的工具链时接触到了 Dinky，这里记录一下它解决了哪些问题。</p>
<!-- -->
<p>Dinky 是一款开源的 <strong>Flink 作业管理与开发平台</strong>，整体设计轻量、易用，能够统一管理多个 Flink 集群。<br>
开发者可以通过 Web 控制台直接在线编写和调试 Flink SQL，并在完成开发后一键提交到指定的 Flink 集群执行作业，大大降低了实时计算任务的开发和运维成本。</p>
<p>换句话说，它把「写 SQL、调试、提交、监控」这一整条链路收进了一个浏览器页面。对比原生 Flink 的使用方式——本地写代码、打 jar 包、命令行提交、去 Flink Web UI 看状态——这种一站式的体验对纯 SQL 化的实时任务开发非常省事。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="官网地址">官网地址<a href="https://tanzhuo.xyz/2023/09/08/dinky#%E5%AE%98%E7%BD%91%E5%9C%B0%E5%9D%80" class="hash-link" aria-label="官网地址的直接链接" title="官网地址的直接链接" translate="no">​</a></h2>
<p><a href="https://github.com/DataLinkDC/dinky" target="_blank" rel="noopener noreferrer" class="">https://github.com/DataLinkDC/dinky</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2023-09-image-45b4a814277ea0241a0bcacb441651e3.webp" width="1600" height="832" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="功能描述">功能描述<a href="https://tanzhuo.xyz/2023/09/08/dinky#%E5%8A%9F%E8%83%BD%E6%8F%8F%E8%BF%B0" class="hash-link" aria-label="功能描述的直接链接" title="功能描述的直接链接" translate="no">​</a></h2>
<p>Dinky 内置 <strong>整库同步能力</strong>，可以将微服务系统中的数据库表数据同步到实时数仓，从而有效解决微服务架构下的数据孤岛问题。</p>
<p>这一点值得展开说说。微服务架构下每个服务都有自己的数据库，数据分析时往往需要把几十上百张表汇聚到一起。如果用原生 Flink CDC，通常一张表就要写一个 source 定义，表多了既繁琐又占用大量数据库连接。整库同步的思路是：一个作业读取整个库的变更日志（如 MySQL 的 binlog），再在作业内部按表分流写入下游，配置量和资源占用都小得多。</p>
<p>同时，Dinky 支持 <strong>多版本 Flink SQL 的开发与管理</strong>，并提供 <strong>数据血缘分析能力</strong>，帮助开发者更清晰地了解数据流向及依赖关系，便于问题排查与系统维护。</p>
<p>血缘分析的价值在任务规模上来之后才明显：当某张结果表数据异常时，可以顺着血缘图向上追溯它依赖的中间表和源表，而不是靠人脑记忆或翻代码；反过来要下线或改动一张表时，也能先看清有哪些下游任务会受影响。</p>
<p>整体来看，Dinky 在实时数据开发、任务管理以及数据治理方面的功能较为完善，是一款非常实用的 Flink 数据开发平台，值得在实时数仓项目中进行使用和推广。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实时数仓建设"><strong>实时数仓建设</strong><a href="https://tanzhuo.xyz/2023/09/08/dinky#%E5%AE%9E%E6%97%B6%E6%95%B0%E4%BB%93%E5%BB%BA%E8%AE%BE" class="hash-link" aria-label="实时数仓建设的直接链接" title="实时数仓建设的直接链接" translate="no">​</a></h2>
<p>通过 Flink SQL + Dinky，可以快速构建实时数仓任务，例如：</p>
<ul>
<li class="">用户行为实时分析</li>
<li class="">交易数据实时统计</li>
<li class="">实时风控系统</li>
</ul>
<p>这类场景的共同点是：数据源源不断进来，业务方要求分钟级甚至秒级看到结果。传统的离线数仓按天或按小时跑批，满足不了这种时效性要求；而用 Flink SQL 把逻辑写成流式任务，再交给 Dinky 托管生命周期，开发节奏和写离线 SQL 已经很接近了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据同步与-cdc"><strong>数据同步与 CDC</strong><a href="https://tanzhuo.xyz/2023/09/08/dinky#%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5%E4%B8%8E-cdc" class="hash-link" aria-label="数据同步与-cdc的直接链接" title="数据同步与-cdc的直接链接" translate="no">​</a></h2>
<p>利用 Dinky 的整库同步能力，可以实现数据库变更数据捕获（CDC），将业务数据库中的数据实时同步到数据仓库或消息队列中。</p>
<p>CDC 的原理简单说就是订阅数据库的事务日志：每一条 INSERT / UPDATE / DELETE 都会在日志中留下记录，CDC 工具把这些记录解析成变更事件流。相比定时全表扫描的同步方式，它对源库的侵入小、延迟低，还能捕获到删除操作。一个典型的 Flink SQL CDC 源表定义大致长这样：</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 声明一张 CDC 源表，实时捕获 MySQL 中 orders 表的变更</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">TABLE</span><span class="token plain"> orders_source </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  order_id </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">BIGINT</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  user_id </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">BIGINT</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  amount </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">DECIMAL</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token number" style="color:hsl(29, 54%, 61%)">10</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">2</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token plain">order_id</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token string" style="color:hsl(95, 38%, 62%)">'connector'</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">'mysql-cdc'</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">   </span><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 使用 MySQL CDC 连接器读取 binlog</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token string" style="color:hsl(95, 38%, 62%)">'hostname'</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">'...'</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token string" style="color:hsl(95, 38%, 62%)">'database-name'</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">'...'</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token string" style="color:hsl(95, 38%, 62%)">'table-name'</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">'orders'</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><br></div></code></pre></div></div>
<p>在 Dinky 中这段 SQL 可以直接在线调试、预览结果，确认无误后再提交到集群，省去了本地反复打包验证的过程。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实时数据开发平台"><strong>实时数据开发平台</strong><a href="https://tanzhuo.xyz/2023/09/08/dinky#%E5%AE%9E%E6%97%B6%E6%95%B0%E6%8D%AE%E5%BC%80%E5%8F%91%E5%B9%B3%E5%8F%B0" class="hash-link" aria-label="实时数据开发平台的直接链接" title="实时数据开发平台的直接链接" translate="no">​</a></h2>
<p>Dinky 也可以作为企业内部统一的实时计算开发平台，为数据开发工程师提供标准化的开发环境。</p>
<p>统一平台的意义不只是省事：SQL 脚本集中托管、有版本记录，任务的提交和运维方式一致，新人接手项目时不需要先搞清楚每个人各自的提交脚本和环境差异。这对团队协作的价值往往比单个功能点更大。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/09/08/dinky#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class="">Dinky 本身不运行计算任务，它是提交和管理的入口，Flink 集群仍然需要单独部署和维护，两者的版本兼容性要提前确认。</li>
<li class="">使用 CDC 整库同步前，需要确认源数据库已开启变更日志（如 MySQL 的 binlog），并给同步账号授予相应权限，否则作业起不来。</li>
<li class="">在线调试很方便，但调试环境的资源和数据量与生产集群不同，逻辑复杂的任务上线前仍建议在生产同规格环境验证一遍。</li>
</ul>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>选型时建议直接拉起一个测试环境，用自己业务里最复杂的一条链路（比如一张大表的整库同步 + 一段多表 join 的 SQL）跑通全流程，比看功能列表更能说明问题。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/09/08/dinky#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Dinky 补上的是 Flink 生态里「开发体验」这块短板：在线写 SQL、一键提交、整库同步、血缘分析，覆盖了实时数仓开发的主要日常。如果团队的实时任务以 Flink SQL 为主，又苦于没有统一的开发和管理入口，它值得纳入选型范围。</p>]]></content:encoded>
            <category>大数据</category>
        </item>
        <item>
            <title><![CDATA[SonarQube]]></title>
            <link>https://tanzhuo.xyz/2023/09/08/sonarqube</link>
            <guid>https://tanzhuo.xyz/2023/09/08/sonarqube</guid>
            <pubDate>Fri, 08 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[SonarQube 代码质量管理平台入门：静态分析的工作机制、常用质量指标、Maven 项目接入方式，以及在 CI/CD 流程中作为 Quality Gate 的组合用法。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tanzhuo.xyz/2023/09/08/sonarqube#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>代码写完能跑，不代表它是好代码。这篇记录一下我对 SonarQube 的整理：它能查出什么问题、背后怎么工作、以及在 CI/CD 里怎么用。</p>
<!-- -->
<p>为什么这个话题值得单独写一篇？因为代码质量问题有个特点：写的时候看不见，出事的时候很贵。人工 Code Review 能拦住一部分，但受限于评审者的精力和经验，很多重复劳动（比如检查空指针、资源未关闭、硬编码密码）完全可以交给工具。团队规模一大，没有一个统一的、自动化的质量标准，代码库很快就会各写各的。</p>
<p>SonarQube 是一个用于 <strong>代码质量管理</strong> 的开源平台，可以帮助开发者在开发过程中持续检测代码中的潜在问题。<br>
通过静态代码分析、代码覆盖率统计以及多维度的质量指标，SonarQube 能够在代码进入生产环境之前发现缺陷、安全漏洞和性能隐患，从而提升系统的整体稳定性并降低后期维护成本。</p>
<p>在团队协作和持续集成环境中，SonarQube 通常作为 <strong>代码质量守门人（Quality Gate）</strong> 使用，使代码质量检查自动化、标准化。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="工作机制">工作机制<a href="https://tanzhuo.xyz/2023/09/08/sonarqube#%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="工作机制的直接链接" title="工作机制的直接链接" translate="no">​</a></h2>
<p>简单说一下它是怎么工作的，理解了机制，后面的配置就不难了。</p>
<p>SonarQube 的分析属于 <strong>静态分析</strong>：不运行代码，而是把源码解析成语法树，再用一套规则引擎去匹配已知的问题模式。整个体系分成两个角色：</p>
<ol>
<li class=""><strong>Scanner（扫描端）</strong>：跑在构建机上，负责解析源码、执行规则、收集覆盖率报告，然后把分析结果上报给服务端。Maven、Gradle、命令行都有对应的 Scanner。</li>
<li class=""><strong>Server（服务端）</strong>：负责存储历史数据、渲染 Web 报告、执行 Quality Gate 判定。每次扫描的结果都会和历史版本对比，所以能看到质量趋势。</li>
</ol>
<p>Quality Gate 本质上是一组阈值断言，比如"新代码的 Bug 数为 0"、"新代码覆盖率不低于某个比例"。扫描结束后服务端逐条判定，任何一条不满足，整体状态就是失败——CI 流水线可以拿这个状态决定是否中断发布。这也是它区别于"跑完给个报告看看"类工具的关键：结果是可以拦截流程的。</p>
<p>另外值得一提的是"新代码（New Code）"这个概念：对存量老项目，一次性修完所有历史问题不现实，SonarQube 的默认策略是只对新增和修改的代码从严要求，存量问题逐步消化。这让老项目也能平滑接入。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="提高代码质量"><strong>提高代码质量</strong><a href="https://tanzhuo.xyz/2023/09/08/sonarqube#%E6%8F%90%E9%AB%98%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F" class="hash-link" aria-label="提高代码质量的直接链接" title="提高代码质量的直接链接" translate="no">​</a></h2>
<p>SonarQube 可以自动检测代码中的潜在问题，例如：</p>
<ul>
<li class="">代码缺陷（Bug）</li>
<li class="">安全漏洞（Security Vulnerabilities）</li>
<li class="">代码异味（Code Smell）</li>
<li class="">潜在性能问题</li>
</ul>
<p>这几类问题的严重程度是递减的：Bug 是大概率会出错的逻辑，比如条件永远为真、可能的空指针解引用；安全漏洞对应注入、硬编码凭证这类风险；代码异味则不影响正确性，但会让代码越来越难改——比如超长方法、过深的嵌套。SonarQube 会给每个问题标注严重级别和预估修复时间，方便排优先级。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="提升代码可维护性"><strong>提升代码可维护性</strong><a href="https://tanzhuo.xyz/2023/09/08/sonarqube#%E6%8F%90%E5%8D%87%E4%BB%A3%E7%A0%81%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7" class="hash-link" aria-label="提升代码可维护性的直接链接" title="提升代码可维护性的直接链接" translate="no">​</a></h2>
<p>SonarQube 会对代码进行多维度分析，例如：</p>
<ul>
<li class="">复杂度</li>
<li class="">重复代码</li>
<li class="">测试覆盖率</li>
<li class="">技术债（Technical Debt）</li>
</ul>
<p>通过这些指标，开发者可以更直观地了解系统的健康状态，并针对性地进行重构和优化。</p>
<p>这里的技术债是个很直观的度量：把所有待修复问题的预估工时加总，得到"还清欠债"需要的时间。它未必精确，但趋势有意义——技术债持续上涨，说明团队在透支未来的开发效率。</p>
<p>开发者可以在代码发布前就发现并修复这些问题，从而减少生产环境故障。</p>
<p>除了检测能力本身，SonarQube 在工程实践上还有几个优势：</p>
<ol>
<li class="">丰富的插件生态系统：SonarQube支持多种编程语言和框架，并提供了丰富的插件生态，可以根据项目需求选择合适的插件来扩展功能。</li>
<li class="">持续集成和持续部署：SonarQube可以与持续集成（CI）和持续部署（CD）工具集成，实现自动化的代码检查和质量度量，从而确保每次构建都具有良好的代码质量。</li>
<li class="">提高团队协作效率：SonarQube提供了一个集中式的平台，让团队成员可以在一个统一的地方查看代码质量和度量指标，有助于提高团队协作效率。</li>
<li class="">更好的代码维护：通过分析代码质量指标，开发者可以更好地了解代码的健康状况，从而更有针对性地进行维护和优化。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="接入示例">接入示例<a href="https://tanzhuo.xyz/2023/09/08/sonarqube#%E6%8E%A5%E5%85%A5%E7%A4%BA%E4%BE%8B" class="hash-link" aria-label="接入示例的直接链接" title="接入示例的直接链接" translate="no">​</a></h2>
<p>示例代码：<br>
假设我们有一个Java项目，首先需要在项目中引入SonarQube的Maven插件，然后执行以下命令来运行静态代码分析：</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">&lt;!-- Maven插件配置 --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">build</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">plugins</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">plugin</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token comment" style="color:hsl(220, 10%, 40%)">&lt;!-- SonarQube 官方提供的 Maven Scanner 插件 --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">groupId</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain">org.sonarsource.scanner.maven</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">groupId</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">artifactId</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain">sonar-maven-plugin</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">artifactId</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">version</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain">3.9.1.2184</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">version</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">executions</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">execution</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                    </span><span class="token comment" style="color:hsl(220, 10%, 40%)">&lt;!-- 绑定到 verify 阶段：单元测试跑完之后、安装部署之前执行扫描 --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                    </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">phase</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain">verify</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">phase</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                    </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">goals</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                        </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">goal</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain">sonar-check</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">goal</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                    </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">goals</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">execution</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">executions</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">plugin</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">plugins</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">build</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><br></div></code></pre></div></div>
<p>除了插件本身，Scanner 还需要知道把结果发给谁：服务端地址（<code>sonar.host.url</code>）和认证令牌（<code>sonar.login</code> 或 token）一般配置在 Maven 的 <code>settings.xml</code> 或 CI 的环境变量里，不建议写死在 <code>pom.xml</code> 中提交到仓库。</p>
<p>执行<code>mvn clean verify</code>命令后，SonarQube会自动分析项目的源代码，并在SonarQube服务器上生成相应的报告。开发者可以通过访问SonarQube服务器上的Web界面查看报告，并根据报告中的信息进行代码优化。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="在-devops-中的组合使用">在 DevOps 中的组合使用<a href="https://tanzhuo.xyz/2023/09/08/sonarqube#%E5%9C%A8-devops-%E4%B8%AD%E7%9A%84%E7%BB%84%E5%90%88%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="在 DevOps 中的组合使用的直接链接" title="在 DevOps 中的组合使用的直接链接" translate="no">​</a></h2>
<p>单机跑扫描只是起点，SonarQube 真正发挥价值是在流水线里。在实际的 DevOps 流程中，SonarQube 通常会与 <strong>CI/CD 工具链</strong> 配合使用，例如：</p>
<ol>
<li class="">开发人员提交代码到 Git 仓库</li>
<li class="">CI 系统（如 Jenkins）触发构建任务</li>
<li class="">构建过程中执行 SonarQube 代码扫描</li>
<li class="">根据 Quality Gate 判断是否允许继续发布</li>
<li class="">扫描结果通过钉钉 / 企业微信 / 邮件通知团队</li>
</ol>
<p>第 4 步是整条链路的核心：Quality Gate 不通过就中断流水线，把质量问题挡在合并或发布之前，而不是事后翻报告。配合平台的 Pull Request 分析能力，还可以把问题直接标注到评审界面上，让 Code Review 聚焦在设计层面，机械性检查交给工具。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/09/08/sonarqube#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>实际部署和使用中，有几点容易踩坑：</p>
<ol>
<li class=""><strong>服务端资源要求不低</strong>。SonarQube 内置了 Elasticsearch 做索引，对内存有一定要求；Linux 上部署时通常还需要调大 <code>vm.max_map_count</code> 等内核参数，否则服务起不来。小团队可以先用 Docker 单机跑，但别放在配置太紧张的机器上。</li>
<li class=""><strong>覆盖率不是 SonarQube 算的</strong>。它只负责读取覆盖率报告（Java 生态常见的是 JaCoCo 生成的），如果构建里没有先生成报告，页面上覆盖率会一直是 0，这是新接入时最常见的疑惑。</li>
<li class=""><strong>规则要按团队实际裁剪</strong>。默认规则集比较全，直接全量启用容易产生大量告警，团队很快会对结果"免疫"。建议从默认 Quality Profile 起步，把明显不适用的规则禁掉，让每条告警都值得处理。</li>
<li class=""><strong>老项目从"新代码"抓起</strong>。不要试图一次清零存量问题，把 Quality Gate 的约束加在新代码上，存量债务列入长期计划慢慢还。</li>
</ol>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>把扫描放在流水线的哪个位置也有讲究：太早（每次 push 都全量扫）会拖慢反馈，太晚（只在发布前扫）问题堆积难改。比较常见的做法是在合并请求和主干构建时各扫一次。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/09/08/sonarqube#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>SonarQube 解决的核心问题是：把代码质量从"靠人盯"变成"靠流程保证"。静态分析找出 Bug、漏洞和代码异味，多维指标量化可维护性，Quality Gate 再把这些标准接入 CI/CD，不达标就不放行。接入成本并不高——一个 Maven 插件加一条命令就能跑起来，难的是后续把规则、阈值调整到适合团队的程度，让它成为真正被信任的守门人，而不是被忽略的红色警告。</p>]]></content:encoded>
            <category>CI/CD</category>
            <category>技术</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Canal 组件]]></title>
            <link>https://tanzhuo.xyz/2023/09/06/canal</link>
            <guid>https://tanzhuo.xyz/2023/09/06/canal</guid>
            <pubDate>Wed, 06 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Canal 是阿里巴巴开源的基于 MySQL Binlog 的增量订阅与消费组件。本文梳理它的工作原理、与常见 CDC 组件的对比,以及集群高可用部署中的注意事项。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tanzhuo.xyz/2023/09/06/canal#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>做业务系统时经常会遇到这样的需求:数据库里的一行数据变了,下游的缓存、搜索引擎、数据仓库也要跟着变。靠业务代码双写容易漏,靠定时任务全量比对又太重。这类问题有一个更优雅的解法——直接订阅数据库的变更日志,这也是我整理这篇 Canal 笔记的原因。</p>
<!-- -->
<p>Canal 是阿里巴巴开源的一款 <strong>基于 MySQL Binlog 的数据实时订阅与消费组件</strong>，常用于实现数据库变更数据捕获（CDC，Change Data Capture）。</p>
<p>在实际业务场景中，数据库中的数据变更往往不仅仅用于业务读写，还可能需要同步到其他系统，例如：</p>
<ul>
<li class="">实时数据同步（数据库 → 数据仓库）</li>
<li class="">数据变更推送到 MQ（Kafka / RocketMQ）</li>
<li class="">构建实时数据分析或监控系统</li>
<li class="">搜索引擎数据同步（如 Elasticsearch）</li>
</ul>
<p>这些场景的共同点是:下游系统关心的不是"数据库现在长什么样",而是"数据发生了哪些变化"。如果让业务代码在每次写库后手动通知下游,不仅侵入性强,还很难保证和数据库事务的一致性——写库成功但通知失败,数据就悄悄不一致了。而 Binlog 是 MySQL 自身保证写入的日志,以它为数据源天然避开了双写问题。</p>
<p>为了实现这些能力，Canal 通过 <strong>模拟 MySQL Slave 的方式订阅 Binlog 日志</strong>，解析出数据库的增删改操作，并将这些变更数据实时推送给下游消费系统，从而实现数据的准实时同步。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="工作原理">工作原理<a href="https://tanzhuo.xyz/2023/09/06/canal#%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86" class="hash-link" aria-label="工作原理的直接链接" title="工作原理的直接链接" translate="no">​</a></h2>
<p>Canal 的实现方式借用了 MySQL 主从复制的机制。正常的主从复制流程是:Slave 向 Master 发起 dump 请求,Master 将 Binlog 事件持续推送给 Slave,Slave 再重放这些事件完成数据同步。</p>
<p>Canal 做的事情,就是把自己"伪装"成一个 Slave:</p>
<ol>
<li class="">向 MySQL 发送 dump 协议请求,和真实从库使用同一套交互方式;</li>
<li class="">MySQL 把 Binlog 事件推送给 Canal,Canal 解析二进制日志,还原出每一行数据的变更内容;</li>
<li class="">解析后的结构化事件交给下游消费——既可以由客户端通过 TCP 主动拉取,也可以直接投递到 Kafka / RocketMQ 等消息队列。</li>
</ol>
<p>对 MySQL 来说,Canal 就是一个普通的从库,不需要在数据库侧安装任何插件,这也是这种方案侵入性低的原因。</p>
<p>要让 Canal 正常订阅,MySQL 侧需要满足几个前提条件:</p>
<div class="language-properties codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-properties codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># my.cnf 中的关键配置</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">log-bin</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">mysql-bin        # 开启 Binlog</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">binlog-format</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">ROW        # 必须为 ROW 模式,才能拿到每一行变更前后的完整数据</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">server_id</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">1              # 主从体系中每个节点的唯一标识,不能与 Canal 配置的 slaveId 冲突</span><br></div></code></pre></div></div>
<p>其中 Binlog 格式必须是 ROW 模式。STATEMENT 模式记录的是 SQL 语句本身,无法还原出每行数据的具体变化;只有 ROW 模式会记录行级别的前后镜像,CDC 场景才有意义。此外,Canal 连接 MySQL 使用的账号需要具备 <code>REPLICATION SLAVE</code>、<code>REPLICATION CLIENT</code> 权限,和真实从库的要求一致。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常见-cdc-组件对比">常见 CDC 组件对比<a href="https://tanzhuo.xyz/2023/09/06/canal#%E5%B8%B8%E8%A7%81-cdc-%E7%BB%84%E4%BB%B6%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="常见 CDC 组件对比的直接链接" title="常见 CDC 组件对比的直接链接" translate="no">​</a></h2>
<p>目前市面上常见的 CDC 组件主要包括：</p>
<ul>
<li class="">Canal</li>
<li class="">Debezium</li>
<li class="">Flink CDC</li>
</ul>
<p>Canal 目前只支持MySQL数据库。5.x 8.x 版本。</p>
<p><a href="https://github.com/alibaba/canal" target="_blank" rel="noopener noreferrer" class="">GitHub - alibaba/canal: 阿里巴巴 MySQL binlog 增量订阅&amp;消费组件</a></p>
<p>这类组件的核心工作机制基本一致：<strong>通过解析数据库的 Binlog 日志，获取数据变更内容以及具体的操作类型（INSERT / UPDATE / DELETE）</strong>，并将这些变更数据转化为结构化事件供下游系统消费。</p>
<p>三者的差异主要在生态定位上:Debezium 构建在 Kafka Connect 之上,支持 MySQL、PostgreSQL 等多种数据库,适合已有 Kafka 体系的团队;Flink CDC 把变更捕获直接嵌入 Flink 流计算,捕获和加工在同一个作业里完成;Canal 则更轻量,专注于 MySQL 这一个方向。</p>
<p>需要注意的是，<strong>Canal 目前主要支持 MySQL 数据库（5.x 与 8.x 版本）</strong>，因此在 MySQL 生态中被广泛用于构建实时数据同步与数据分发系统。如果技术栈是纯 MySQL,不想引入 Kafka Connect 或 Flink 这样的重依赖,Canal 是比较务实的选择。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="高可用说明">高可用说明<a href="https://tanzhuo.xyz/2023/09/06/canal#%E9%AB%98%E5%8F%AF%E7%94%A8%E8%AF%B4%E6%98%8E" class="hash-link" aria-label="高可用说明的直接链接" title="高可用说明的直接链接" translate="no">​</a></h2>
<p>在高可用方面，Canal 提供了 <strong>集群部署模式</strong>。在集群架构中，每个 Service 节点负责管理多个同步任务实例（Instance），并通过任务分发机制实现负载分担。</p>
<p>集群模式依赖 ZooKeeper 做协调:多个 Server 节点会去争抢同一个 Instance 的运行权,同一时刻只有一个节点真正在跑该 Instance,其余节点处于 standby 状态。运行节点故障后,standby 节点通过 ZooKeeper 感知并接管任务,同时消费位点(即 Binlog 的消费进度)也记录在 ZooKeeper 中,接管方可以从上一个位点继续消费,避免数据丢失或大量重复。</p>
<p>不过在实际运行过程中，偶尔可能会因为网络波动、数据库连接异常或资源限制等原因，导致某些同步任务实例终止。因此在生产环境中，Canal 通常会配合 <strong>自动重启机制或运维监控系统</strong>，以保证任务能够在异常情况下自动恢复运行。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/09/06/canal#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>结合原理,有几个点在落地前最好先想清楚:</p>
<ol>
<li class="">
<p><strong>Binlog 格式与保留时间</strong>。上游库必须是 ROW 模式;同时 Binlog 保留时间要足够长,否则 Canal 停机时间一长,位点对应的日志已被清理,任务就无法续传,只能重新初始化。</p>
</li>
<li class="">
<p><strong>消费语义是至少一次</strong>。故障切换或重启后,可能会重复投递一小段变更事件,下游消费端需要按主键做幂等处理,不能假设每条变更只到达一次。</p>
</li>
<li class="">
<p><strong>顺序性依赖分区策略</strong>。投递到 MQ 时,如果多张表或多个主键的变更被打散到不同分区,消费顺序就可能和 Binlog 顺序不一致。对顺序敏感的场景,通常按表名或主键做分区路由。</p>
</li>
<li class="">
<p><strong>Instance 假死问题</strong>。任务实例有时并不会干净地退出,而是停在某个位点不再推进。监控不能只看进程存活,还要看位点是否持续前进,以及与数据库当前 Binlog 位置的延迟。</p>
</li>
</ol>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>上线前可以做一次"断链演练":手动 kill 掉正在运行的 Server 节点,观察 standby 是否按预期接管、位点是否续传成功,比出了故障再验证要从容得多。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/09/06/canal#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Canal 的思路并不复杂:伪装成 MySQL 从库,用标准复制协议订阅 Binlog,把行级变更还原成结构化事件供下游消费。它对数据库零侵入、部署轻量,在纯 MySQL 技术栈里做实时同步和数据分发很合适。工程上需要重点关照的,是 ROW 格式与 Binlog 保留策略、下游的幂等消费,以及基于位点推进情况的监控——这些做扎实了,链路的稳定性才有保障。</p>]]></content:encoded>
            <category>Java</category>
            <category>Linux</category>
            <category>DB</category>
            <category>大数据</category>
        </item>
        <item>
            <title><![CDATA[Polaris Mesh]]></title>
            <link>https://tanzhuo.xyz/2023/09/01/polaris</link>
            <guid>https://tanzhuo.xyz/2023/09/01/polaris</guid>
            <pubDate>Fri, 01 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[PolarisMesh 是腾讯开源的一站式云原生服务治理平台，围绕服务注册发现、流量治理、容错保护与配置管理提供完整能力。本文梳理其核心特性，并与 Nacos 做定位对比。]]></description>
            <content:encoded><![CDATA[<p>PolarisMesh 提供了完整的微服务治理能力，是腾讯开源的一站式云原生服务治理平台。平台围绕 <strong>服务注册与发现、流量治理、容错保护以及配置管理</strong> 等核心能力，为微服务架构提供稳定可靠的运行基础。通过 PolarisMesh，服务实例可以自动完成注册与发现，同时结合智能负载均衡策略，实现请求在多实例之间的合理分配，从而提升系统整体的稳定性与吞吐能力。</p>
<!-- -->
<p>官网：<a href="http://polarismesh.cn/" target="_blank" rel="noopener noreferrer" class="">PolarisMesh</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="微服务治理">微服务治理<a href="https://tanzhuo.xyz/2023/09/01/polaris#%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86" class="hash-link" aria-label="微服务治理的直接链接" title="微服务治理的直接链接" translate="no">​</a></h2>
<p>在流量治理方面，PolarisMesh 支持 <strong>服务限流、熔断降级、重试机制以及灰度发布</strong> 等能力。当某个服务出现异常或响应时间过长时，系统可以自动触发熔断保护，避免故障进一步扩散。同时，通过灵活的流量控制策略，可以按比例、按标签或按版本进行流量调度，使系统在版本升级或新功能发布时更加安全可控。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="高性能代理">高性能代理<a href="https://tanzhuo.xyz/2023/09/01/polaris#%E9%AB%98%E6%80%A7%E8%83%BD%E4%BB%A3%E7%90%86" class="hash-link" aria-label="高性能代理的直接链接" title="高性能代理的直接链接" translate="no">​</a></h2>
<p>PolarisMesh 内置高性能代理组件 Polaris Sidecar，可作为服务网格中的数据面组件。该代理基于高性能网络模型设计，能够实现高效的请求转发与响应处理，在保证服务治理能力的同时尽可能降低网络开销。通过代理模式，应用服务无需侵入业务代码即可获得完整的服务治理能力，接入成本大大降低。</p>
<p>此外，PolarisMesh 还支持 <strong>Proxyless 模式</strong>，即无需 Sidecar 代理也能接入服务治理能力。开发者可以通过 SDK 或 Java Agent 的方式直接接入 PolarisMesh，在不同架构场景下灵活选择最合适的接入方式。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="多协议支持">多协议支持<a href="https://tanzhuo.xyz/2023/09/01/polaris#%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%94%AF%E6%8C%81" class="hash-link" aria-label="多协议支持的直接链接" title="多协议支持的直接链接" translate="no">​</a></h2>
<p>PolarisMesh 支持多种主流通信协议，例如 <strong>HTTP、gRPC、TCP 等</strong>，能够满足不同类型微服务之间的通信需求。同时，平台提供多语言 SDK，包括 <strong>Java、Go、Python、C++ 等主流开发语言</strong>，并支持 Spring Cloud、Dubbo 等常见微服务框架，开发者可以快速将现有系统接入 PolarisMesh，而无需对业务代码做大规模改造。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="服务配置管理">服务配置管理<a href="https://tanzhuo.xyz/2023/09/01/polaris#%E6%9C%8D%E5%8A%A1%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86" class="hash-link" aria-label="服务配置管理的直接链接" title="服务配置管理的直接链接" translate="no">​</a></h2>
<p>除了服务治理能力外，PolarisMesh 还提供配置中心功能，用于集中管理微服务运行所需的各类配置。开发者可以通过配置中心统一管理服务参数，并支持动态配置更新，应用无需重启即可加载新的配置，运维效率与灵活性都因此提高。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="可观测性能力">可观测性能力<a href="https://tanzhuo.xyz/2023/09/01/polaris#%E5%8F%AF%E8%A7%82%E6%B5%8B%E6%80%A7%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="可观测性能力的直接链接" title="可观测性能力的直接链接" translate="no">​</a></h2>
<p>在微服务架构中，可观测性是保障系统稳定运行的重要能力。PolarisMesh 提供完善的监控与日志能力，可以实时采集服务调用链路、请求成功率、延迟指标等关键数据。借助这些指标，运维人员能快速定位系统瓶颈和异常服务，提高故障排查效率。</p>
<p>同时，PolarisMesh 还提供 <strong>事件中心与操作审计能力</strong>，能够记录系统中发生的重要事件和用户操作记录，为系统运维和安全审计提供依据。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="扩展性和灵活性">扩展性和灵活性<a href="https://tanzhuo.xyz/2023/09/01/polaris#%E6%89%A9%E5%B1%95%E6%80%A7%E5%92%8C%E7%81%B5%E6%B4%BB%E6%80%A7" class="hash-link" aria-label="扩展性和灵活性的直接链接" title="扩展性和灵活性的直接链接" translate="no">​</a></h2>
<p>PolarisMesh 采用云原生架构设计，天然支持 <strong>Kubernetes、容器环境以及混合云部署</strong>。平台组件之间通过松耦合方式通信，用户可以根据业务需求灵活扩展各个模块。此外，PolarisMesh 还支持与 Service Mesh 架构结合使用，使微服务系统在保持高性能的同时具备更强的治理能力。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="polarismesh-对比-nacos">PolarisMesh 对比 Nacos<a href="https://tanzhuo.xyz/2023/09/01/polaris#polarismesh-%E5%AF%B9%E6%AF%94-nacos" class="hash-link" aria-label="PolarisMesh 对比 Nacos的直接链接" title="PolarisMesh 对比 Nacos的直接链接" translate="no">​</a></h2>
<p>在微服务架构中，PolarisMesh 与 Nacos 都属于常见的服务治理组件，但两者的设计定位和能力侧重点有所不同。</p>
<p>从定位上来看，<strong>Nacos 更偏向于服务注册中心与配置中心</strong>，主要解决服务注册发现和配置管理问题；而 <strong>PolarisMesh 则定位为完整的微服务治理平台</strong>，不仅提供服务注册与配置管理，还提供更丰富的流量治理与服务治理能力。</p>
<p>从架构能力上来看，PolarisMesh 在流量治理、服务治理以及可观测性方面提供了更完整的一体化能力，例如路由策略、限流、熔断以及灰度发布等功能，而 Nacos 更多侧重于服务注册与配置管理本身。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/09/01/polaris#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>PolarisMesh 把注册发现、流量治理、容错保护、配置管理和可观测性收敛在一个平台里，Sidecar 与 Proxyless 两种接入方式也给了不同架构的团队选择余地。如果只需要注册中心加配置中心，Nacos 足够轻量；一旦限流、熔断、灰度这些治理需求浮出水面，一体化的 PolarisMesh 能省去拼装多个组件的成本。选型前不妨先梳理清楚自己的治理需求，再决定引入哪一层能力。</p>]]></content:encoded>
            <category>技术</category>
            <category>Go</category>
            <category>K8S</category>
        </item>
        <item>
            <title><![CDATA[阿里 ask 集群]]></title>
            <link>https://tanzhuo.xyz/2023/07/29/a-li-ask-ji-qun</link>
            <guid>https://tanzhuo.xyz/2023/07/29/a-li-ask-ji-qun</guid>
            <pubDate>Sat, 29 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[记录一次对阿里云 ASK（Serverless Kubernetes）的调研：它的定位与运行机制、相比自建 K8S 的优劣势，以及选型时需要注意的成本与厂商绑定问题。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tanzhuo.xyz/2023/07/29/a-li-ask-ji-qun#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>最近在评估团队的容器化方案时，绕不开一个问题：K8S 集群到底是自建、买托管，还是干脆连节点都不要了。ASK 这种 Serverless 形态的集群正好落在最后一档，值得单独记录一下。</p>
<!-- -->
<p>对小团队来说，自建 K8S 的隐性成本经常被低估：控制面要做高可用，etcd 要备份，版本要升级，节点要打补丁。这些工作不产生业务价值，却一样都不能省。云厂商的托管方案就是冲着这块来的，而 ASK 更进一步，把节点这一层也从用户视野里拿掉了。</p>
<p>阿里云ASK（Alibaba Cloud ACK）集群是阿里云提供的托管式Kubernetes服务。它基于开源的Kubernetes项目，为用户提供了一个简便易用、弹性伸缩、高可用性和安全可靠的容器化部署和管理平台，相当于无服务器Kubernetes容器服务。您无需购买节点即可直接部署容器应用，无需对集群进行节点维护和容量规划，并且根据应用配置的CPU和内存资源量进行按需付费。ASK集群提供完善的Kubernetes兼容能力，同时降低了Kubernetes使用门槛，让您更专注于应用程序，而不是管理底层基础设施。</p>
<p>ASK 中的 SK 指的就是 Serverless Kubernetes，官方文档里现在也叫 ACK Serverless。它和普通托管版 ACK 的核心区别在于：托管版帮你管控制面，Worker 节点仍然是你自己的 ECS；而 ASK 连 Worker 节点的概念都没有，你面对的最小资源单位直接就是 Pod。</p>
<p><a href="https://help.aliyun.com/zh/ack/serverless-kubernetes/product-overview/ask-overview?spm=a2c4g.750001.0.i1&amp;ref=tanzhuo.xyz" target="_blank" rel="noopener noreferrer" class="">什么是容器服务 Serverless 版ACK Serverless_容器服务 Kubernetes 版 ACK-阿里云帮助中心</a></p>
<p>ASK集群中的Pod基于阿里云弹性容器实例ECI运行在安全隔离的容器运行环境中。每个Pod容器实例底层通过轻量级虚拟化安全沙箱技术完全强隔离，容器实例间互不影响。</p>
<p>展开说一下这个机制。传统 K8S 里，多个 Pod 共享同一台宿主机的内核，隔离靠的是 namespace 和 cgroup，属于软隔离；一旦出现容器逃逸类漏洞，同节点的其他 Pod 都可能受影响。ECI 的做法是给每个 Pod 套一层轻量级虚拟机沙箱，Pod 之间不共享内核，隔离级别接近虚拟机，但启动速度和资源开销又比传统虚拟机小得多。对用户来说，这层沙箱是透明的——你提交的还是标准的 Deployment、Service 这些 K8S 资源，调度层会把 Pod 落到 ECI 上运行。</p>
<p>也正因为按 Pod 声明的 CPU 和内存计费，资源的 requests/limits 配置在 ASK 里不再只是调度参数，而是直接决定账单的数字，这一点和自建集群的思维方式很不一样。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ask集群优势">ASK集群优势<a href="https://tanzhuo.xyz/2023/07/29/a-li-ask-ji-qun#ask%E9%9B%86%E7%BE%A4%E4%BC%98%E5%8A%BF" class="hash-link" aria-label="ASK集群优势的直接链接" title="ASK集群优势的直接链接" translate="no">​</a></h2>
<ol>
<li class="">简便易用：ASK集群提供了直观易懂的图形化界面和命令行工具，使用户能够轻松创建、部署和管理Kubernetes集群。</li>
<li class="">弹性伸缩：ASK集群支持根据实际需求自动进行扩展和收缩，确保应用程序始终具备良好的性能，并且能够灵活地适应流量变化。</li>
<li class="">高可用性：ASK集群采用多可用区架构，并提供自动容错和自动恢复功能，确保应用程序在发生故障时仍能保持可用状态，最大程度减少业务中断时间。</li>
<li class="">安全可靠：ASK集群提供了多层安全机制，包括网络隔离、访问控制和数据加密等，以保护用户的应用程序和数据的安全性。</li>
<li class="">整合生态系统：ASK集群与阿里云的其他产品和服务无缝集成，例如容器镜像服务、日志服务和云监控等，为用户提供全面的解决方案。</li>
</ol>
<p>这几条里我觉得最实际的是弹性伸缩。自建集群做弹性，扩 Pod 之前得先保证有节点可调度，节点扩容本身就要几分钟起步，还得配合 cluster-autoscaler 调参；ASK 把节点这一层抽掉之后，扩容就只剩「拉起 Pod」这一件事，弹性链路短了很多。对于流量有明显波峰波谷的业务，这个差异会直接反映在响应速度和成本上。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="部分劣势">部分劣势<a href="https://tanzhuo.xyz/2023/07/29/a-li-ask-ji-qun#%E9%83%A8%E5%88%86%E5%8A%A3%E5%8A%BF" class="hash-link" aria-label="部分劣势的直接链接" title="部分劣势的直接链接" translate="no">​</a></h2>
<ol>
<li class="">价格相对较高：使用ASK集群需要支付一定的服务费用，对于预算有限的用户来说可能是一个考虑因素，对比直接包年包月的ACK集群来说，按需付费如果资源分配不均匀将产生额外的费用。</li>
<li class="">学习曲线较陡峭：对于没有使用过Kubernetes的用户来说，需要花费一些时间学习和适应ASK集群的概念和操作方式。</li>
<li class="">受制于云服务提供商：使用ASK集群意味着将应用程序部署在阿里云平台上，用户可能会面临与特定云服务提供商相关的限制和依赖。</li>
</ol>
<p>关于价格再多说一句：按需付费的单价通常高于包年包月的 ECS，所以 ASK 划算的前提是负载确实有弹性——常驻的、7x24 满载跑的服务，放在预留资源上反而更便宜。选型时值得先估算一下自己负载的「常驻部分」和「弹性部分」各占多少。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/07/29/a-li-ask-ji-qun#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）资源规格要认真配。ASK 按 Pod 声明的资源计费，requests 随手写大，账单就跟着涨；写太小又可能被 OOM。上线前最好基于压测结果给出一个贴近真实用量的配置。</p>
<p>2）留意与标准 K8S 的行为差异。没有真实节点意味着 DaemonSet、hostPath、hostNetwork 这类依赖节点的能力用法和普通集群不同，迁移存量应用前要逐项核对，具体支持情况以官方文档为准。</p>
<p>3）冷启动不是零成本。ECI 拉起 Pod 比在已有节点上调度要多一段实例创建时间，对延迟敏感的突发流量场景，可以考虑提前预热或保留一定的常驻副本。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>如果只是想体验 Serverless K8S 的使用方式，可以先用最小规格的 Pod 跑一个测试应用，观察一段时间账单再决定是否迁移核心业务。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="总结">总结<a href="https://tanzhuo.xyz/2023/07/29/a-li-ask-ji-qun#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>相比较于自己搭建K8S集群去维护，投入时间多，维护成本大，后期维护时间长，迭代升级繁琐，补丁漏洞安全性需要自己保障，配置繁琐，需要对自己团队的运维能力有一定要求。直接使用ASK集群可以免去这样的烦恼，把更多的精力投入到产研中。并且在后续的流量增长时，只需要扩容pod副本即可，无需管理底层node节点资源的物理限制。</p>
<p>我的看法是：团队规模不大、又没有专职运维的情况下，ASK 这类 Serverless 集群是个务实的起点；等业务规模上来、成本敏感度提高，再评估是否迁回托管版或自建，也并不算晚。</p>]]></content:encoded>
            <category>K8S</category>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[架构设计原则]]></title>
            <link>https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze</link>
            <guid>https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze</guid>
            <pubDate>Sun, 18 Jun 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[系统架构设计需要在可扩展、可靠、性能、安全、可维护等多个维度之间做权衡。本文梳理这些常见的设计原则，以及架构师应具备的思考方式。]]></description>
            <content:encoded><![CDATA[<p>在进行系统架构设计时，通常需要从多个维度综合考虑系统能力。一个优秀的系统架构不仅要满足当前业务需求，还需要能够适应未来的业务增长和技术演进。常见需要重点关注的设计原则包括以下几个方面。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="可扩展性">可扩展性<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E5%8F%AF%E6%89%A9%E5%B1%95%E6%80%A7" class="hash-link" aria-label="可扩展性的直接链接" title="可扩展性的直接链接" translate="no">​</a></h2>
<p>系统设计应该具备良好的可扩展能力，能够支持未来业务增长和需求变化。架构在设计时通常需要采用模块化、分层架构等方式，使系统可以在不影响整体稳定性的情况下方便地增加新功能、扩展数据规模或支持更多用户。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="可靠性">可靠性<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E5%8F%AF%E9%9D%A0%E6%80%A7" class="hash-link" aria-label="可靠性的直接链接" title="可靠性的直接链接" translate="no">​</a></h2>
<p>系统设计需要具备较高的可靠性，能够保证系统在长期运行中的稳定性和可用性。架构设计通常需要考虑故障恢复机制、容错机制以及数据备份策略，以确保在系统出现异常时能够快速恢复业务。</p>
<p>一个高可靠系统通常需要具备以下能力：</p>
<ul>
<li class="">自动侦测故障</li>
<li class="">自动修复问题</li>
<li class="">自动切换（Failover）</li>
</ul>
<p>通过这些机制可以最大程度减少系统故障对业务造成的影响。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="极致性能">极致性能<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E6%9E%81%E8%87%B4%E6%80%A7%E8%83%BD" class="hash-link" aria-label="极致性能的直接链接" title="极致性能的直接链接" translate="no">​</a></h2>
<p>系统架构还需要关注性能设计，确保系统能够快速响应用户请求，并处理大量并发操作。常见的性能优化手段包括：</p>
<ul>
<li class="">合理的数据结构与算法设计</li>
<li class="">缓存机制</li>
<li class="">负载均衡</li>
<li class="">并发控制</li>
</ul>
<p>在实际系统中，性能通常通过 <strong>TPS（Transactions Per Second，每秒事务数）</strong> 来衡量。系统性能与并发用户数并不是简单的正相关关系，一般来说：</p>
<ul>
<li class="">系统的最大 TPS 在一定范围内是固定的</li>
<li class="">并发用户数可以通过排队、限流等方式进行调节</li>
</ul>
<p>性能测试时通常需要模拟最坏情况，对服务器进行压力测试。例如：</p>
<ul>
<li class="">大型系统：10000 ~ 50000 并发用户</li>
<li class="">中小型系统：约 5000 并发用户</li>
</ul>
<p>系统吞吐量的计算公式为：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">吞吐量 (TPS) = 并发数 / 平均响应时间</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安全性">安全性<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E5%AE%89%E5%85%A8%E6%80%A7" class="hash-link" aria-label="安全性的直接链接" title="安全性的直接链接" translate="no">​</a></h2>
<p>系统设计必须具备良好的安全性，以保护用户数据和系统资源。常见的安全设计包括：</p>
<ul>
<li class="">身份认证（Authentication）</li>
<li class="">访问控制（Authorization）</li>
<li class="">数据加密</li>
<li class="">安全审计</li>
</ul>
<p>通过多层安全机制，可以有效防止数据泄露、非法访问以及潜在攻击。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="可维护性">可维护性<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E5%8F%AF%E7%BB%B4%E6%8A%A4%E6%80%A7" class="hash-link" aria-label="可维护性的直接链接" title="可维护性的直接链接" translate="no">​</a></h2>
<p>良好的系统架构应该具备高可维护性，使系统在后期能够方便地进行修改、扩展和维护。通常需要做到：</p>
<ul>
<li class="">清晰的代码结构</li>
<li class="">完整的文档说明</li>
<li class="">规范的代码注释</li>
<li class="">易于调试与测试</li>
</ul>
<p>这样可以帮助开发人员快速理解系统并进行迭代开发。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="可伸缩性">可伸缩性<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E5%8F%AF%E4%BC%B8%E7%BC%A9%E6%80%A7" class="hash-link" aria-label="可伸缩性的直接链接" title="可伸缩性的直接链接" translate="no">​</a></h2>
<p>系统还需要具备良好的伸缩能力，可以根据业务规模动态扩展或缩减资源。常见的扩展方式包括：</p>
<ul>
<li class=""><strong>垂直扩展（Scale Up）</strong>：提升单机资源，例如增加 CPU、内存</li>
<li class=""><strong>水平扩展（Scale Out）</strong>：增加服务器节点，通过分布式架构分担负载</li>
</ul>
<p>现代系统架构通常优先选择水平扩展方式，以支持大规模业务增长。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="高可用性">高可用性<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7" class="hash-link" aria-label="高可用性的直接链接" title="高可用性的直接链接" translate="no">​</a></h2>
<p>高可用性是大型系统的重要目标之一。系统需要在出现故障时仍然能够持续提供服务。常见实现方式包括：</p>
<ul>
<li class="">多节点部署</li>
<li class="">负载均衡</li>
<li class="">服务自动切换</li>
<li class="">数据多副本机制</li>
</ul>
<p>通过这些机制，可以最大程度降低系统宕机对用户造成的影响。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="可测试性">可测试性<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E5%8F%AF%E6%B5%8B%E8%AF%95%E6%80%A7" class="hash-link" aria-label="可测试性的直接链接" title="可测试性的直接链接" translate="no">​</a></h2>
<p>系统设计需要具备良好的可测试能力，以支持自动化测试和持续集成。通常需要通过模块化和解耦设计，使系统能够方便地进行：</p>
<ul>
<li class="">单元测试</li>
<li class="">集成测试</li>
<li class="">系统测试</li>
</ul>
<p>良好的可测试性可以显著提升软件质量和开发效率。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="可迁移性">可迁移性<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E5%8F%AF%E8%BF%81%E7%A7%BB%E6%80%A7" class="hash-link" aria-label="可迁移性的直接链接" title="可迁移性的直接链接" translate="no">​</a></h2>
<p>系统架构还需要具备良好的迁移能力，使系统能够在不同环境中方便部署和运行。例如：</p>
<ul>
<li class="">支持不同操作系统或云平台</li>
<li class="">使用标准化接口</li>
<li class="">减少对特定平台的依赖</li>
</ul>
<p>这样可以降低未来系统迁移或升级的成本。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="成本指标">成本指标<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E6%88%90%E6%9C%AC%E6%8C%87%E6%A0%87" class="hash-link" aria-label="成本指标的直接链接" title="成本指标的直接链接" translate="no">​</a></h2>
<p>在小规模项目中，成本通常不是主要关注点。但当系统规模逐渐扩大时，成本就会成为架构设计中的重要指标。</p>
<p>需要注意的是：</p>
<p><strong>低成本、高性能、高可用三者往往存在一定冲突。</strong></p>
<p>因此在架构设计中，低成本通常不是首要目标，而是需要在满足性能和可用性的前提下进行综合权衡。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="架构师应该具备的思考方式">架构师应该具备的思考方式<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E6%9E%B6%E6%9E%84%E5%B8%88%E5%BA%94%E8%AF%A5%E5%85%B7%E5%A4%87%E7%9A%84%E6%80%9D%E8%80%83%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="架构师应该具备的思考方式的直接链接" title="架构师应该具备的思考方式的直接链接" translate="no">​</a></h2>
<p>在实际工程中，架构师并不仅仅是设计系统结构，更重要的是具备整体思考能力。一个成熟的架构师通常需要从多个维度去权衡系统设计，而不是只关注某一个技术点。</p>
<p>1）<strong>全局视角</strong>。架构师需要站在系统整体的角度思考问题，包括业务发展、技术选型、团队能力以及未来扩展空间，而不仅仅是解决当前的技术问题。</p>
<p>2）<strong>权衡能力</strong>。在真实的系统设计中，很少存在"完美方案"。性能、成本、复杂度、可维护性之间往往需要进行取舍。架构师的核心能力之一，就是在这些因素之间找到最合理的平衡点。</p>
<p>3）<strong>长期思维</strong>。架构设计不仅要解决当前问题，还要考虑系统在三年、五年甚至更长时间的发展。好的架构往往能够支持业务不断演进，而不是每隔一段时间就需要推倒重来。</p>
<p>4）<strong>复杂问题拆解能力</strong>。大型系统往往非常复杂，架构师需要能够把复杂系统拆解为多个清晰的模块，使团队能够分工协作并持续迭代。</p>
<p>5）<strong>技术判断力</strong>。面对不断变化的技术生态，需要能够判断哪些技术真正适合业务，哪些只是短期热点，从而做出稳定可靠的技术决策。</p>
<p>简单来说，架构师不仅是技术设计者，更是系统长期发展的规划者。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/06/18/xi-tong-she-zhi-yuan-ze#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>架构设计没有银弹，这些原则之间本身就存在张力：极致性能与低成本冲突，高可用与架构简洁冲突。实际设计时应先明确业务当前阶段真正的核心诉求，再围绕它做取舍，而不是试图在所有维度上都做到最好。对架构师而言，比掌握具体技术更重要的，是在这些维度之间持续权衡的能力。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Ent-ORM]]></title>
            <link>https://tanzhuo.xyz/2023/06/13/go</link>
            <guid>https://tanzhuo.xyz/2023/06/13/go</guid>
            <pubDate>Tue, 13 Jun 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Ent 是 Meta 开源的 Go ORM 框架，采用 Schema First 与代码生成的设计。本文梳理它的设计理念、优劣与发展历程，并与 GORM、XORM、SQLBoiler 做对比。]]></description>
            <content:encoded><![CDATA[<p>Ent 是由 Facebook（现 Meta）开发的一款基于 <strong>Go 语言的 ORM 框架</strong>，核心理念是 Schema First + Code Generation。这篇文章整理一下它的来历、优劣，以及与其他 Go ORM 框架的对比。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tanzhuo.xyz/2023/06/13/go#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>Ent 最初作为 Facebook 内部项目被设计和实现，用于解决大型系统中数据模型复杂、关系众多以及数据访问层难以维护的问题。随着 Facebook 内部系统规模的不断扩大，传统 ORM 在 <strong>类型安全、查询可维护性以及复杂关系建模</strong> 方面逐渐暴露出一定局限性，因此 Facebook 设计了一套全新的 ORM 方案——Ent。</p>
<p>Ent 的核心理念是 <strong>Schema First + Code Generation</strong>。开发者首先通过 Go 代码定义数据模型 Schema，然后通过代码生成工具生成完整的数据访问层代码，包括实体结构体、查询构建器、关系操作以及 CRUD 方法。由于大部分逻辑在 <strong>编译阶段生成并校验</strong>，Ent 可以在保证开发效率的同时提供更高的类型安全性和更好的运行性能。</p>
<p>开源之后，Ent 很快在 Go 社区获得了广泛关注，并逐渐成为 <strong>Go 生态中最具特色的 ORM 框架之一</strong>，尤其适用于数据关系复杂的大型系统，例如社交平台、电商系统以及微服务架构中的核心数据服务。</p>
<p><a href="https://github.com/ent/ent" target="_blank" rel="noopener noreferrer" class="">GitHub - ent/ent: An entity framework for Go</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="框架的优势">框架的优势<a href="https://tanzhuo.xyz/2023/06/13/go#%E6%A1%86%E6%9E%B6%E7%9A%84%E4%BC%98%E5%8A%BF" class="hash-link" aria-label="框架的优势的直接链接" title="框架的优势的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>高效性能</strong>：Ent 采用 <strong>代码生成（Code Generation）</strong> 的方式构建数据访问层。相比传统 ORM 在运行时通过反射解析结构体生成 SQL，Ent 在编译阶段就生成完整的数据操作代码，运行时开销更小，性能更接近手写 SQL。这种设计使得 Ent 在高并发场景下依然可以保持较好的性能表现。</li>
<li class=""><strong>易于使用</strong>：Ent 的查询构建器是 <strong>强类型 DSL（Domain Specific Language）</strong>。所有查询字段、关系和操作都由代码生成器自动生成，因此在编译阶段即可发现错误。</li>
<li class=""><strong>可扩展性</strong>：Ent 提供了丰富的扩展机制，开发者可以根据实际需求进行定制。通过这些扩展能力，可以很方便地将 Ent 与现有系统进行深度集成。</li>
<li class=""><strong>类型安全</strong>：Ent 借助 Go 语言的类型系统来确保代码的正确性，减少了运行时错误的风险。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="框架的不足">框架的不足<a href="https://tanzhuo.xyz/2023/06/13/go#%E6%A1%86%E6%9E%B6%E7%9A%84%E4%B8%8D%E8%B6%B3" class="hash-link" aria-label="框架的不足的直接链接" title="框架的不足的直接链接" translate="no">​</a></h2>
<p>相比其他 ORM 框架，Ent 的社区规模较小，文档和资料相对较少。在一些特定场景下，Ent 的性能可能会受到限制，需要针对性优化。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ent-的发展史">Ent 的发展史<a href="https://tanzhuo.xyz/2023/06/13/go#ent-%E7%9A%84%E5%8F%91%E5%B1%95%E5%8F%B2" class="hash-link" aria-label="Ent 的发展史的直接链接" title="Ent 的发展史的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>2018 年初</strong>：Ent 在 Facebook 内部立项并开始开发。</li>
<li class=""><strong>2019 年初</strong>：Facebook 将 Ent 框架开源。开源后，Ent 得到了广泛的关注和使用，社区规模逐渐扩大。</li>
<li class=""><strong>2019 年中期</strong>：Ent 进行了一系列版本更新，包括性能优化、bug 修复与新功能添加，框架变得更加稳定和高效。</li>
<li class=""><strong>2020 年初</strong>：Ent 发布 1.0 版本，增加了多数据库支持、自定义类型等新能力。1.0 版本的发布标志着 Ent 已经成熟稳定，可以用于生产环境。</li>
<li class=""><strong>2020 年中期至今</strong>：随着框架不断发展和完善，越来越多的开发者开始使用 Ent，并为其贡献代码和文档。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ent-与其他-go-orm-框架对比">Ent 与其他 Go ORM 框架对比<a href="https://tanzhuo.xyz/2023/06/13/go#ent-%E4%B8%8E%E5%85%B6%E4%BB%96-go-orm-%E6%A1%86%E6%9E%B6%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="Ent 与其他 Go ORM 框架对比的直接链接" title="Ent 与其他 Go ORM 框架对比的直接链接" translate="no">​</a></h2>
<p>在 Go 生态中，常见的 ORM 框架包括 <strong>GORM、XORM、SQLBoiler 和 Ent</strong>。不同框架在设计理念上存在明显差异。</p>






























<table><thead><tr><th>框架</th><th>类型</th><th>核心特点</th></tr></thead><tbody><tr><td>GORM</td><td>运行时 ORM</td><td>Go 最流行的 ORM，使用简单，生态成熟</td></tr><tr><td>XORM</td><td>运行时 ORM</td><td>轻量级 ORM，适合简单业务</td></tr><tr><td>SQLBoiler</td><td>代码生成 ORM</td><td>根据数据库 Schema 生成代码，性能较高</td></tr><tr><td>Ent</td><td>Schema-first + Code Generation</td><td>强类型查询构建，适合复杂关系系统</td></tr></tbody></table>
<p>从设计模式来看：</p>
<ul>
<li class=""><strong>GORM / XORM</strong>：运行时 ORM，通过反射解析结构体生成 SQL，上手简单。</li>
<li class=""><strong>SQLBoiler</strong>：基于数据库 Schema 生成代码，类型安全和性能较好。</li>
<li class=""><strong>Ent</strong>：通过 Go Schema 定义模型，再自动生成数据访问代码，强调 <strong>类型安全与复杂关系建模</strong>。</li>
</ul>
<p>简单来说：</p>
<ul>
<li class="">小型项目或快速开发：<strong>GORM 更合适</strong></li>
<li class="">数据关系复杂或大型系统：<strong>Ent 更有优势</strong></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/06/13/go#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Ent 的核心竞争力在于 Schema First 与代码生成带来的强类型保障：查询错误在编译阶段就能暴露，而不是等到线上运行时才发现。代价是社区规模和资料丰富度不如 GORM，上手也需要先接受代码生成的工作流。如果项目的数据关系复杂、对类型安全要求高，Ent 值得认真考虑；如果只是小型项目或快速原型，GORM 这类运行时 ORM 会更省事。</p>]]></content:encoded>
            <category>Go</category>
        </item>
        <item>
            <title><![CDATA[Kratos]]></title>
            <link>https://tanzhuo.xyz/2023/05/17/k-2</link>
            <guid>https://tanzhuo.xyz/2023/05/17/k-2</guid>
            <pubDate>Wed, 17 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Kratos 是 Bilibili 开源的 Go 微服务框架，本文介绍它的设计优点与核心功能，并与 Spring Cloud、Dubbo、Go-Micro 等常见框架做对比。]]></description>
            <content:encoded><![CDATA[<p>Kratos 是一款基于 Go 语言的开源微服务框架，由 Bilibili 开源并维护，提供了微服务所需的服务发现、注册、HTTP、gRPC、限流、配置中心等常用功能。框架设计合理，轻量，易上手。</p>
<!-- -->
<p>官方文档：<a href="https://go-kratos.dev/" target="_blank" rel="noopener noreferrer" class="">Kratos</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="框架优点">框架优点<a href="https://tanzhuo.xyz/2023/05/17/k-2#%E6%A1%86%E6%9E%B6%E4%BC%98%E7%82%B9" class="hash-link" aria-label="框架优点的直接链接" title="框架优点的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>高性能</strong>：Kratos 利用 Go 的协程和高效的并发模型，能够支撑高并发场景，请求和响应的处理开销较低。</li>
<li class=""><strong>简单易用</strong>：框架提供了丰富的文档和示例，设计简洁，不需要开发者掌握过多的概念和技术，上手门槛低。</li>
<li class=""><strong>模块化设计</strong>：内置配置、日志、数据库等一系列模块，方便快速搭建微服务；同时支持插件化开发，可以按需扩展框架功能。</li>
<li class=""><strong>高度可定制</strong>：提供丰富的配置选项，可按需求定制；也支持自定义中间件、拦截器等，开发者可以自行扩展。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="具体功能">具体功能<a href="https://tanzhuo.xyz/2023/05/17/k-2#%E5%85%B7%E4%BD%93%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="具体功能的直接链接" title="具体功能的直接链接" translate="no">​</a></h2>
<ol>
<li class="">微服务注册与发现：支持 etcd、consul 等多种注册中心。</li>
<li class="">配置管理：支持多种配置源，如本地文件、etcd 等。</li>
<li class="">日志管理：支持多种日志输出方式，如文件、控制台、Kafka 等。</li>
<li class="">数据库访问：支持 MySQL、PostgreSQL、Redis 等多种存储。</li>
<li class="">HTTP 服务：支持 HTTP、HTTPS 服务。</li>
<li class="">gRPC 服务：原生支持 gRPC 服务。</li>
<li class="">安全：提供安全中间件，支持 JWT 鉴权、TLS 等。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="微服务框架对比">微服务框架对比<a href="https://tanzhuo.xyz/2023/05/17/k-2#%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%A1%86%E6%9E%B6%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="微服务框架对比的直接链接" title="微服务框架对比的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>与 Spring Cloud 对比</strong>：Spring Cloud 是 Java 生态的微服务框架，相比 Kratos 学习曲线较陡，需要掌握较多的概念和技术，整体也较为重量级，部署和运行成本更高。</li>
<li class=""><strong>与 Dubbo 对比</strong>：Dubbo 是阿里巴巴开源的 Java 微服务框架，不支持 Go 语言；相比之下 Kratos 的文档和示例更贴近 Go 开发者，学习和使用成本更低。</li>
<li class=""><strong>与 gRPC 对比</strong>：gRPC 是 Google 开源的跨语言 RPC 框架，性能出色，但它只解决通信层的问题，缺少服务治理相关的中间件、拦截器等配套能力，学习曲线也较陡。</li>
<li class=""><strong>与 Go-Micro 对比</strong>：Go-Micro 同为 Go 语言微服务框架，设计更灵活，支持多种协议和注册中心，也支持多语言，但学习曲线较陡，需要掌握较多的概念和技术。</li>
<li class=""><strong>与 Gin 对比</strong>：Gin 是 Go 语言的 Web 框架，更加轻量，适合快速搭建简单的 Web 服务，但缺少服务注册与发现、RPC 调用等微服务框架的能力。</li>
<li class=""><strong>与 Micro 对比</strong>：Micro 也是 Go 语言的微服务框架，设计灵活、支持多种协议和注册中心，但概念较多，上手难度大于 Kratos。</li>
<li class=""><strong>与 Beego 对比</strong>：Beego 是 Go 语言的 Web 框架，定位与 Gin 类似，适合快速搭建 Web 服务，同样缺少服务注册与发现、RPC 调用等微服务能力。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/05/17/k-2#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>在 Go 语言的微服务框架里，Kratos 在性能、易用性和功能完整度之间取得了不错的平衡。它把服务发现、配置管理、日志、gRPC/HTTP 双协议这些微服务的基础设施都内置好了，又保持了轻量和可定制的特性。相比 Gin、Beego 这类纯 Web 框架，它补齐了服务治理能力；相比 Go-Micro、Micro，它的概念更少、上手更快。加上 Bilibili 持续维护和丰富的文档示例，对想在 Go 里落地微服务的团队来说，Kratos 是一个值得优先考察的选项。</p>]]></content:encoded>
            <category>Go</category>
        </item>
        <item>
            <title><![CDATA[Neo4j 图形数据库]]></title>
            <link>https://tanzhuo.xyz/2023/05/16/neo4j-graph-database</link>
            <guid>https://tanzhuo.xyz/2023/05/16/neo4j-graph-database</guid>
            <pubDate>Tue, 16 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Neo4j 是采用图形数据模型的高性能图数据库，擅长处理关系密集型场景。本文整理它的收费模式、数据结构与主要功能，并补充选型时的注意事项。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tanzhuo.xyz/2023/05/16/neo4j-graph-database#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>业务里一旦出现多层关联查询，关系型数据库的 Join 就会变得笨重。这篇笔记整理一下 Neo4j 这款图数据库：它是什么、怎么收费、数据是怎么组织的，以及什么场景下值得引入。</p>
<!-- -->
<p>Neo4j 是一款高性能的图数据库，它采用 <strong>图形数据模型（Graph Model）</strong> 来存储和处理数据。与传统的关系型数据库相比，Neo4j 更擅长处理复杂关系型数据，特别适用于 <strong>关系密集型数据场景</strong>。</p>
<p>在很多业务系统中，数据之间往往存在大量关联关系，例如：</p>
<ul>
<li class="">社交网络中的好友关系</li>
<li class="">推荐系统中的用户与商品关系</li>
<li class="">知识图谱中的实体关系</li>
<li class="">网络安全中的攻击路径分析</li>
</ul>
<p>在这些场景下，如果使用传统关系型数据库进行查询，往往需要大量 <strong>Join 操作</strong>，查询复杂度和性能都会迅速下降。而图数据库可以直接通过关系进行遍历，因此在处理复杂关系时效率更高。</p>
<p>为什么 Join 会成为瓶颈？在关系型数据库里，"关系"并不是一等公民，它靠外键加中间表来表达。每做一层 Join，数据库都要在索引里做一次查找，把两张表的行匹配起来；关联层数越深、数据量越大，这个匹配成本涨得越快。典型的例子是"好友的好友的好友"这类查询：在 MySQL 里要对同一张关系表自连接三次，而在图数据库里只是沿着指针多走两步。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="收费模式">收费模式<a href="https://tanzhuo.xyz/2023/05/16/neo4j-graph-database#%E6%94%B6%E8%B4%B9%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="收费模式的直接链接" title="收费模式的直接链接" translate="no">​</a></h2>
<p>官网入口：<a href="https://neo4j.com/" target="_blank" rel="noopener noreferrer" class="">Neo4j Graph Database &amp; Analytics – The Leader in Graph Databases</a></p>
<p>Neo4j 采用开源加商业授权的双轨模式，选型前先弄清两个版本的边界。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="社区版community-edition">社区版（Community Edition）<a href="https://tanzhuo.xyz/2023/05/16/neo4j-graph-database#%E7%A4%BE%E5%8C%BA%E7%89%88community-edition" class="hash-link" aria-label="社区版（Community Edition）的直接链接" title="社区版（Community Edition）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">开源免费</li>
<li class="">适用于个人项目或中小型应用</li>
<li class="">只能部署单实例，不提供集群与在线热备能力</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="企业版enterprise-edition">企业版（Enterprise Edition）<a href="https://tanzhuo.xyz/2023/05/16/neo4j-graph-database#%E4%BC%81%E4%B8%9A%E7%89%88enterprise-edition" class="hash-link" aria-label="企业版（Enterprise Edition）的直接链接" title="企业版（Enterprise Edition）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">商业授权</li>
<li class="">提供集群、高可用、安全认证等高级功能</li>
<li class="">面向对可用性和权限管控有硬性要求的生产环境</li>
</ul>
<p>简单说：功能验证、内部工具、数据量可控的场景，社区版够用；一旦要求高可用或细粒度权限，就绕不开企业版，这笔授权成本需要提前算进方案里。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="图数据库数据结构">图数据库数据结构<a href="https://tanzhuo.xyz/2023/05/16/neo4j-graph-database#%E5%9B%BE%E6%95%B0%E6%8D%AE%E5%BA%93%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84" class="hash-link" aria-label="图数据库数据结构的直接链接" title="图数据库数据结构的直接链接" translate="no">​</a></h2>
<p>图数据库的数据结构为</p>
<p>Node (节点)<br>
Relationship (关系)<br>
Property (属性)</p>
<p>三者的分工很清晰：节点表示实体（人、商品、设备），关系表示实体之间的连接且<strong>必须有方向和类型</strong>，属性则是挂在节点或关系上的键值对。对照关系型数据库理解：节点近似表里的一行，关系近似外键，但关系本身也能携带属性（比如"好友关系"上可以存建立时间），这是外键做不到的。</p>
<p>例如：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">(A)-[FRIEND]-&gt;(B)</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">(B)-[FRIEND]-&gt;(C)</span><br></div></code></pre></div></div>
<p>关系是 <strong>直接存储在节点之间的指针</strong>。数据库只需要 <strong>顺着关系指针遍历</strong>，不需要做表连接。所以对于关联关系的查询远超关系型数据库。这种设计有个专门的说法，叫 <strong>免索引邻接（index-free adjacency）</strong>：找一个节点的邻居不走全局索引，而是从节点自身的指针直接出发，遍历一步的开销只和这个节点的关系数量有关，和全库数据量无关。</p>
<p>查询用的是 Cypher 语言，写法和上面的图形表示几乎一致：</p>
<div class="language-cypher codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-cypher codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">// 查询 A 的好友的好友（两层关系遍历）</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">MATCH (a:Person {name: 'A'})-[:FRIEND]-&gt;()-[:FRIEND]-&gt;(fof)</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">RETURN fof.name</span><br></div></code></pre></div></div>
<p>模式匹配直接画出来就是查询语句，这也是图数据库建模直观的原因之一。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="主要功能">主要功能<a href="https://tanzhuo.xyz/2023/05/16/neo4j-graph-database#%E4%B8%BB%E8%A6%81%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="主要功能的直接链接" title="主要功能的直接链接" translate="no">​</a></h2>
<p>图形数据存储和查询：Neo4j使用图形数据模型来存储数据，可以轻松处理复杂的关系数据。</p>
<p>高效的查询性能：由于采用了图形数据模型，Neo4j可以轻松地处理深度查询和复杂的关系查询，具有更高的查询性能。查询耗时主要取决于遍历经过的子图大小，而不是整库的规模。</p>
<p>可扩展性：Neo4j可以轻松地扩展到数百亿个节点和关系，具有出色的可扩展性。</p>
<p>ACID事务支持：Neo4j支持ACID事务，确保数据的一致性和可靠性。这一点把它和很多只做离线分析的图计算框架区分开——它是可以承接在线写入的数据库，而不只是分析工具。</p>
<p>Neo4j 通过图数据模型，使数据关系表达更加直观，并且在复杂关系查询场景下具有明显性能优势。在传统业务系统中，关系型数据库仍然是核心数据存储方式。但在 <strong>关系密集型场景</strong>（例如社交关系、推荐系统、知识图谱等），图数据库可以作为关系分析引擎，与传统数据库形成互补。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/05/16/neo4j-graph-database#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）别拿图数据库替代关系型数据库。聚合统计、批量报表这类按列扫描的场景，图模型没有优势，合理的架构通常是 RDB 存主数据、Neo4j 存关系子集。</p>
<p>2）建模思路要换。图建模先想清楚"节点是什么、关系叫什么、方向朝哪"，而不是先设计表结构；关系的方向和类型定错了，后面的查询会非常别扭。</p>
<p>3）控制遍历深度。Cypher 支持变长路径匹配，写查询时不限定深度或不加节点标签，很容易演变成全图扫描，拖垮整个实例。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>引入前先用真实的关系查询做验证：如果业务里的查询大多只有一两层 Join，关系型数据库配好索引往往就够了，没必要多维护一套存储。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/05/16/neo4j-graph-database#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>图数据库解决的核心问题只有一个：让"关系"成为一等公民，把 Join 换成指针遍历。Neo4j 社区版足够用来验证想法，生产高可用则要考虑企业版授权。它不是关系型数据库的替代品，而是关系密集型场景下的补充引擎——先确认查询模式确实是深层关联，再决定引入。</p>]]></content:encoded>
            <category>DB</category>
        </item>
        <item>
            <title><![CDATA[数据库分类]]></title>
            <link>https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku</link>
            <guid>https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku</guid>
            <pubDate>Tue, 16 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[从数据模型、处理方式、部署方式、存储介质与访问方式等维度梳理数据库分类，并介绍各类数据库的典型使用场景。]]></description>
            <content:encoded><![CDATA[<p>数据库可以从多个维度进行分类，不同的分类方式反映了数据库在<strong>数据模型、处理能力、部署方式、存储介质以及应用场景</strong>等方面的差异。理解这些分类，有助于在实际系统架构设计中选择合适的数据库方案。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="按数据模型分类">按数据模型分类<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E6%8C%89%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B%E5%88%86%E7%B1%BB" class="hash-link" aria-label="按数据模型分类的直接链接" title="按数据模型分类的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="关系型数据库rdbms">关系型数据库（RDBMS）<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93rdbms" class="hash-link" aria-label="关系型数据库（RDBMS）的直接链接" title="关系型数据库（RDBMS）的直接链接" translate="no">​</a></h3>
<p>关系型数据库以<strong>表（Table）<strong>作为核心数据结构，通过</strong>行和列</strong>组织数据，并使用 <strong>SQL（Structured Query Language）</strong> 进行数据查询和管理。不同表之间通过**主键（Primary Key）和外键（Foreign Key）**建立关系，从而实现复杂的数据关联查询。</p>
<p>它的特点是：</p>
<ul>
<li class="">数据结构清晰，强约束</li>
<li class="">支持复杂查询（Join、聚合等）</li>
<li class="">支持事务和 ACID 特性</li>
<li class="">适合结构化数据存储</li>
</ul>
<p>典型数据库：MySQL、PostgreSQL、Oracle、SQL Server 等。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="非关系型数据库nosql">非关系型数据库（NoSQL）<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E9%9D%9E%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93nosql" class="hash-link" aria-label="非关系型数据库（NoSQL）的直接链接" title="非关系型数据库（NoSQL）的直接链接" translate="no">​</a></h3>
<p>NoSQL（Not Only SQL）数据库不使用传统的关系型表结构，而是采用更灵活的数据模型，以适应大规模数据和高并发访问场景。</p>
<p>它的特点是：</p>
<ul>
<li class="">数据结构灵活</li>
<li class="">扩展能力强</li>
<li class="">高并发性能好</li>
<li class="">通常弱事务或无事务</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="按处理方式分类">按处理方式分类<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E6%8C%89%E5%A4%84%E7%90%86%E6%96%B9%E5%BC%8F%E5%88%86%E7%B1%BB" class="hash-link" aria-label="按处理方式分类的直接链接" title="按处理方式分类的直接链接" translate="no">​</a></h2>
<ul>
<li class="">事务型数据库：支持事务处理和 ACID 特性（原子性、一致性、隔离性、持久性）。</li>
<li class="">非事务型数据库：不支持事务处理和 ACID 特性，但具有更高的性能和可扩展性。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="按部署方式分类">按部署方式分类<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E6%8C%89%E9%83%A8%E7%BD%B2%E6%96%B9%E5%BC%8F%E5%88%86%E7%B1%BB" class="hash-link" aria-label="按部署方式分类的直接链接" title="按部署方式分类的直接链接" translate="no">​</a></h2>
<ul>
<li class="">单机数据库：部署在一台服务器上，所有数据和计算都在同一节点完成。</li>
<li class="">分布式数据库：将数据分布在多台服务器上，通过网络进行协同工作，从而实现更高的可扩展性和可用性。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="按存储方式分类">按存储方式分类<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E6%8C%89%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F%E5%88%86%E7%B1%BB" class="hash-link" aria-label="按存储方式分类的直接链接" title="按存储方式分类的直接链接" translate="no">​</a></h2>
<ul>
<li class="">内存数据库：将数据存储在 <strong>RAM（内存）</strong> 中，因此读写速度非常快，如 Redis、Memcached。</li>
<li class="">磁盘数据库：将数据持久化存储在<strong>磁盘或 SSD</strong> 上，如 MySQL、PostgreSQL、Oracle 等。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="按访问方式分类">按访问方式分类<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E6%8C%89%E8%AE%BF%E9%97%AE%E6%96%B9%E5%BC%8F%E5%88%86%E7%B1%BB" class="hash-link" aria-label="按访问方式分类的直接链接" title="按访问方式分类的直接链接" translate="no">​</a></h2>
<ul>
<li class="">OLTP（Online Transaction Processing）数据库主要用于<strong>在线事务处理系统</strong>。</li>
<li class="">OLAP（Online Analytical Processing）数据库主要用于<strong>数据分析和决策支持</strong>，如 ClickHouse、Doris、StarRocks、Snowflake 等。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用场景介绍">使用场景介绍<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E4%BB%8B%E7%BB%8D" class="hash-link" aria-label="使用场景介绍的直接链接" title="使用场景介绍的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="关系型数据库rdbms-1">关系型数据库（RDBMS）<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93rdbms-1" class="hash-link" aria-label="关系型数据库（RDBMS）的直接链接" title="关系型数据库（RDBMS）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">适用于需要处理大量结构化数据的应用场景，如金融、电子商务、物流等。</li>
<li class="">适用于需要支持事务处理和 ACID 特性的应用场景，如银行、电信、航空等。</li>
<li class="">适用于需要进行复杂查询和分析的应用场景，如数据仓库、商业智能等。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="非关系型数据库nosql-1">非关系型数据库（NoSQL）<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E9%9D%9E%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93nosql-1" class="hash-link" aria-label="非关系型数据库（NoSQL）的直接链接" title="非关系型数据库（NoSQL）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">文档型数据库：适用于处理半结构化数据的应用场景，如博客、新闻、评论等。</li>
<li class="">键值对数据库：适用于处理简单的键值对数据的应用场景，如缓存、会话管理等。</li>
<li class="">列式数据库：适用于处理大量结构化数据的应用场景，如日志、事件、时间序列数据等。</li>
<li class="">图形数据库：适用于处理复杂的关系型数据的应用场景，如社交网络、推荐系统、生物信息学、网络安全等。</li>
<li class="">对象数据库：适用于需要支持面向对象编程的应用场景，如游戏开发、多媒体应用等。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="内存数据库">内存数据库<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E5%86%85%E5%AD%98%E6%95%B0%E6%8D%AE%E5%BA%93" class="hash-link" aria-label="内存数据库的直接链接" title="内存数据库的直接链接" translate="no">​</a></h3>
<ul>
<li class="">适用于需要快速响应的应用场景，如高并发的交易处理、实时数据分析等。</li>
<li class="">适用于需要快速读写的应用场景，如缓存、会话管理等。</li>
<li class="">适用于需要进行实时计算和分析的应用场景，如实时风控、实时推荐等。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="分布式数据库">分布式数据库<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E5%88%86%E5%B8%83%E5%BC%8F%E6%95%B0%E6%8D%AE%E5%BA%93" class="hash-link" aria-label="分布式数据库的直接链接" title="分布式数据库的直接链接" translate="no">​</a></h3>
<ul>
<li class="">适用于需要高可用性和容错性的应用场景，如分布式计算、分布式存储等。</li>
<li class="">适用于需要支持大规模数据处理和分析的应用场景，如大数据、机器学习等。</li>
<li class="">适用于需要支持多地域部署和数据共享的应用场景，如云计算、物联网等。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/05/16/ren-shi-shu-ju-ku#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>数据库的分类维度很多，同一款产品往往同时属于多个类别，比如 MySQL 既是关系型数据库，也是磁盘存储的事务型数据库。选型时不必纠结于分类本身，而应从数据结构、一致性要求、并发规模和查询模式出发，找到与业务匹配的那一类。实际系统中，多种数据库配合使用也很常见，例如用 Redis 做缓存、MySQL 存业务数据、ClickHouse 跑分析查询。理解每类数据库的定位，才能在架构设计时做出合理取舍。</p>]]></content:encoded>
            <category>DB</category>
        </item>
        <item>
            <title><![CDATA[防重复提交]]></title>
            <link>https://tanzhuo.xyz/2023/05/09/fang-zhong-fu-ti-jiao</link>
            <guid>https://tanzhuo.xyz/2023/05/09/fang-zhong-fu-ti-jiao</guid>
            <pubDate>Tue, 09 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[服务端生成一次性 Token 存入 Redis，随表单下发给客户端，提交时校验通过后立即删除。记录这套机制防重复提交与 CSRF 的原理、实现步骤和安全注意事项。]]></description>
            <content:encoded><![CDATA[<p>提交按钮被双击、网络超时后自动重试、浏览器回退再次提交，都可能让同一个表单被服务器处理两次。使用 Token 令牌机制可以有效地防止 CSRF 攻击和重复提交：在提交表单时，服务器会生成一个 Token 令牌并将其存储在 Redis 中，然后作为表单的隐藏字段或 URL 参数传递给客户端，提交时校验、用完即删。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么这个问题值得处理">为什么这个问题值得处理<a href="https://tanzhuo.xyz/2023/05/09/fang-zhong-fu-ti-jiao#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%99%E4%B8%AA%E9%97%AE%E9%A2%98%E5%80%BC%E5%BE%97%E5%A4%84%E7%90%86" class="hash-link" aria-label="为什么这个问题值得处理的直接链接" title="为什么这个问题值得处理的直接链接" translate="no">​</a></h2>
<p>重复提交的业务后果往往比表面看起来严重：下单接口被处理两次就是两笔订单，扣款请求被重试两次就是重复扣款。前端把提交按钮置灰只能挡住手滑，挡不住网络层的自动重试，更挡不住恶意构造的请求，所以这层防护最终要落在服务端。</p>
<p>CSRF 攻击的场景则是攻击者诱导用户的浏览器向目标站点发起请求，它能得逞的原因是浏览器会自动携带 Cookie。而 Token 不放在 Cookie 里，第三方站点拿不到这个值，因此一套 Token 机制可以同时防住这两类问题。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="基本原理">基本原理<a href="https://tanzhuo.xyz/2023/05/09/fang-zhong-fu-ti-jiao#%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86" class="hash-link" aria-label="基本原理的直接链接" title="基本原理的直接链接" translate="no">​</a></h2>
<p>Token 令牌机制是一种常用的 Web 应用程序防止重复提交和 CSRF 攻击的方法。它的基本思想是在每次提交表单时，服务器会生成一个 Token 令牌，并将其存储在 Redis 中。然后，将这个 Token 令牌作为表单的一个隐藏字段或 URL 参数传递给客户端。客户端提交表单时，将这个 Token 令牌一并提交给服务器。服务器在处理表单时，会检查这个 Token 令牌是否正确，并在处理完表单后删除这个 Token 令牌。</p>
<p>两个防护目标在机制上各有侧重：防重复提交靠的是"校验通过后立即删除"，同一个 Token 第二次到达时已经不存在，请求会被拒绝；防 CSRF 靠的是 Token 只下发给合法页面，攻击者构造的请求里没有它。Redis 在这里承担两件事：集中存储，让多实例部署时任何一台机器都能完成校验；过期控制，利用 TTL 自动清理没有被消费的 Token。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实现步骤">实现步骤<a href="https://tanzhuo.xyz/2023/05/09/fang-zhong-fu-ti-jiao#%E5%AE%9E%E7%8E%B0%E6%AD%A5%E9%AA%A4" class="hash-link" aria-label="实现步骤的直接链接" title="实现步骤的直接链接" translate="no">​</a></h2>
<p>1）在服务器端生成一个 Token 令牌，并将其存储在 Redis 中。Token 令牌可以使用随机数、UUID 或其他方法生成，需要保证其足够随机和唯一。写入时顺便设置过期时间：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># NX 保证不覆盖已有 Token，EX 设置过期时间（秒），避免无效 Token 长期堆积</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">SET submit:token:</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">tokenValue</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">1</span><span class="token plain"> EX </span><span class="token number" style="color:hsl(29, 54%, 61%)">600</span><span class="token plain"> NX</span><br></div></code></pre></div></div>
<p>2）将这个 Token 令牌传递给客户端。如果是表单提交，可以将 Token 令牌作为一个隐藏字段传递；如果是 AJAX 请求，可以将 Token 令牌作为一个 HTTP 头传递。</p>
<p>3）客户端提交表单时，将这个 Token 令牌一并提交给服务器。如果是表单提交，可以在表单的提交事件中添加一段 JavaScript 代码，将 Token 令牌添加为一个隐藏字段；如果是 AJAX 请求，可以在发送请求前将 Token 令牌添加为一个 HTTP 头。</p>
<p>4）服务器在处理表单时，会检查这个 Token 令牌是否正确。如果 Token 令牌正确，则说明这是一个有效的请求，可以继续处理；否则，说明这是一个重复提交或 CSRF 攻击，应该拒绝处理。</p>
<p>5）在处理完表单后，服务器需要从 Redis 中删除这个 Token 令牌，以防止重复使用。</p>
<p>第 4、5 步的"校验"和"删除"必须作为一个原子操作完成。如果先 GET 再 DEL 分两步执行，两个并发请求可能同时通过校验，防重复提交就失效了。可以用一段 Lua 脚本让 Redis 原子地完成校验加删除：</p>
<div class="language-lua codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-lua codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">-- 校验并删除：Token 存在则删除并返回 1，否则返回 0</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">-- 整段脚本在 Redis 中原子执行，并发请求只有一个能成功</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">if redis.call('GET', KEYS[1]) then</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  return redis.call('DEL', KEYS[1])</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">else</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  return 0</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">end</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安全注意事项">安全注意事项<a href="https://tanzhuo.xyz/2023/05/09/fang-zhong-fu-ti-jiao#%E5%AE%89%E5%85%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" class="hash-link" aria-label="安全注意事项的直接链接" title="安全注意事项的直接链接" translate="no">​</a></h2>
<p>Token 令牌机制可以有效地防止重复提交和 CSRF 攻击，但是需要注意保护 Token 令牌的安全性，否则可能会被攻击者利用。具体来说，需要注意以下几点：</p>
<p>Token 令牌需要足够随机和唯一，否则可能会被攻击者猜测或重复使用。</p>
<p>Token 令牌需要保护其传输的安全性，否则可能会被攻击者截获或篡改。传输一律走 HTTPS，不要把 Token 拼在会被记录到日志的 URL 里。</p>
<p>Token 令牌需要设置有效期限，否则可能会被攻击者重复使用。</p>
<p>Token 令牌需要保护其存储在 Redis 中的安全性，否则可能会被攻击者盗取或篡改。Redis 实例不应暴露在公网，并开启访问认证。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/05/09/fang-zhong-fu-ti-jiao#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p>校验和删除分两步写是最常见的错误实现。压测或用户快速双击时，两个请求都能通过 GET 校验，各自处理一遍业务。务必用 Lua 脚本或等价手段保证原子性。</p></div></div>
<p>过期时间要结合用户实际填表时长来定。设得太短，用户填一个长表单的工夫 Token 已经过期，提交必然失败；设得太长，无效 Token 又会在 Redis 里堆积。</p>
<p>校验失败时前端要给出明确反馈，引导用户刷新页面重新获取 Token 后再提交，而不是静默丢弃请求，否则用户只会觉得"点了没反应"然后继续狂点。</p>
<p>另外要想清楚删除的时机：业务处理失败时是否归还 Token、允许用户直接重试，取决于具体业务对幂等的要求，没有统一答案。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/05/09/fang-zhong-fu-ti-jiao#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Token 令牌机制的核心是"一次性凭证"：服务器发放、Redis 存储、提交校验、用完即删，一套流程同时解决了重复提交和 CSRF 两个问题。落地时重点盯住四件事——Token 的随机性、传输与存储的安全、合理的有效期，以及校验删除的原子性。这些做到了，它就是一个成本很低、收益明确的通用防护手段。</p>]]></content:encoded>
            <category>Java</category>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[Docker 原理]]></title>
            <link>https://tanzhuo.xyz/2023/05/06/docker</link>
            <guid>https://tanzhuo.xyz/2023/05/06/docker</guid>
            <pubDate>Sat, 06 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Docker 学习笔记：Daemon 与 Client 的交互流程、Namespace/Cgroups/联合文件系统的容器实现原理，以及镜像分层与数据卷的底层存储机制。]]></description>
            <content:encoded><![CDATA[<p>Docker 的内部架构、容器实现原理与底层存储原理笔记。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="内部架构组件">内部架构组件<a href="https://tanzhuo.xyz/2023/05/06/docker#%E5%86%85%E9%83%A8%E6%9E%B6%E6%9E%84%E7%BB%84%E4%BB%B6" class="hash-link" aria-label="内部架构组件的直接链接" title="内部架构组件的直接链接" translate="no">​</a></h2>





























<table><thead><tr><th>组件</th><th>描述</th></tr></thead><tbody><tr><td>Docker Daemon</td><td>Docker 的主要组件，是一个守护进程，负责管理容器的创建、启动、停止、删除等操作。</td></tr><tr><td>Docker Client</td><td>与 Docker Daemon 交互的命令行工具，用户可以使用该工具发送命令来操作 Docker 容器。</td></tr><tr><td>Docker Registry</td><td>存储 Docker 镜像的中央仓库，可以通过 Docker Client 从该仓库中拉取所需的镜像。</td></tr><tr><td>Docker Image</td><td>容器的基础，是一个只读的文件系统，包含了应用程序、依赖库和配置文件等。可以通过 Dockerfile 文件创建。</td></tr><tr><td>Docker Container</td><td>Docker 镜像的一个实例，包含了一个完整的文件系统、运行时环境和系统工具。</td></tr></tbody></table>
<p>Docker Daemon 和 Docker Client 之间采用 RESTful API 进行通信：Docker Client 发送命令到 Docker Daemon，Docker Daemon 接收并处理命令，最终返回结果给 Docker Client。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2023-05-image-3-8e8f77d570d13db756ced57e591ce0d5.webp" width="816" height="742" class="img_ev3q"></p>
<p>当 Docker Daemon 接收到创建容器的命令时，它会先检查本地是否已经存在所需的 Docker 镜像。如果本地不存在该镜像，则 Docker Daemon 会从 Docker Registry 中拉取所需的镜像；如果本地已经存在该镜像，则 Docker Daemon 会根据该镜像创建容器，并将其放到一个独立的命名空间中运行。</p>
<p>容器运行时，Docker Daemon 会将容器的输出和错误信息输出到 stdout 和 stderr，用户可以通过 Docker Client 查看容器输出。当用户不再需要容器时，可以使用 Docker Client 发送命令停止该容器。Docker Daemon 会发送 SIGTERM 信号给容器中的进程，让它们优雅地退出；如果进程在一定时间内没有退出，则 Docker Daemon 会发送 SIGKILL 信号，强制杀死进程。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="容器实现原理">容器实现原理<a href="https://tanzhuo.xyz/2023/05/06/docker#%E5%AE%B9%E5%99%A8%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86" class="hash-link" aria-label="容器实现原理的直接链接" title="容器实现原理的直接链接" translate="no">​</a></h2>
<p>Docker 容器的实现原理可以分为以下几个方面：</p>
<ol>
<li class="">命名空间（Namespace）：Docker 使用命名空间来隔离不同容器的进程、网络、文件系统和用户等资源。每个容器都有自己的命名空间，使得容器之间互相独立，互不影响。</li>
<li class="">控制组（Control Groups，简称 Cgroups）：Docker 使用 Cgroups 来限制容器使用的 CPU、内存、网络带宽等资源。每个容器都被分配了一定的资源配额，使得容器之间可以公平地共享主机资源。</li>
<li class="">联合文件系统（Union File System）：Docker 使用联合文件系统来实现容器的文件系统隔离。每个容器都有自己的文件系统，但是可以共享主机文件系统中的一部分内容。这种方式使得容器的文件系统非常轻量化，可以快速创建和销毁。</li>
<li class="">容器镜像（Container Image）：Docker 容器是基于容器镜像创建的，容器镜像是一个只读的文件系统，包含了应用程序、依赖库和配置文件等。Docker 使用联合文件系统来实现容器镜像的分层存储，使得容器镜像可以共享和复用，大大减少了存储空间和下载时间。</li>
<li class="">容器运行时（Container Runtime）：Docker 使用容器运行时来启动和管理容器。容器运行时负责创建容器的命名空间、Cgroups 和联合文件系统，然后启动容器中的进程。容器运行时还负责监控容器的状态和资源使用情况，并根据需要调整容器的资源配额。</li>
</ol>
<p>Docker 的运行原理就是通过 Docker Daemon 和 Docker Client 之间的交互，来创建、启动、停止和删除 Docker 容器。Docker 镜像是 Docker 容器的基础，可以通过 Dockerfile 文件创建；Docker 容器运行在独立的命名空间中，可以提供隔离和安全性。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="底层存储原理">底层存储原理<a href="https://tanzhuo.xyz/2023/05/06/docker#%E5%BA%95%E5%B1%82%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86" class="hash-link" aria-label="底层存储原理的直接链接" title="底层存储原理的直接链接" translate="no">​</a></h2>
<p>Docker 的底层存储原理主要涉及容器镜像和容器数据卷两个方面。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="容器镜像存储">容器镜像存储<a href="https://tanzhuo.xyz/2023/05/06/docker#%E5%AE%B9%E5%99%A8%E9%95%9C%E5%83%8F%E5%AD%98%E5%82%A8" class="hash-link" aria-label="容器镜像存储的直接链接" title="容器镜像存储的直接链接" translate="no">​</a></h3>
<p>Docker 使用联合文件系统（Union File System）来实现容器镜像的分层存储。容器镜像由多个只读层组成，每个层都可以被共享和复用。当容器启动时，Docker 将这些只读层联合在一起，并在其上叠加一个可写层，形成容器的文件系统。这种方式使得容器镜像非常轻量化，可以快速创建和销毁。</p>
<p>Docker 默认使用 AUFS（Advanced Multi-Layered Unification File System）作为联合文件系统，它支持多层联合、快速创建和销毁、只读层共享等特性。Docker 还支持其他的联合文件系统，比如 OverlayFS、DeviceMapper 和 Btrfs 等。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="容器数据卷存储">容器数据卷存储<a href="https://tanzhuo.xyz/2023/05/06/docker#%E5%AE%B9%E5%99%A8%E6%95%B0%E6%8D%AE%E5%8D%B7%E5%AD%98%E5%82%A8" class="hash-link" aria-label="容器数据卷存储的直接链接" title="容器数据卷存储的直接链接" translate="no">​</a></h3>
<p>Docker 容器的数据卷是一个可供容器读写的目录或文件，可以用于在容器之间共享数据、持久化存储等。Docker 支持多种类型的容器数据卷，包括主机挂载卷、命名卷和匿名卷等。</p>
<p>主机挂载卷是将主机上的目录或文件挂载到容器中，容器可以读写该目录或文件；命名卷是由 Docker 管理的数据卷，可以给数据卷指定一个名字，方便管理和共享；匿名卷是由 Docker 自动生成的数据卷，用于存储容器中的临时数据。</p>
<p>Docker 容器的数据卷存储主要是基于 Linux 的文件系统挂载机制实现的。当容器启动时，Docker 会将数据卷挂载到容器中的指定目录，容器中的数据写入该目录即落到卷上；容器停止时，Docker 会卸载数据卷，数据仍保留在卷中，从而实现持久化存储。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/05/06/docker#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Docker 的架构并不复杂：Client 通过 RESTful API 向 Daemon 发送指令，Daemon 负责镜像拉取与容器的完整生命周期管理。容器本身依赖 Linux 的 Namespace 做资源隔离、Cgroups 做资源限制，联合文件系统则支撑了镜像的分层存储与容器的可写层。数据的持久化交给数据卷，通过文件系统挂载机制让数据独立于容器的生命周期。理解了这几个机制，容器"轻量、可移植、可复用"的特性也就不难解释了。</p>]]></content:encoded>
            <category>Docker</category>
        </item>
        <item>
            <title><![CDATA[Flink CDC 组件]]></title>
            <link>https://tanzhuo.xyz/2023/05/05/flink</link>
            <guid>https://tanzhuo.xyz/2023/05/05/flink</guid>
            <pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[介绍 Flink CDC 的基本概念与内置组件：从变更日志捕获的原理讲起，梳理 Source、Debezium Connector、Sink 等核心模块，以及使用中的常见注意事项。]]></description>
            <content:encoded><![CDATA[<p>最近在梳理实时数据链路时，重新整理了一遍 Flink CDC 的相关知识。这类"把数据库变更实时搬出来"的需求几乎每个数据团队都会遇到，值得单独写一篇记录。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="背景">背景<a href="https://tanzhuo.xyz/2023/05/05/flink#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="背景的直接链接" title="背景的直接链接" translate="no">​</a></h2>
<p>做数据同步这件事，最朴素的方案是定时跑批：每天凌晨全量拉一遍业务库，写进数仓。这种方式实现简单，但延迟以小时甚至天计，数据量大了以后对源库的压力也不小。业务方一旦提出"报表要看实时的"，跑批方案就顶不住了。CDC 正是为了解决这个问题而出现的技术路线，而 Flink CDC 则是把 CDC 能力和流计算引擎结合得比较彻底的一个实现。</p>
<p>Flink CDC（Change Data Capture）是 Apache Flink 生态中的一个重要组件，用于实时捕获数据库中的数据变更，并将这些变更转换为数据流进行处理。在现代数据架构中，CDC 技术被广泛用于构建实时数据同步、实时数据仓库以及事件驱动系统。</p>
<p>与传统的数据同步方式（如定时全量同步）相比，CDC 能够通过读取数据库的 <strong>Binlog / WAL 等变更日志</strong> 来捕获数据变化，从而实现低延迟、低侵入的数据同步。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2023-05-flink-header-logo-5b83b4e45940e5c5870da4d58bde8b88.svg" width="427" height="220" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="什么是-cdc">什么是 CDC<a href="https://tanzhuo.xyz/2023/05/05/flink#%E4%BB%80%E4%B9%88%E6%98%AF-cdc" class="hash-link" aria-label="什么是 CDC的直接链接" title="什么是 CDC的直接链接" translate="no">​</a></h2>
<p>CDC（Change Data Capture）即 <strong>变更数据捕获</strong>，它的核心思想是：<br>
当数据库中的数据发生 <strong>INSERT / UPDATE / DELETE</strong> 操作时，将这些变化记录下来，并实时传递到下游系统。</p>
<p>这里可以稍微展开一下它的实现机制。CDC 大体分两类：一类是基于查询的，靠轮询时间戳或自增主键字段来发现新数据，实现简单但抓不到 DELETE，也容易漏掉两次轮询之间被多次修改的记录；另一类是基于日志的，直接消费数据库为主从复制而维护的变更日志——MySQL 的 Binlog、PostgreSQL 的 WAL 都属于这类。日志里天然记录了每一行数据变更前后的完整状态，所以基于日志的 CDC 能拿到全量的增删改事件，且对源库几乎没有额外查询压力。Flink CDC 走的就是日志这条路。</p>
<p>在实际的数据架构中，CDC 常用于：</p>
<ul>
<li class="">数据库 → 数据仓库（实时数仓）</li>
<li class="">数据库 → Kafka（事件流）</li>
<li class="">数据库 → 搜索引擎（如 Elasticsearch）</li>
<li class="">数据库 → 缓存系统（如 Redis）</li>
</ul>
<p>因此，CDC 是现代 <strong>实时数据平台（Real-time Data Platform）</strong> 的关键基础能力之一。</p>
<p>Flink CDC 组件的工作原理是通过捕获源数据系统的变更日志，将其转化为数据流并进行实时处理。这个过程可以在不影响源系统的情况下进行，因为CDC组件只是读取源系统的日志，而不会对源系统进行写操作。从数据库的视角看，CDC 客户端和一个普通的从库没有本质区别——它伪装成一个复制客户端，向主库订阅变更日志，再把日志事件解析成结构化的变更记录交给 Flink 处理。</p>
<p>Flink CDC组件的优点在于它可以支持多种数据源，包括关系型数据库（如MySQL，Oracle等）和NoSQL数据库（如MongoDB，Cassandra等）。此外，它还能够支持多种数据格式，如JSON，CSV等。</p>
<p>另一个容易被低估的优点是它和 Flink 本身的整合深度。变更流进入 Flink 之后，就是一条普通的数据流，可以直接套用 Flink 的窗口计算、多流 Join、状态管理和 checkpoint 容错机制。也就是说，"捕获变更"和"处理变更"在同一个引擎里完成，不需要再单独维护一套 Kafka Connect 集群做中转，链路更短，运维对象也更少。</p>
<p>使用Flink CDC组件可以实现实时的数据同步和数据流处理。例如，我们可以使用CDC组件将MySQL数据库中的数据同步到Kafka，然后使用Flink进行实时数据处理。这样就可以实现数据的实时同步和处理，从而提高数据的效率和准确性。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink-cdc组件">Flink CDC组件<a href="https://tanzhuo.xyz/2023/05/05/flink#flink-cdc%E7%BB%84%E4%BB%B6" class="hash-link" aria-label="Flink CDC组件的直接链接" title="Flink CDC组件的直接链接" translate="no">​</a></h2>
<p>从整体上看，一条 Flink CDC 链路的分工大致是：Source 负责"进"，Sink 负责"出"，中间由 Flink 的算子和状态机制完成计算。Flink CDC组件内置了以下几个组件：</p>
<ol>
<li class="">Source：该组件用于从数据源中读取数据变更日志，并将其转换为Flink数据流。通常一个 CDC Source 会先对存量数据做一次快照读取，再无缝衔接到增量日志消费，这样下游拿到的就是"全量 + 增量"的完整数据。</li>
<li class="">Debezium Connector：该组件是一个开源的CDC工具，可以连接多种数据源（如MySQL、PostgreSQL、MongoDB等）并捕获数据变更日志。Flink CDC 在底层复用了 Debezium 的日志解析能力，把它以内嵌方式跑在 Flink 作业里，省去了独立部署 Debezium 服务的成本。</li>
<li class="">Sink：该组件用于将Flink数据流写入目标数据源中，例如Kafka、HDFS、Elasticsearch等。Sink 配合 Flink 的 checkpoint 机制，可以在作业失败重启时避免数据丢失。</li>
<li class="">State：该组件用于处理有状态的数据流，例如，如果需要将两个数据流进行合并，则需要使用State组件来存储中间状态。CDC 场景下 State 尤其重要——比如维表关联、按主键去重、把 UPDATE 事件还原成最新镜像，都依赖状态存储。</li>
<li class="">Table API：该组件提供了一个SQL-like的API，可以方便地进行数据流处理和查询。对于大多数同步类需求，用 Flink SQL 声明一张 CDC 源表和一张目标表，写一条 INSERT INTO 就能跑通整条链路，不需要写 Java 代码。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/05/05/flink#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>实际使用中有几个点值得提前留意：</p>
<ol>
<li class="">
<p>源库的日志配置要先确认。以 MySQL 为例，Binlog 需要开启且格式为 ROW，否则拿不到行级别的变更前后镜像；连接账号也需要具备读取 Binlog 相关的复制权限。</p>
</li>
<li class="">
<p>日志保留时间不能太短。如果作业停了一段时间再恢复，而这期间的 Binlog 已经被数据库清理掉，作业就找不到断点位置，只能重新做全量快照。</p>
</li>
<li class="">
<p>下游要能处理 UPDATE 和 DELETE。CDC 流是一条包含回撤语义的变更流，如果 Sink 端（比如只支持追加写入的存储）消化不了删除和更新事件，链路设计上就要额外处理。</p>
</li>
</ol>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p>CDC 作业本质上是一个长期运行的流作业，checkpoint 一定要配置好。没有 checkpoint 的 CDC 作业一旦失败,既丢断点又可能丢数据。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/05/05/flink#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>CDC 解决的是"数据库里的变化如何低延迟、低侵入地流出来"这个问题，基于日志的实现是目前的主流路线。Flink CDC 的价值在于把日志捕获（借助 Debezium）和流式计算（Flink 引擎本身）收拢到了一个框架里：Source 读变更、Sink 写目标、State 撑起有状态计算、Table API 降低使用门槛。对于想搭实时数仓或者做异构数据同步的团队，它是一个链路短、组件少、值得优先考虑的选项。</p>]]></content:encoded>
            <category>大数据</category>
        </item>
        <item>
            <title><![CDATA[Mysql 时间四舍五入]]></title>
            <link>https://tanzhuo.xyz/2023/05/05/mysql-shi-jian-si-she-wu-ru</link>
            <guid>https://tanzhuo.xyz/2023/05/05/mysql-shi-jian-si-she-wu-ru</guid>
            <pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[MySQL 的 DATETIME 默认只精确到秒，Java 传入的毫秒会被四舍五入，可能凭空多出一秒。记录原理、DATETIME(fsp) 与 Java 截断两种解法。]]></description>
            <content:encoded><![CDATA[<p>MySQL 的 DATETIME 默认精确到秒，而 Java 的 LocalDateTime 精确到纳秒。把带毫秒的时间写入 MySQL 时，超出列精度的部分会被四舍五入，存进去的时间可能凭空多出一秒。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="问题现象">问题现象<a href="https://tanzhuo.xyz/2023/05/05/mysql-shi-jian-si-she-wu-ru#%E9%97%AE%E9%A2%98%E7%8E%B0%E8%B1%A1" class="hash-link" aria-label="问题现象的直接链接" title="问题现象的直接链接" translate="no">​</a></h2>
<p>例如，Java 侧的时间是 <code>2022-01-01 12:34:56.789</code>，写入一个普通 DATETIME 列后，查出来变成了 <code>2022-01-01 12:34:57</code>——毫秒部分被四舍五入进位了。</p>
<p>这个偏差平时不起眼，但在几个场景下会暴露出来：</p>
<ol>
<li class="">
<p>单测里把实体存库再查出来，用 <code>assertEquals</code> 比较时间字段，偶发失败——只有毫秒大于等于 500 时才进位，所以问题时隐时现。</p>
</li>
<li class="">
<p>用创建时间做游标分页或范围查询，边界记录被多算或漏算一条。</p>
</li>
<li class="">
<p>日志里的时间和库里的时间差一秒，排查链路时对不上。</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="原理简述">原理简述<a href="https://tanzhuo.xyz/2023/05/05/mysql-shi-jian-si-she-wu-ru#%E5%8E%9F%E7%90%86%E7%AE%80%E8%BF%B0" class="hash-link" aria-label="原理简述的直接链接" title="原理简述的直接链接" translate="no">​</a></h2>
<p>MySQL 从 5.6.4 开始，DATETIME、TIMESTAMP、TIME 都支持小数秒，精度由类型定义里的 fsp（fractional seconds precision）决定，取值 0 到 6，默认为 0，也就是只到秒。</p>
<p>关键行为是：当插入值的精度高于列精度时，MySQL 按 SQL 标准做四舍五入，而不是截断，且默认不产生任何告警。所以 <code>.789</code> 会把秒位进上去，而 <code>.400</code> 则被静默丢弃——两种结果都和 Java 内存里的值不一致。</p>
<p>Java 这边，<code>LocalDateTime</code> 内部以纳秒保存时间，JDBC 驱动写库时会把小数秒一并传给服务端，于是舍入就发生在 MySQL 一侧。<code>java.sql.Timestamp</code> 同样是纳秒精度，只把 Java 类型换成它并不能避免数据库端的舍入，问题的根子在列的精度定义上。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="解决方案">解决方案<a href="https://tanzhuo.xyz/2023/05/05/mysql-shi-jian-si-she-wu-ru#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88" class="hash-link" aria-label="解决方案的直接链接" title="解决方案的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-提高列精度">1) 提高列精度<a href="https://tanzhuo.xyz/2023/05/05/mysql-shi-jian-si-she-wu-ru#1-%E6%8F%90%E9%AB%98%E5%88%97%E7%B2%BE%E5%BA%A6" class="hash-link" aria-label="1) 提高列精度的直接链接" title="1) 提高列精度的直接链接" translate="no">​</a></h3>
<p>如果业务需要毫秒精度（绝大多数记录创建、更新时间的场景够用），直接把列定义成 <code>DATETIME(3)</code>：</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token keyword" style="color:hsl(286, 60%, 67%)">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">TABLE</span><span class="token plain"> t_order</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">MODIFY</span><span class="token plain"> create_time </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">DATETIME</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token number" style="color:hsl(29, 54%, 61%)">3</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">NOT</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(29, 54%, 61%)">NULL</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><br></div></code></pre></div></div>
<p>需要微秒就用 <code>DATETIME(6)</code>。注意配套的默认值函数也要带精度，<code>NOW()</code> 只到秒，要写 <code>NOW(3)</code> 或 <code>CURRENT_TIMESTAMP(3)</code>，否则默认值本身就没有小数秒。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-在-java-侧提前截断">2) 在 Java 侧提前截断<a href="https://tanzhuo.xyz/2023/05/05/mysql-shi-jian-si-she-wu-ru#2-%E5%9C%A8-java-%E4%BE%A7%E6%8F%90%E5%89%8D%E6%88%AA%E6%96%AD" class="hash-link" aria-label="2) 在 Java 侧提前截断的直接链接" title="2) 在 Java 侧提前截断的直接链接" translate="no">​</a></h3>
<p>如果不想动表结构，就在写库前把精度对齐到秒：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">// 丢弃秒以下的部分，和 DATETIME(0) 的精度对齐</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token class-name" style="color:hsl(29, 54%, 61%)">LocalDateTime</span><span class="token plain"> now </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(29, 54%, 61%)">LocalDateTime</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">.</span><span class="token function" style="color:hsl(207, 82%, 66%)">now</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">.</span><span class="token function" style="color:hsl(207, 82%, 66%)">truncatedTo</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token class-name" style="color:hsl(29, 54%, 61%)">ChronoUnit</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">.</span><span class="token constant" style="color:hsl(29, 54%, 61%)">SECONDS</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><br></div></code></pre></div></div>
<p>这样内存里的值和落库的值完全一致，比较、断言都不会再有偏差。公共的实体基类或审计字段填充器里统一处理一次即可。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/05/05/mysql-shi-jian-si-she-wu-ru#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class="">舍入是"进位"而不是"抹零"。很多人下意识以为多余精度会被截掉，实际是四舍五入，这正是"时间多一秒"这类怪问题的来源。MySQL 8.0 提供了 <code>TIME_TRUNCATE_FRACTIONAL</code> 这个 sql_mode，可以把行为改成截断，但它影响全局行为，改之前要评估存量业务。</li>
<li class="">改成 <code>DATETIME(3)</code> 后，存量数据的小数秒都是 <code>.000</code>，新旧数据混合排序时注意这一点。</li>
<li class="">fsp 越高占用的存储越多，按需选择即可，没必要一律用 <code>DATETIME(6)</code>。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/05/05/mysql-shi-jian-si-she-wu-ru#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>时间"多一秒"的本质是精度不匹配：Java 传纳秒，MySQL 列只存到秒，多余部分被四舍五入。两条路解决——要么把列改成 <code>DATETIME(3)</code>/<code>DATETIME(6)</code> 让数据库存得下，要么在 Java 侧 <code>truncatedTo(ChronoUnit.SECONDS)</code> 提前对齐。原则只有一条：让内存里的精度和列定义的精度一致，写进去的就是读出来的。</p>]]></content:encoded>
            <category>DB</category>
        </item>
        <item>
            <title><![CDATA[数据湖 Iceberg]]></title>
            <link>https://tanzhuo.xyz/2023/05/05/shu-ju-hu</link>
            <guid>https://tanzhuo.xyz/2023/05/05/shu-ju-hu</guid>
            <pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[整理数据湖与数据仓库的差异，介绍开源表格式 Apache Iceberg 的定位与核心能力：版本控制、元数据管理、分区管理与快照，并记录一些选型时的注意点。]]></description>
            <content:encoded><![CDATA[<p>目前比较流行的开源数据湖 Iceberg。</p>
<!-- -->
<p>最近在梳理大数据存储方案时，绕不开"数据湖"这个词。业务数据早已不只是关系型数据库里的表：埋点日志、JSON 报文、图片音视频，各种形态的数据都要有个地方落。传统数仓在这类场景下越来越吃力，而围绕数据湖的一批开源项目正好补上了这个缺口。这篇就把数据湖与数仓的区别、以及其中比较有代表性的 Iceberg 简单整理一下，作为自己的学习笔记。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据湖与数据仓库">数据湖与数据仓库<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-hu#%E6%95%B0%E6%8D%AE%E6%B9%96%E4%B8%8E%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93" class="hash-link" aria-label="数据湖与数据仓库的直接链接" title="数据湖与数据仓库的直接链接" translate="no">​</a></h2>
<p>数据湖是一个用于存储结构化和非结构化数据的集中式数据存储库，它可以存储各种类型的数据，包括传统的关系型数据、半结构化数据、非结构化数据等。数据湖的主要特点是具有高度的灵活性和可扩展性，能够方便地进行数据的存储和处理。</p>
<p>数据湖的一个核心理念是"先存后管"（schema-on-read）：数据以原始格式写入廉价的分布式存储（如 HDFS 或对象存储），到真正分析时才去解析结构。这和数仓"先建模后入库"（schema-on-write）的思路正好相反，也是它能兼容各种数据形态的原因。</p>
<p>相比之下，传统的数据仓库主要用于存储结构化数据，它们通常采用关系型数据库进行存储和管理。数据仓库的主要特点是具有高度的规范化和结构化，能够保证数据的准确性和一致性。但是，数据仓库的缺点在于它们不够灵活，无法存储和处理非结构化或半结构化数据，而且难以进行扩展和升级。</p>
<p>数据湖相比数据仓库的优势主要有以下几点：</p>
<ol>
<li class="">存储不受限制：数据湖可以存储各种类型的数据，包括传统的结构化数据、半结构化数据、非结构化数据等。这使得数据湖更加灵活，适用于不同类型和规模的数据存储需求。</li>
<li class="">处理速度更快：数据湖通常采用分布式存储和计算技术，能够实现高速数据处理和分析。而传统的数据仓库则往往需要进行多次数据转换和计算，导致处理速度较慢。</li>
<li class="">成本更低：数据湖通常采用开源技术，如Hadoop、Spark等，成本相对较低，而传统的数据仓库则需要采用商业数据库软件，成本较高。</li>
<li class="">更加灵活：数据湖具有更高的灵活性，能够方便地进行数据的存储和处理。而传统的数据仓库则往往需要进行多次数据转换和计算，导致数据处理过程变得复杂和困难。</li>
</ol>
<p>当然，灵活是有代价的。裸的数据湖只是一堆文件目录，缺少事务、缺少模式约束，写入方和读取方稍不注意就会互相踩踏，时间一长很容易变成"数据沼泽"。表格式（table format）这一层抽象就是为了解决这个问题——在文件之上定义出"表"的语义，Iceberg 正是这一类项目。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="apache-iceberg">Apache Iceberg<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-hu#apache-iceberg" class="hash-link" aria-label="Apache Iceberg的直接链接" title="Apache Iceberg的直接链接" translate="no">​</a></h2>
<p><a href="http://iceberg.incubator.apache.org/" target="_blank" rel="noopener noreferrer" class="">Apache Iceberg</a></p>
<p>Iceberg是一个开源的数据表格式和处理库，它旨在解决数据湖中数据表的管理和处理问题。Iceberg是由Netflix开发的，目前已经成为Apache软件基金会的顶级项目之一。</p>
<p>Iceberg解决了传统数据湖中的一些问题，例如数据表的版本控制、数据表的元数据管理、数据分区管理、数据表的快照管理等。Iceberg支持多种数据存储和处理系统，包括Hadoop、Spark、Presto、Flink等。</p>
<p>这里值得展开说一下它的工作机制。Iceberg 本身不存数据，数据仍然是 Parquet、ORC 这类列存文件；它管理的是数据之上的元数据层。元数据大致分三级：</p>
<ol>
<li class="">快照（snapshot）：表在某一时刻的完整状态，每次写入提交都会生成一个新快照，旧快照依然可读。</li>
<li class="">清单列表与清单文件（manifest list / manifest）：记录快照包含哪些数据文件，以及每个文件的分区范围、行数、列级统计等信息。</li>
<li class="">数据文件：真正的列存文件，元数据只引用、不修改它们。</li>
</ol>
<p>写入时新元数据先落盘，最后通过一次对表指针的原子替换完成提交，这就让文件系统之上有了类似数据库的 ACID 语义。查询时引擎先读元数据，利用其中的统计信息跳过无关文件，而不必像 Hive 那样去 List 整个目录——在对象存储上，这一点对性能影响很大。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="iceberg-的主要特点">Iceberg 的主要特点<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-hu#iceberg-%E7%9A%84%E4%B8%BB%E8%A6%81%E7%89%B9%E7%82%B9" class="hash-link" aria-label="Iceberg 的主要特点的直接链接" title="Iceberg 的主要特点的直接链接" translate="no">​</a></h2>
<p>Iceberg 的主要特点有：</p>
<ol>
<li class="">数据表版本控制：Iceberg支持数据表版本控制，可以对数据表进行版本管理和控制。用户可以轻松地回滚到历史版本，或者将不同版本的数据表进行合并。</li>
<li class="">数据表元数据管理：Iceberg提供了一种元数据管理机制，可以方便地对数据表进行管理和查询。用户可以通过元数据来了解数据表的结构、分区、数据统计等信息。</li>
<li class="">数据分区管理：Iceberg支持多种分区方式，包括哈希分区、范围分区、哈希范围混合分区等。用户可以根据自己的需求选择不同的分区方式。</li>
<li class="">数据表快照管理：Iceberg支持对数据表进行快照管理，可以轻松地进行数据备份和恢复。</li>
</ol>
<p>分区这一点还有个细节：Iceberg 的分区是"隐藏分区"。分区值由列值通过转换函数（比如按天截断时间戳）自动推导，查询时直接用原始列写过滤条件即可，引擎会自动利用分区裁剪。写查询的人不需要知道表是怎么分区的，这比 Hive 里手动维护分区列的方式友好不少。分区规则本身也可以演进，改规则不需要重写历史数据。</p>
<p>模式演进（schema evolution）同样是它的强项：加列、删列、改名都只改元数据，列通过唯一 ID 而不是名字或位置来跟踪，避免了老式表格式里改个列名就可能读错数据的问题。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-hu#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ol>
<li class="">小文件问题依然存在。流式写入频率高时会产生大量小数据文件和元数据文件，需要定期跑合并（compaction），否则查询性能会持续劣化。</li>
<li class="">快照不会自动清理。每次提交都留一个快照，历史快照引用的文件不会被删除，长期不做快照过期处理，存储会越积越多。</li>
<li class="">Catalog 选型要先想清楚。Iceberg 的表指针需要一个 catalog 来管理（Hive Metastore、JDBC 等都可以），不同引擎要访问同一张表，得共用同一套 catalog，迁移起来比较麻烦。</li>
<li class="">同类项目还有 Delta Lake 和 Apache Hudi，三者定位相近但生态侧重不同，选型前最好结合自己主用的计算引擎做对比，不必默认跟风。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-hu#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>数据湖解决的是"什么数据都能存"的问题，但只有存储没有管理，湖很容易变成沼泽。Iceberg 这类表格式在文件之上补齐了事务、版本、分区和模式管理，让数据湖具备了接近数仓的表语义，同时保留了开放格式和低成本的优势。对我来说，理解它的关键在于那三层元数据结构——想明白快照和清单文件是怎么组织的，回滚、时间旅行、分区裁剪这些能力也就顺理成章了。后面有机会再单独记录在 Flink / Spark 上实际读写 Iceberg 表的过程。</p>]]></content:encoded>
            <category>大数据</category>
        </item>
        <item>
            <title><![CDATA[数据结构：栈]]></title>
            <link>https://tanzhuo.xyz/2023/05/05/shu-ju-jie-gou</link>
            <guid>https://tanzhuo.xyz/2023/05/05/shu-ju-jie-gou</guid>
            <pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[栈是一种先进后出(LIFO)的数据结构,只允许在栈顶插入和删除,可用数组或链表实现。本文梳理栈的基本操作、复杂度,以及它在函数调用、表达式求值、编译器和操作系统中的应用。]]></description>
            <content:encoded><![CDATA[<p>栈(Stack)是一种先进后出(LIFO)的数据结构，它只允许在栈顶进行插入和删除操作。栈可以用数组或链表来实现。</p>
<!-- -->
<p>之所以想单独写一篇栈，是因为它几乎是最简单的数据结构，却又无处不在：函数调用、异常堆栈、浏览器的后退按钮、编辑器的撤销操作，背后都是同一个模型。很多看起来复杂的问题(比如括号匹配、表达式求值)，一旦想到用栈，思路立刻就清晰了。理解栈，也是理解递归和调用栈溢出这类问题的前提。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="栈的基本概念">栈的基本概念<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-jie-gou#%E6%A0%88%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="栈的基本概念的直接链接" title="栈的基本概念的直接链接" translate="no">​</a></h2>
<p>在栈中，插入和删除操作通常称为入栈(push)和出栈(pop)。当插入一个元素时，它被放置在栈顶，当删除一个元素时，它是从栈顶删除的。栈顶是栈中最新添加的元素，栈底是栈中最早添加的元素。</p>
<p>可以把栈想象成一摞盘子：新盘子只能放在最上面，取盘子也只能从最上面取。这个"只开一个口"的约束正是栈的价值所在——它天然记录了"谁最后进来"，所以特别适合处理需要"原路返回"的场景。</p>
<p>栈的应用非常广泛，例如，计算机中的函数调用和递归调用都是通过栈来实现的。当一个函数被调用时，它的参数、返回地址和局部变量等信息被压入栈中，当函数返回时，这些信息又从栈中弹出。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="基本操作">基本操作<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-jie-gou#%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C" class="hash-link" aria-label="基本操作的直接链接" title="基本操作的直接链接" translate="no">​</a></h2>
<p>以下是栈的基本操作：</p>
<p>push(element)：将一个元素压入栈顶。</p>
<p>pop()：从栈顶弹出一个元素。</p>
<p>top()：返回栈顶元素，但不对栈进行修改。</p>
<p>isEmpty()：判断栈是否为空。</p>
<p>size()：返回栈中元素的个数。</p>
<p>注意 pop 和 top 的区别：pop 会移除栈顶元素并返回它，top(有些实现里叫 peek)只是"看一眼"，栈本身不变。写代码时混用这两个操作是常见的 bug 来源。</p>
<p>用数组实现栈非常直接，只需要维护一个指向栈顶的下标：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">// 基于数组的简单栈实现，仅演示核心逻辑</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">public</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(29, 54%, 61%)">ArrayStack</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">private</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">int</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"> data</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">private</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">int</span><span class="token plain"> top </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">-</span><span class="token number" style="color:hsl(29, 54%, 61%)">1</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"> </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 栈顶下标，-1 表示空栈</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">public</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(29, 54%, 61%)">ArrayStack</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token keyword" style="color:hsl(286, 60%, 67%)">int</span><span class="token plain"> capacity</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        data </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">new</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">int</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">capacity</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">public</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">void</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">push</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token keyword" style="color:hsl(286, 60%, 67%)">int</span><span class="token plain"> element</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 实际使用时这里应做扩容或抛出栈满异常</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        data</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token operator" style="color:hsl(207, 82%, 66%)">++</span><span class="token plain">top</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> element</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"> </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 先移动栈顶指针，再写入</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">public</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">int</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">pop</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 空栈时应抛出异常，这里省略检查</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">return</span><span class="token plain"> data</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">top</span><span class="token operator" style="color:hsl(207, 82%, 66%)">--</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"> </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 返回栈顶元素，同时回退指针</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">public</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">int</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">top</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">return</span><span class="token plain"> data</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">top</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"> </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 只读取，不修改栈</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">public</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">boolean</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">isEmpty</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">return</span><span class="token plain"> top </span><span class="token operator" style="color:hsl(207, 82%, 66%)">==</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">-</span><span class="token number" style="color:hsl(29, 54%, 61%)">1</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">public</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">int</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">size</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">return</span><span class="token plain"> top </span><span class="token operator" style="color:hsl(207, 82%, 66%)">+</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">1</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><br></div></code></pre></div></div>
<p>链表实现则是把链表头当作栈顶，push 就是头插，pop 就是删除头节点，好处是不需要预先分配容量。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="复杂度分析">复杂度分析<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-jie-gou#%E5%A4%8D%E6%9D%82%E5%BA%A6%E5%88%86%E6%9E%90" class="hash-link" aria-label="复杂度分析的直接链接" title="复杂度分析的直接链接" translate="no">​</a></h2>
<p>栈的时间复杂度为O(1)，因为所有操作都是在栈顶进行的。但是，栈的空间复杂度为O(n)，因为需要存储所有元素。</p>
<p>换个角度说：栈的高效恰恰来自它的限制。因为不允许在中间位置插入或删除，所有操作都退化成对栈顶的一次读写，不需要移动其他元素，也不需要遍历。数组实现中唯一的例外是扩容时的搬迁，但均摊下来入栈仍然是常数时间。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="栈的典型应用">栈的典型应用<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-jie-gou#%E6%A0%88%E7%9A%84%E5%85%B8%E5%9E%8B%E5%BA%94%E7%94%A8" class="hash-link" aria-label="栈的典型应用的直接链接" title="栈的典型应用的直接链接" translate="no">​</a></h2>
<p>在计算机中，栈(Stack)被广泛应用于函数调用、表达式求值、编译器、操作系统等领域。</p>
<ol>
<li class="">函数调用：当一个函数被调用时，它的参数、返回地址和局部变量等信息被压入栈中，当函数返回时，这些信息又从栈中弹出。这个过程被称为函数调用栈，它是实现函数调用的基础。</li>
<li class="">表达式求值：当计算机对一个表达式进行求值时，通常使用栈来实现。例如，将中缀表达式转换成后缀表达式时，需要使用栈来存储运算符，以便正确计算表达式的值。</li>
<li class="">编译器：编译器将源代码转换成目标代码的过程中，使用栈来实现语法分析和代码生成等功能。例如，在编译器中，使用栈来存储变量、函数、语句等信息。</li>
<li class="">操作系统：操作系统中的进程调度、中断处理等功能也需要使用栈来实现。例如，当一个进程被中断时，操作系统会将当前进程的上下文信息（寄存器的值、程序计数器的值等）压入栈中，然后执行中断处理程序。当中断处理程序执行完毕后，操作系统会从栈中弹出上下文信息，恢复当前进程的执行。</li>
</ol>
<p>这四个场景有一个共同点：都存在"进入—处理—按相反顺序退出"的嵌套结构。函数嵌套调用、括号嵌套、中断嵌套，本质上都是同一类问题，所以都落在栈这个模型上。平时刷题遇到括号匹配、单调栈、深度优先遍历的非递归写法，也都是这个思路的延伸。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-jie-gou#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ol>
<li class="">
<p>空栈操作：对空栈执行 pop 或 top 是未定义行为或直接抛异常，调用前要么先用 isEmpty 判断，要么明确依赖异常处理，不能想当然。</p>
</li>
<li class="">
<p>栈溢出：递归本质上是在消耗调用栈，递归层数过深会导致栈溢出(Java 中就是 StackOverflowError)。遇到深度不可控的递归，可以改写成显式栈加循环的迭代版本，把数据挪到堆上。</p>
</li>
</ol>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>Java 中不建议再使用 java.util.Stack——它继承自 Vector，方法带同步开销且设计陈旧。官方文档推荐用 ArrayDeque 来充当栈。</p></div></div>
<ol start="3">
<li class="">pop 与 top 混淆：只想读栈顶却调用了 pop，会悄悄丢掉元素，这类 bug 在循环里尤其难排查。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/05/05/shu-ju-jie-gou#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>栈是"约束换效率"的典型例子：只开放栈顶一个操作口，换来所有操作 O(1) 的代价，同时天然契合一切嵌套、回溯类的问题。掌握了 push/pop/top 这几个操作和 LIFO 的心智模型，再去看函数调用栈、表达式求值这些机制，就只是同一个模型在不同层面的重复应用。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[常见 Ingress 对比]]></title>
            <link>https://tanzhuo.xyz/2023/05/03/ingress</link>
            <guid>https://tanzhuo.xyz/2023/05/03/ingress</guid>
            <pubDate>Wed, 03 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[对比 k8s 集群中常用的 Ingress 控制器,梳理 Ingress 的工作原理,以及如何暴露 TCP/UDP 四层服务。]]></description>
            <content:encoded><![CDATA[<p>本次来对比一下k8s集群中经常使用到的一些 ingress 组件，ingress 它充当了集群中 HTTP 和 HTTPS 流量的入口点。Ingress 可以将流量路由到 Kubernetes 集群内的不同 Service 上，从而实现负载均衡和流量控制的功能。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么要关注-ingress">为什么要关注 Ingress<a href="https://tanzhuo.xyz/2023/05/03/ingress#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%85%B3%E6%B3%A8-ingress" class="hash-link" aria-label="为什么要关注 Ingress的直接链接" title="为什么要关注 Ingress的直接链接" translate="no">​</a></h2>
<p>集群内的服务默认只能在集群内部互相访问。想把服务暴露到外部,最直接的办法是 NodePort 或 LoadBalancer 类型的 Service,但这两种方式各有局限:NodePort 端口范围受限、不好管理域名和路径;LoadBalancer 则每暴露一个服务就要占用一个负载均衡器,在云上意味着实打实的费用。</p>
<p>Ingress 解决的正是这个问题:用一个统一的入口承接所有外部 HTTP/HTTPS 流量,再按域名和路径分发到集群内的各个 Service。选哪个 Ingress 控制器,直接决定了后续路由配置、TLS 管理、四层暴露这些日常运维工作的体验,所以值得在选型阶段就把几个主流方案摆在一起比较。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="工作原理">工作原理<a href="https://tanzhuo.xyz/2023/05/03/ingress#%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86" class="hash-link" aria-label="工作原理的直接链接" title="工作原理的直接链接" translate="no">​</a></h2>
<p>Ingress 的工作原理是通过将 HTTP 和 HTTPS 请求映射到 Service 上来实现的。在 Kubernetes 中，Service 是一种用于将流量路由到 Pod 的资源对象。当 Ingress 接收到来自外部的 HTTP 或 HTTPS 请求时，它会根据请求中的主机名和路径信息，在 Ingress 规则中进行匹配，并将请求路由到相应的 Service 上。</p>
<p>这里有一个容易混淆的点:Ingress 资源本身只是一份声明式的路由规则,写进去并不会自动生效。真正干活的是 Ingress 控制器——它以 Pod 的形式跑在集群里,持续 watch API Server 上的 Ingress、Service、Endpoint 等资源变化,把这些规则翻译成自己底层代理(比如 Nginx、Envoy、Traefik 自带的代理)的配置,然后热加载生效。也就是说:</p>
<ol>
<li class="">Ingress 资源:声明"哪个域名的哪个路径转发到哪个 Service";</li>
<li class="">Ingress 控制器:监听规则变化,生成并加载代理配置;</li>
<li class="">底层代理:实际接收外部请求并做转发。</li>
</ol>
<p>请求匹配时,控制器先按 Host 匹配虚拟主机,再按 Path 匹配路由规则,最后把请求转发到对应 Service 背后的 Pod。TLS 证书通常以 Secret 的形式挂到 Ingress 规则上,由控制器负责在入口处完成卸载。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常见-ingress-控制器">常见 Ingress 控制器<a href="https://tanzhuo.xyz/2023/05/03/ingress#%E5%B8%B8%E8%A7%81-ingress-%E6%8E%A7%E5%88%B6%E5%99%A8" class="hash-link" aria-label="常见 Ingress 控制器的直接链接" title="常见 Ingress 控制器的直接链接" translate="no">​</a></h2>
<p>要使用 Ingress，需要先安装 Ingress 控制器。Kubernetes 并没有提供默认的 Ingress 控制器，但有许多第三方的 Ingress 控制器可供选择：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2023-05-image-bc5d955ed94dca937cb7cdc2516b29d2.webp" width="1518" height="718" class="img_ev3q"></p>
<p>简单说说几个常见方案的取舍:</p>
<ol>
<li class="">Nginx Ingress Controller:社区最常见的选择,基于 Nginx 做反向代理,文档和案例丰富,大量配置通过 annotation 完成,适合作为默认选项;</li>
<li class="">Traefik:自带 Dashboard,配置动态生效,不需要像 Nginx 那样 reload,对接 Let's Encrypt 自动签发证书也比较顺手;</li>
<li class="">Istio Ingress Gateway:如果集群已经上了 Istio 服务网格,用它的 Gateway 做入口可以和网格内的流量治理(灰度、熔断、遥测)打通,但单独为了做入口而引入 Istio 就太重了;</li>
<li class="">其他如 HAProxy、Kong 等,分别在性能和 API 网关能力上有自己的侧重。</li>
</ol>
<p>选型思路可以很朴素:没有特殊需求就用 Nginx Ingress;想要动态配置和更现代的运维体验可以看 Traefik;已经在用服务网格就顺势用网格自带的 Gateway。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="暴露四层服务">暴露四层服务<a href="https://tanzhuo.xyz/2023/05/03/ingress#%E6%9A%B4%E9%9C%B2%E5%9B%9B%E5%B1%82%E6%9C%8D%E5%8A%A1" class="hash-link" aria-label="暴露四层服务的直接链接" title="暴露四层服务的直接链接" translate="no">​</a></h2>
<p>其中，我们会经常想要暴露一些服务到集群外部提供访问，不仅仅是HTTP的7层协议，也有一些基于4层协议，例如Mysql，Redis，Mongdb等服务。</p>
<p>但 Kubernetes 的 Ingress 规范本身只支持 HTTP 和 HTTPS 协议，因此默认的 Ingress 控制器也只支持这两种协议。但是，一些第三方的 Ingress 控制器可以通过扩展来支持 TCP 和 UDP 协议的暴露。</p>
<ol>
<li class="">Traefik：Traefik 支持 TCP 协议的暴露，可以通过在 Ingress 规则中指定 <code>traefik.tcp.routers</code> 和 <code>traefik.tcp.services</code> 来配置 TCP 服务和路由。</li>
<li class="">Istio：Istio 是一个服务网格框架，它可以通过其 Ingress Gateway 组件支持 TCP 协议的暴露。可以通过在 Ingress Gateway 中定义 VirtualService 来配置 TCP 服务和路由。</li>
</ol>
<p>补充一点,Nginx Ingress Controller 也能暴露 TCP/UDP 服务,做法是维护 <code>tcp-services</code> 和 <code>udp-services</code> 两个 ConfigMap,把"对外端口 → 命名空间/Service:端口"的映射写进去,再在控制器的 Service 上放开对应端口。这不走 Ingress 资源,而是控制器自己的扩展机制。</p>
<p>四层暴露和七层有个本质区别:TCP 层没有 Host 和 Path 的概念,无法像 HTTP 那样靠域名区分后端,只能靠端口号区分服务。所以每暴露一个 TCP 服务就要占用入口的一个端口,服务多了之后端口管理会变得琐碎。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2023/05/03/ingress#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ol>
<li class="">装了 Ingress 资源却不生效,多半是集群里根本没装控制器,或者 Ingress 上的 <code>ingressClassName</code> 和控制器不匹配——集群里跑多个控制器时尤其容易踩这个坑;</li>
<li class="">数据库类服务(MySQL、Redis 等)通过 Ingress 暴露到公网要慎重,四层转发不带任何认证加固,更稳妥的方式是走内网、VPN 或跳板机;</li>
<li class="">不同控制器的 annotation 互不通用,从 Nginx 迁到 Traefik 时,原来靠 annotation 实现的重写、限流、超时配置都要逐条翻译;</li>
<li class="">长连接类的四层服务要留意代理的空闲超时配置,默认值往往比数据库客户端的连接池预期短,容易出现连接被中途掐断的现象。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2023/05/03/ingress#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Ingress 是集群南北向流量的统一入口,规则由 Ingress 资源声明,实际转发由控制器完成。七层场景下几个主流控制器都能胜任,差异主要在配置方式和生态;四层暴露则超出了 Ingress 规范本身,需要依赖 Traefik、Istio Gateway 或 Nginx Ingress 的 ConfigMap 这类各家自己的扩展机制。选型时结合团队已有技术栈和运维习惯来定,比追求功能列表更实际。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[初探 Server-Mesh]]></title>
            <link>https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh</link>
            <guid>https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh</guid>
            <pubDate>Wed, 14 Dec 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[从微服务的局限性谈起，梳理 Service Mesh 的 Sidecar 实现思路与 Istio 等主流方案，再到基于服务网格发展出的 Knative 与 Serverless。]]></description>
            <content:encoded><![CDATA[<p><strong>本次技术分享只做技术宏观思维与认知上的分享，具体配置细节与部署不在此次讨论范围。</strong></p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>本次讨论围绕三个点展开：</p>
<ol>
<li class="">微服务的局限性</li>
<li class="">了解 Server-Mesh 的功能与实现方式</li>
<li class="">目前基于 Server-Mesh 而实现的 Serverless</li>
</ol>
<p>我们最熟悉、最常用的 Java、Spring-Cloud、Spring-Cloud-Alibaba 等技术体系，归属于传统微服务的架构设计。此设计存在 SDK 强耦合问题，每次底层 SDK 版本的更新极大可能影响到业务层面的服务稳定性。SDK 与代码环境强耦合，就会存在诸多限制，不能完整地运用市面上不同语言与其对应的生态组件来做更擅长的领域，例如：灵活快速的 Go、Python 语言及其对应的协程、爬虫、神经网络等组件；严谨并且没有垃圾回收开销的 Rust 语言，更适合企业级中间件高效率低内存占用场景。所以每种语言有它各自的优势与生态环境，以更长远的发展来看，大体量的龙头企业将会把业务部署与实现落地到不同的语言上进行迭代是一种趋势。</p>
<p>传统微服务体系图：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-12-9fdf013b22e8458233744cad658d63a2-18ebc7a7b51196340270f5c789e43fc9.webp" width="479" height="422" class="img_ev3q"></p>
<p>并且如今 JVM 引以为傲的跨操作系统优势，其特性也被 K8S、Docker、Containerd、CRI-O、Kata 等容器技术与生态体系取代。垃圾回收机制、动态代理、Agent、ZGC 等高级特性也被其他语言借鉴。目前来看支撑 Java 语言地位的是长年积累下来的生态体系（Apache、Spring、Ali、Huawei 等开源社区注入新的中间件，例如知名的 Kafka、Hadoop、Hbase、RocketMQ、Flink、Seata 等等，以及后续继续推出的中间件来保持语言生态的活跃）。但随着时间的推移，长远看来（20-30 年以上，主观预言，仅供参考），这些优势将可能被后起之秀所占据，下一代微服务体系势必将会在各类语言优势的基础上进行迭代更新。</p>
<p>个人思考：Java 已被 Oracle 收购，但 Oracle 并不是技术激进型企业，后续很有可能会限制住 Java 的发展。虽然有开源版本，但开源力量不如企业集中。目前 IT 界大佬 Google 的 Go 语言已经迅速崛起，并已占据 Java 部分市场，这可能也是字节、B站、腾讯、百度、京东、小米等企业选择它的原因之一。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="服务网格">服务网格<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E6%9C%8D%E5%8A%A1%E7%BD%91%E6%A0%BC" class="hash-link" aria-label="服务网格的直接链接" title="服务网格的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="简介">简介<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E7%AE%80%E4%BB%8B" class="hash-link" aria-label="简介的直接链接" title="简介的直接链接" translate="no">​</a></h3>
<p>回到我们关注的 Server-Mesh（服务网格），其目前主要的实现思路是 Sidecar（边车模式）。K8S 中我们用 Pod 来做基本部署单元，Sidecar 则是在一个 Pod 中部署 Proxy 容器，Proxy 容器与业务 Server 容器部署在同一个 Pod 中（Pod 容器注入），并且 Proxy 容器将会代理掉 Server 容器的所有流量。</p>
<p>Istio 实现 Server-Mesh 架构宏观图：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-12-image-50-29cab4357ba6d65501863969da0b1a2a.webp" width="1057" height="715" class="img_ev3q"></p>
<p><strong>citadel：</strong> 负责身份认证和证书管理的核心安全组件。</p>
<p><strong>galley：</strong> 负责配置的管理组件，如验证配置文件格式和内容的正确性，并将这些配置信息提供给 pilot 和 mixer。</p>
<p><strong>pilot：</strong> 控制中枢，包含了服务发现和规则转化与下发。</p>
<p><strong>proxy：</strong> 由 C++ 开发的 Envoy 与 Pilot-agent 实现，提供了动态服务发现、负载均衡、TLS、熔断、健康检查、流量拆分、灰度等功能，另外生成遥测数据，为微服务提供可观测能力。</p>
<p><strong>Ingressgateway：</strong> 入口处的 gateway，即网格外访问网格内的服务就是通过这个 gateway 进行的。</p>
<p>通过 Proxy 容器，实现了业务代码与其 SDK 完全解耦，并且将获得许多功能特性：<strong>流量镜像、灰度发布、服务注册发现、远程调用、流量熔断、降级、链路追踪、平面控制、内网数据加密传输（分布式事务目前没有特别的解决方案，因为事务严格来说不属于服务治理层面）</strong>。并且对程序员来说完全透明，开发人员不需要了解其部署细节，几乎完全按单机版开发，没有语言限制，不依赖任何微服务 SDK。企业又可以选用不同的语言特性开发不同的功能模块（可能会带来更大的代码管理成本、沟通成本，团队掌握的语言更加广泛）。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="官方-demo">官方 Demo<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E5%AE%98%E6%96%B9-demo" class="hash-link" aria-label="官方 Demo的直接链接" title="官方 Demo的直接链接" translate="no">​</a></h3>
<p>官方 Demo Book-Info 微服务，可通过 Helm 一键部署到 K8S 环境，即可体验其特性（Book-Info 架构图）：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-12-noistio-6757dcc885fcdd61bb4011d88efc5dd7.svg" width="662" height="453" class="img_ev3q"></p>
<p>Bookinfo 微服务使用了 Istio 进行边车部署，使用了 4 种不同的语言程序，并且 Java 语言开发的 Reviews 服务提供了 3 个不同版本的应用，可以很好地体现出 Istio 在多版本控制、流量管理、流量迁移、整合等多方面的能力。大家可以自己去部署体验一下，可以很直观地感受到 Service-Mesh 的功能之强大、配置之灵活（都是秒级生效的配置，无感知流量迁移）。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="市面上最常见实现">市面上最常见实现<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E5%B8%82%E9%9D%A2%E4%B8%8A%E6%9C%80%E5%B8%B8%E8%A7%81%E5%AE%9E%E7%8E%B0" class="hash-link" aria-label="市面上最常见实现的直接链接" title="市面上最常见实现的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="istio">Istio<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#istio" class="hash-link" aria-label="Istio的直接链接" title="Istio的直接链接" translate="no">​</a></h3>
<p>Istio 是由 Google、IBM 和 Lyft 发起的开源 Service Mesh 框架。该项目在 2017 年推出，并在 2018 年 7 月发布了 1.0 版本，截至目前 2022-12-06 版本为 1.16.0。Istio 是 Service Mesh 目前实现的典型代表，如果说 Sidecar 是整个 Service Mesh 的数据面，那么 Istio 主要在控制面上做了更多的改进。Istio 使用 Envoy 作为 Sidecar，控制面相关全部使用 Golang 编写，性能上有了很大的提升。Istio 首先是一个服务网格，但又不仅仅是服务网格：在 Linkerd、Envoy 这样的典型服务网格之上，Istio 提供了一个完整的解决方案，为整个服务网格提供行为洞察和操作控制，以满足微服务应用程序的多样化需求。</p>
<p><a href="https://istio.io/" target="_blank" rel="noopener noreferrer" class="">Istio</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="linkerd2">Linkerd2<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#linkerd2" class="hash-link" aria-label="Linkerd2的直接链接" title="Linkerd2的直接链接" translate="no">​</a></h3>
<p>Linkerd 是 Buoyant 公司 2016 年率先开源的高性能网络代理，是业界的第一款 Service Mesh 框架，主要用于解决分布式环境中服务之间通信面临的一些问题，如网络不可靠、不安全、延迟丢包等。Linkerd 最初使用 Scala 语言编写，其版本 Linkerd2 使用 Go 与 Rust 重构。</p>
<p><a href="https://linkerd.io/" target="_blank" rel="noopener noreferrer" class="">The world’s lightest, fastest service mesh.</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="conduit">Conduit<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#conduit" class="hash-link" aria-label="Conduit的直接链接" title="Conduit的直接链接" translate="no">​</a></h3>
<p>Conduit 于 2017 年 12 月发布，是 Buoyant 继 Linkerd 后赞助的另外一个开源项目，作为 Linkerd 面向 Kubernetes 的独立版本。Conduit 旨在彻底简化用户在 Kubernetes 上使用服务网格的复杂度，提高用户体验，而不是像 Linkerd 一样针对各种平台进行优化。Conduit 的主要目标是轻量级、高性能、安全并且非常容易理解和使用。同 Linkerd 和 Istio 一样，Conduit 也包含数据平面和控制平面，其中数据平面由 Rust 语言开发，使得 Conduit 使用极少的内存资源，而控制平面由 Go 语言开发。</p>
<p><a href="https://buoyant.io/" target="_blank" rel="noopener noreferrer" class="">Buoyant. All of the service mesh. None of the service mess.</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="不同架构对比图">不同架构对比图<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E4%B8%8D%E5%90%8C%E6%9E%B6%E6%9E%84%E5%AF%B9%E6%AF%94%E5%9B%BE" class="hash-link" aria-label="不同架构对比图的直接链接" title="不同架构对比图的直接链接" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-12-075097e8-ce36-4d97-bba0-5864fa1f31c3-66b23389ce67b4293afd3eb03341d981.webp" width="1542" height="1352" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h3>
<p>Kubernetes 的出现已经解决了运维中容器部署、高可用、多副本、容器迁移、弹性扩容、滚动更新、镜像版本管理、服务探活、计算资源分配、计算节点监控等的大部分问题，拥有优秀的自动运维机制。但 K8S 在应用层 Service 容器上的监控与流量管理、容器之间的相互调用、监控、注册、配置、流量治理等功能并未提供。为了补充此功能模块则出现了 Server-Mesh 体系的技术，来扩展 K8S 在这方面的能力，使得微服务不再关注具体配置、环境细节，侧重关心业务功能实现，并且摆脱语言环境的限制。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="serverless">Serverless<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#serverless" class="hash-link" aria-label="Serverless的直接链接" title="Serverless的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="简介-1">简介<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E7%AE%80%E4%BB%8B-1" class="hash-link" aria-label="简介的直接链接" title="简介的直接链接" translate="no">​</a></h3>
<p>随着微服务理念不断深入人心，越来越多的企业把自身的应用逐步由单体转变成微服务架构，Container 容器技术的出现也加速了这个转移过程。虽然它有效地解决了众多服务运行环境差异问题，但是随着服务数目的增多，容器的编排与管理成为了新的问题。Kubernetes 的出现则解决了大规模微服务容器编排部署所带来的挑战，让整个行业意识到 PaaS 的落地能够成为现实。而当微服务体系下的容器数目越来越多，服务治理、流量管理成为必然要解决的问题，因而 Istio 出现了，基于网络代理与控制相分离的实现策略，允许对服务控制策略进行有效合理的管控。</p>
<p>架构的迭代到这里仿佛到了很美好的阶段：</p>
<ul>
<li class=""><strong>微服务</strong>：解决应用内聚、臃肿的问题。</li>
<li class=""><strong>Container</strong>：解决服务运行环境差异和部署问题。</li>
<li class=""><strong>Kubernetes</strong>：解决大量微容器编排管理、"聚合"部署问题。</li>
<li class=""><strong>Istio</strong>：解决服务上线面临的流量、发布、治理等一系列容器相关的问题。</li>
</ul>
<p>这个阶段乍一看，构建容器云仿佛有了一个完整的链路和解决方式，一切都将变得那么"完美"。</p>
<p>但让我们回过头来深入分析一下，微服务体系下的服务交互目前是否存在问题。首先，不管是 HTTP 还是 RPC，本质上都是服务与服务的远程调用，开发应用程序中无法做到服务与服务间的彼此透明。这会导致一个问题：不管微服务业务拆分多么"精细"，本质上业务单元之间仍然不能独立运行和迭代发展，没有彻底解耦；同时在面向不同开发领域衍生时，无法选择最合适的实现方式。所以我们希望可以基于不同的"模板"、"配置"把开发环境标准化处理，同时提供"事件"机制，将服务与服务交互的耦合度降到最低。其次是服务线上运行的动态伸缩问题：当下 Kubernetes 环境下的弹性伸缩，需要由客户收集监测数据并自主手动实现，但是我们更希望服务线上能够更加自动化和智能化。</p>
<p>最后是服务标准化问题。我们希望服务内部的模型是标准的、能够快速复制和快速构建的；服务通信是标准的：协议标准，格式标准；运行环境是标准的：快速部署，快速迁移。</p>
<p>于是 Knative 的出现恰好解决了远程直接调用、服务线上自动扩容、版本快照以及一系列标准化问题。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="knative">Knative<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#knative" class="hash-link" aria-label="Knative的直接链接" title="Knative的直接链接" translate="no">​</a></h3>
<p>Knative 是 2018 年 Google 推出的 Serverless 世界的利器，可在任何公有或私有云上实现无服务器架构，这样用户可以使用无服务器编程技术。目前参与的公司主要是 Google、Pivotal、IBM、Red Hat，于 2018 年 7 月 24 日对外发布，当前还处于快速发展的阶段。与 Kubernetes 不同，K8S 需要始终运行至少一个 Pod 实例才能为应用程序提供服务，而 Knative 可以缩容到零（<strong>冷热启动技术</strong>）。当客户端请求访问您的应用程序时，Knative 才开始实际运行应用程序的 Pod。这可以节省大量用于保持应用整年运行的费用。例如访问不那么频繁、低频率使用的功能模块，可以考虑冷启动应用，以节约内存和计算资源（可通过技术手段优化容器启动时间，减少冷启动延迟【最快毫秒级启动】，目前国内做得最好的应该是阿里云）。</p>
<p>官网地址：</p>
<p>官方文档 <a href="https://knative.dev/docs/" target="_blank" rel="noopener noreferrer" class="">knative.dev/docs</a> 与 <a href="https://github.com/knative" target="_blank" rel="noopener noreferrer" class="">github.com/knative</a></p>
<p>Knative 的目标是在 Kubernetes 之上为整个开发生命周期提供帮助。它的具体实现方式是：首先使你作为开发人员能够以你想要的语言和方式来编写代码，其次帮助你构建和打包应用程序，最后帮助你运行和伸缩应用程序。Knative 主要由 Build、Serving 和 Eventing 三大核心组件构成，正是依靠这三个核心组件，驱动着 Knative 这艘 Serverless 巨轮前行。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="build编译系统">Build（编译系统）<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#build%E7%BC%96%E8%AF%91%E7%B3%BB%E7%BB%9F" class="hash-link" aria-label="Build（编译系统）的直接链接" title="Build（编译系统）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">内部构建：它的构建完成是在 Kubernetes 中进行的，快速地把函数编译上线，与整个 Kubernetes 生态结合更为紧密。</li>
<li class="">标准化：它旨在提供一个通用的标准化构建组件，可以作为其他更大系统中的一部分；部署脚本标准化结构化，可以更好地将服务进行迁移部署。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="serving服务系统">Serving（服务系统）<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#serving%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F" class="hash-link" aria-label="Serving（服务系统）的直接链接" title="Serving（服务系统）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">快速部署：快速部署 Serverless 容器。</li>
<li class="">按需扩容：支持自动扩缩容和收缩到 0 实例。</li>
<li class="">路由策略：基于 Istio 组件，提供路由和网络编程。</li>
<li class="">版本快照：支持部署快照（生产环境容器快照，可长期保存，并任意恢复到某个快照版本）。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="eventing事件系统">Eventing（事件系统）<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#eventing%E4%BA%8B%E4%BB%B6%E7%B3%BB%E7%BB%9F" class="hash-link" aria-label="Eventing（事件系统）的直接链接" title="Eventing（事件系统）的直接链接" translate="no">​</a></h3>
<p><strong>Source(源)、Channel(通道)、Subscription(订阅)</strong></p>
<p>事件系统使得生产和消费事件变得容易，抽象出事件源，并允许操作人员使用自己选择的消息传递层。Serverless 中最重要的是基于事件的触发机制，也就是说当某件事发生时，就触发某个特定的函数。事件概念的出现，让函数和具体的调用方能够解耦：函数部署出来不用关心谁会调用它，而事件源触发也不用关心谁会处理它。简而言之，在我们的代码中并不需要去书写具体调用方 Service，只需要关注事件发送与处理事件即可（相信对 MQ 队列熟悉的人已经心里有底了，不过 Eventing 的事件系统更加完备一点，有多种事件处理模式）。我们的 Service 在远程 RPC 或 HTTP 调用时，不需要关注具体服务实例，也不需要去订阅注册中心，只需要发出事件源即可，具体事件的返回数据全部由 Eventing 完成，真正实现了服务间的透明，解决了服务耦合问题。<strong>【正所谓耦合与解耦往往只是差了一个中间层】</strong></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-12-650cc9d17fb6fb116fb265ff782a307308c-1508a550eb4a75c7aff60d1624fa8156.webp" width="1017" height="722" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="整体优势">整体优势<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E6%95%B4%E4%BD%93%E4%BC%98%E5%8A%BF" class="hash-link" aria-label="整体优势的直接链接" title="整体优势的直接链接" translate="no">​</a></h3>
<p><strong>便利性</strong>：Knative 以 Kubernetes 作为其底层框架，因此无论是线上还是线下，任何 Kubernetes 集群，无论是云上 Kubernetes 服务还是自建 Kubernetes 集群，都可通过安装 Knative 插件快速搭建 Serverless 平台。</p>
<p><strong>标准化</strong>：Knative 联合 CNCF，把所有事件标准化，统一为 CloudEvent，提供事件的跨平台能力，同时让函数和具体的调用方能够解耦。</p>
<p><strong>服务间解耦</strong>：使用 Knative 使得应用不再与底层依赖服务强绑定，可以跨云实现业务互通。</p>
<p><strong>成熟的生态</strong>：Knative 基于 Kubernetes 体系构建，与 Kubernetes 生态结合更紧密。</p>
<p><strong>自动伸缩</strong>：监控应用的请求，并自动扩缩容，借助于 Istio（ambassador、gloo 等）天生支持蓝绿发布、回滚功能，方便应用发布流程。</p>
<p><strong>应用监控</strong>：支持日志的收集、查找和分析，并支持 metrics 数据展示、调用关系 tracing。</p>
<p><strong>快照部署</strong>：对每次发布的服务记录其快照信息，并可长期保存，可在任意时间节点无感知地恢复到某个快照版本。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="总结">总结<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>Serverless（无服务器架构）成为了目前新的技术热点，它是在传统容器技术和 Server-Mesh 服务网格上发展起来的。无服务器云函数可以让开发者无需关心服务器的部署运营，只需开发最核心的业务逻辑或者函数，即可实现上线运营，并自动具备分布容灾能力、依据负载自动扩缩容，在公有云中按照实际调用次数、执行时长、计算资源消耗来计费，做到没有计算资源浪费，更好地节约企业开销。</p>
<p><strong>Serverless 大体上可以分为两种类型：【BaaS 是后端即服务，FaaS 是函数即服务】</strong></p>
<p><strong>BaaS：</strong> 无服务器首先用于描述显著或完全包含第三方云托管应用程序和服务的应用程序，以管理服务器端逻辑和状态。这些通常是"富客户端"应用程序——比如单页网络应用程序或移动应用程序——使用庞大的云可访问数据库生态系统（例如 Parse、Firebase）、身份验证服务（例如 Auth0、AWS Cognito）等等。这些类型的服务以前被描述为"后端即服务"，也就是我们的 Spring-boot、Tomcat、Dubbo、Weblogic、Gin、Flask 等后端常用的容器与服务中间件。</p>
<p><strong>FaaS：</strong> 无服务器也可以指服务器端逻辑仍然由应用程序开发人员编写的应用程序。但与传统体系结构不同，它在无状态计算容器中运行，这些容器是事件触发的，只需要实现一个函数，无需关心其他环境，是短暂的（可能只持续一次调用），并且完全由第三方管理。理解这一点的一种方法是"作为服务的功能"或 "FaaS"。国外 AWS Lambda 是目前函数即服务平台最受欢迎的实现之一，国内目前提供 FaaS 服务的有阿里云上的 FC 函数计算服务。</p>
<p>简而言之，无服务器架构的出现不是为了取代传统的应用，而是从具有高度灵活性的使用模式及事件驱动的特点出发，帮助我们减少部署、提高扩展性并减少代码背后基础设施的维护负担，为我们提供更多的可能性去选择更适合企业的部署方案。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="后续思考">后续思考<a href="https://tanzhuo.xyz/2022/12/14/chu-tan-service-mesh#%E5%90%8E%E7%BB%AD%E6%80%9D%E8%80%83" class="hash-link" aria-label="后续思考的直接链接" title="后续思考的直接链接" translate="no">​</a></h2>
<ol>
<li class="">
<p>未来的部署架构会怎样发展？其高可用、高性能、高并发 3H 的特性将会如何衍生新的技术体系？企业部署的私有云可靠性如何做到 6-12 个 9 的可用性【<strong>6 个 9：(1-99.9999%)*365*24*60*60=31秒，一年停机时间不能超过 31 秒；但 12 个 9 可就是不小的挑战，从企业成本角度来说没必要，甚至不可能去实现，一年停机时间不能超过 (1-99.9999999999%)*365*24*60*60=0.00003秒</strong>】？配合异地多活的架构体系，又可以衍生出多少新的中间件技术呢？</p>
</li>
<li class="">
<p>这种架构就只有优点没有缺点吗？其开销与体量、可控性是否适合所有企业？企业抛开业务场景直接上 Serverless 体系是否科学？</p>
</li>
<li class="">
<p>有没有发现软件上的容器生态技术，与硬件超融合技术有异曲同工之妙？其目的都是弹性、迁移、节约成本、灵活、敏捷、更可靠。</p>
</li>
</ol>
<p>如有不足欢迎各位补充，留言交流。</p>]]></content:encoded>
            <category>K8S</category>
        </item>
        <item>
            <title><![CDATA[Idea 默认快捷键]]></title>
            <link>https://tanzhuo.xyz/2022/11/25/idea-mo-ren-kuai-jie-jian</link>
            <guid>https://tanzhuo.xyz/2022/11/25/idea-mo-ren-kuai-jie-jian</guid>
            <pubDate>Fri, 25 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[整理 IntelliJ IDEA 默认键位中最常用的快捷键，按查找、编辑、重构、调试分类，附记忆思路与冲突排查方法。]]></description>
            <content:encoded><![CDATA[<p>记录一下 IDEA 默认的快捷键。写 Java 这几年，真正让效率上台阶的不是插件，而是把双手从鼠标上拿开——这篇按使用场景把默认键位过一遍。</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-11-20200928180550215-f90b66344ed049dc5d225ec73fc74153.webp" width="1600" height="702" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么值得背默认键位">为什么值得背默认键位<a href="https://tanzhuo.xyz/2022/11/25/idea-mo-ren-kuai-jie-jian#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%80%BC%E5%BE%97%E8%83%8C%E9%BB%98%E8%AE%A4%E9%94%AE%E4%BD%8D" class="hash-link" aria-label="为什么值得背默认键位的直接链接" title="为什么值得背默认键位的直接链接" translate="no">​</a></h2>
<p>IDEA 支持自定义 Keymap，也能一键切成 Eclipse 或 VS Code 风格。但我更建议直接用默认键位，理由很简单：换电脑、连同事的机器、看官方文档和视频教程时，默认键位到处都能用，肌肉记忆不会作废。</p>
<p>下面的快捷键以 Windows/Linux 默认 Keymap 为准，macOS 上大多把 <code>Ctrl</code> 换成 <code>Cmd</code>、<code>Alt</code> 换成 <code>Option</code> 即可，个别差异会单独标注。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="查找与跳转">查找与跳转<a href="https://tanzhuo.xyz/2022/11/25/idea-mo-ren-kuai-jie-jian#%E6%9F%A5%E6%89%BE%E4%B8%8E%E8%B7%B3%E8%BD%AC" class="hash-link" aria-label="查找与跳转的直接链接" title="查找与跳转的直接链接" translate="no">​</a></h2>
<p>这一类使用频率最高，建议优先练熟。</p>
<ul>
<li class=""><strong>双击 Shift</strong>：Search Everywhere，全局搜索类、文件、符号、设置项，不知道用什么就用它。</li>
<li class=""><strong>Ctrl + N</strong>：按名字找类；<strong>Ctrl + Shift + N</strong>：找文件；<strong>Ctrl + Alt + Shift + N</strong>：找符号（方法、字段）。</li>
<li class=""><strong>Ctrl + Shift + F</strong>：全工程文本搜索；<strong>Ctrl + Shift + R</strong>：全工程替换。</li>
<li class=""><strong>Ctrl + B</strong>（或 Ctrl + 鼠标左键）：跳到定义；<strong>Ctrl + Alt + B</strong>：跳到实现类，看接口时比跳定义更有用。</li>
<li class=""><strong>Alt + F7</strong>：Find Usages，查一个方法或字段被谁调用。</li>
<li class=""><strong>Ctrl + E</strong>：最近打开的文件；<strong>Ctrl + Shift + E</strong>：最近编辑过的文件。</li>
<li class=""><strong>Ctrl + G</strong>：跳到指定行号，排查日志里的堆栈时常用。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="编辑与生成代码">编辑与生成代码<a href="https://tanzhuo.xyz/2022/11/25/idea-mo-ren-kuai-jie-jian#%E7%BC%96%E8%BE%91%E4%B8%8E%E7%94%9F%E6%88%90%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="编辑与生成代码的直接链接" title="编辑与生成代码的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>Alt + Enter</strong>：上下文快捷修复，导包、补 try-catch、创建缺失的方法都靠它，可以说是 IDEA 的灵魂键。</li>
<li class=""><strong>Alt + Insert</strong>（macOS 是 Cmd + N）：Generate 菜单，生成构造器、getter/setter、equals/hashCode、重写方法。</li>
<li class=""><strong>Ctrl + D</strong>：复制当前行；<strong>Ctrl + Y</strong>：删除当前行。注意 Ctrl + Y 在很多编辑器里是重做，IDEA 里是删行，刚上手容易误触。</li>
<li class=""><strong>Ctrl + W</strong> / <strong>Ctrl + Shift + W</strong>：按语法结构扩大/缩小选区，选一个表达式、一个代码块非常顺手。</li>
<li class=""><strong>Ctrl + Alt + L</strong>：格式化代码；<strong>Ctrl + Alt + O</strong>：优化 import。</li>
<li class=""><strong>Ctrl + /</strong>：行注释；<strong>Ctrl + Shift + /</strong>：块注释。</li>
<li class=""><strong>Shift + Alt + 上/下</strong>：整行上下移动；<strong>Ctrl + Shift + 上/下</strong>：按语法块移动，不会把语句移出方法体。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重构">重构<a href="https://tanzhuo.xyz/2022/11/25/idea-mo-ren-kuai-jie-jian#%E9%87%8D%E6%9E%84" class="hash-link" aria-label="重构的直接链接" title="重构的直接链接" translate="no">​</a></h2>
<p>重构快捷键的价值在于安全：IDEA 会同步修改所有引用，比手工搜索替换可靠得多。</p>
<ul>
<li class=""><strong>Shift + F6</strong>：重命名，类、方法、变量通用。</li>
<li class=""><strong>Ctrl + Alt + M</strong>：抽取方法（Extract Method）；<strong>Ctrl + Alt + V</strong>：抽取变量。</li>
<li class=""><strong>Ctrl + Alt + C</strong>：抽取常量；<strong>Ctrl + Alt + F</strong>：抽取字段。</li>
<li class=""><strong>Ctrl + Alt + Shift + T</strong>：Refactor This，弹出当前位置可用的全部重构项，记不住上面几个时用这一个兜底。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="运行与调试">运行与调试<a href="https://tanzhuo.xyz/2022/11/25/idea-mo-ren-kuai-jie-jian#%E8%BF%90%E8%A1%8C%E4%B8%8E%E8%B0%83%E8%AF%95" class="hash-link" aria-label="运行与调试的直接链接" title="运行与调试的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>Shift + F10</strong>：运行当前配置；<strong>Shift + F9</strong>：调试运行。</li>
<li class=""><strong>F8</strong>：单步跳过；<strong>F7</strong>：单步进入；<strong>Shift + F8</strong>：跳出当前方法。</li>
<li class=""><strong>F9</strong>：恢复执行，跑到下一个断点。</li>
<li class=""><strong>Ctrl + F8</strong>：在当前行打断点/取消断点。</li>
<li class=""><strong>Alt + F8</strong>：调试时打开 Evaluate Expression，临时执行一段表达式看结果。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/11/25/idea-mo-ren-kuai-jie-jian#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>快捷键失灵时，先别急着重装，大概率是被别的软件抢占了：</p>
<p>1）Windows 下输入法、网易云音乐、QQ 等常驻软件会注册全局热键，<code>Ctrl + Alt + L</code> 这类组合最容易被占用，去对应软件里关掉全局热键即可。</p>
<p>2）Linux 桌面环境（GNOME/KDE）自带的窗口管理快捷键也会和 IDEA 冲突，可以在系统设置里改掉系统侧的绑定。</p>
<p>3）在 IDEA 里按 <strong>Ctrl + Shift + A</strong>（Find Action）输入功能名，能查到任何动作当前绑定的键位，这是排查冲突和找冷门功能的万能入口。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>不要试图一次背完。挑三五个高频键（双击 Shift、Alt + Enter、Ctrl + W、Shift + F6）先用一周，形成肌肉记忆后再加下一批，比对着清单硬背有效得多。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/11/25/idea-mo-ren-kuai-jie-jian#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>快捷键的收益是复利：单次只省一两秒，但查找、跳转、重构每天要做上百次。默认键位跨机器通用，配合 Ctrl + Shift + A 随时反查，基本不需要额外记忆负担。这份清单我自己也会持续用到，忘了就回来翻。</p>]]></content:encoded>
            <category>Java</category>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[线程池核心线程数-计算方式]]></title>
            <link>https://tanzhuo.xyz/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi</link>
            <guid>https://tanzhuo.xyz/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi</guid>
            <pubDate>Mon, 21 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[从计算密集、IO 密集与混合型三种工况出发,讨论线程池核心线程数的估算公式,以及为什么最终还是要靠压测来调整。]]></description>
            <content:encoded><![CDATA[<p>线程池的核心线程数该设多少,取决于程序的工况。一般分为三种场景:计算密集型、IO 密集型和混合型,下面分别来看。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="计算密集型">计算密集型<a href="https://tanzhuo.xyz/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi#%E8%AE%A1%E7%AE%97%E5%AF%86%E9%9B%86%E5%9E%8B" class="hash-link" aria-label="计算密集型的直接链接" title="计算密集型的直接链接" translate="no">​</a></h2>
<p>需要大量的计算,对 CPU 占用率高,CPU Loading 90-100%。除开 CPU 需要读/写 I/O(硬盘/内存),但这些 I/O 只需要很短的时间就可以完成,更多的是 CPU 在做大量数据运算、数学运算。例如数据分析、数据流处理,此类程序运行过程中 CPU 占用率一般都很高。</p>
<p>假如在单核 CPU 情况下,线程池有 6 个线程,但由于是单核,同一时间只能运行一个线程,考虑到线程之间还有上下文切换的时间消耗,还不如单个线程执行高效。所以,单核 CPU 处理计算密集型程序,就不要使用多线程了。</p>
<p>假如是 6 个核心的 CPU,设置 6 个线程数,理论上运行速度可以提升 6 倍(实际上达不到,多线程之间有并发以及需要优化的地方)。每个线程都有 CPU 来运行,不会发生等待 CPU 时间片的情况,也没有线程切换的开销。多核 CPU 处理计算密集型程序才合适,而且中间可能没有线程的上下文切换(<strong>一核心对应一线程,一般配置不会超过 CPU 数量 + 1</strong>)。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="io密集型">IO密集型<a href="https://tanzhuo.xyz/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi#io%E5%AF%86%E9%9B%86%E5%9E%8B" class="hash-link" aria-label="IO密集型的直接链接" title="IO密集型的直接链接" translate="no">​</a></h2>
<p>和计算密集型相反,更多的是处理磁盘、网络上的 IO,对 CPU 占用不高,大部分时间是 CPU 在等待 IO 响应,等待期间线程被阻塞、CPU 空闲,压力更多在磁盘或网络的传输效率上。这种场景下简单地按 CPU 核心数 × 2 来设置线程数其实不够严谨,IO 密集型有对应的公式可以套用。例如 Web 应用的后台,更多的场景是增删改查数据库或缓存,很多接口的耗时都花在了磁盘、网络 IO 上,那么设置线程池核心线程数时就可以根据服务器分配的 CPU 资源套用公式。</p>
<p>《Java 并发编程实战》中的计算公式:</p>
<p><strong>Nthreads = Ncpu × Ucpu × (1 + W/C)</strong></p>
<ul>
<li class="">Ncpu:CPU 核心数</li>
<li class="">Ucpu:CPU 利用率</li>
<li class="">W/C:等待时间 / 计算时间</li>
</ul>
<p>虽然可以通过公式得出预期的线程数,但在真实的程序中,<strong>一般很难获得准确的等待时间和计算时间,因为程序很复杂,不只是"计算"</strong>。一段代码中会有很多内存读写、计算、I/O 等复合操作,精确获取这两个指标很难,所以光靠公式计算线程数过于理想化。不过我们可以在此基础上,再通过压力测试对核心线程数做具体调整,达到最高的效率预期。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="混合型">混合型<a href="https://tanzhuo.xyz/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi#%E6%B7%B7%E5%90%88%E5%9E%8B" class="hash-link" aria-label="混合型的直接链接" title="混合型的直接链接" translate="no">​</a></h2>
<p>有一些应用程序,计算处理数据的同时又需要进行磁盘或网络的数据传输。对于这样的程序如何配置线程池才能获得最高性能?一般来说会根据服务器的 CPU 核心数进行拆分,创建两个线程池:一个线程池对应计算部分,另一个对应 IO 部分,这样配置比较合理。网上也有另一种方案:</p>
<p><strong>核心线程数 = (线程等待时间 / 线程 CPU 时间 + 1) × CPU 核心数</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="真实程序中的线程数">真实程序中的线程数<a href="https://tanzhuo.xyz/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi#%E7%9C%9F%E5%AE%9E%E7%A8%8B%E5%BA%8F%E4%B8%AD%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%95%B0" class="hash-link" aria-label="真实程序中的线程数的直接链接" title="真实程序中的线程数的直接链接" translate="no">​</a></h2>
<p>那么在实际的程序中,或者说一些 Java 业务系统中,线程数(线程池大小)规划多少合适呢?</p>
<p><strong>先说结论:没有固定答案。先设定预期,比如期望的 CPU 利用率是多少、负载多少、GC 频率多少之类的指标,然后通过公式设置核心线程数,再通过测试不断调整到一个合理的线程数。</strong></p>
<p>公式只能套出一个大概值,实际受干扰的因素特别多,尤其是服务器计算资源的争夺。如果应用程序直接跑在服务器上、没有其他程序干扰,通过不断压测和调整就能找到合适的线程数。但目前很多应用已经容器化,在 K8S 环境中容器分布在不同的 worker 节点上,一个 worker 节点可能运行着很多容器,如果不对容器做资源限制,很容易发生计算资源争夺。这就需要把每个容器的 CPU、内存、硬盘、网络等资源限制做好,不然运行效率很可能远低于预期(计算资源被其他容器占走了)。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>线程池核心线程数没有放之四海皆准的数值:计算密集型贴着 CPU 核心数配置,IO 密集型可以用 Nthreads = Ncpu × Ucpu × (1 + W/C) 估算,混合型则考虑按计算和 IO 拆分成两个线程池。公式给出的只是起点,等待时间与计算时间在真实程序里很难精确测量,还要叠加容器化环境下的资源争夺问题。所以合理的做法是:先按公式定一个初始值,设定好 CPU 利用率、负载等预期指标,再通过压测逐步调整到符合预期为止。</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[InnoDB 之 BufferPool 详解]]></title>
            <link>https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool</link>
            <guid>https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool</guid>
            <pubDate>Wed, 16 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[详解 InnoDB BufferPool 的分层结构与页管理:Page Hash、Free/LRU/Flush 链表,冷热数据分区,以及脏页刷盘的四种时机。]]></description>
            <content:encoded><![CDATA[<p>在 InnoDB 存储引擎中,为了减少磁盘 IO 次数、提高服务性能,所有 DML 操作都是在内存中的 BufferPool 里完成的,再由异步刷盘策略把修改的数据落盘。本文详细梳理 BufferPool 的数据结构、页管理方式,以及淘汰和刷盘策略。</p>
<!-- -->
<p>BufferPool 官方文档:</p>
<p><a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool.html" target="_blank" rel="noopener noreferrer" class="">MySQL :: MySQL 8.0 Reference Manual :: 15.5.1 Buffer Pool</a></p>
<p>为什么需要设计 BufferPool?有计算机基础的人都知道,内存和硬盘的效率是天差地别的,对 CPU 来说硬盘的传输与响应时间太低下。所以 MySQL 设计出 BufferPool 来映射磁盘中的真实数据,在内存中完成数据的管理与操作,DML 语句的执行效率自然就起飞了。至于如何保障 BufferPool 中的数据在宕机、掉电的场景下不丢失(物理损坏这种就没办法了,硬盘都给你扬了),之前我的文章中也有讲解:</p>
<p><a href="https://tanzhuo.xyz/mysql-zhixing/" target="_blank" rel="noopener noreferrer" class="">MySQL 执行链路</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="buffer-pool-简介">Buffer Pool 简介<a href="https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool#buffer-pool-%E7%AE%80%E4%BB%8B" class="hash-link" aria-label="Buffer Pool 简介的直接链接" title="Buffer Pool 简介的直接链接" translate="no">​</a></h2>
<p>InnoDB 中的数据访问以 Page 为单位,每个 Page 默认大小为 16KB,Buffer Pool 就是用来管理和缓存这些 Page 的。InnoDB 将一块连续的内存划分给 Buffer Pool 使用,并将其拆分为多个 Buffer Pool Instance 来更好地管理这块内存。每个 Instance 的大小相等,通过算法保证一个 Page 只会落在某个特定的 Instance 中,这种多 Instance 的模式提升了 Buffer Pool 的并发性能。</p>
<p>每一个 Buffer Pool Instance 内部都会维护自己的一套管理结构。InnoDB 以 16KB Page 为单位把数据从磁盘文件读取到内存,并通过一个 LRU List 来缓存这些 Page:经常访问的 Page 在 LRU List 的前面,不经常访问的在后面。访问一个 Page 时,先从 Buffer Pool 中查找,未命中则读磁盘数据文件,取到 Page 后放入 LRU List;当一个 Instance 中没有可用的空闲 Page 时,会对 LRU List 中的 Page 进行淘汰。</p>
<p>Buffer Pool 中还夹杂了不少 Page 压缩的逻辑,即把实际的 16KB Page 压缩为 8KB、4KB、2KB、1KB,这部分逻辑本文暂时跳过,先按默认 16KB Page 来梳理主干逻辑。</p>
<p>目前来看 BufferPool 是一个分层的结构:</p>
<ul>
<li class="">BufferPool 由多个 Buffer Pool Instance 组成。</li>
<li class="">Buffer Pool Instance 由多个 Buffer Chunk 组成(默认每个 Buffer Chunk 是 128MB),Buffer Chunk 管理一片连续的内存区域,叫 Buffer Chunk Memory。</li>
<li class="">Buffer Chunk 由多个 Buffer Page 组成(简称 Page,默认 16KB)。</li>
</ul>
<p>这种分层结构是为了降低高并发下 mutex 的竞争。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-11-842fb6ff-945d-442d-a28e-0d7a9ef039b1-3f3a18424cb951f5053c4f5ca00a1237.webp" width="1440" height="425" class="img_ev3q"></p>
<p>本文主要讲解 BufferPool 中的数据结构、管理方式以及淘汰、刷盘策略。首先看官方给出的 LRU 数据缓冲结构图:</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="376" height="603" class="img_ev3q"></p>
<p>Buffer Pool 的<strong>默认总大小是 128M</strong>,可以通过 <code>innodb-buffer-pool-size = xxxxxxx</code> 按需修改(官方建议专用数据库服务器上可以给到物理内存的大部分)。它分为 New Sublist 与 Old Sublist,是不是想到了 JVM 中的新生代和老年代?意思确实差不多:New Sublist 存放最频繁活跃的数据,Old Sublist 存放不经常访问的数据,Old Sublist 默认占 BufferPool 3/8 的比例。</p>
<p>由于内存资源稀缺,不可能把所有磁盘表数据都加载到内存,需要一定的淘汰策略。BufferPool 采用的是 LRU 算法加上 MySQL 自己的变种逻辑来实现内存数据淘汰,也被称为冷热数据处理:经常访问的数据向 New Sublist 区域移动,不经常访问的数据向 Old Sublist 移动,内存占用过高时优先淘汰 Old Sublist 中的数据。新读取的页会放到 Old Sublist 的 Head 位置,如果这个页经常被访问就会移动到 New Sublist 中,否则继续待在 Old Sublist。</p>
<p>BufferPool 内部还有一块区域叫做 ChangeBuffer,如图:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-11-image-13-7519334f554b72ba0b825625b2d25334.webp" width="581" height="408" class="img_ev3q"></p>
<p>ChangeBuffer 用来缓存那些目标页不在 BufferPool 中的 DML 变更,避免每次修改都先把页从磁盘读上来,等对应的页后续被读入内存时再合并这些变更,从而减少随机 IO。默认情况下 ChangeBuffer 占 BufferPool 25% 的比例,最大可以调整至 50%。</p>
<p>比较完整的 BufferPool 结构图(还有一些链表没有画出来,例如压缩链表):</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-11-image-17-f0b9ba6de43eca5efd0e508bef36c291.webp" width="1600" height="763" class="img_ev3q"></p>
<p>在开始梳理之前,有些前置概念需要了解:</p>
<ul>
<li class=""><strong>数据页</strong>:已经加载了磁盘数据的页。</li>
<li class=""><strong>脏页</strong>:已经做了修改操作的页,需要刷回磁盘。</li>
<li class=""><strong>空白页</strong>:没有加载数据的页。</li>
<li class=""><strong>控制块</strong>:各个管理链表中节点存储的 BufferPool 页指针。</li>
<li class=""><strong>缓冲页</strong>:统一表示 BufferPool 中的页(页大小都为 16KB)。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="设置多个-buffer-pool-instance">设置多个 Buffer Pool Instance<a href="https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool#%E8%AE%BE%E7%BD%AE%E5%A4%9A%E4%B8%AA-buffer-pool-instance" class="hash-link" aria-label="设置多个 Buffer Pool Instance的直接链接" title="设置多个 Buffer Pool Instance的直接链接" translate="no">​</a></h2>
<p>MySQL 服务器启动的时候,会向操作系统申请 BufferPool 的内存空间。多线程场景下,各个链表都需要加锁处理,当 BufferPool 特别大、并发访问量又特别高时,单一的 BufferPool 会影响处理速度。所以会把 BufferPool 分成多个小的 BufferPool,称为 Buffer Pool Instance,它们各自独立申请内存空间、独立管理链表,多线程访问时互不影响。可以通过参数修改实例数量:</p>
<div class="language-properties codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-properties codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 创建 2 个 buffer pool 实例</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">innodb_buffer_pool_instances</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">2</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 每个实例占用的内存 = 总大小除以实例数</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># innodb_buffer_pool_size / innodb_buffer_pool_instances</span><br></div></code></pre></div></div>
<p>因为创建和管理多个实例本身也有开销,MySQL 官方规定:当 innodb_buffer_pool_size 在 1G 以下时默认只有一个实例,设置多个也是无效的,只有大于 1G 时才鼓励设置多个实例。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="buffer-pool-对页的管理">Buffer Pool 对页的管理<a href="https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool#buffer-pool-%E5%AF%B9%E9%A1%B5%E7%9A%84%E7%AE%A1%E7%90%86" class="hash-link" aria-label="Buffer Pool 对页的管理的直接链接" title="Buffer Pool 对页的管理的直接链接" translate="no">​</a></h2>
<p>数据页在 BufferPool 中不是胡乱存放的,而是由哈希表和若干个链表组织起来,以便支持前文介绍的各种操作。链表中的节点内容就是 Page Descriptor:</p>
<ol>
<li class=""><strong>page hash(快速访问)</strong>:所有的页都由一张哈希表组织,叫 page hash,用于在 buffer pool 中快速访问到一个页,page hash 的 key 就是 page id。</li>
<li class=""><strong>free list(页的申请)</strong>:当需要把页载入 buffer pool 时,需要为其分配一个 buf_block_t 作为 page descriptor。如何找到空闲未被占用的 buf_block_t?它们都挂在 free list 上。</li>
<li class=""><strong>LRU list(页的淘汰)</strong>:buffer pool 的空间有限,当没有空间存放从磁盘载入的页时,需要把现有的一些页淘汰掉,使用的就是 LRU 算法。</li>
<li class=""><strong>flush list(脏页写回)</strong>:当一个页变成脏页时,需要 page cleaner 线程定期将其写回磁盘,这些脏页都在 flush list 上。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="free-list-链表管理">Free List 链表管理<a href="https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool#free-list-%E9%93%BE%E8%A1%A8%E7%AE%A1%E7%90%86" class="hash-link" aria-label="Free List 链表管理的直接链接" title="Free List 链表管理的直接链接" translate="no">​</a></h2>
<p>最初启动 MySQL 服务器时,需要完成 BufferPool 的初始化:先向操作系统申请 BufferPool 的内存空间,然后把它划分成若干对控制块和缓冲页。此时并没有真实的磁盘页被缓存到 BufferPool 中(因为还没用到),之后随着程序运行,才会不断有磁盘上的页被缓存进来。那么问题来了:从磁盘读取一个页到 BufferPool 时,该放到哪个缓冲页的位置?或者说,怎么区分 BufferPool 中哪些缓冲页是空闲的、哪些已经被使用了?</p>
<p>最好在某个地方记录下哪些缓冲页是可用的,这时缓冲页对应的控制块就派上了大用场:把所有空闲缓冲页对应的控制块作为节点放到一个链表中,这个链表就称为 Free 链表(空闲链表)。刚完成初始化的 BufferPool 中所有缓冲页都是空闲的,所以每个缓冲页对应的控制块都会加入 Free 链表。</p>
<p>为了更好地管理 Free 链表,这里特意定义了一个基节点,包含链表的头节点地址、尾节点地址,以及当前链表中节点的数量等信息。需要注意的是,基节点占用的内存并不包含在为 BufferPool 申请的那一大片连续内存之内,而是单独申请的一块内存空间。</p>
<p>有了 Free 链表之后事情就好办了:每当需要从磁盘加载一个页到 BufferPool 时,就从 Free 链表中取一个空闲的缓冲页,把该缓冲页对应控制块的信息填上(该页所在的表空间、页号之类的信息),然后把这个控制块从 Free 链表中移除,表示该缓冲页已经被使用了。这里要清楚,我们真正从链表中获取的是控制块,通过控制块才能访问到真正的页。同理,"遍历 BufferPool 中的缓冲页"实际是"遍历各个缓冲页对应的控制块"。</p>
<p>总结:只要管理好了 Free 链表,就知道 BufferPool 中有哪些空闲的页。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="page-hash-缓冲页的哈希处理">Page Hash 缓冲页的哈希处理<a href="https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool#page-hash-%E7%BC%93%E5%86%B2%E9%A1%B5%E7%9A%84%E5%93%88%E5%B8%8C%E5%A4%84%E7%90%86" class="hash-link" aria-label="Page Hash 缓冲页的哈希处理的直接链接" title="Page Hash 缓冲页的哈希处理的直接链接" translate="no">​</a></h2>
<p>当需要访问某个页中的数据时,会把该页从磁盘加载到 BufferPool 中;如果该页已经在 BufferPool 里,直接使用就可以了。那么问题来了:怎么知道该页在不在 BufferPool 中?难不成要依次遍历各个缓冲页?一个 BufferPool 中缓冲页那么多,都遍历一遍岂不是要累死?</p>
<p>回头想想,我们其实是根据表空间号 + 页号来定位一个页的,相当于表空间号 + 页号是一个 Key,缓冲页控制块就是对应的 Value。通过 Key 快速找到 Value,用的自然是哈希表。</p>
<p>所以可以用表空间号 + 页号作为 Key、缓冲页控制块的地址作为 Value 来创建一个哈希表。需要访问某个页的数据时,先根据表空间号 + 页号查哈希表:如果有对应的缓冲页,直接使用;如果没有,就从 Free 链表中选一个空闲缓冲页,把磁盘中对应的页加载到该缓冲页的位置。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lru-list-链表管理">LRU List 链表管理<a href="https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool#lru-list-%E9%93%BE%E8%A1%A8%E7%AE%A1%E7%90%86" class="hash-link" aria-label="LRU List 链表管理的直接链接" title="LRU List 链表管理的直接链接" translate="no">​</a></h2>
<p>LRU 链表是对 BufferPool 缓冲区空间的一种管理方法。想想看,如果一直往缓冲区加载数据页,迟早有一天缓冲区会被装满,这时肯定要有一个机制来释放那些没用的数据页,这个机制就是 LRU 算法(最近最少使用算法)。</p>
<p>可以把该算法看成一个链表,这个链表分为两个部分(MySQL 的变种):一部分存储使用频率非常高的缓冲页,也就是热数据,称为 Young 区域(New Sublist);另一部分存储使用频率不高的缓冲页,也就是冷数据,称为 Old 区域(Old Sublist)。InnoDB 按比例把 LRU 链表分成两截,可以通过参数 <strong>innodb_old_blocks_pct</strong> 查看 Old 区域所占的比例。</p>
<p>有了这两个区域,InnoDB 设计者就可以针对 BufferPool 命中率的情况做优化了:</p>
<ul>
<li class="">针对预读的页面可能不进行后续访问的优化。设计者规定,当磁盘上的某个页面初次加载到 BufferPool 的某个缓冲页时,该缓冲页对应的控制块会放到 Old 区域的头部。这样一来,预读到 BufferPool 却没有后续访问的页面会逐渐从 Old 区域被逐出,而不会影响 Young 区域中使用频繁的缓冲页。</li>
<li class="">针对全表扫描时短时间内访问大量低频页面的优化。全表扫描时,虽然首次加载的页放到了 Old 区域头部,但后续会被马上访问到,每次访问又会把该页放到 Young 区域头部,这样仍然会把那些使用频率高的页面排挤下去。设计者认为,全表扫描过程中即使某个页面中有很多条记录、每读一条记录都算访问一次页面,这个过程花费的时间也非常短。所以规定:对某个处于 Old 区域的缓冲页进行第一次访问时,在它对应的控制块中记录下访问时间;如果后续的访问时间与第一次访问的时间在某个间隔之内,该页面就不会从 Old 区域移动到 Young 区域头部,否则才移动。这个间隔时间由系统变量 innodb_old_blocks_time 控制。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flush-list-链表管理">Flush List 链表管理<a href="https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool#flush-list-%E9%93%BE%E8%A1%A8%E7%AE%A1%E7%90%86" class="hash-link" aria-label="Flush List 链表管理的直接链接" title="Flush List 链表管理的直接链接" translate="no">​</a></h2>
<p>如果修改了 BufferPool 中某个缓冲页的数据,它就与磁盘上的页不一致了,这样的缓冲页称为脏页。当然,可以每次修改完就立即刷新到磁盘对应的页上,但频繁写磁盘会严重影响性能,所以每次修改缓冲页后并不急着刷盘,而是在未来的某个时间点再刷新。</p>
<p>但如果不立即刷盘,之后刷盘时怎么知道 BufferPool 中哪些页是脏页、哪些是从没被修改过的页?所以不得不再创建一个存储脏页的链表:凡是被修改过的缓冲页对应的控制块,都会作为节点加入这个链表。因为这些节点对应的缓冲页都需要被刷新到磁盘上,所以称为 Flush 链表。Flush 链表的构造与 Free 链表差不多。另外,如果一个缓冲页是空闲的,它肯定不是脏页;如果是脏页,肯定不空闲——也就是说,某个缓冲页对应的控制块不可能既是 Free 链表的节点又是 Flush 链表的节点,只能处于其中一个链表中。</p>
<p><strong>总结</strong>:Free 链表可以简单理解为缓冲区中所有空闲页组成的链表,Flush 链表则是所有被修改过的页(脏页)组成的链表。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="脏页刷盘机制">脏页刷盘机制<a href="https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool#%E8%84%8F%E9%A1%B5%E5%88%B7%E7%9B%98%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="脏页刷盘机制的直接链接" title="脏页刷盘机制的直接链接" translate="no">​</a></h2>
<p>脏页在以下几种情况下会被刷回磁盘:</p>
<p>1)Redo Log 写不下了。Redo Log 作为持久化的保障,每次更新操作都必须记录,如果 Redo Log 写不下了,就必须刷页,把数据同步到磁盘,然后移动 Redo Log 的 CheckPoint 指针空出新的位置。此时如果内存不紧张,刷完的页不需要淘汰出内存。</p>
<p>2)内存放不下数据页了。查询操作会把磁盘中的数据页读入内存再返回,如果内存放不下了,就必须淘汰最久未使用的数据页;如果被淘汰的是脏页,必须先刷进磁盘再淘汰。</p>
<p>3)数据库空闲时。如果数据库认为当前处于空闲状态,就会对脏页进行刷盘,此时内存如果不紧张,刷完后不进行淘汰。</p>
<p>4)数据库正常关闭时。关闭时也会进行刷页,把之前的修改持久化到硬盘。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/11/16/mysql-buffer-pool#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>BufferPool 是 InnoDB 用内存换 IO 的核心设计:数据以 16KB 的 Page 为单位缓存在内存中,DML 操作先改内存、再异步落盘。为降低并发下的锁竞争,它被拆分为多个 Instance,内部再通过 Page Hash 实现快速定位,Free、LRU、Flush 三条链表分别负责空闲页分配、冷热数据淘汰与脏页写回。LRU 的 New/Old 分区加上 innodb_old_blocks_time 的时间窗口,解决了预读和全表扫描对热数据的冲刷问题。理解了这几条链表的协作方式,BufferPool 的整体运作机制也就清晰了。</p>]]></content:encoded>
            <category>DB</category>
        </item>
        <item>
            <title><![CDATA[MySQL 执行链路]]></title>
            <link>https://tanzhuo.xyz/2022/11/15/mysql-zhixing</link>
            <guid>https://tanzhuo.xyz/2022/11/15/mysql-zhixing</guid>
            <pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[梳理一条 SQL 在 MySQL 中的完整执行链路：从连接器到执行器，Buffer Pool 如何减少磁盘 IO，以及 Redo Log、Bin Log、Undo Log 各自的职责与二阶段提交。]]></description>
            <content:encoded><![CDATA[<p>一条 SQL 从客户端提交到数据真正落盘，中间要经过连接器、分析器、优化器、执行器，还要靠 Buffer Pool 和三大日志的配合。这篇把整条执行链路串起来。</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-11-image-11-3b98bcb2a74b924e1f92f8c0c2e9a703.webp" width="1368" height="922" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="从连接器到执行器">从连接器到执行器<a href="https://tanzhuo.xyz/2022/11/15/mysql-zhixing#%E4%BB%8E%E8%BF%9E%E6%8E%A5%E5%99%A8%E5%88%B0%E6%89%A7%E8%A1%8C%E5%99%A8" class="hash-link" aria-label="从连接器到执行器的直接链接" title="从连接器到执行器的直接链接" translate="no">​</a></h2>
<p>当客户端 SQL 语句提交给 MySQL 服务器时首先需要建立连接，连接器会与客户端进行连接，并校验用户身份信息、权限信息。如果权限通过，则通过 SQL 分析器对 SQL 语句进行语义分析（检查 SQL 语句是否合法，能否解析通过），分析完成后将会对 SQL 的执行过程进行优化，例如剔除不必要的查询条件 1=1，以及选择最优的索引与 Where 条件的字段排序。</p>
<p>然后交给执行器执行此 SQL 语句，执行器将会根据数据库所使用的存储引擎执行 SQL。MySQL 在设计时分为了两层，即 Server 层和存储引擎层。这样做的好处就是解耦，可以根据不同的场景去选择合适的存储引擎。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="buffer-pool">Buffer Pool<a href="https://tanzhuo.xyz/2022/11/15/mysql-zhixing#buffer-pool" class="hash-link" aria-label="Buffer Pool的直接链接" title="Buffer Pool的直接链接" translate="no">​</a></h2>
<p>常用的 InnoDB 存储引擎是以页（16KB）为单位来管理存储空间的，任何的增删改查操作最终都会操作完整的一个页，会将整个页加载到 Buffer Pool 中并且所有的数据操作都是在 Buffer Pool 中完成。Buffer Pool 通常设置为服务器 70%-80% 左右的内存占用，这样数据的操作直接在内存中执行，从而提高数据操作的效率（如果每一条 DML 语句都要进行 IO 操作，那么数据库的磁盘很容易变成瓶颈，改一条数据需要先从磁盘中读取，然后刷盘到磁盘则是 2 次 IO，一次读一次写）。</p>
<p>我们通过一个例子说明。先假设没有 Buffer Pool，user 表里面只有一条记录，记录的 age = 1，需要执行以下三条 SQL：</p>
<p>事务A：update user set age = 2（一读一写 2次IO）<br>
事务B：update user set age = 3（一读一写 2次IO）<br>
事务C：update user set age = 4（一读一写 2次IO）</p>
<p>每一次都需要从磁盘读取数据到内存做一次修改然后刷盘到磁盘，那么操作下来总共是 6 次 IO。</p>
<p>如果加入 Buffer Pool 后：</p>
<p>事务A：update user set age = 2（读到 Buffer Pool 并做修改 1次读IO）<br>
事务B：update user set age = 3（修改 Buffer Pool 中的数据 0次IO）<br>
事务C：update user set age = 4（修改 Buffer Pool 中的数据 并刷入磁盘 1次写IO）</p>
<p>只有第一次需要去加载数据页，后续的操作都是直接在内存中完成，加入 Buffer Pool 之后操作下来总共是 2 次 IO。（这里只是简单演示基本逻辑，不完全是这么简单的优化逻辑，但总的来看 Buffer Pool 就是把磁盘中的数据映射到内存中来进行操作，减少磁盘 IO 次数。但 Buffer Pool 在内存中进行数据修改操作，万一出现宕机或者断电数据很容易丢失。）</p>
<p>查看 Buffer Pool 信息：</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 输出中包含 Buffer Pool 大小、命中率、脏页数量等信息</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">SHOW</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">ENGINE</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">INNODB</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">STATUS</span><br></div></code></pre></div></div>
<p>此时就需要有几个很重要的 Log 日志来进行辅助：Redo Log（重做日志）、Bin Log（二进制日志）、Undo Log（回滚日志）。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redo-log重做日志">Redo Log（重做日志）<a href="https://tanzhuo.xyz/2022/11/15/mysql-zhixing#redo-log%E9%87%8D%E5%81%9A%E6%97%A5%E5%BF%97" class="hash-link" aria-label="Redo Log（重做日志）的直接链接" title="Redo Log（重做日志）的直接链接" translate="no">​</a></h2>
<p>就是在数据修改之后，先将修改后的值记录到磁盘上的 Redo Log 中，就算突然断电了，Buffer Pool 中的数据全部丢失了，来电的时候也可以根据 Redo Log 恢复 Buffer Pool。这样既利用到了 Buffer Pool 的内存高效性，也保证了数据不会丢失。Redo Log 刷盘有三种策略：</p>
<ul>
<li class="">设置为 0：每次事务提交不进行刷盘操作（系统默认 Master Thread 每隔 1s 进行一次重做日志的同步）</li>
<li class="">设置为 1：每次提交事务都将进行同步刷盘操作，这也是最保险的，因为如果这个时候崩溃了代表事务没有 commit 成功，因此也不用恢复什么数据（默认）</li>
<li class="">设置为 2：表示每次事务提交时都只把 Redo Log Buffer 内容写入 Page Cache，不进行同步，由文件系统（OS）自己决定什么时候同步到磁盘</li>
</ul>
<p>此处的 Redo Log 刷盘是顺序写入 WAL（Write-ahead logging），翻译成中文就是预写式日志，对比起随机落盘效率大幅度提高，Kafka、RocketMQ 等 MQ 队列都是采用顺序写日志来提高磁盘的写入能力。虽然磁盘顺序写已经很高效了，但是和内存操作还是有一定的差距。为了进一步提高 Redo Log 的效率，于是在内存中搞出了 Change Buffer（默认的大小为 16MB，位于 Buffer Pool 中，可以设置占比大小百分比）来在内存中做记录，只有事务提交才进行磁盘顺序写入。</p>
<p>Change Buffer 中分了很多的 Block，每个 Block 的大小为 512kb，每一个事务产生的所有 Redo Log 称为一个 Group。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bin-log二进制日志">Bin Log（二进制日志）<a href="https://tanzhuo.xyz/2022/11/15/mysql-zhixing#bin-log%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%97%A5%E5%BF%97" class="hash-link" aria-label="Bin Log（二进制日志）的直接链接" title="Bin Log（二进制日志）的直接链接" translate="no">​</a></h2>
<p>Bin Log 是记录所有数据库表结构变更（例如 CREATE、ALTER TABLE…）以及表数据修改（INSERT、UPDATE、DELETE…）的二进制日志。不会记录 SELECT 和 SHOW 这类操作，因为这类操作对数据本身并没有修改，但可以通过查询通用日志来查看 MySQL 执行过的所有语句。</p>
<p>需要注意的一点是，即便 <strong>update 操作没有造成数据变化，也是会记入 Bin Log</strong>。</p>
<p>和 Redo Log 日志类似，Bin Log 也有着自己的刷盘策略，通过 sync_binlog 参数控制：</p>
<ul>
<li class="">设置为 0：每次提交事务前将 Bin Log 写入 OS Cache，由操作系统控制什么时候刷到磁盘</li>
<li class="">设置为 1：采用同步写磁盘的方式来写 Bin Log，不使用 OS Cache 来写 Bin Log</li>
<li class="">设置为 n：当每进行 n 次事务提交之后，调用一次 Fsync 将 OS Cache 中的 Bin Log 强制刷到磁盘</li>
</ul>
<p>Bin Log 有两个常用的使用场景：</p>
<ul>
<li class="">主从复制：MySQL Replication 在 Master 端开启 Bin Log，Master 把它的二进制日志传递给 Slaves 来达到 Master-Slave 数据一致的目的。</li>
<li class="">数据恢复：通过 mysqlbinlog 工具来恢复数据。</li>
</ul>
<p>那么问题来了，Bin Log 和 Redo Log 都是记录的修改之后的值，这两者有什么区别呢？有 Redo Log 为什么还需要 Bin Log 呢？</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="区分-bin-log-和-redo-log">区分 Bin Log 和 Redo Log<a href="https://tanzhuo.xyz/2022/11/15/mysql-zhixing#%E5%8C%BA%E5%88%86-bin-log-%E5%92%8C-redo-log" class="hash-link" aria-label="区分 Bin Log 和 Redo Log的直接链接" title="区分 Bin Log 和 Redo Log的直接链接" translate="no">​</a></h3>
<ul>
<li class="">Bin Log 属于 MySQL Server 层，Redo Log 属于 Engine 层</li>
<li class="">Bin Log 所有引擎都可用，Redo Log 是 InnoDB 独有</li>
<li class="">Bin Log 记录的是逻辑操作，Redo Log 记录的是更新的内容</li>
<li class="">Bin Log 是追加写形成多个文件，Redo Log 是固定大小的几个文件循环写</li>
<li class="">事务执行时多个操作一直向 Redo Log 中写入，最后提交时才写入 Bin Log</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="undo-log回滚日志">Undo Log（回滚日志）<a href="https://tanzhuo.xyz/2022/11/15/mysql-zhixing#undo-log%E5%9B%9E%E6%BB%9A%E6%97%A5%E5%BF%97" class="hash-link" aria-label="Undo Log（回滚日志）的直接链接" title="Undo Log（回滚日志）的直接链接" translate="no">​</a></h2>
<p>在数据修改的时候，不仅记录了 Redo Log，还记录了相对应的 Undo Log，如果因为某些原因导致事务失败或回滚了，可以借助该 Undo Log 进行回滚。Undo Log 是采用段（segment）的方式来记录的，每个 Undo Log 操作在记录的时候占用一个 Undo Log Segment。作用：保存了事务发生之前的数据的一个版本，可以用于回滚，同时可以提供多版本并发控制下的读。需要注意的是，Undo Log 默认存在全局表空间里面，你可以简单地理解成 Undo Log 也是记录在一个 MySQL 的表里面，插入一条 Undo Log 和插入一条普通数据是类似的。也就是说，写 Undo Log 的过程中同样也是要写入 Redo Log 的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="执行链路">执行链路<a href="https://tanzhuo.xyz/2022/11/15/mysql-zhixing#%E6%89%A7%E8%A1%8C%E9%93%BE%E8%B7%AF" class="hash-link" aria-label="执行链路的直接链接" title="执行链路的直接链接" translate="no">​</a></h2>
<p>经过了上面基础知识的铺垫，我们来看一下整个 SQL 的执行链路：</p>
<p>0、这里跳过连接器、解析器、优化器步骤（上面已经描述过）。</p>
<p>1、首先 Undo Log 会记录此次事务的回滚信息（事务ID，回滚指针），以便于事务进行回滚以及对应的事务隔离 MVCC。</p>
<p>2、然后再操作 Buffer Pool 中的数据，在操作的同时也要同步把数据操作信息添加到 Change Buffer。而如果这个数据页还没有在内存中的话，在<strong>不影响数据一致性</strong>的前提下，InnoDB 会先将这些更新操作缓存在 Change Buffer 中，这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候，将数据页读入内存，然后执行 Change Buffer 中与这个页有关的操作。（但某些情况下也需要先将数据页读入内存后再做操作）</p>
<p>3、数据变更操作信息添加到 Change Buffer 中后，操作信息也要同步刷盘到 Redo Log 日志中进行持久存储，以防止宕机导致操作数据丢失。（这里的同步是表示事务提交后才考虑刷盘到磁盘中，事务没提交刷盘没意义。MySQL 默认配置 Redo Log 同步刷盘，如果改成异步刷盘，在突然宕机或掉电情况则会出现数据丢失，内存中 Change Buffer 记录的信息都会丢失）</p>
<p>4、具体的 SQL 操作也需要添加到 Bin Log Cache 中，但 Bin Log Cache 只是记录的逻辑操作信息（具体执行的 DML DDL DQL 语句），没有记录数据变更信息。Bin Log Cache 是每个线程私有的一块内存空间（默认 32K），用于指定存储整个事务生成的 Bin Log Event 的内存大小，对于大事务来讲很可能超过这个参数的设置，则需要开启 Bin Log 临时文件用于存储。</p>
<p>5、客户端事务提交，则进行二阶段提交。首先刷盘 Redo Log 日志记录，并修改 Redo Log 中记录的状态为 prepare；然后刷盘 Bin Log 日志记录，Bin Log 刷盘成功后将会把对应的 Redo Log 状态从 prepare 改为 commit，因为必须保持两边日志的一致性，如果两份日志数据不一致就会导致主从数据不一致以及各种问题。此时就可以响应客户端事务提交成功，但此时数据（脏页）并未落盘，依然存储在 Buffer Pool 中，只是对应的记录日志已经全部成功。</p>
<p>6、MySQL 服务器会异步刷盘（<strong>合适的刷页时机</strong>）内存中 Buffer Pool 的脏页数据，只有把脏页数据刷入磁盘中，自此数据才算真正落盘。在异步刷盘中如果出现任何宕机或断电场景，启动后 InnoDB 都可以根据 Redo Log 重做之前的 Buffer Pool 数据。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="合适的刷页时机">合适的刷页时机<a href="https://tanzhuo.xyz/2022/11/15/mysql-zhixing#%E5%90%88%E9%80%82%E7%9A%84%E5%88%B7%E9%A1%B5%E6%97%B6%E6%9C%BA" class="hash-link" aria-label="合适的刷页时机的直接链接" title="合适的刷页时机的直接链接" translate="no">​</a></h2>
<p>1、Redo Log 写不下了。Redo Log 作为持久化的保障，每次更新操作都必须记录，如果 Redo Log 写不下了，那么就必须进行刷页，将数据同步到磁盘中，然后移动 Redo Log 的 CheckPoint 指针，空出新的位置。如果此时内存不紧张，那么刷完的页不需要淘汰出内存。</p>
<p>2、内存放不下数据页了。查询操作会将磁盘中的数据页读入内存，然后再进行返回，如果内存放不下数据页了，那么就必须淘汰最久未使用的数据页，如果该数据页是脏页，那么必须刷进磁盘然后淘汰。</p>
<p>3、数据库空闲时。如果数据库认为当前是一个空闲状态，那么就会对脏页进行刷页，此时内存如果不紧张，那么刷完后不进行淘汰。</p>
<p>4、数据库正常关闭时。数据库关闭的时候，也会进行刷页，将之前做的修改持久化到硬盘。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="总结">总结<a href="https://tanzhuo.xyz/2022/11/15/mysql-zhixing#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>MySQL 通过一系列日志的顺序写入来提高持久化性能，并且通过 Buffer Pool 组件把磁盘中的数据映射到内存中进行 DML 操作，大幅度提高了吞吐性能。同时采用异步刷盘，规避了不同表、行数据修改导致磁盘随机写入的低性能问题。Redo Log 保证宕机后数据可恢复，Bin Log 支撑主从复制与数据恢复，Undo Log 负责回滚与 MVCC，三者配合二阶段提交，共同保证了性能与数据一致性的平衡。</p>]]></content:encoded>
            <category>DB</category>
        </item>
        <item>
            <title><![CDATA[HTTP 协议发展史]]></title>
            <link>https://tanzhuo.xyz/2022/11/14/http</link>
            <guid>https://tanzhuo.xyz/2022/11/14/http</guid>
            <pubDate>Mon, 14 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[梳理 HTTP 从 0.9 到 3.0 的演进脉络：每一代协议解决了什么问题，又留下了什么问题，以及 QUIC 如何重新设计传输层。]]></description>
            <content:encoded><![CDATA[<p>HTTP 是浏览器与服务端之间最主要的通信协议。HTTP 是应用层协议（7 层），应用层产生的数据会通过传输层协议作为载体，传输到互联网上的其他主机中，而这个载体就是 TCP 协议（3.0 改用 UDP）：基于 TCP 建立连接，然后传输对应的内容信息。</p>
<!-- -->
<p>20 世纪 60 年代，美国国防部高等研究计划署（ARPA）建立了 ARPA 网，这被认为是互联网的起源。70 年代，研究人员基于对 ARPA 网的实践和思考，发明出了著名的 TCP/IP 协议。该协议具有良好的分层结构和稳定的性能，并在 80 年代中期进入了 UNIX 系统内核，促使更多的计算机接入了网络。</p>
<p>1989 年，蒂姆·伯纳斯-李博士发表了一篇论文，提出了在互联网上构建超链接文档系统的构想。在这篇文章中他确立了三项关键技术：URI、HTML、HTTP。</p>
<p>基于这三项技术，超文本系统可以完美地运行在互联网上，李博士把这个系统称为“万维网”（World Wide Web）。于是 HTTP 协议开始在互联网上流行，目前最常见的是 HTTP/1.1 以及 HTTP/2.0。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-11-image-10-e734574d102a56652a23e4ce91a69df4.webp" width="944" height="356" class="img_ev3q"></p>
<p><strong>其实每一代 HTTP 协议的迭代，都是建立在上一代 HTTP 的缺点上进行优化。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http09">HTTP/0.9<a href="https://tanzhuo.xyz/2022/11/14/http#http09" class="hash-link" aria-label="HTTP/0.9的直接链接" title="HTTP/0.9的直接链接" translate="no">​</a></h2>
<p>1991 年 HTTP（HyperText Transfer Protocol，超文本传输协议）正式诞生，当时的版本是 0.9。从名字可以看出，该协议的作用是传输超文本内容 HTML。协议定义了客户端发起请求、服务端响应请求的通信模式。请求报文内容只有 1 行：GET + 请求的文件路径。由于没有区分消息类型，因此只能传输文本信息。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http10">HTTP/1.0<a href="https://tanzhuo.xyz/2022/11/14/http#http10" class="hash-link" aria-label="HTTP/1.0的直接链接" title="HTTP/1.0的直接链接" translate="no">​</a></h2>
<p>随着互联网的发展以及浏览器的出现，单纯的文本内容已经无法满足用户需求了，浏览器希望通过 HTTP 来传输脚本、样式、图片、音频和视频等不同类型的文件，所以在 1996 年 HTTP 更新的 1.0 版本中引入了如下特性：</p>
<ul>
<li class="">增加了 HEAD、POST 等新方法</li>
<li class="">增加了响应状态码，标记可能的错误原因</li>
<li class="">引入了协议版本号概念</li>
<li class="">引入了 HTTP Header（头部）的概念，让 HTTP 处理请求和响应更加灵活</li>
<li class="">传输的数据不再局限于文本</li>
</ul>
<p>其中最核心的改变是增加了头部设定，头部内容以键值对的形式设置。请求头部通过 Accept 字段告诉服务端可以接收的文件类型，响应头部再通过 Content-Type 字段告诉浏览器返回文件的类型。头部字段不仅用于解决不同类型文件传输的问题，也可以实现缓存、认证信息等很多其他功能。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http11">HTTP/1.1<a href="https://tanzhuo.xyz/2022/11/14/http#http11" class="hash-link" aria-label="HTTP/1.1的直接链接" title="HTTP/1.1的直接链接" translate="no">​</a></h2>
<p>随着互联网的迅速发展，HTTP/1.0 也已经无法满足需求，最核心的就是连接问题。具体来说，HTTP/1.0 每进行一次通信，都需要经历<strong>建立连接</strong>、<strong>传输数据</strong>和<strong>断开连接</strong>三个阶段。当一个页面引用了较多的外部文件时，这个建立连接和断开连接的过程就会增加大量网络开销。</p>
<p>HTTP/1.1 中默认使用 Connection: keep-alive，避免了连接反复建立和释放的开销。但服务器必须按照客户端请求的先后顺序依次回送结果，以保证客户端能够区分出每次请求的响应内容——通过头部字段来判断当前请求的数据是否已经全部接收，不允许同时存在两个并行的响应。</p>
<p><strong>为了解决 HTTP/1.0 的问题，1999 年推出的 HTTP/1.1 有以下特点：</strong></p>
<ul>
<li class="">长连接（Connection: keep-alive）：引入了 TCP 连接复用，即一个 TCP 连接默认不关闭，可以被多个请求复用</li>
<li class="">并发连接：对一个域名的请求允许分配多个长连接（缓解了长连接中的「队头阻塞」问题）</li>
<li class="">引入管道机制，一个 TCP 连接可以同时发送多个请求（响应的顺序必须和请求的顺序一致，因此不常用）</li>
<li class="">增加了 PUT、DELETE、OPTIONS、PATCH 等新的方法</li>
<li class="">新增缓存字段（Cache-Control、ETag）</li>
<li class="">请求头中引入了 Range 字段，支持断点续传</li>
<li class="">允许响应数据分块（chunked），利于传输大文件</li>
<li class="">强制要求 Host 头，让互联网主机托管成为可能</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http20">HTTP/2.0<a href="https://tanzhuo.xyz/2022/11/14/http#http20" class="hash-link" aria-label="HTTP/2.0的直接链接" title="HTTP/2.0的直接链接" translate="no">​</a></h2>
<p>HTTP/1.1 通过长连接减少了大量创建/断开连接造成的性能消耗，但是它的并发能力受到限制，表现在两个方面：</p>
<ul>
<li class="">HTTP/1.1 使用持久连接时，一个连接中同一时刻只能处理一个请求。当前的请求没有结束之前，其他的请求只能处于阻塞状态，这种情况被称为「队头阻塞」</li>
<li class="">浏览器为了减轻服务器的压力，限制了同一个域名下的 HTTP 连接数，一般为 6 ~ 8 个</li>
</ul>
<p>2015 年正式发布的 HTTP/2 默认不再使用 ASCII 编码传输，而是改为<strong>二进制数据</strong>，以提升传输效率。</p>
<p>客户端在发送请求时会将每个请求的内容封装成不同的带有编号的二进制帧（Frame），然后将这些帧同时发送给服务端。服务端接收到数据之后，会将相同编号的帧合并为完整的请求信息。同样，服务端返回结果、客户端接收结果也遵循这个帧的拆分与组合的过程。</p>
<p>有了二进制分帧后，对于同一个域，客户端只需要与服务端建立一个连接即可完成通信需求，这种利用一个连接来发送多个请求的方式称为「<strong>多路复用</strong>」，每一条路都被称为一个 stream（流）。</p>
<p>HTTP/2.0 的主要改动包括：</p>
<ul>
<li class="">数据通过二进制协议传输，不再是纯文本</li>
<li class="">多路复用，废弃了 1.1 中的管道</li>
<li class="">使用专用算法压缩头部，减少数据传输量</li>
<li class="">通过设置数据帧的优先级，让服务器优先处理某些请求</li>
<li class="">允许服务器主动向客户端推送数据</li>
<li class="">头部字段全部改为小写；引入了伪头部的概念，出现在头部字段之前，以冒号开头</li>
<li class="">增强了安全性，“事实上”要求加密通信（TLS）</li>
</ul>
<p>HTTP/2.0 虽然已经发布了多年，不过由于 HTTP/1.1 实在太过经典和强势，目前 HTTP/2.0 的普及率还不算高，仍然有很多网站使用的是 HTTP/1.1 版本。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http30">HTTP/3.0<a href="https://tanzhuo.xyz/2022/11/14/http#http30" class="hash-link" aria-label="HTTP/3.0的直接链接" title="HTTP/3.0的直接链接" translate="no">​</a></h2>
<p>当然 HTTP/2 也并非完美。如果客户端或服务端在通信时出现数据包丢失，或者任何一方的网络出现中断，那么整个 TCP 连接就会暂停。</p>
<p>HTTP/2 由于采用二进制分帧进行多路复用，通常只使用一个 TCP 连接进行传输，在丢包或网络中断的情况下，后面的所有数据都会被阻塞。但对于 HTTP/1.1 来说，可以开启多个 TCP 连接，任何一个 TCP 出现问题都不会影响其他连接，剩余的 TCP 连接还可以正常传输数据。这种情况下 HTTP/2 的表现反而不如 HTTP/1。</p>
<p>2018 年 HTTP/3 将底层依赖的 TCP 改成 UDP，从而彻底解决了这个问题。UDP 相对于 TCP 而言最大的特点是传输数据时不需要建立连接，可以同时发送多个数据包，传输效率很高；缺点是没有确认机制来保证对方一定能收到数据。于是 Google 另起炉灶，搞了一个基于 UDP 的 <strong>QUIC 协议</strong>，并使用在了 HTTP/3 上。目前许多浏览器与硬件厂商已经开始兼容 HTTP/3.0 协议。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="quic-协议">QUIC 协议<a href="https://tanzhuo.xyz/2022/11/14/http#quic-%E5%8D%8F%E8%AE%AE" class="hash-link" aria-label="QUIC 协议的直接链接" title="QUIC 协议的直接链接" translate="no">​</a></h2>
<p>百度百科：<a href="https://baike.baidu.com/item/QUIC/17341272?fr=aladdin&amp;ref=tanzhuo.xyz" target="_blank" rel="noopener noreferrer" class="">QUIC_百度百科 (baidu.com)</a></p>
<p>QUIC 谐音 quick，意思就是<strong>快</strong>。它是 Google 提出来的一个基于 UDP 的传输协议，所以 QUIC 又被叫做“<strong>快速 UDP 互联网连接</strong>”。QUIC 的第一个特征就是快，为什么说它快，它到底快在哪呢？</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-更快的连接建立">1) 更快的连接建立<a href="https://tanzhuo.xyz/2022/11/14/http#1-%E6%9B%B4%E5%BF%AB%E7%9A%84%E8%BF%9E%E6%8E%A5%E5%BB%BA%E7%AB%8B" class="hash-link" aria-label="1) 更快的连接建立的直接链接" title="1) 更快的连接建立的直接链接" translate="no">​</a></h3>
<p>我们知道，HTTP 协议在传输层使用 TCP 进行报文传输，而且 HTTPS、HTTP/2.0 还采用了 TLS 协议进行加密，这样就会产生握手带来的连接延迟：TCP 三次握手（一次 RTT）加上 TLS 握手（两次 RTT），如下图所示。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2025-11-a602adffbd744406a31ccf802fb74569-e939433e4bbc48bcff4c9b3fe613dac5.webp" width="1069" height="601" class="img_ev3q"></p>
<p>对于很多短连接场景，这种握手延迟影响较大，而且无法消除。毕竟 RTT 是人类和效率的终极斗争。</p>
<p>相比之下，QUIC 的握手连接更快，因为它使用了 UDP 作为传输层协议，减少了三次握手的时间延迟。而且 QUIC 的加密采用了 TLS 协议的最新版本 TLS 1.3。相对之前的 TLS 1.1-1.2，TLS 1.3 允许客户端无需等待 TLS 握手完成就开始发送应用程序数据，可以支持 1 RTT 和 0 RTT，从而达到<strong>快速建立连接</strong>的效果。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-没有队头阻塞">2) 没有队头阻塞<a href="https://tanzhuo.xyz/2022/11/14/http#2-%E6%B2%A1%E6%9C%89%E9%98%9F%E5%A4%B4%E9%98%BB%E5%A1%9E" class="hash-link" aria-label="2) 没有队头阻塞的直接链接" title="2) 没有队头阻塞的直接链接" translate="no">​</a></h3>
<p>前面说过，HTTP/2.0 虽然解决了应用层的队头阻塞问题，但是其连接还是基于 TCP，无法解决传输层的请求阻塞问题。</p>
<p>而 UDP 本身没有建立连接这个概念，并且 QUIC 使用的 stream 之间是相互隔离的，不会阻塞其他 stream 数据的处理，所以使用 UDP 并不会造成队头阻塞。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-可靠性与更准确的-rtt">3) 可靠性与更准确的 RTT<a href="https://tanzhuo.xyz/2022/11/14/http#3-%E5%8F%AF%E9%9D%A0%E6%80%A7%E4%B8%8E%E6%9B%B4%E5%87%86%E7%A1%AE%E7%9A%84-rtt" class="hash-link" aria-label="3) 可靠性与更准确的 RTT的直接链接" title="3) 可靠性与更准确的 RTT的直接链接" translate="no">​</a></h3>
<p>在 TCP 中，为了保证数据的可靠性，使用了<strong>序号+确认号</strong>机制来实现：一旦带有 synchronize sequence number 的包发送到服务器，服务器都会在一定时间内进行响应；如果过了这段时间没有响应，客户端就会重传这个包，直到服务器收到数据包并作出响应为止。</p>
<blockquote>
<p>那么 TCP 是如何判断它的重传超时时间呢？</p>
</blockquote>
<p>TCP 一般采用的是<strong>自适应重传算法</strong>，这个超时时间会根据往返时间 RTT 动态调整。由于重传时每次都使用相同的序号，无法区分响应对应的是原始包还是重传包，导致 RTT 的计算结果不太准确。</p>
<p>虽然 QUIC 没有使用 TCP 协议，但是它也保证了可靠性。QUIC 实现可靠性的机制是 <strong>Packet Number</strong>，这个序列号可以认为是 synchronize sequence number 的替代者，并且也是递增的。与 syn 不同的是，不管服务器有没有接收到数据包，这个 Packet Number 都会 +1；而 syn 只有在服务器发送 ack 响应之后才会 +1。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2025-11-quic-35e9b29c19f3401c32ef72eaa77d6abb.webp" width="1600" height="946" class="img_ev3q"></p>
<p>比如有一个 PN = 10 的数据包在发送的过程中由于某些原因迟迟没到服务器，那么客户端会重传一个 PN = 11 的数据包。经过一段时间后客户端收到 PN = 10 的响应，再回送响应报文，此时的 RTT 就是 PN = 10 这个数据包在网络中的生存时间，这样计算相对比较准确。</p>
<blockquote>
<p>虽然 QUIC 保证了数据包的可靠性，但是数据的可靠性是如何保证的呢？</p>
</blockquote>
<p>QUIC 引入了一个 <strong>stream offset</strong> 的概念。一个 stream 可以传输多个 stream offset，每个 stream offset 其实就是一个 PN 标识的数据。即使某个 PN 标识的数据丢失，PN +1 后重传的仍旧是原来 PN 所标识的数据，等到所有 PN 标识的数据发送到服务器，就会进行重组，以此来保证数据可靠性。到达服务器的 stream offset 会按照顺序进行组装，这同时也保证了数据的顺序性。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-可插拔的拥塞控制">4) 可插拔的拥塞控制<a href="https://tanzhuo.xyz/2022/11/14/http#4-%E5%8F%AF%E6%8F%92%E6%8B%94%E7%9A%84%E6%8B%A5%E5%A1%9E%E6%8E%A7%E5%88%B6" class="hash-link" aria-label="4) 可插拔的拥塞控制的直接链接" title="4) 可插拔的拥塞控制的直接链接" translate="no">​</a></h3>
<p>众所周知，TCP 协议的具体实现是由操作系统内核来完成的，应用程序只能使用，不能对内核进行修改。随着移动端和越来越多的设备接入互联网，性能逐渐成为一个非常重要的衡量指标。虽然移动网络发展得非常快，但是用户端的更新却非常缓慢，我仍然看见很多地区的计算机还在使用 XP 系统，尽管它早已停止发展多年。服务端系统不依赖用户升级，但由于操作系统升级涉及底层软件和运行库的更新，所以也比较保守和缓慢。</p>
<p>QUIC 协议的一个重要特点就是<strong>可插拔性</strong>，能够动态更新和升级。QUIC 在应用层实现了拥塞控制算法，不需要操作系统和内核的支持，遇到拥塞控制算法切换时，只需要在服务器重新加载一遍即可，不需要停机和重启。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-流量控制与安全性">5) 流量控制与安全性<a href="https://tanzhuo.xyz/2022/11/14/http#5-%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6%E4%B8%8E%E5%AE%89%E5%85%A8%E6%80%A7" class="hash-link" aria-label="5) 流量控制与安全性的直接链接" title="5) 流量控制与安全性的直接链接" translate="no">​</a></h3>
<p>我们知道 TCP 的流量控制是通过<strong>滑动窗口</strong>来实现的。QUIC 也实现了流量控制，同样使用窗口更新 <code>window_update</code> 来告诉对端它可以接受的字节数。</p>
<p>TCP 协议头部没有经过加密和认证，所以在传输的过程中很可能被篡改。与之不同的是，QUIC 中的报文头部都经过认证，报文也经过加密处理。这样只要对 QUIC 的报文有任何修改，接收端都能够及时发现，保证了安全性。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-quic-的优势总结">6) QUIC 的优势总结<a href="https://tanzhuo.xyz/2022/11/14/http#6-quic-%E7%9A%84%E4%BC%98%E5%8A%BF%E6%80%BB%E7%BB%93" class="hash-link" aria-label="6) QUIC 的优势总结的直接链接" title="6) QUIC 的优势总结的直接链接" translate="no">​</a></h3>
<p>总的来说，QUIC 具有下面这些优势：</p>
<ul>
<li class="">使用 UDP 协议，不需要三次握手建立连接，同时也缩短了 TLS 建立连接的时间。</li>
<li class="">解决了队头阻塞问题。</li>
<li class="">实现动态可插拔，在应用层实现了拥塞控制算法，可以随时切换。</li>
<li class="">报文头和报文体分别进行认证和加密处理，保障安全性。</li>
<li class="">连接能够平滑迁移。</li>
</ul>
<p>连接平滑迁移指的是，你的手机或者移动设备在 4G 信号和 WiFi 等网络之间切换时，不会断线重连，用户甚至没有任何感知，能够直接实现平滑的信号切换。目前 QUIC 协议已经被写进了 RFC 9000 中。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/11/14/http#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>回顾 HTTP 的演进：0.9 只能传输 HTML 文本，1.0 引入头部让传输内容多样化，1.1 用长连接解决了反复建连的开销，2.0 用二进制分帧和多路复用突破了并发瓶颈。但只要底层还是 TCP，传输层的队头阻塞就无法根除，于是 HTTP/3 换用基于 UDP 的 QUIC，把连接建立、可靠传输和拥塞控制都搬到了应用层重新设计。每一代协议都在为上一代的短板买单，相信 HTTP/3.0 会在越来越多的场景被使用。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[JVM 线程池扩容机制]]></title>
            <link>https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong</link>
            <guid>https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong</guid>
            <pubDate>Mon, 14 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[HotSpot 中 Java 线程与内核线程一对一映射，创建销毁成本高。梳理 ThreadPoolExecutor 的核心参数、任务处理与扩容回收流程，以及无界队列导致配置失效的坑。]]></description>
            <content:encoded><![CDATA[<p>在 <strong>HotSpot VM 的线程模型</strong>中，Java 线程与操作系统线程是一对一映射关系（JDK19 之后虽然引入了虚拟线程，但传统线程仍然是这种模型）。也就是说，一个 Java 线程会对应一个操作系统内核线程（KLT / LWP）。</p>
<!-- -->
<p>当 Java 创建线程时，需要调用操作系统内核 API 创建对应的内核线程，操作系统需要为其分配栈空间、调度信息等资源；当 Java 线程结束时，对应的内核线程也会被回收。因此<strong>线程创建和销毁成本较高，线程数量也不能无限增加</strong>。</p>
<p>当线程数量过多时：</p>
<ol>
<li class="">线程创建会带来较高的系统开销</li>
<li class="">CPU 在多个线程之间频繁进行 <strong>上下文切换（Context Switch）</strong></li>
<li class="">大量线程可能导致 <strong>系统性能下降甚至 OOM</strong></li>
</ol>
<p>因此，在实际开发中通常使用 <strong>线程池（ThreadPoolExecutor）</strong> 来统一管理线程生命周期。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="threadpoolexecutor-核心参数">ThreadPoolExecutor 核心参数<a href="https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong#threadpoolexecutor-%E6%A0%B8%E5%BF%83%E5%8F%82%E6%95%B0" class="hash-link" aria-label="ThreadPoolExecutor 核心参数的直接链接" title="ThreadPoolExecutor 核心参数的直接链接" translate="no">​</a></h2>













































<table><thead><tr><th>参数</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td>corePoolSize</td><td>int</td><td>核心线程数，线程池中长期保留的线程数量</td></tr><tr><td>maximumPoolSize</td><td>int</td><td>最大线程数，当任务队列满时允许创建的最大线程数量</td></tr><tr><td>keepAliveTime</td><td>long</td><td>非核心线程的最大空闲存活时间</td></tr><tr><td>unit</td><td>TimeUnit</td><td>keepAliveTime 的时间单位</td></tr><tr><td>workQueue</td><td>BlockingQueue</td><td>任务队列，用于存放等待执行的任务</td></tr><tr><td>threadFactory</td><td>ThreadFactory</td><td>线程创建工厂，用于自定义线程名称、优先级等</td></tr><tr><td>handler</td><td>RejectedExecutionHandler</td><td>拒绝策略，当任务无法被执行时的处理方式</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="线程池的任务处理流程">线程池的任务处理流程<a href="https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E4%BB%BB%E5%8A%A1%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="线程池的任务处理流程的直接链接" title="线程池的任务处理流程的直接链接" translate="no">​</a></h2>
<p>当有新的任务提交到线程池时，线程池会按照以下顺序处理。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1先使用核心线程执行任务">1）先使用核心线程执行任务<a href="https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong#1%E5%85%88%E4%BD%BF%E7%94%A8%E6%A0%B8%E5%BF%83%E7%BA%BF%E7%A8%8B%E6%89%A7%E8%A1%8C%E4%BB%BB%E5%8A%A1" class="hash-link" aria-label="1）先使用核心线程执行任务的直接链接" title="1）先使用核心线程执行任务的直接链接" translate="no">​</a></h3>
<p>如果当前运行线程数 <strong>小于</strong> <code>corePoolSize</code>，线程池会直接创建新线程执行任务。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2核心线程已满--任务进入队列">2）核心线程已满 → 任务进入队列<a href="https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong#2%E6%A0%B8%E5%BF%83%E7%BA%BF%E7%A8%8B%E5%B7%B2%E6%BB%A1--%E4%BB%BB%E5%8A%A1%E8%BF%9B%E5%85%A5%E9%98%9F%E5%88%97" class="hash-link" aria-label="2）核心线程已满 → 任务进入队列的直接链接" title="2）核心线程已满 → 任务进入队列的直接链接" translate="no">​</a></h3>
<p>如果当前线程数 <strong>已经达到</strong> <code>corePoolSize</code>，新任务不会立即触发线程创建，而是进入 <code>BlockingQueue</code> <strong>任务队列</strong> 等待执行。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3队列已满--扩容线程池">3）队列已满 → 扩容线程池<a href="https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong#3%E9%98%9F%E5%88%97%E5%B7%B2%E6%BB%A1--%E6%89%A9%E5%AE%B9%E7%BA%BF%E7%A8%8B%E6%B1%A0" class="hash-link" aria-label="3）队列已满 → 扩容线程池的直接链接" title="3）队列已满 → 扩容线程池的直接链接" translate="no">​</a></h3>
<p>如果任务队列已经达到最大容量，且当前线程数 <strong>小于</strong> <code>maximumPoolSize</code>，此时线程池会 <strong>创建新的线程处理任务</strong>。这些线程称为 <strong>非核心线程</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4超过最大线程数--拒绝策略">4）超过最大线程数 → 拒绝策略<a href="https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong#4%E8%B6%85%E8%BF%87%E6%9C%80%E5%A4%A7%E7%BA%BF%E7%A8%8B%E6%95%B0--%E6%8B%92%E7%BB%9D%E7%AD%96%E7%95%A5" class="hash-link" aria-label="4）超过最大线程数 → 拒绝策略的直接链接" title="4）超过最大线程数 → 拒绝策略的直接链接" translate="no">​</a></h3>
<p>如果队列已满，且当前线程数已经达到 <code>maximumPoolSize</code>，此时线程池会触发 <strong>RejectedExecutionHandler 拒绝策略</strong>。</p>
<p>常见策略：</p>
<ul>
<li class="">AbortPolicy（默认，直接抛异常）</li>
<li class="">CallerRunsPolicy</li>
<li class="">DiscardPolicy</li>
<li class="">DiscardOldestPolicy</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="keepalivetime-的作用">keepAliveTime 的作用<a href="https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong#keepalivetime-%E7%9A%84%E4%BD%9C%E7%94%A8" class="hash-link" aria-label="keepAliveTime 的作用的直接链接" title="keepAliveTime 的作用的直接链接" translate="no">​</a></h2>
<p>当线程池中的 <strong>非核心线程</strong>在 <code>keepAliveTime</code> 时间内 <strong>没有执行任何任务</strong> 时，该线程会被销毁。因此核心线程默认不会销毁，非核心线程空闲超过 <code>keepAliveTime</code> 会被回收。这样线程池可以在 <strong>高峰期扩容，在低负载时回收线程</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一个非常容易踩的坑">一个非常容易踩的坑<a href="https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong#%E4%B8%80%E4%B8%AA%E9%9D%9E%E5%B8%B8%E5%AE%B9%E6%98%93%E8%B8%A9%E7%9A%84%E5%9D%91" class="hash-link" aria-label="一个非常容易踩的坑的直接链接" title="一个非常容易踩的坑的直接链接" translate="no">​</a></h2>
<p>如果 <code>workQueue</code> 使用 <strong>无界队列（如 LinkedBlockingQueue 默认构造）</strong>，任务会一直进入队列，队列几乎不会满。于是线程池永远走不到扩容那一步，<code>maximumPoolSize</code> 和 <code>keepAliveTime</code> <strong>基本不会生效</strong>。这也是很多线上系统 <strong>线程池配置失效</strong> 的常见原因。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/11/14/jvm-thread-kuorong#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>线程池的扩容路径可以概括为：核心线程 → 任务队列 → 非核心线程 → 拒绝策略。<code>maximumPoolSize</code> 只有在队列满之后才会生效，<code>keepAliveTime</code> 则决定了非核心线程的回收时机。配置线程池时，队列类型的选择和线程数参数同样重要，无界队列会让扩容与拒绝策略形同虚设。理解这条完整链路，线上"线程池参数不生效"的大多数问题就有了答案。</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[布隆过滤器详解]]></title>
            <link>https://tanzhuo.xyz/2022/11/13/bu-long-guo-lu-qi</link>
            <guid>https://tanzhuo.xyz/2022/11/13/bu-long-guo-lu-qi</guid>
            <pubDate>Sun, 13 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[从位图与多重哈希讲起,梳理布隆过滤器的判定特点、误判率的来源与权衡,以及为什么原始结构不支持删除、计数布隆过滤器如何补上这一课。]]></description>
            <content:encoded><![CDATA[<p>做缓存的人多半绕不开一个问题:请求打过来的 key 在缓存和数据库里都不存在,查询却每次都穿透到数据库。要挡住这类请求,先得能快速判断"这个 key 到底存不存在",布隆过滤器就是为这件事而生的。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="背景介绍">背景介绍<a href="https://tanzhuo.xyz/2022/11/13/bu-long-guo-lu-qi#%E8%83%8C%E6%99%AF%E4%BB%8B%E7%BB%8D" class="hash-link" aria-label="背景介绍的直接链接" title="背景介绍的直接链接" translate="no">​</a></h2>
<p>在 redis 缓存的场景里,如果有人拿大量根本不存在的 key 来请求,缓存永远不命中,压力全部落到数据库上,这就是常说的缓存穿透。想拦截这类请求,最朴素的思路是把所有合法的 key 存进一个集合,查询前先判断一下。但 key 的数量一大,用 HashSet 这类结构存全量 key,内存开销很难接受。布隆过滤器用极小的空间换来一个"可能有误差但足够快"的存在性判断,正好卡在这个需求点上。</p>
<p>布隆过滤器（Bloom Filter）是1970年由布隆提出的，它实际上是由一个很长的二进制向量和一系列随机映射函数组成。布隆过滤器使用场景一般是防止redis缓存穿透，使用布隆过滤器可以更好的节省空间，并且快速定位元素是否存在。</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="763" height="403" class="img_ev3q"></p>
<p>布隆过滤器通过 hash key 来定位 位图（Bitmap，其实就是bit数组）中对应的下标，并且在这个数组中每一个位置只有0和1两种状态，每个位置只占用1个比特（bit），其中0表示没有元素存在，1表示有元素存在。</p>
<p>具体来说，写入一个元素时，会用 k 个不同的哈希函数分别对 key 计算，得到 k 个下标，把位图中这 k 个位置全部置为 1。查询的时候走同样的流程：k 个位置只要有任何一个是 0，说明这个 key 从来没被写入过；只有 k 个位置全部为 1，才判断"可能存在"。整个过程只有哈希计算和位运算，时间复杂度是 O(k)，与已存入的元素数量无关，这也是它查询快的原因。</p>
<p><strong>注意：两个不同的key哈希出来所对应的下标位可能存在部分重复，这样可以减少内存的占用，但也有概率会出现哈希碰撞，原本不存在的key哈希之后位图中都为1的情况。</strong></p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="742" height="390" class="img_ev3q"></p>
<p>所以通过上面的现象，我们从布隆过滤器的角度可以得出布隆过滤器主要有2大特点：</p>
<p><strong>1、如果布隆过滤器判断一个元素存在，那么这个元素可能存在。</strong></p>
<p><strong>2、如果布隆过滤器判断一个元素不存在，那么这个元素一定不存在。</strong></p>
<p>这两条特点决定了它的用法：它适合做"前置拦截"，把一定不存在的请求挡在外面；但不能拿它的"存在"结论当真，判断存在之后仍然要走缓存或数据库做二次确认。</p>
<p>因为布隆过滤器中总是会存在误判率，因为哈希碰撞是不可能百分百避免的。布隆过滤器对这种误判率称之为假阳性概率，即：False Positive Probability，简称为fpp。</p>
<p>误判率的大小主要由三个因素决定：位图的长度 m、哈希函数的个数 k、以及已经写入的元素数量 n。写入的元素越多，位图中被置为 1 的比特就越多，一个陌生 key 的 k 个位置"恰好全是 1"的概率也就越高。所以布隆过滤器在创建时通常要求预估元素规模和期望的 fpp，由实现（比如 Guava 的 BloomFilter）反推出合适的 m 和 k。</p>
<p>为避免fpp，我们可以加大位图长度或者多次哈希来减少冲突概率，但加大位图需要更多的内存空间，多次哈希又需要更多的cpu资源，需要做好对应的资源开销预算，选择合适的方式。这本质上是一个空间、CPU 与准确率三者之间的权衡，没有免费的午餐。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="如何进行删除对应key-">如何进行删除对应key ？<a href="https://tanzhuo.xyz/2022/11/13/bu-long-guo-lu-qi#%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E5%88%A0%E9%99%A4%E5%AF%B9%E5%BA%94key-" class="hash-link" aria-label="如何进行删除对应key ？的直接链接" title="如何进行删除对应key ？的直接链接" translate="no">​</a></h2>
<p>上面的布隆过滤器我们知道，判断一个元素存在就是判断对应下标位置是否为1来确定的，但是如果要删除掉一个元素是不能直接把1改成0的，因为这个位置可能存在其他元素，<strong>所以原始的布隆过滤器是无法支持删除操作的</strong>，如果需要支持删除，那我们应该怎么做呢？最简单的做法就是加一个计数器，就是说位数组的每个位如果不存在就是0，存在几个元素就存具体的数字，而不仅仅只是存1，那么这就有一个问题，本来存1就是一位就可以满足了，但是如果要存具体的数字比如说2，那就需要2位了，所以带有计数器的布隆过滤器会占用更大的空间。</p>
<p>这种改良版本一般叫计数布隆过滤器（Counting Bloom Filter）：写入时把 k 个位置的计数器各加 1，删除时各减 1，只有计数器减到 0 才表示该位置真正空了。代价除了空间膨胀（每个位置从 1 bit 变成若干 bit），还多了一个隐患——如果误删了一个从未写入过的元素，计数器被错误地减掉，就可能把本来存在的元素"删没了"，引入假阴性。所以删除操作只应该对确认写入过的元素执行。</p>
<p>如果业务上删除需求不强，还有一种更省事的做法：不删除，定期用最新的全量数据重建一个新的布隆过滤器，替换旧的。重建期间旧过滤器继续服务，切换是原子的，很多缓存穿透场景用的就是这个思路。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/11/13/bu-long-guo-lu-qi#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）容量要预估充分。布隆过滤器创建之后位图长度就固定了，元素写入超过预估规模后，fpp 会显著恶化，而且没法在线扩容，只能重建。</p>
<p>2）"存在"不等于真的存在。误判放行的 key 依然会打到数据库，布隆过滤器只能大幅减少穿透，不能百分之百消灭，数据库侧的兜底（比如缓存空值）仍然需要。</p>
<p>3）哈希函数要和写入方保持一致。如果多个服务共用一个布隆过滤器（比如放在 redis 里），各端的哈希实现和参数必须完全相同，否则判断结果就是错的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/11/13/bu-long-guo-lu-qi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>布隆过滤器用一个位图加 k 个哈希函数，换来了空间占用极小、查询 O(k) 的存在性判断，代价是存在假阳性、且原始结构不支持删除。它的两条判定特点——"说不存在就一定不存在，说存在只是可能存在"——决定了它最适合做前置拦截，典型场景就是防缓存穿透。需要删除时可以换计数布隆过滤器，或者干脆走定期重建的路子;而容量预估和 fpp 的设定,则要在内存、CPU 和准确率之间按自己的业务算一笔账。</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[隐藏性能杀手之「伪共享」]]></title>
            <link>https://tanzhuo.xyz/2022/11/03/wei-gong-xiang</link>
            <guid>https://tanzhuo.xyz/2022/11/03/wei-gong-xiang</guid>
            <pubDate>Thu, 03 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[从 CPU 多级缓存与 Cache Line 讲起，分析多核并发下伪共享问题的成因，以及缓存行填充等常见的规避手段。]]></description>
            <content:encoded><![CDATA[<p>随着CPU工艺的发展，目前的高端CPU已经存在几十核心百多个线程，并为CPU设计出了一二三级缓存。CPU的核心有了这些缓存就可以加快数据的处理，从而减少访问内存的频率，这样CPU的计算性能可以进一步得到提高。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cpu-缓存结构与访问延迟">CPU 缓存结构与访问延迟<a href="https://tanzhuo.xyz/2022/11/03/wei-gong-xiang#cpu-%E7%BC%93%E5%AD%98%E7%BB%93%E6%9E%84%E4%B8%8E%E8%AE%BF%E9%97%AE%E5%BB%B6%E8%BF%9F" class="hash-link" aria-label="CPU 缓存结构与访问延迟的直接链接" title="CPU 缓存结构与访问延迟的直接链接" translate="no">​</a></h2>
<p>CPU 的缓存结构以及内存、硬盘的层次关系如下图：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-11-image-7-55a2ece8a91bbe178b8999954644bf4f.webp" width="1156" height="1166" class="img_ev3q"></p>
<p>众所周知，CPU 访问一次内存的开销是非常大的，想要获取一次磁盘上的数据更是需要等待较长的时间。虽然目前已经有 mmap 这类技术来缓解这种情况，但总体来说 CPU 的计算性能是整个计算机结构中的天花板，其他硬件在数据传输速度上对比起来就显得拖后腿。我们来看一下 CPU 访问各级存储的延迟：</p>









































<table><thead><tr><th>存储器</th><th>存储介质</th><th>介质成本(美元)</th><th>随机访问延迟</th></tr></thead><tbody><tr><td>L1 Cache</td><td>SRAM</td><td>7</td><td>1ns</td></tr><tr><td>L2 Cache</td><td>SRAM</td><td>7</td><td>4ns</td></tr><tr><td>Memory</td><td>DRAM</td><td>0.015</td><td>100ns</td></tr><tr><td>Disk</td><td>SSD (NAND)</td><td>0.0004</td><td>150us</td></tr><tr><td>Disk</td><td>HDD</td><td>0.00004</td><td>10ms</td></tr></tbody></table>
<p>可以看出，外部存储设备容量越大、成本越低，能存的数据更多，但访问速度也更慢；访问速度越快的设备造价越高。CPU 访问 L1 Cache 的速度比访问内存快 100 倍，这就是 CPU 里会有 L1~L3 Cache 的原因——把 Cache 作为 CPU 与内存之间的缓存层，减少对内存的访问频率。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="缓存行-cache-line">缓存行 Cache Line<a href="https://tanzhuo.xyz/2022/11/03/wei-gong-xiang#%E7%BC%93%E5%AD%98%E8%A1%8C-cache-line" class="hash-link" aria-label="缓存行 Cache Line的直接链接" title="缓存行 Cache Line的直接链接" translate="no">​</a></h2>
<p>CPU 从内存中读取数据到 Cache 的时候，并不是一个字节一个字节读取，而是一块一块地读取，这一块一块的数据被称为 Cache Line（缓存行）。所以 <strong>Cache Line 是 CPU 从内存读取数据到 Cache 的单位</strong>。</p>
<p>至于 Cache Line 的大小，在 Linux 系统可以用下面的命令查看：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看 cpu0 的 L1 缓存行大小，单位为字节</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">$ </span><span class="token function" style="color:hsl(207, 82%, 66%)">cat</span><span class="token plain"> /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size</span><br></div></code></pre></div></div>
<p>主流 CPU 的 Cache Line 一般是 64 字节，而苹果研发的 M1 芯片，其 Cache Line 已经是 128 字节。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="什么是伪共享">什么是伪共享<a href="https://tanzhuo.xyz/2022/11/03/wei-gong-xiang#%E4%BB%80%E4%B9%88%E6%98%AF%E4%BC%AA%E5%85%B1%E4%BA%AB" class="hash-link" aria-label="什么是伪共享的直接链接" title="什么是伪共享的直接链接" translate="no">​</a></h2>
<p>我们知道了内存中的数据是以 Cache Line 为单位拷贝到 CPU Cache 中的，接下来就来介绍 Cache 伪共享是什么，又如何避免这个问题。</p>
<p>假设有一个双核心的 CPU，两个核心并行运行着两个不同的线程，它们同时从内存中读取两个不同的数据：类型为 <code>long</code> 的变量 A 和 B（各 8 字节）。CPU1 想要获取 A 进行修改，CPU2 想要获取 B 进行修改，但这两个数据的地址在<strong>物理内存(DRAM)上是连续的</strong>。如果 Cache Line 的大小是 64 字节，并且变量 A 在 Cache Line 的开头位置，那么 B 就紧跟在 A 的地址后面，两个数据位于<strong>同一个 Cache Line 中</strong>。又因为 Cache Line 是 CPU 从内存读取数据到 Cache 的单位，所以这两个数据会被同时读入到两个 CPU 核心各自的 Cache 中。</p>
<p>我们来思考一个问题：如果这两个不同核心的线程分别修改 A、B 的数据，比如 CPU1 的线程只修改了变量 A，CPU2 的线程只修改了变量 B，会发生什么呢？</p>
<p>此时两边核心各自的 Cache Line 中数据就会不一致，这很有可能影响到最终的计算结果。为了保障两边的 Cache Line 数据一致，于是出现了多核缓存一致性的 MESI 协议。</p>
<p>为了遵守 MESI，一个核心修改数据后不得不通知其他核心：你持有的 Cache Line 已经过期，需要重新去内存中获取一遍，这就导致了性能损耗。</p>
<p>当多个 CPU 核心并发修改同一个 Cache Line 中互不相干的变量时，就会出现伪共享（False Sharing）问题。明明各自操作的是不同数据，却因为共享同一个缓存行而互相拖累：一个核心更新了 Cache Line 中的数据，其他核心就要重新加载整行数据，这个过程会耗费不少时间。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="如何避免伪共享">如何避免伪共享<a href="https://tanzhuo.xyz/2022/11/03/wei-gong-xiang#%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E4%BC%AA%E5%85%B1%E4%BA%AB" class="hash-link" aria-label="如何避免伪共享的直接链接" title="如何避免伪共享的直接链接" translate="no">​</a></h2>
<p>最常见的手段是 Cache Line 填充（Padding）。原理是在变量之间填充一些无用的数据，把热点变量隔离到不同的缓存行中，使得不同 CPU 核心访问的数据不再落在同一个 Cache Line 上，从而避免伪共享。</p>
<p>此外还有一些编程层面的技巧，比如把需要频繁修改的数据主动安排到不同的 Cache Line 中，或者使用线程本地存储（Thread Local Storage，TLS）来避免不同线程之间的数据竞争。硬件层面，Intel 提供了 Cache Allocation Technology（CAT），可以让程序员显式地控制 Cache 的分配方式；还有 Cache Partitioning 技术，可以将 Cache 分成多个区域，每个区域分配给不同的线程使用，同样能规避不同线程之间的数据竞争和伪共享问题。</p>
<p>除了伪共享，还有一些其他与 Cache 相关的性能问题需要注意。比如 Cache Miss：当 CPU 需要访问一个不在 Cache 中的数据时，就会产生 Cache Miss，此时 CPU 需要从内存中读取数据，这个过程耗时较长。为了减少 Cache Miss，可以借助预取（Prefetching）、合理的 Cache 替换算法（Cache Replacement Algorithm）等手段。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/11/03/wei-gong-xiang#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Cache 是计算机体系结构中提升性能的关键一环，而 Cache Line 作为读写单位，也带来了伪共享这样的隐藏陷阱。多个核心并发修改同一缓存行中的不同变量时，MESI 协议的一致性开销会让性能明显下降。规避思路也很直接：通过填充或数据布局，让热点变量各自独占一个缓存行。在编写高并发代码时，多留意数据在内存中的排布方式，往往能避开这类看不见的性能杀手。</p>]]></content:encoded>
            <category>Java</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[安全容器运行时 Kata]]></title>
            <link>https://tanzhuo.xyz/2022/11/01/an-quan-rong-qi-zhi-k-a-fa</link>
            <guid>https://tanzhuo.xyz/2022/11/01/an-quan-rong-qi-zhi-k-a-fa</guid>
            <pubDate>Tue, 01 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[传统容器与宿主机共享内核，隔离性上存在安全隐患。本文介绍 Kata 通过轻量级虚拟机实现内核级隔离的思路，以及它的特点与不足。]]></description>
            <content:encoded><![CDATA[<p>传统的轻量级容器基于 Linux namespace 和 cgroup 进行容器隔离，在带来轻量、简洁、高效的同时，也带来了安全上的隐患。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="共享内核的隐患">共享内核的隐患<a href="https://tanzhuo.xyz/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#%E5%85%B1%E4%BA%AB%E5%86%85%E6%A0%B8%E7%9A%84%E9%9A%90%E6%82%A3" class="hash-link" aria-label="共享内核的隐患的直接链接" title="共享内核的隐患的直接链接" translate="no">​</a></h2>
<p>容器虽然提供了一个与系统中其它进程资源相隔离的执行环境，但与宿主机是共享内核的。如果其中某一个容器被攻击，进而把宿主机的内核攻掉了，那么其他容器势必都会崩溃，后果不堪设想。</p>
<p>那么有没有既安全又比较高效的容器技术呢？于是便出现了 Kata 容器技术。</p>
<p><a href="https://katacontainers.io/" target="_blank" rel="noopener noreferrer" class="">Kata Containers - Open Source Container Runtime Software</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="kata-的实现思路">Kata 的实现思路<a href="https://tanzhuo.xyz/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#kata-%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="Kata 的实现思路的直接链接" title="Kata 的实现思路的直接链接" translate="no">​</a></h2>
<p>Kata 实际上是通过创建轻量级虚拟机实现容器之间的资源隔离，再在虚拟机中运行容器运行时。这样容器就在专用内核中运行，提供网络、I/O 和内存的隔离，并可以通过虚拟化 VT 扩展利用硬件强制隔离。提供安全性的同时，它仍然有很高的性能。虽然需要损失部分性能（目前版本已经到了 3.0，性能已有大幅度提高），但好处也是显而易见的：单个容器出现问题、被恶意攻击，绝对不会影响到宿主机，间接保护了其他运行中的容器。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="开源地址">开源地址<a href="https://tanzhuo.xyz/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#%E5%BC%80%E6%BA%90%E5%9C%B0%E5%9D%80" class="hash-link" aria-label="开源地址的直接链接" title="开源地址的直接链接" translate="no">​</a></h2>
<p>Github : <a href="https://github.com/kata-containers/kata-containers" target="_blank" rel="noopener noreferrer" class="">https://github.com/kata-containers/kata-containers</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-11-image-80526d4327e0fdb37b1097e86ec4fe97.webp" width="640" height="309" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="特点">特点<a href="https://tanzhuo.xyz/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#%E7%89%B9%E7%82%B9" class="hash-link" aria-label="特点的直接链接" title="特点的直接链接" translate="no">​</a></h2>
<p>Kata Containers 还具有以下特点：</p>
<ol>
<li class="">灵活性：Kata Containers 可以在多种操作系统和容器运行时环境中运行。它支持多种操作系统，如 Linux、Windows 和 FreeBSD，并且可以与多种容器运行时环境，如 Docker、CRI-O 和 containerd 兼容。</li>
<li class="">可扩展性：Kata Containers 可以轻松地扩展到大规模部署中。它可以集成到 Kubernetes 等容器编排平台中，从而可以管理大量的容器。</li>
<li class="">安装简便：Kata Containers 的安装非常简单。它可以通过包管理工具（如 apt、yum 或 zypper）进行安装，并且有一个方便的命令行工具，可以帮助用户快速配置和启动容器。</li>
<li class="">社区支持：Kata Containers 有一个活跃的开源社区，由全球各地的开发人员共同维护和发展。这个社区提供了各种文档、教程和支持，可以帮助用户更好地了解和使用 Kata Containers。</li>
<li class="">安全：在专用内核中运行，提供网络、I/O 和内存的隔离，并且可以利用具有虚拟化 VT 扩展的硬件强制隔离。</li>
<li class="">兼容性：支持行业标准，包括 OCI 容器格式、Kubernetes CRI 接口，以及传统虚拟化技术。</li>
<li class="">轻量：消除了在成熟的 VM 中嵌套容器的要求。</li>
<li class="">高性能：提供与标准 Linux 容器接近相同的性能（性能还是存在损耗）。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="不足">不足<a href="https://tanzhuo.xyz/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#%E4%B8%8D%E8%B6%B3" class="hash-link" aria-label="不足的直接链接" title="不足的直接链接" translate="no">​</a></h2>
<p>Kata Containers 的部分缺点：</p>
<ol>
<li class="">性能损失：虽然 Kata Containers 的性能已经大幅度提高，但相对于传统的 Linux 容器来说，它仍然会有一定的性能损失。这是因为 Kata Containers 使用了轻量级虚拟机来实现容器隔离，从而增加了一些额外的开销。</li>
<li class="">安装和配置复杂：与传统的 Linux 容器相比，Kata Containers 的安装和配置可能会更加复杂。用户需要安装和配置虚拟化软件，如 QEMU 和 KVM，以及 Kata Containers 运行时等组件。</li>
<li class="">对硬件要求高：Kata Containers 需要使用支持虚拟化 VT 扩展的硬件，这可能会限制它在一些老旧的服务器上的使用。</li>
<li class="">生态系统不够成熟：虽然 Kata Containers 有一个活跃的开源社区，但与一些其他容器技术相比，它的生态系统可能还不够成熟。这意味着用户可能需要自己编写一些工具或插件来满足其特定的需求。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Kata 的核心思路是用轻量级虚拟机换取内核级隔离：每个容器跑在专用内核里，攻破单个容器不会波及宿主机和其他容器。代价是一定的性能损耗、更复杂的安装配置，以及对硬件虚拟化能力的要求。它并不是要取代传统容器，而是提供了一种新的取舍。如果业务涉及多租户或对隔离性要求较高，值得为这部分负载启用 Kata；反之，标准 Linux 容器仍是更简单的选择。</p>]]></content:encoded>
            <category>Docker</category>
            <category>K8S</category>
        </item>
        <item>
            <title><![CDATA[Istio 之流量镜像]]></title>
            <link>https://tanzhuo.xyz/2022/11/01/is-ti-o</link>
            <guid>https://tanzhuo.xyz/2022/11/01/is-ti-o</guid>
            <pubDate>Tue, 01 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[在 k8s 集群中借助 Istio 的 Envoy 代理实现流量镜像:把真实请求复制一份发给影子服务,响应被丢弃,对业务完全透明。整理原理、VirtualService 配置写法与几个实际踩坑点。]]></description>
            <content:encoded><![CDATA[<p>在 k8s 集群中部署 Istio 作为 Service Mesh 之后,利用 Envoy 代理转发流量的特性,只需在配置文件里加几行就能开启流量镜像。镜像流量的响应不会返回给客户端,对原有业务代码完全透明,用得当的话可以在生产环境发挥很大价值。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="什么是流量镜像">什么是流量镜像<a href="https://tanzhuo.xyz/2022/11/01/is-ti-o#%E4%BB%80%E4%B9%88%E6%98%AF%E6%B5%81%E9%87%8F%E9%95%9C%E5%83%8F" class="hash-link" aria-label="什么是流量镜像的直接链接" title="什么是流量镜像的直接链接" translate="no">​</a></h2>
<p>流量镜像也叫影子流量(Shadow Traffic):Envoy 在把客户端请求转发给真实服务的同时,复制一份发送到指定的镜像服务。关键点有两个:</p>
<p>1)镜像请求是"发后即忘"(fire-and-forget)的,Envoy 不会等待镜像服务的响应,更不会把它返回给客户端。镜像服务处理慢、报错甚至挂掉,都不影响主链路。</p>
<p>2)镜像发生在流量进入业务容器之前,由 Sidecar 代理完成,业务代码不需要任何改动,只要另外部署一套接收镜像流量的服务即可。</p>
<p>官方文档见:<a href="https://istio.io/latest/zh/docs/tasks/traffic-management/mirroring/" target="_blank" rel="noopener noreferrer" class="">镜像</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="典型使用场景">典型使用场景<a href="https://tanzhuo.xyz/2022/11/01/is-ti-o#%E5%85%B8%E5%9E%8B%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="典型使用场景的直接链接" title="典型使用场景的直接链接" translate="no">​</a></h2>
<p>1、测试环境:测试版本的容器服务可以使用生产实例的真实流量,不会影响正常生产的关键路径。例如预发布环境就可以更好的进行实时测试校验,减少发布后出现的各种异常情况,给开发团队更好的上线信心 😂,上线几乎可以做到清晰明了,不用熬夜加班发版。</p>
<p>2、数据采集:同步收集请求信息,则可在其他容器中做风控分析,日志记录以便于得到对应的用户画像信息。</p>
<p>3、性能与兼容性验证:新版本服务、重构后的接口、换了框架或依赖的实现,都可以先用镜像流量"陪跑"一段时间,对比新旧两边的日志和指标,确认行为一致后再正式切流。</p>
<p>4、问题复现:线上偶发的异常请求很难在测试环境构造,把流量镜像到一个开了详细日志或调试开关的实例上,就能拿到第一手现场。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="配置方式">配置方式<a href="https://tanzhuo.xyz/2022/11/01/is-ti-o#%E9%85%8D%E7%BD%AE%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="配置方式的直接链接" title="配置方式的直接链接" translate="no">​</a></h2>
<p>Istio 中流量镜像通过 VirtualService 的 <code>mirror</code> 字段声明。假设 <code>httpbin</code> 服务有 v1、v2 两个版本,想把打到 v1 的流量镜像一份给 v2,写法大致如下:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token key atrule" style="color:hsl(29, 54%, 61%)">apiVersion</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> networking.istio.io/v1beta1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">kind</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> VirtualService</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> httpbin</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">spec</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">hosts</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> httpbin</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">http</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">route</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 真实流量:全部转发给 v1,响应返回客户端</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">destination</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">host</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> httpbin</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">subset</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">weight</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">100</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 镜像流量:复制一份发给 v2,响应被丢弃</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">mirror</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">host</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> httpbin</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">subset</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> v2</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 镜像的采样比例,不写默认 100%</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">mirrorPercentage</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">100.0</span><br></div></code></pre></div></div>
<p>几个字段的含义:</p>
<p>1)<code>route.destination</code> 是主路由,决定真实请求发给谁,客户端拿到的是它的响应。</p>
<p>2)<code>mirror</code> 指定镜像目标,同样是 host + subset 的组合,subset 需要提前在 DestinationRule 里定义好。</p>
<p>3)<code>mirrorPercentage</code> 控制镜像采样比例。镜像目标容量比生产小的时候,按 1%、10% 这样逐步放量比直接 100% 稳妥得多。</p>
<p>配置写好后 <code>kubectl apply -f</code> 即可生效,不需要重启任何业务 Pod,这也是 Sidecar 模式的好处之一。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/11/01/is-ti-o#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1)镜像流量的 Host 头会被加上 <code>-shadow</code> 后缀,例如 <code>httpbin:8000</code> 会变成 <code>httpbin-shadow:8000</code>。镜像服务如果对 Host 做了校验或路由,要提前处理这个差异;反过来,这个后缀也可以用来在日志里区分真实流量和影子流量。</p>
<p>2)镜像的是请求,不是幂等性。写接口被镜像后会在影子环境再执行一次,如果镜像服务连的是同一个数据库、同一个下游,就可能造成重复写入或重复调用第三方。要么让镜像服务连独立的数据源,要么在镜像侧对写操作做拦截。</p>
<p>3)镜像会放大出口带宽和 Envoy 的开销,请求体大、QPS 高的服务尤其明显,先用 <code>mirrorPercentage</code> 小比例验证再放量。</p>
<p>4)镜像服务的报错不会影响客户端,这既是优点也是盲区——影子环境挂了没有用户感知,需要单独给它配监控告警,否则"陪跑"很可能早就停了都没人发现。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/11/01/is-ti-o#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>流量镜像把"用生产流量验证"这件高风险的事变成了低成本操作:一段 VirtualService 配置,业务零改动,主链路零影响。预发验证、风控分析、问题复现这些场景都能受益。需要留意的主要是写操作的副作用、Host 头的 <code>-shadow</code> 后缀以及镜像侧自身的监控,把这几点处理好,它就是一个非常值得常备的工具。</p>]]></content:encoded>
            <category>K8S</category>
        </item>
        <item>
            <title><![CDATA[分布式事务之 Seata]]></title>
            <link>https://tanzhuo.xyz/2022/10/31/seata</link>
            <guid>https://tanzhuo.xyz/2022/10/31/seata</guid>
            <pubDate>Mon, 31 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[微服务架构下每个业务模块各有独立的 DB 存储节点，跨库调用带来分布式事务问题。本文梳理 Seata 的角色划分，以及 AT、TCC、Saga、XA 四种模式的原理与取舍。]]></description>
            <content:encoded><![CDATA[<p>在企业应用开发中，随着分布式框架的发展，生产环境里会有很多数据库实例。特别是在微服务领域，我们通常会让每个业务 Service 模块对应一个自身业务的 DB 存储节点，再对这个存储节点做高可用部署。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="事务问题">事务问题<a href="https://tanzhuo.xyz/2022/10/31/seata#%E4%BA%8B%E5%8A%A1%E9%97%AE%E9%A2%98" class="hash-link" aria-label="事务问题的直接链接" title="事务问题的直接链接" translate="no">​</a></h2>
<p>编写 Service 业务逻辑时，一个业务操作往往会远程调用其他业务模块，产生的数据会落盘到不同的存储节点。为了保障多个数据库实例之间事务的 ACID 特性，就遇到了分布式事务问题。</p>
<p>分布式事务要做到的事情可以概括为两条：</p>
<p>1）如果整个业务调用链路均成功，那么整个调用链路对应的数据库做事务提交。</p>
<p>2）如果调用链路中抛出了异常，那么整个调用链路对应的数据库做回滚操作。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="seata-的角色划分">Seata 的角色划分<a href="https://tanzhuo.xyz/2022/10/31/seata#seata-%E7%9A%84%E8%A7%92%E8%89%B2%E5%88%92%E5%88%86" class="hash-link" aria-label="Seata 的角色划分的直接链接" title="Seata 的角色划分的直接链接" translate="no">​</a></h2>
<p>在 Seata 分布式事务解决方案中，一般有以下这些角色：</p>
<ul>
<li class="">TC (Transaction Coordinator)：事务协调者。</li>
<li class="">TM (Transaction Manager)：事务管理器，在 AP 上进行集成。</li>
<li class="">RM (Resource Manager)：资源管理器，管理分支事务处理的资源，与 TC 交谈以注册分支事务和报告分支事务的状态，并驱动分支事务提交或回滚。</li>
<li class="">AP (Application Program)：访问 RM 的应用程序。</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-11-145942191-7a2d469f-94c8-4cd2-8c7e-46ad75683636-604f44d73d72d865af48e8fc188c9a9e.webp" width="1534" height="908" class="img_ev3q"></p>
<p><strong>图中的 TC 事务协调者组件需要单独部署，并加入到整个微服务集群的注册中心中，目前常见的注册中心均支持，如 Nacos、Zookeeper、Etcd、Eureka、Consul，同时需要在 AP 中做对应的连接配置。TC 模块必须保障高可用，如果该模块宕机，分布式事务将无从谈起。</strong></p>
<p><a href="http://seata.io/zh-cn/docs/user/txgroup/transaction-group-and-ha.html" target="_blank" rel="noopener noreferrer" class="">事务分组与高可用</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="刚性事务与柔性事务">刚性事务与柔性事务<a href="https://tanzhuo.xyz/2022/10/31/seata#%E5%88%9A%E6%80%A7%E4%BA%8B%E5%8A%A1%E4%B8%8E%E6%9F%94%E6%80%A7%E4%BA%8B%E5%8A%A1" class="hash-link" aria-label="刚性事务与柔性事务的直接链接" title="刚性事务与柔性事务的直接链接" translate="no">​</a></h2>
<p>刚性事务：通常无业务改造，强一致性，原生支持回滚/隔离性，低并发，适合短事务。对应方案：XA 协议（2PC、JTA、JTS）、3PC。</p>
<p>柔性事务：有业务改造，最终一致性，需要实现补偿接口与资源锁定接口，高并发，适合长事务。对应方案：TCC/FMT、Saga（状态机模式、Aop 模式）、本地事务消息、消息事务（半消息）。</p>
<p>那什么是长事务，什么是短事务呢？</p>
<p>长事务：长时间占用某个资源（X 锁、间隙锁、表锁、页锁），修改的数据较多或执行速度较慢，业务流程长。其他事务访问该资源时会被阻塞等待，容易导致死锁。</p>
<p>短事务：对资源的占用很短暂，修改的数据较少或执行很快完成，业务流程短，事务很快结束，产生死锁的概率较小。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四种事务模式">四种事务模式<a href="https://tanzhuo.xyz/2022/10/31/seata#%E5%9B%9B%E7%A7%8D%E4%BA%8B%E5%8A%A1%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="四种事务模式的直接链接" title="四种事务模式的直接链接" translate="no">​</a></h2>
<p>目前 Seata 支持 4 种分布式事务的管理模式。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="at-模式">AT 模式<a href="https://tanzhuo.xyz/2022/10/31/seata#at-%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="AT 模式的直接链接" title="AT 模式的直接链接" translate="no">​</a></h3>
<p>AT 模式算是 Seata 中最具特色的模式，总的来说是优化过的 2PC，属于二阶段提交的一种。使用这种模式需要在本地数据库节点中创建一张 undolog 表，通过代理 DataSourceProxy 拦截应用程序执行的 DML SQL 语句，对 SQL 做语义解析并转化为查询 SQL，把执行前对应的数据镜像存入 undolog 表，作为二阶段回滚使用。以上步骤均由代理模式完成，所以实现了零业务逻辑代码入侵。</p>
<p>但此方案并非完美，性能损失相当大：一次 DML SQL 会多出一次语义转化并执行查询 SQL，以及新增数据镜像到 undolog 表；如果发生二阶段回滚，还需要获取本地锁，再回退原始数据镜像。此外还有一个比较严重的问题：如果在回滚时，其他本地事务没有走 Seata 的全局事务管理去获取全局锁，而是直接通过本地事务修改了 undolog 中某条数据镜像对应的数据，那么 Seata 回滚时会发现回滚镜像与当前本地数据对不上，出现数据丢失问题，有点类似于 CAS 中的 ABA 问题。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tcc-模式">TCC 模式<a href="https://tanzhuo.xyz/2022/10/31/seata#tcc-%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="TCC 模式的直接链接" title="TCC 模式的直接链接" translate="no">​</a></h3>
<p>TCC 模式较为简单，其实也是 2PC 两阶段提交。它和 AT 模式的区别是需要自己实现一阶段 prepare 准备、二阶段 commit 以及 rollback 逻辑，Seata 的事务管理器会依次调用这些实现方法。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="saga-模式">Saga 模式<a href="https://tanzhuo.xyz/2022/10/31/seata#saga-%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="Saga 模式的直接链接" title="Saga 模式的直接链接" translate="no">​</a></h3>
<p>Saga 由一系列本地事务构成。每一个本地事务在更新完数据库之后，会发布一条消息或一个事件，触发 Saga 中下一个本地事务的执行。如果某个本地事务因业务规则无法满足而失败，Saga 会对这个失败事务之前已成功提交的所有事务执行补偿操作。</p>
<p>所以 Saga 算是实现起来最繁琐的一种模式，但基于 Saga 的状态机可以非常灵活地控制每一个分布式事务步骤。对于长事务场景，更需要自己细粒度地控制每一种情况：不关注整个分布式事务的状态，只需要做好每个事件的处理，以及自身方法的幂等性即可。但它的事务没有阶段提交一说，都是本地直接提交。如果在回滚途中，其他事务读取并修改了这些数据，就会发生脏读、脏写。对于这种情况，我想还需要自己实现一套 txid 方案来规避。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="xa-模式">XA 模式<a href="https://tanzhuo.xyz/2022/10/31/seata#xa-%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="XA 模式的直接链接" title="XA 模式的直接链接" translate="no">​</a></h3>
<p>XA 模式首先要求数据库支持 XA 协议，其实现有 2PC、3PC，且完全由数据库自身提供，应用程序不需要做任何处理。本质上和 AT 模式很相似，区别是数据库在执行 XA 事务的过程中，为了强一致性会持续占用数据资源（X 锁、间隙锁、表锁、页锁，视数据范围而定）。如果是长事务，在高并发场景下非常容易出现死锁，数据库性能也会下降得比较厉害。如果都是短事务，则可以使用此模式。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/31/seata#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>分布式事务的本质是让跨库调用链路要么一起提交、要么一起回滚。Seata 通过 TC、TM、RM 的角色分工把这件事产品化，并提供了 AT、TCC、Saga、XA 四种模式。AT 靠数据镜像做到零入侵，适合大多数常规场景；TCC 和 Saga 把补偿逻辑交给业务，换取更高的并发与灵活性；XA 依赖数据库原生支持，只适合短事务。选型时先分清业务是长事务还是短事务、能接受强一致还是最终一致，再对号入座。</p>]]></content:encoded>
            <category>DB</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Helm 包管理工具]]></title>
            <link>https://tanzhuo.xyz/2022/10/27/ku-be</link>
            <guid>https://tanzhuo.xyz/2022/10/27/ku-be</guid>
            <pubDate>Thu, 27 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[当集群里的 yaml 文件多到难以维护时，Helm 用 Chart 把配置打包成可复用、可版本化的应用包。这篇梳理它解决的问题、核心概念与常用命令，顺带推荐一款 UI 管理工具。]]></description>
            <content:encoded><![CDATA[<p>众所周知，k8s可以在多个node节点上管理容器资源，但每个容器需要对应的pod-yaml文件来进行管理。</p>
<!-- -->
<p>这篇文章想聊的其实是 yaml 文件失控的问题。单个应用写一份 Deployment、一份 Service 不算什么负担，但集群里的应用一多，配置文件的数量会以肉眼可见的速度膨胀：改一个镜像标签要翻好几个文件，换一套环境又要把整批配置复制出来逐个改参数。到了这一步，靠人肉维护 yaml 就不再现实了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="从一堆-yaml-说起">从一堆 yaml 说起<a href="https://tanzhuo.xyz/2022/10/27/ku-be#%E4%BB%8E%E4%B8%80%E5%A0%86-yaml-%E8%AF%B4%E8%B5%B7" class="hash-link" aria-label="从一堆 yaml 说起的直接链接" title="从一堆 yaml 说起的直接链接" translate="no">​</a></h2>
<p>我们先说问题：如果在一个namespace中有几十个pod需要进行配合部署，例如 mysql 集群，kafka集群，但部署kafka集群又需要先部署 zookeeper集群，应用程序又需要Redis 集群，以及一些MQ队列集群，还有应用程序自身的部署，微服务可能多达50-100个甚至更多的应用。</p>
<p>这样的场景让你来写 yaml 配置文件 我想你可能是拒绝的，就算你部署了一次，好不容易部署完成了，但异地机房集群还要再次部署怎么办? 更何况怎么做到多个不同机房中集群的配置信息完全一致？</p>
<p>这些 yaml 之间还有隐含的部署顺序和大量重复的配置项——镜像仓库地址、资源限额、存储类名称，几乎每个文件里都要抄一遍。一旦某个公共参数变了，漏改任何一处都是隐患。</p>
<p>所以这里的问题很明显了，我们需要一个可以集中管理所有 yaml 配置文件并且可以复用的工具。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="helmk8s-上的包管理器">Helm：k8s 上的包管理器<a href="https://tanzhuo.xyz/2022/10/27/ku-be#helmk8s-%E4%B8%8A%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86%E5%99%A8" class="hash-link" aria-label="Helm：k8s 上的包管理器的直接链接" title="Helm：k8s 上的包管理器的直接链接" translate="no">​</a></h2>
<p>于是 Helm 就诞生了，他的定位类似于 CentOS 中的 Yum 包管理工具，又或者是 Debian中的 apt 包管理工具，只不过这次的包管理是基于 k8s 环境。</p>
<p>Helm 中的每个包都称为一个Chart，一个Chart是一个目录。目录里的核心内容可以概括成三部分：<code>Chart.yaml</code> 描述这个包的名称和版本等元信息；<code>values.yaml</code> 存放默认配置值，安装时可以被外部覆盖；<code>templates/</code> 目录里放的是带模板语法的 k8s 资源文件。安装时 Helm 把 values 渲染进模板，生成真正的 yaml 再提交给集群，一次安装的结果称为一个 Release。</p>
<p>配置复用的关键就在这套模板机制上：不同机房、不同环境共用同一个 Chart，各自只维护一份小小的 values 文件，机房间配置不一致的问题自然就消解了。Chart 还可以声明依赖，像"部署 kafka 之前要先有 zookeeper"这种关系可以交给子 Chart 处理，不必再靠人来记顺序。</p>
<p>应用发布者可以通过Helm打包应用，管理应用依赖关系，管理应用版本并发布应用到软件仓库进行统一管理，在 k8s 中需要部署时 只需要一条指令即可部署整套 pod 应用。</p>
<p>日常用到的命令并不多：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 添加一个 Chart 仓库（以常用的 bitnami 仓库为例）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">helm repo </span><span class="token function" style="color:hsl(207, 82%, 66%)">add</span><span class="token plain"> bitnami https://charts.bitnami.com/bitnami</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 更新本地缓存的仓库索引</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">helm repo update</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 一条命令部署整套应用，Chart 会自动创建所需的全部资源</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">helm </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> my-mysql bitnami/mysql</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看当前 namespace 下已部署的 Release</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">helm list</span><br></div></code></pre></div></div>
<p>部署之后的升级和回滚也由 Helm 接管，每次变更都会记录成一个新的版本：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 用自定义的 values 文件覆盖默认配置并升级</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">helm upgrade my-mysql bitnami/mysql </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-f</span><span class="token plain"> my-values.yaml</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 升级出问题时回滚到上一个版本</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">helm rollback my-mysql</span><br></div></code></pre></div></div>
<p>Helm的官方地址：</p>
<p><a href="https://helm.sh/" target="_blank" rel="noopener noreferrer" class="">Helm</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="图形化管理kubeapps">图形化管理：Kubeapps<a href="https://tanzhuo.xyz/2022/10/27/ku-be#%E5%9B%BE%E5%BD%A2%E5%8C%96%E7%AE%A1%E7%90%86kubeapps" class="hash-link" aria-label="图形化管理：Kubeapps的直接链接" title="图形化管理：Kubeapps的直接链接" translate="no">​</a></h2>
<p>官方的中文文档写的比较详细，有兴趣可以学习一下，我在这里再推荐一款 Helm 的UI 管理工具，可以更方便的管理企业 Helm 包信息。</p>
<p>kubeapps</p>
<p><a href="https://kubeapps.dev/" target="_blank" rel="noopener noreferrer" class="">Kubeapps - Kubeapps</a></p>
<p>Kubeapps 本身就是一个部署在集群里的 Web 应用，可以在页面上浏览仓库中的 Chart、通过表单填写配置完成安装，也能查看和升级已有的 Release。对不方便让所有人都用命令行操作集群的团队来说，这类工具能省不少事。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/10/27/ku-be#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-自定义配置放在自己的-values-文件里">1) 自定义配置放在自己的 values 文件里<a href="https://tanzhuo.xyz/2022/10/27/ku-be#1-%E8%87%AA%E5%AE%9A%E4%B9%89%E9%85%8D%E7%BD%AE%E6%94%BE%E5%9C%A8%E8%87%AA%E5%B7%B1%E7%9A%84-values-%E6%96%87%E4%BB%B6%E9%87%8C" class="hash-link" aria-label="1) 自定义配置放在自己的 values 文件里的直接链接" title="1) 自定义配置放在自己的 values 文件里的直接链接" translate="no">​</a></h3>
<p>尽量用 <code>-f</code> 传入独立的 values 文件来覆盖默认值，而不是直接改 Chart 内的 <code>values.yaml</code>。前者在 Chart 升级时可以原样沿用，后者的改动很容易在更新 Chart 时被覆盖丢失。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-卸载动作要想清楚">2) 卸载动作要想清楚<a href="https://tanzhuo.xyz/2022/10/27/ku-be#2-%E5%8D%B8%E8%BD%BD%E5%8A%A8%E4%BD%9C%E8%A6%81%E6%83%B3%E6%B8%85%E6%A5%9A" class="hash-link" aria-label="2) 卸载动作要想清楚的直接链接" title="2) 卸载动作要想清楚的直接链接" translate="no">​</a></h3>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p><code>helm uninstall</code> 会把 Release 关联的资源一并删除。对 mysql 这类有状态应用，动手前先确认存储卷的保留策略，避免连数据一起清掉。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-chart-版本和应用版本是两回事">3) Chart 版本和应用版本是两回事<a href="https://tanzhuo.xyz/2022/10/27/ku-be#3-chart-%E7%89%88%E6%9C%AC%E5%92%8C%E5%BA%94%E7%94%A8%E7%89%88%E6%9C%AC%E6%98%AF%E4%B8%A4%E5%9B%9E%E4%BA%8B" class="hash-link" aria-label="3) Chart 版本和应用版本是两回事的直接链接" title="3) Chart 版本和应用版本是两回事的直接链接" translate="no">​</a></h3>
<p><code>Chart.yaml</code> 里的 version 指 Chart 自身的打包版本，appVersion 才对应里面软件的版本。升级前看清楚变的是哪一个，避免以为只是改了模板、实际却把应用也升了级。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/27/ku-be#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Helm 把散落各处的 yaml 收拢成 Chart，用模板加 values 的方式解决了配置复用和多环境一致性的问题，部署、升级、回滚都收敛成一条命令。规模小的时候手写 yaml 还能应付，应用一多，早点引入 Helm 这层包管理，后面会省下大量重复劳动。再配合 Kubeapps 这样的 UI 工具，团队协作时的门槛也能降低不少。</p>]]></content:encoded>
            <category>K8S</category>
        </item>
        <item>
            <title><![CDATA[Kubernetes Pod Yaml文件解析]]></title>
            <link>https://tanzhuo.xyz/2022/10/27/kubernetes-2</link>
            <guid>https://tanzhuo.xyz/2022/10/27/kubernetes-2</guid>
            <pubDate>Thu, 27 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[逐字段注释 Pod YAML:metadata、containers、探针、资源限制、volumes 等常用配置的含义与用法,并整理拼写陷阱和 kubectl explain 的查询技巧。]]></description>
            <content:encoded><![CDATA[<p>Pod 是 Kubernetes 里最小的调度单元,几乎所有工作负载最终都会落到 Pod 的 YAML 定义上。这份笔记整理了 Pod YAML 的全字段注释,以 <code>kubectl explain</code> 查询结果为准,例如:<code>kubectl explain pod.spec.volumes</code>。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么要读懂-pod-yaml">为什么要读懂 Pod YAML<a href="https://tanzhuo.xyz/2022/10/27/kubernetes-2#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E8%AF%BB%E6%87%82-pod-yaml" class="hash-link" aria-label="为什么要读懂 Pod YAML的直接链接" title="为什么要读懂 Pod YAML的直接链接" translate="no">​</a></h2>
<p>平时用 Deployment、StatefulSet 部署应用,它们的 <code>spec.template</code> 部分本质上就是一个 Pod 模板。排查容器起不来、探针失败、挂载路径不对这类问题,最后都要回到 Pod 定义上逐字段核对。与其每次翻文档,不如把常用字段的含义一次性过一遍。</p>
<p>字段拿不准的时候,最可靠的方式是问 API 本身:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看某个字段的文档说明</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl explain pod.spec.containers.livenessProbe</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 递归列出某个字段下的全部子字段</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl explain pod.spec.volumes </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--recursive</span><br></div></code></pre></div></div>
<p><code>kubectl explain</code> 直接读取集群的 OpenAPI Schema,和当前集群版本严格一致,比任何抄来的注释表都准。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="全字段注释参考">全字段注释参考<a href="https://tanzhuo.xyz/2022/10/27/kubernetes-2#%E5%85%A8%E5%AD%97%E6%AE%B5%E6%B3%A8%E9%87%8A%E5%8F%82%E8%80%83" class="hash-link" aria-label="全字段注释参考的直接链接" title="全字段注释参考的直接链接" translate="no">​</a></h2>
<p>下面是带注释的完整参考(注释仅作速查,以 <code>kubectl explain</code> 为准):</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token key atrule" style="color:hsl(29, 54%, 61%)">apiVersion</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> v1            //版本</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">kind</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> pod                 //类型，pod</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">                 //元数据</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String            //元数据，pod的名字</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">namespace</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String       //元数据，pod的命名空间</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">labels</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">                 //元数据，标签列表</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String        //元数据，标签的名字</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">annotations</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">            //元数据</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">自定义注解列表</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String        //元数据</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">自定义注解名字</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">spec</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">                     //pod中容器的详细定义</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">containers</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">             //pod中的容器列表，可以有多个容器</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String          //容器名称</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">image</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String         //容器中的镜像名称</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">imagesPullPolicy</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">Always</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">|</span><span class="token plain">Never</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">|</span><span class="token plain">IfNotPresent</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">//获取镜像的策略</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">一直拉取</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">从不拉取</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">本地有镜像不拉取</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">command</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">String</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">     //容器的启动命令列表（不配置的话使用镜像打包时使用的启动命令）</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">args</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">String</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">        //容器启动参数列表</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">workingDir</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String    //容器的工作目录</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">volumeMounts</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">         //挂载到到容器内部的存储卷设置</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String        //应用Pod定义的共享存储卷名称</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">需要使用volumes</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">部分定义的共享存储卷名称</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">mountPath</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String   //存储卷在容器内Mount的绝对路径</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">应少于512个字符</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">readOnly</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> boolean   //是否为只读模式</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">默认为读写模式</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">ports</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">                //容器需要暴露的端口号列表</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String        //端口的名称</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">containerPort</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> int  //容器要暴露的端口</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">hostPort</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> int       //容器所在主机监听的端口(容器暴露端口映射到宿主机的端口)</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">默认与containerPort相同</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">设置hostPort时</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">同一台主机将无法启动该容器的第二个副本</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">protocol</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String    //端口协议</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">支持TCP和UDP</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">默认为TCP</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">env</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">                  //容器运行前要设置的环境列表</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String        //环境变量的名称</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String       //环境变量的值</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">resources</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">            //资源限制和资源请求的设置</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">limits</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">             //资源限制的设置</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">cpu</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> Srting       //CPU限制</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">单位为core数</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">将用于docker run </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">cpu</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">shares参数</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">memory</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String    //内存限制</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">单位可以为MiB</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">GiB等</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">将用于docker run </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">memory参数</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">requeste</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">           //资源请求设置</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">cpu</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String       //cpu请求</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">单位为core数</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">容器启动的初始可用数量</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">memory</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String    //内存请求</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">单位为MiB或GiB</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">容器启动的初始可用数量</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">livenessProbe</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">        //pod内容器健康检查的设置</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">探测几次无反应后</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">将自动重启该容器</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">探测方式包括</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">exec</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">httpGet</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">tcpSocket</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">exec</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">               //exec探测方式</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">command</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">String</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"> //exec方式需要指定的命令或脚本</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">httpGet</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">            //通过httpget检查健康</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">需要指定path</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">port</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">path</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String      //网址URL路径（去除对应的域名或IP地址的部分）</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">port</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> number      //对应端口  </span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">host</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String      //域名或IP地址</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">scheme</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> Srtring   //对应的检测协议，如http</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">httpHeaders</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">      //指定报文头部信息</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> Stirng    //头部信息名称</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">          </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">value</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String   //头部信息值</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">tcpSocket</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">          //通过tcpSocket检查健康</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">port</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> number      //探测端口的端口号</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">initialDelaySeconds</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> 0//容器启动完成后首次探测的时间</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">单位为s</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">timeoutSeconds</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> 0     //探测等待响应超时时间</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">单位为s</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">默认为1</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">超时认为容器不健康</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">容器将重启</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">periodSeconds</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> 0      //定期探测时间设置</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">单位为s</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">默认为10</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">successThreshold</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> 0   //探测几次成功后认为成功</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">failureThreshold</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> 0   //探测几次失败后认为失败</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">securityContext</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">        //安全配置</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">privileged</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> false     //</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">restartPolicy</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">Always</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">|</span><span class="token plain">Never</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">|</span><span class="token plain">OnFailure</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">//重启策略</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">终止定会重启</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">除正常结束(退出码为0)外其他非0退出码终止才重启</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">Pod终止后退出码报告给master不重启Pod</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">nodeSelector</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> object      //设置Node的Label</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">key</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">value格式指定</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">Pod将被调度到具有这些Label的Node上</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">imagePullSecrets</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">         //pull镜像时使用Secrets名称</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">以name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">sercretkey格式指定</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String            //参照物名称</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">hostNetwork</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> false        //是否使用主机网络模式</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">默认为false</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">设置为true</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">表示使用宿主机网络</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">不使用docker网桥</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">此Pod无法在同一台机上启动第2个副本</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">volumes</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">                  //在该pod上定义共享存储卷列表</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String            //共享存储卷名称</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">一个Pod内</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">每个存储卷定义一个名称</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">供spec</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.containers</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.volumeMounts</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.name应用</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">其类型很多</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">如</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">emptyDir</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">hostPath等</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">emptyDir</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain">            //emptyDir类型的存储卷</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">零时目录</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">与Pod同生命周期</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">空对象</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">hostPath</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">               //hostPath类型的存储卷</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">表示挂载Pod所在主机的目录</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">通过volumes</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain">.hostNetwork.path指定</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">path</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> string          //Pod所在主机的目录</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">将用于容器中的mount的目录</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">secret</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">                 //类型为secret的存储卷</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">表示挂载集群预定义的secret对象到容器的内部</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">secretName</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String    //存储卷名称</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">items</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">                 //当仅需挂载一个Secret对象中的指定Key时使用</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">key</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String         //key值</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">path</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String        //映射文件的相对路径</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">configMap</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">              //类型为configMap的存储卷</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">表示挂载集群预定义的configMap对象到容器的内部</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String          //使用configMap的名称</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">items</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">                //当仅需挂载一个ConfigMap对象中的指定Key时使用</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">key</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String         //定义key</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">path</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> String        //映射文件的相对路径</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="几个重点字段的展开说明">几个重点字段的展开说明<a href="https://tanzhuo.xyz/2022/10/27/kubernetes-2#%E5%87%A0%E4%B8%AA%E9%87%8D%E7%82%B9%E5%AD%97%E6%AE%B5%E7%9A%84%E5%B1%95%E5%BC%80%E8%AF%B4%E6%98%8E" class="hash-link" aria-label="几个重点字段的展开说明的直接链接" title="几个重点字段的展开说明的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-command-与-args">1) command 与 args<a href="https://tanzhuo.xyz/2022/10/27/kubernetes-2#1-command-%E4%B8%8E-args" class="hash-link" aria-label="1) command 与 args的直接链接" title="1) command 与 args的直接链接" translate="no">​</a></h3>
<p><code>command</code> 对应容器运行时的 entrypoint,<code>args</code> 对应传给它的参数。两者都不写时,使用镜像自带的 ENTRYPOINT 和 CMD;只写 <code>args</code> 时,镜像的 ENTRYPOINT 保留,CMD 被覆盖;写了 <code>command</code> 则镜像里的 ENTRYPOINT 和 CMD 都不再生效。排查"容器一启动就退出"时,先确认这两个字段有没有意外覆盖镜像的默认启动命令。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-resources-与-limits">2) resources:requests 与 limits<a href="https://tanzhuo.xyz/2022/10/27/kubernetes-2#2-resources-%E4%B8%8E-limits" class="hash-link" aria-label="2) resources:requests 与 limits的直接链接" title="2) resources:requests 与 limits的直接链接" translate="no">​</a></h3>
<p><code>requests</code> 是调度依据——调度器按它判断节点剩余资源够不够;<code>limits</code> 是运行时上限——内存超过 limit 容器会被 OOMKill,CPU 超过 limit 则被限流(不会杀容器)。两者都不填,Pod 会以 BestEffort 的 QoS 等级运行,节点资源紧张时最先被驱逐。生产环境建议至少填 requests。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-健康探针">3) 健康探针<a href="https://tanzhuo.xyz/2022/10/27/kubernetes-2#3-%E5%81%A5%E5%BA%B7%E6%8E%A2%E9%92%88" class="hash-link" aria-label="3) 健康探针的直接链接" title="3) 健康探针的直接链接" translate="no">​</a></h3>
<p><code>livenessProbe</code> 判定容器是否还活着,失败达到 <code>failureThreshold</code> 次后 kubelet 会重启容器。除它之外还有 <code>readinessProbe</code>(判定是否就绪,失败只会把 Pod 从 Service 端点摘除,不重启容器),字段结构完全相同。<code>initialDelaySeconds</code> 要留够应用启动时间,否则应用还没起来就被探针判死,陷入反复重启的循环。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-volumes-与-volumemounts">4) volumes 与 volumeMounts<a href="https://tanzhuo.xyz/2022/10/27/kubernetes-2#4-volumes-%E4%B8%8E-volumemounts" class="hash-link" aria-label="4) volumes 与 volumeMounts的直接链接" title="4) volumes 与 volumeMounts的直接链接" translate="no">​</a></h3>
<p>存储是"两段式"声明:先在 <code>spec.volumes</code> 里定义卷(emptyDir、hostPath、secret、configMap 等类型),再在容器的 <code>volumeMounts</code> 里通过 <code>name</code> 引用并指定挂载路径。两边的 <code>name</code> 必须完全一致,这是新手最常见的报错来源之一。emptyDir 随 Pod 删除而销毁,只适合临时数据;hostPath 直接挂宿主机目录,Pod 换节点后数据就"丢"了,一般只用于日志采集这类节点级场景。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/10/27/kubernetes-2#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>上面这份注释表在圈内流传很广,但抄写版本里混入了几处拼写问题,直接照抄会导致 apply 报错或字段被静默忽略:</p>
<ul>
<li class=""><code>kind: pod</code> 应为 <code>kind: Pod</code>,资源类型首字母大写;</li>
<li class=""><code>imagesPullPolicy</code> 正确字段名是 <code>imagePullPolicy</code>,没有 s;</li>
<li class=""><code>resources</code> 下的 <code>requeste</code> 正确写法是 <code>requests</code>;</li>
<li class="">hostPath 的路径字段就是 <code>volumes[].hostPath.path</code>,注释里写的 <code>hostNetwork.path</code> 是笔误。</li>
</ul>
<p>字段名拼错时,取决于集群的校验策略,可能直接被拒绝,也可能被当作未知字段忽略——后者更隐蔽,配置看似生效实际没有。所以再强调一次:动手前用 <code>kubectl explain</code> 核对字段名,或者用 <code>kubectl apply --dry-run=server -f pod.yaml</code> 让 API Server 先做一遍校验。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>YAML 里的注释应使用 <code>#</code> 而不是 <code>//</code>。上表的 <code>//</code> 注释只是速查用的标注,直接拷贝进真实的 YAML 文件前需要删掉或改成 <code>#</code>。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/27/kubernetes-2#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Pod YAML 的字段虽多,常用的其实集中在几块:metadata 的 labels、containers 的镜像与启动命令、resources、三类探针、volumes 挂载。注释表适合快速定位字段位置,但字段名和默认值一律以 <code>kubectl explain</code> 的输出为准——它读的是当前集群的 Schema,不会过期,也不会有抄写错误。</p>]]></content:encoded>
            <category>K8S</category>
        </item>
        <item>
            <title><![CDATA[Fiddler 安装并抓取HTTPS请求]]></title>
            <link>https://tanzhuo.xyz/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu</link>
            <guid>https://tanzhuo.xyz/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu</guid>
            <pubDate>Thu, 20 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[介绍 Fiddler 的安装、系统代理配置，以及通过配置伪造证书抓取 HTTPS 加密请求的原理与操作步骤。]]></description>
            <content:encoded><![CDATA[<p>众所周知 Fiddler 是一个非常实用的抓包工具（主要是对于Http请求）而且有很多的扩展功能可以去官网下载，我们在开发中，难免需要抓包工具来查看接口数据 或者 测试接口的安全性, 如未签名而进行值的修改，下面将介绍它的安装以及配置 证书 来抓取Https加密信息。</p>
<!-- -->
<p>做接口开发时，最常见的诉求就是"请求到底发出去没有、参数对不对、服务端返回了什么"。前端 F12 只能看浏览器自己发的请求，App、桌面程序、后台服务之间的调用它看不到；而日志又往往只记了业务字段，漏掉了真正想核对的原始报文。抓包工具正好补上这一段：它以代理的身份坐在客户端和服务器中间，把每一次请求、响应完整地摊在你面前。Fiddler 就是这类工具里配置成本最低、对 HTTP 支持最顺手的一个。</p>
<p>麻烦的地方在于 HTTPS。现在的接口基本都走 HTTPS，报文全程加密，代理即便截到了流量也只是一堆密文。想看到明文，就得让 Fiddler 用"中间人"的方式介入 TLS 握手——这也是本文要展开讲清楚的部分。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="下载与安装">下载与安装<a href="https://tanzhuo.xyz/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu#%E4%B8%8B%E8%BD%BD%E4%B8%8E%E5%AE%89%E8%A3%85" class="hash-link" aria-label="下载与安装的直接链接" title="下载与安装的直接链接" translate="no">​</a></h2>
<p>Fiddler 官方网址：</p>
<p><a href="https://www.telerik.com/fiddler" target="_blank" rel="noopener noreferrer" class="">Fiddler | Web Debugging Proxy and Troubleshooting Solutions</a></p>
<p>选择对应版本进行下载。个人调试选免费的 Fiddler Classic 就够用，无需注册企业版。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-18-a3c29b8f26df9a8ea23e14cbb0b92512.webp" width="1600" height="783" class="img_ev3q"></p>
<p>安装完成后运行：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-19-be4e61d0ca02ec3e5799e4a490c813e8.webp" width="1600" height="869" class="img_ev3q"></p>
<p>此时我们不做任何配置的情况下，已经可以直接抓包 Http 请求，（因为Http都是明文传输，当然也需要请求走系统代理火狐游览器需要设置"手工配置代理"），但是我们如果项目中使用了 Https 请求，由于Https 请求的安全性，所有数据都是加密的，那么我们怎么抓取其中的信息呢？</p>
<p>这里补充一句代理的机制：Fiddler 启动时会把自己注册成系统代理，默认监听在 <code>127.0.0.1:8888</code>。走系统代理的程序（大多数 Windows 应用、Chrome、Edge）会自动把流量转给它。而 Firefox 有自己独立的代理设置，不吃系统代理，所以需要在其网络设置里"手工配置代理"，指向同一个地址端口：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">HTTP 代理:  127.0.0.1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">端口:       8888</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="抓取-https-的原理">抓取 HTTPS 的原理<a href="https://tanzhuo.xyz/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu#%E6%8A%93%E5%8F%96-https-%E7%9A%84%E5%8E%9F%E7%90%86" class="hash-link" aria-label="抓取 HTTPS 的原理的直接链接" title="抓取 HTTPS 的原理的直接链接" translate="no">​</a></h2>
<p>首先我们要配置一下 Fiddler 的证书，为什么要配置这个玩意呢？ 有人会问， 那么我们先来简单看一下原理：</p>
<p>1、Fiddler 拦截到我们自己客户端的 Https 请求。</p>
<p>2、把拦截到的请求转发给请求服务器。</p>
<p>3、请求服务器响应，并生成证书文件发送给我们客户端，Fiddler 拦截，获取到请求服务器响应的证书文件，并把我们配置的伪造证书发送给我们自己的客户端。</p>
<p>4、客户端接受到伪造证书，再次请求数据。数据已经根据伪造证书加密。</p>
<p>5、Fiddler 拦截获取其中所有请求信息，再根据3,步骤中拦截到的正真证书文件 进行信息加密，发送给请求服务器。</p>
<p>总的来说, Fiddler 起到了中间转发数据的角色。所以可以获取到我们客户端发送以及服务器响应的数据信息。</p>
<p>说白了，这就是一次"善意的中间人攻击"。HTTPS 的安全依赖于证书信任链：客户端拿到服务器证书后，会去校验它是不是由自己信任的根证书机构签发的。Fiddler 之所以能替换证书还不被客户端拒绝，前提是你把它的根证书装进了系统的"受信任的根证书颁发机构"里——一旦装了，Fiddler 现签的伪造证书就通过了校验，客户端才肯用它加密后续数据，明文也就落到了 Fiddler 手里。这也解释了下面第 2 步"信任根证书"为什么不可省略。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="证书的配置">证书的配置<a href="https://tanzhuo.xyz/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu#%E8%AF%81%E4%B9%A6%E7%9A%84%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="证书的配置的直接链接" title="证书的配置的直接链接" translate="no">​</a></h2>
<p><strong>Fiddler 证书的配置：</strong></p>
<p>1、Tools -&gt; Options -&gt; HTTPS</p>
<p>点击 Decrypt HTTPS traffic 选中 from all processes</p>
<p>这一步是打开 HTTPS 解密的总开关。<code>from all processes</code> 表示解密所有进程的 HTTPS 流量；如果只想抓某个程序，可以改成 <code>from browsers only</code> 或用后面的进程过滤，避免无关流量把列表刷满。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-20-404125e5766d5ffd58605f55e31804eb.webp" width="1600" height="869" class="img_ev3q"></p>
<p>2、Actions -&gt; Trust Root Certificate</p>
<p>这一步就是前面原理里说的关键动作：把 Fiddler 的根证书安装到系统信任库。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-21-621c2ed823d08158c78ad432ddde9c39.webp" width="1600" height="869" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-22-7f14841bfa953c40fea063f5d9037588.webp" width="1600" height="869" class="img_ev3q"></p>
<p>3、点击yes后证书配置成功，开启抓包即可查看到Https请求内容信息</p>
<p>弹窗点 <code>Yes</code> 确认信任，证书即安装完成。此时回到主界面，之前显示为加密乱码的 HTTPS 会话就能看到明文的请求头、请求体和响应体了。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-23-507eb4cb4ef1dcdbd24c643d2132007d.webp" width="1600" height="869" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class="">抓不到某个 App 的包：多半是它没走系统代理，或者做了证书校验绕过。Android 7 以后应用默认不信任用户安装的证书，需要 App 本身配置或用其它手段，Fiddler 单靠装证书抓不到。</li>
<li class="">客户端做了 SSL Pinning（证书固定）：这类应用把服务器证书指纹写死在代码里，看到 Fiddler 换的证书会直接拒绝连接，普通配置无解。</li>
<li class="">Firefox 抓不到：确认它的独立代理指向了 <code>127.0.0.1:8888</code>，别忘了这一步。</li>
</ul>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p>Fiddler 的根证书装进系统后，等于给这台机器开了一个可解密所有 HTTPS 的口子。调试结束记得在 Tools -&gt; Options -&gt; HTTPS 里点 <code>Remove Interception Certificates</code>，或到系统证书管理里删掉 <code>DO_NOT_TRUST_FiddlerRoot</code>，别把它长期留在开发机上。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>抓 HTTP 明文几乎零配置，抓 HTTPS 的核心只有两件事：打开 Decrypt HTTPS traffic，以及信任 Fiddler 的根证书。理解了"中间人 + 伪造证书"这条主线，遇到抓不到包时也能大致判断卡在了哪一环——是没走代理、证书没信任，还是对方做了 Pinning。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Redis 常用UI工具]]></title>
            <link>https://tanzhuo.xyz/2022/10/20/redis-chang-yong-uigong-ju</link>
            <guid>https://tanzhuo.xyz/2022/10/20/redis-chang-yong-uigong-ju</guid>
            <pubDate>Thu, 20 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Redis 命令行查数据不够直观，记录几款常用的 Redis 图形化客户端：RDM、AnotherRedisDesktopManager、QuickRedis，并附上选择建议与注意事项。]]></description>
            <content:encoded><![CDATA[<p>Redis虽然可以通过客户端执行命令查看数据信息，但并没有图形化界面来的高效，故此记录几款常用的Redis UI工具。</p>
<!-- -->
<p>日常开发里查 Redis 的场景其实很琐碎：确认某个 key 有没有写进去、看看序列化后的 value 长什么样、检查 TTL 是否符合预期。用 <code>redis-cli</code> 当然都能做到，但要在 <code>keys</code> / <code>scan</code>、<code>type</code>、<code>ttl</code>、<code>get</code> / <code>hgetall</code> 之间来回切换，效率不高；遇到 Hash、ZSet 这类结构化数据，纯文本输出读起来也费劲。一款顺手的图形化客户端能把这些操作收敛到一个界面里，键列表、数据结构、TTL 一目了然，所以值得把用过的几款记下来，方便以后换机器时直接装。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-desktop-manager">Redis Desktop Manager<a href="https://tanzhuo.xyz/2022/10/20/redis-chang-yong-uigong-ju#redis-desktop-manager" class="hash-link" aria-label="Redis Desktop Manager的直接链接" title="Redis Desktop Manager的直接链接" translate="no">​</a></h2>
<p>收费，可以试用半个月，也可以在网上找到破解版，只是版本可能并不是最新版本。</p>
<p>RDM 算是老牌工具了，早期版本开源免费，后来转为付费订阅并改名 RESP.app，这也是很多人转投其他免费客户端的原因。功能上比较全面：支持 SSH 隧道、SSL 连接，内置控制台可以直接敲命令，对各种数据类型的展示也比较成熟。如果公司愿意付费，它仍然是一个稳妥的选择。</p>
<p><a href="https://resp.app/" target="_blank" rel="noopener noreferrer" class="">RESP.app (formerly Redis Desktop Manager) - GUI for Redis ® available on Windows, macOS, iPad and Linux.</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="anotherredisdesktopmanager">AnotherRedisDesktopManager<a href="https://tanzhuo.xyz/2022/10/20/redis-chang-yong-uigong-ju#anotherredisdesktopmanager" class="hash-link" aria-label="AnotherRedisDesktopManager的直接链接" title="AnotherRedisDesktopManager的直接链接" translate="no">​</a></h2>
<p>开源免费，好用，UI简洁好看。</p>
<p>名字就带着一点对 RDM 收费的调侃意味。它主打的是稳定：加载大量 key 时不会卡死或崩溃，这一点在生产库 key 数量上百万时尤其重要——它用的是 <code>scan</code> 渐进式遍历而不是一次性拉全量。日常需要的功能都有：多连接管理、SSH/SSL、集群模式、命令行终端、暗色主题，key 支持按分隔符折叠成树形展示。我自己目前主要用的就是它。</p>
<p><a href="https://github.com/qishibo/AnotherRedisDesktopManager" target="_blank" rel="noopener noreferrer" class="">GitHub - qishibo/AnotherRedisDesktopManager: 🚀🚀🚀A faster, better and more stable redis desktop manager [GUI client], compatible with Linux, Windows, Mac. What’s more, it won’t crash when loading massive keys.</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="quickredis">QuickRedis<a href="https://tanzhuo.xyz/2022/10/20/redis-chang-yong-uigong-ju#quickredis" class="hash-link" aria-label="QuickRedis的直接链接" title="QuickRedis的直接链接" translate="no">​</a></h2>
<p>QuickRedis 是一款 永久免费 的 Redis 可视化管理工具。它支持直连、哨兵、集群模式，支持亿万数量级的 key，还有令人兴奋的 UI。QuickRedis 支持 Windows 、 Mac OS X 和 Linux 下运行。QuickRedis 是一个效率工具，当别人在努力敲命令的时候，而你已经在喝茶。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-6874747-d065e1e6a63c9c853df4c8faa4fb757b.webp" width="1600" height="936" class="img_ev3q"></p>
<p>它对哨兵和集群模式的支持是开箱即用的，不需要像命令行那样自己处理 <code>MOVED</code> 重定向或者手动找主节点，这对连生产环境的集群来说省事不少。界面走的是树形目录风格，把 key 按冒号分隔符组织成层级，习惯了按业务前缀命名 key 的话会很舒服。</p>
<p><a href="https://github.com/quick123official/quick_redis_blog" target="_blank" rel="noopener noreferrer" class="">GitHub - quick123official/quick_redis_blog: QuickRedis is a free forever Redis Desktop manager. It supports direct connection, sentinel, and cluster mode, supports multiple languages, supports hundreds of millions of keys, and has an amazing UI. Supports both Windows, Mac OS X and Linux platform.</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/10/20/redis-chang-yong-uigong-ju#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）连生产环境要克制。图形化工具点几下就能删 key、改 value，误操作成本比命令行更低。建议生产连接只用只读账号，或者至少在连接命名上做明显区分，避免把测试库的操作习惯带到线上。</p>
<p>2）小心全量扫描。部分客户端在展示键列表时如果用 <code>keys *</code> 而不是 <code>scan</code>，在大库上会阻塞 Redis 主线程。选工具时可以留意这一点，AnotherRedisDesktopManager 在这方面口碑不错。</p>
<p>3）value 显示乱码通常不是工具的问题。业务代码用 JDK 序列化或 Protobuf 写入的二进制数据，客户端只能按原样展示字节。部分工具支持选择反序列化方式或格式化 JSON，用之前可以看看有没有这个选项。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>连接远程 Redis 时优先走 SSH 隧道，而不是直接把 6379 端口暴露在公网上。上面几款工具都支持配置 SSH 连接。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/20/redis-chang-yong-uigong-ju#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>三款工具里，RDM（RESP.app）功能最全但收费；AnotherRedisDesktopManager 开源免费、稳定省心，适合作为默认选择；QuickRedis 同样免费，树形 UI 和对哨兵、集群的支持是亮点。工具本身没有绝对优劣，选一款顺手的，把查数据的时间省下来就够了。</p>]]></content:encoded>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[7层网络协议]]></title>
            <link>https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi</link>
            <guid>https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi</guid>
            <pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[梳理 OSI 七层模型：每一层解决什么问题、数据单位叫什么、对应哪些常见设备与协议，以及它和 TCP/IP 模型的关系。]]></description>
            <content:encoded><![CDATA[<p>排查网络问题时，最常用的思路就是分层定位：网线通不通、IP 能不能 ping 通、端口有没有监听、应用返回了什么。这套思路的源头就是 OSI 七层模型。这里把七层各自解决的问题过一遍，作为自己的备忘。</p>
<!-- -->
<p>先说为什么要分层。网络通信要处理的事情太多：信号怎么在介质上传输、出错了怎么办、跨网络怎么找到对方、应用之间怎么约定数据格式。把这些问题按职责切开，每一层只依赖下一层提供的服务，只向上一层暴露接口，各层就可以独立设计和替换。OSI 模型把整个过程切成七层，从下往上依次是：物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="物理层">物理层<a href="https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi#%E7%89%A9%E7%90%86%E5%B1%82" class="hash-link" aria-label="物理层的直接链接" title="物理层的直接链接" translate="no">​</a></h2>
<p>解决两个硬件之间怎么通信的问题，常见的物理媒介有光纤、电缆、中继器等。它主要定义物理设备标准，如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。</p>
<p>它的主要作用是传输比特流（就是由1、0转化为电流强弱来进行传输，到达目的地后在转化为1、0，也就是我们常说的数模转换与模数转换）。这一层的数据叫做比特。</p>
<p>换句话说，物理层只关心电气特性和机械特性——电压多高算 1、接口长什么样、一秒能传多少比特，完全不理解传的内容是什么。网卡的物理接口、集线器都工作在这一层。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据链路层">数据链路层<a href="https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi#%E6%95%B0%E6%8D%AE%E9%93%BE%E8%B7%AF%E5%B1%82" class="hash-link" aria-label="数据链路层的直接链接" title="数据链路层的直接链接" translate="no">​</a></h2>
<p>在计算机网络中由于各种干扰的存在，物理链路是不可靠的。该层的主要功能就是：通过各种控制协议，将有差错的物理信道变为无差错的、能可靠传输数据帧的数据链路。</p>
<p>它的具体工作是接收来自物理层的位流形式的数据，并封装成帧，传送到上一层；同样，也将来自上层的数据帧，拆装为位流形式的数据转发到物理层。这一层的数据叫做帧。</p>
<p>封装成帧之后，才有了"一段数据从哪开始、到哪结束"的边界，也才能在帧尾附加校验字段做差错检测，收到坏帧直接丢弃。这一层用 MAC 地址标识同一链路上的设备，以太网、交换机都工作在这一层，所以交换机也常被叫作二层设备。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="网络层">网络层<a href="https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi#%E7%BD%91%E7%BB%9C%E5%B1%82" class="hash-link" aria-label="网络层的直接链接" title="网络层的直接链接" translate="no">​</a></h2>
<p>计算机网络中如果有多台计算机，怎么找到要发的那台？如果中间有多个节点，怎么选择路径？这就是路由要做的事。</p>
<p>该层的主要任务就是：通过路由选择算法，为报文（该层的数据单位，由上一层数据打包而来）通过通信子网选择最适当的路径。这一层定义的是IP地址，通过IP地址寻址，所以产生了IP协议。</p>
<p>和链路层的区别在于作用范围：MAC 地址只在同一段链路内有效，IP 地址则是全网寻址。数据包每经过一个路由器，链路层的帧会被拆掉重新封装，但 IP 层的源地址和目的地址一路保持不变。要注意 IP 协议本身是"尽力而为"的——它只负责把包往目的地送，不保证送到、不保证顺序，可靠性问题留给上层解决。路由器就是典型的三层设备。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="传输层">传输层<a href="https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi#%E4%BC%A0%E8%BE%93%E5%B1%82" class="hash-link" aria-label="传输层的直接链接" title="传输层的直接链接" translate="no">​</a></h2>
<p>当发送大量数据时，很可能会出现丢包的情况，另一台电脑要告诉是否完整接收到全部的包。如果缺了，就告诉丢了哪些包，然后再发一次，直至全部接收为止。</p>
<p>简单来说，传输层的主要功能就是：监控数据传输服务的质量，保证报文的正确传输。</p>
<p>传输层还解决了另一个问题：IP 地址只能定位到一台主机，但一台主机上跑着很多进程，数据到底交给谁？答案是端口号，IP 加端口才能定位到具体的进程。这一层最有名的两个协议就是 TCP 和 UDP：TCP 面向连接，靠确认、重传、流量控制来保证可靠；UDP 无连接，不保证可靠但开销小，适合对时延敏感、丢一点也无所谓的场景。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="会话层">会话层<a href="https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi#%E4%BC%9A%E8%AF%9D%E5%B1%82" class="hash-link" aria-label="会话层的直接链接" title="会话层的直接链接" translate="no">​</a></h2>
<p>虽然已经可以实现给正确的计算机，发送正确的封装过后的信息了。但我们总不可能每次都要调用传输层协议去打包，然后再调用IP协议去找路由，所以我们要建立一个自动收发包，自动寻址的功能。于是会话层出现了：它的作用就是建立和管理应用程序之间的通信。</p>
<p>它负责的是会话的建立、维持和终止——通信双方从"开始对话"到"结束对话"的整个过程由它管理。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="表示层">表示层<a href="https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi#%E8%A1%A8%E7%A4%BA%E5%B1%82" class="hash-link" aria-label="表示层的直接链接" title="表示层的直接链接" translate="no">​</a></h2>
<p>表示层负责数据格式的转换，将应用处理的信息转换为适合网络传输的格式，或者将来自下一层的数据转换为上层能处理的格式。</p>
<p>典型的工作包括字符编码转换、数据的加密解密和压缩解压。两台机器内部表示数据的方式可能不同，表示层保证一方发出的数据在另一方能被正确理解。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="应用层">应用层<a href="https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi#%E5%BA%94%E7%94%A8%E5%B1%82" class="hash-link" aria-label="应用层的直接链接" title="应用层的直接链接" translate="no">​</a></h2>
<p>应用层是计算机用户，以及各种应用程序和网络之间的接口，其功能是直接向用户提供服务，完成用户希望在网络上完成的各种工作。</p>
<p>我们平时打交道最多的协议都在这一层：浏览网页的 HTTP、传文件的 FTP、发邮件的 SMTP、做域名解析的 DNS。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-----1--96518b37b423d77157eafd5d667beaaa.webp" width="1120" height="1587" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="和-tcpip-模型的关系">和 TCP/IP 模型的关系<a href="https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi#%E5%92%8C-tcpip-%E6%A8%A1%E5%9E%8B%E7%9A%84%E5%85%B3%E7%B3%BB" class="hash-link" aria-label="和 TCP/IP 模型的关系的直接链接" title="和 TCP/IP 模型的关系的直接链接" translate="no">​</a></h2>
<p>OSI 是概念模型，实际互联网用的协议栈是 TCP/IP。TCP/IP 通常划成四层：网络接口层对应 OSI 的物理层和数据链路层，网际层对应网络层，传输层对应传输层，而会话层、表示层、应用层被合并成一个应用层。这也是为什么实际协议里很少见到独立的会话层、表示层协议——它们的职责大多由应用层协议或应用程序自己承担了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）各层数据单位容易记混：物理层是比特，数据链路层是帧，网络层是包（报文），传输层是段。排错和面试都常用到。</p>
<p>2）设备和层的对应关系值得记住：集线器工作在物理层，交换机工作在数据链路层，路由器工作在网络层。"二层交换机""三层交换机"的叫法就是从这里来的。</p>
<p>3）判断一个协议属于哪一层，看它解决的是什么问题，而不是望文生义。比如 DNS 虽然是基础设施，但它是应用层协议；ARP 做的是 IP 到 MAC 的映射，工作在网络层和链路层之间。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>按层定位问题的顺序可以固定下来：先看物理连通（网线、网卡灯），再看链路和 IP（ping 网关、ping 目标），再看传输层（端口是否监听、防火墙是否放行），最后看应用本身的日志。逐层排除比乱试快得多。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/17/7ceng-wang-luo-xie-yi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>七层模型的核心是职责分离：每一层只解决一类问题，向上提供服务，向下使用服务。物理层传比特，链路层保证一段链路可靠，网络层负责全网寻址和路由，传输层保证端到端的传输质量，上面三层则服务于应用本身。理解了每层解决什么问题，再看具体协议就知道它该放在哪里、排错时该从哪层查起。</p>]]></content:encoded>
            <category>Linux</category>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[Docker 基本命令]]></title>
            <link>https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling</link>
            <guid>https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling</guid>
            <pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[整理 Docker 日常最常用的一组命令：拉取镜像、启动容器、查看镜像与容器列表、删除、停止、重启以及进入容器，并补充镜像与容器的关系和几个容易踩的坑。]]></description>
            <content:encoded><![CDATA[<p>刚开始用 Docker 的时候，最常见的困惑不是概念，而是"这个操作到底该敲哪条命令"。这篇把日常用得最多的一组基本命令整理出来，每条都给出格式和例子，方便随手查。</p>
<!-- -->
<p>在看具体命令之前，先理清一件事：<strong>镜像（image）和容器（container）是两个东西</strong>。镜像是只读的模板，包含了运行程序所需的文件系统和配置；容器则是镜像的一个运行实例，在镜像之上加了一层可写层。同一个镜像可以启动多个互不干扰的容器。下面的命令基本都是围绕这两个对象展开的——有的操作镜像（pull、images、rmi），有的操作容器（run、ps、rm、kill、restart、start、exec），分清对象，命令就不容易记混。</p>
<p>另外，Docker 守护进程默认以 root 权限运行，普通用户需要加 <code>sudo</code> 才能执行 docker 命令（或者把用户加入 docker 组），所以下面的例子大多带着 <code>sudo</code>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="帮助">帮助<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E5%B8%AE%E5%8A%A9" class="hash-link" aria-label="帮助的直接链接" title="帮助的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-h</span><br></div></code></pre></div></div>
<p>忘记命令或参数时，先看帮助。也可以对具体子命令查帮助，比如 <code>docker run --help</code>，会列出该子命令支持的所有选项。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="获取镜像">获取镜像<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E8%8E%B7%E5%8F%96%E9%95%9C%E5%83%8F" class="hash-link" aria-label="获取镜像的直接链接" title="获取镜像的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 格式：NAME 是镜像名，TAG 是版本标签</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> pull NAME</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">:TAG</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 例：拉取 centos 的 latest 标签</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> pull centos:latest</span><br></div></code></pre></div></div>
<p><code>pull</code> 从镜像仓库（默认是 Docker Hub）把镜像下载到本地。如果不写 TAG，默认拉取 <code>latest</code>。镜像是分层存储的，拉取时能看到多个层各自的下载进度，已经存在于本地的层会直接复用，不会重复下载。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="启动-container">启动 Container<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E5%90%AF%E5%8A%A8-container" class="hash-link" aria-label="启动 Container的直接链接" title="启动 Container的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 格式</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> run </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">OPTIONS</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"> IMAGE </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">COMMAND</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">ARG</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">..</span><span class="token plain">.</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 例：基于镜像启动一个交互式容器（原文镜像名为 contos，应为 centos）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> run </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-t</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-i</span><span class="token plain"> contos /bin/bash</span><br></div></code></pre></div></div>
<p><code>run</code> 做的事情其实是两步：先基于镜像创建一个新容器，再启动它。几个常用参数：</p>
<p>1）<code>-i</code>：保持标准输入打开，能向容器里输入内容；</p>
<p>2）<code>-t</code>：分配一个伪终端，配合 <code>-i</code> 就能得到一个可交互的 shell；</p>
<p>3）镜像名后面跟的 <code>/bin/bash</code> 是容器启动后执行的命令，这个命令退出，容器也就停止了。</p>
<p>如果本地没有对应镜像，<code>run</code> 会先自动执行一次 pull。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="查看镜像列表">查看镜像列表<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E6%9F%A5%E7%9C%8B%E9%95%9C%E5%83%8F%E5%88%97%E8%A1%A8" class="hash-link" aria-label="查看镜像列表的直接链接" title="查看镜像列表的直接链接" translate="no">​</a></h2>
<p>列出本地的所有 images：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 格式</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> images </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">OPTIONS</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">NAME</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 例：只看 centos 相关的镜像</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> images centos</span><br></div></code></pre></div></div>
<p>输出里会显示镜像的仓库名、标签、镜像 ID、创建时间和大小。后面删除镜像时用到的就是这里的名字或 ID。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="查看容器列表">查看容器列表<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E6%9F%A5%E7%9C%8B%E5%AE%B9%E5%99%A8%E5%88%97%E8%A1%A8" class="hash-link" aria-label="查看容器列表的直接链接" title="查看容器列表的直接链接" translate="no">​</a></h2>
<p>可看到我们创建过的所有 container：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 格式</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">ps</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">OPTIONS</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 例：-a 列出所有容器，包括已经停止的</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">ps</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-a</span><br></div></code></pre></div></div>
<p>注意 <code>docker ps</code> 默认只显示<strong>正在运行</strong>的容器。刚退出的容器用 <code>ps</code> 看不到，很容易误以为容器"消失"了，加上 <code>-a</code> 才能看到全部。输出中的 CONTAINER ID 和 NAMES 都可以用来在后续命令里指定容器。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="删除镜像">删除镜像<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E5%88%A0%E9%99%A4%E9%95%9C%E5%83%8F" class="hash-link" aria-label="删除镜像的直接链接" title="删除镜像的直接链接" translate="no">​</a></h2>
<p>从本地删除一个已经下载的镜像：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 格式：可以一次删多个</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> rmi IMAGE </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">IMAGE</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">..</span><span class="token plain">.</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 例</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> rmi centos:latest</span><br></div></code></pre></div></div>
<p>如果还有容器（哪怕是停止状态的）基于这个镜像创建，<code>rmi</code> 会报错拒绝删除，需要先把相关容器 <code>rm</code> 掉。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="移除容器实例">移除容器实例<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E7%A7%BB%E9%99%A4%E5%AE%B9%E5%99%A8%E5%AE%9E%E4%BE%8B" class="hash-link" aria-label="移除容器实例的直接链接" title="移除容器实例的直接链接" translate="no">​</a></h2>
<p>移除一个或多个容器实例：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 格式</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">rm</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">OPTIONS</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"> CONTAINER </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">CONTAINER</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">..</span><span class="token plain">.</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><br></div></code></pre></div></div>
<p>移除所有运行的容器：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 原文写法如下，实际使用时中间的命令要用 $() 或反引号做命令替换：</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># sudo docker rm $(sudo docker ps -aq)</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">rm</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">ps</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-aq</span><br></div></code></pre></div></div>
<p>这里 <code>docker ps -aq</code> 的 <code>-q</code> 表示只输出容器 ID，把它作为参数传给 <code>docker rm</code>，就实现了批量删除。正在运行的容器不能直接 <code>rm</code>，要先停止，或者加 <code>-f</code> 强制删除。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="停止一个正在运行的容器">停止一个正在运行的容器<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E5%81%9C%E6%AD%A2%E4%B8%80%E4%B8%AA%E6%AD%A3%E5%9C%A8%E8%BF%90%E8%A1%8C%E7%9A%84%E5%AE%B9%E5%99%A8" class="hash-link" aria-label="停止一个正在运行的容器的直接链接" title="停止一个正在运行的容器的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 格式</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">kill</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">OPTIONS</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"> CONTAINER </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">CONTAINNER</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">..</span><span class="token plain">.</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 例：容器 ID 写前几位即可，能唯一定位就行</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">kill</span><span class="token plain"> 026e</span><br></div></code></pre></div></div>
<p><code>kill</code> 默认向容器内的主进程发送 SIGKILL，进程会被立即杀掉，没有清理的机会。如果希望程序有机会优雅退出，用 <code>docker stop</code> 更合适——它先发 SIGTERM，等待一段时间后才发 SIGKILL。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重启一个正在运行的容器">重启一个正在运行的容器<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E9%87%8D%E5%90%AF%E4%B8%80%E4%B8%AA%E6%AD%A3%E5%9C%A8%E8%BF%90%E8%A1%8C%E7%9A%84%E5%AE%B9%E5%99%A8" class="hash-link" aria-label="重启一个正在运行的容器的直接链接" title="重启一个正在运行的容器的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 格式</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> restart </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">OPTIONS</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"> contains</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">CONTAINER</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 例</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> restart 026e</span><br></div></code></pre></div></div>
<p><code>restart</code> 相当于先停止再启动，容器可写层里的数据仍然保留。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="启动一个已经停止的容器">启动一个已经停止的容器<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E5%90%AF%E5%8A%A8%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E5%81%9C%E6%AD%A2%E7%9A%84%E5%AE%B9%E5%99%A8" class="hash-link" aria-label="启动一个已经停止的容器的直接链接" title="启动一个已经停止的容器的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 格式</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> start </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">OPTIONS</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"> CONTAINER </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token plain">CONTAINER</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">..</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 例</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> start 026e</span><br></div></code></pre></div></div>
<p>注意 <code>start</code> 和 <code>run</code> 的区别：<code>run</code> 会创建<strong>新</strong>容器，而 <code>start</code> 只是把一个已存在但停止了的容器重新拉起来，之前在容器里做过的修改都还在。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="进入指定容器">进入指定容器<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E8%BF%9B%E5%85%A5%E6%8C%87%E5%AE%9A%E5%AE%B9%E5%99%A8" class="hash-link" aria-label="进入指定容器的直接链接" title="进入指定容器的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> </span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-it</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">容器名称或编号</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> /bin/bash</span><br></div></code></pre></div></div>
<p><code>exec</code> 在一个正在运行的容器里额外启动一个进程，这里就是再开一个 bash。它比 <code>attach</code> 更常用：从 <code>exec</code> 进去的 shell 里 <code>exit</code>，只是退出这个新开的进程，不会影响容器本身的运行。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）<strong>镜像 ID 与容器 ID 不要混用</strong>。<code>rmi</code> 删镜像、<code>rm</code> 删容器，参数传错了会报"没有该对象"的错误。</p>
<p>2）<strong>容器停止不等于删除</strong>。停止的容器仍然占着名字和磁盘上的可写层，新容器如果用了相同的名字会创建失败，需要先 <code>rm</code> 掉旧的。</p>
<p>3）<strong>优先用 <code>stop</code> 而不是 <code>kill</code></strong>。除非进程卡死不响应，否则给应用一个收到 SIGTERM 后做清理的机会。</p>
<p>4）<strong>容器里的数据是易失的</strong>。容器被 <code>rm</code> 之后，可写层里的数据一并消失，重要数据应该通过数据卷挂载到宿主机。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/17/docker-ji-ben-ming-ling#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这一组命令覆盖了 Docker 的最小工作闭环：<code>pull</code> 拿镜像，<code>run</code> 起容器，<code>images</code> / <code>ps</code> 查看状态，<code>start</code> / <code>stop</code>（<code>kill</code>）/ <code>restart</code> 控制生命周期，<code>exec</code> 进入容器排查问题，最后用 <code>rm</code> / <code>rmi</code> 清理容器和镜像。把"镜像是模板、容器是实例"这条主线记住，剩下的就是熟练度问题了。</p>]]></content:encoded>
            <category>Docker</category>
        </item>
        <item>
            <title><![CDATA[IDEA 常用插件以及开发工具]]></title>
            <link>https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian</link>
            <guid>https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian</guid>
            <pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[整理日常 Java 开发中常用的 IDEA 插件与工具:SonarQube、FindBugs、CheckStyle、Maven Helper、Lombok、Key Promoter X 等,附使用场景说明。]]></description>
            <content:encoded><![CDATA[<p>IDEA 本身已经足够强大,但装对几个插件,能把代码质量检查、依赖排查、格式规范这些日常琐事省下不少时间。这篇整理一下我在 Java 开发中常用的插件和工具,按用途分类,方便按需取用。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="静态代码质量管理工具部署平台">静态代码质量管理工具部署平台<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#%E9%9D%99%E6%80%81%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7%E9%83%A8%E7%BD%B2%E5%B9%B3%E5%8F%B0" class="hash-link" aria-label="静态代码质量管理工具部署平台的直接链接" title="静态代码质量管理工具部署平台的直接链接" translate="no">​</a></h2>
<p><a href="https://www.sonarqube.org/" target="_blank" rel="noopener noreferrer" class="">Code Quality and Code Security | SonarQube</a></p>
<p>SonarQube 严格来说不是 IDEA 插件,而是一个独立部署的代码质量管理平台。它通过静态分析扫描代码中的 bug、漏洞和坏味道(code smell),并给出量化的质量报告。一般的用法是在团队内部署一套服务,配合 CI 流程在每次提交或合并时自动扫描,把质量问题拦在合入之前。IDEA 侧也有对应的插件可以连到服务端,在本地就能看到扫描结果。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="代码质量检查插件-findbugs">代码质量检查插件 FindBugs<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F%E6%A3%80%E6%9F%A5%E6%8F%92%E4%BB%B6-findbugs" class="hash-link" aria-label="代码质量检查插件 FindBugs的直接链接" title="代码质量检查插件 FindBugs的直接链接" translate="no">​</a></h2>
<p>可以直接以插件安装在 IDEA IDE 中。</p>
<p><a href="https://findbugs.sourceforge.net/" target="_blank" rel="noopener noreferrer" class="">FindBugs™ - Find Bugs in Java Programs (sourceforge.net)</a></p>
<p>FindBugs 的原理是分析编译后的字节码,匹配已知的缺陷模式,比如空指针解引用、资源未关闭、equals 与 hashCode 不配对等。它和 SonarQube 的定位互补:SonarQube 偏向团队级的持续质量管理,FindBugs 更适合在本地写完代码后随手扫一遍。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="翻译插件英语不好者必备">翻译插件(英语不好者必备)<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#%E7%BF%BB%E8%AF%91%E6%8F%92%E4%BB%B6%E8%8B%B1%E8%AF%AD%E4%B8%8D%E5%A5%BD%E8%80%85%E5%BF%85%E5%A4%87" class="hash-link" aria-label="翻译插件(英语不好者必备)的直接链接" title="翻译插件(英语不好者必备)的直接链接" translate="no">​</a></h2>
<p>Translation 可以在代码开发视图中直接翻译。</p>
<p>选中变量名、注释或者文档字符串,快捷键一按就能看到译文,不用切出去开浏览器查词典。读英文源码注释、给变量起名的时候尤其顺手。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="代码编码格式约束插件">代码编码格式约束插件<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#%E4%BB%A3%E7%A0%81%E7%BC%96%E7%A0%81%E6%A0%BC%E5%BC%8F%E7%BA%A6%E6%9D%9F%E6%8F%92%E4%BB%B6" class="hash-link" aria-label="代码编码格式约束插件的直接链接" title="代码编码格式约束插件的直接链接" translate="no">​</a></h2>
<p>CheckStyle-IDEA 通过检查对代码编码格式,命名约定,Javadoc,类设计等方面进行代码规范和风格的检查,从而有效约束开发人员更好地遵循代码编写规范。</p>
<p>它和 FindBugs 的侧重点不同:FindBugs 找的是"可能出 bug 的代码",CheckStyle 管的是"写得不规范的代码"。团队可以共用一份规则文件(比如基于阿里或 Google 的 Java 风格规范改出来的),保证所有人产出的代码风格一致,review 时就不用再为格式问题来回拉扯。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="java-maven-包冲突检测工具">Java Maven 包冲突检测工具<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#java-maven-%E5%8C%85%E5%86%B2%E7%AA%81%E6%A3%80%E6%B5%8B%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="Java Maven 包冲突检测工具的直接链接" title="Java Maven 包冲突检测工具的直接链接" translate="no">​</a></h2>
<p>Maven Helper 可以检测整个项目 pom.xml 中的依赖冲突。</p>
<p>Maven 的依赖是传递性的,项目一大,同一个包被不同依赖间接引入不同版本几乎是必然的。默认的 <code>mvn dependency:tree</code> 输出很难读,Maven Helper 直接在 IDEA 里打开 pom.xml 就多出一个依赖分析视图,冲突的依赖会标红,右键就能 exclude 掉不想要的版本,排查 NoSuchMethodError、ClassNotFoundException 这类由版本冲突引起的问题时非常好用。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="代码缩略视图插件">代码缩略视图插件<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#%E4%BB%A3%E7%A0%81%E7%BC%A9%E7%95%A5%E8%A7%86%E5%9B%BE%E6%8F%92%E4%BB%B6" class="hash-link" aria-label="代码缩略视图插件的直接链接" title="代码缩略视图插件的直接链接" translate="no">​</a></h2>
<p>CodeGlance 类似于 VSCode 软件中的缩略视图。</p>
<p>在编辑器右侧显示整个文件的缩略图,长文件里跳转、定位大致位置时比拖滚动条直观。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="项目热部署插件收费">项目热部署插件(收费)<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#%E9%A1%B9%E7%9B%AE%E7%83%AD%E9%83%A8%E7%BD%B2%E6%8F%92%E4%BB%B6%E6%94%B6%E8%B4%B9" class="hash-link" aria-label="项目热部署插件(收费)的直接链接" title="项目热部署插件(收费)的直接链接" translate="no">​</a></h2>
<p>JRebel 减少修改代码与 mapper.xml 后重启,提高工作效率。</p>
<p><a href="https://tanzhuo.xyz/idea-2020-jrebelpo-jie/" target="_blank" rel="noopener noreferrer" class="">IntelliJ IDEA 2020 JRebel破解</a></p>
<p>传统开发流程里,改一行 Java 代码就要重启应用,项目大了一次重启动辄几十秒甚至几分钟。JRebel 的思路是在 JVM 层面做类的热替换,修改代码后无需重启就能生效,连 MyBatis 的 mapper.xml 变更也能覆盖到,比 Spring Boot 自带的 devtools 重启方案覆盖面更广。缺点是商业收费,个人使用成本不低。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="json-字符串对象转-java-文件插件">json 字符串对象转 java 文件插件<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#json-%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AF%B9%E8%B1%A1%E8%BD%AC-java-%E6%96%87%E4%BB%B6%E6%8F%92%E4%BB%B6" class="hash-link" aria-label="json 字符串对象转 java 文件插件的直接链接" title="json 字符串对象转 java 文件插件的直接链接" translate="no">​</a></h2>
<p>GsonFormat</p>
<p>对接第三方接口时,拿到一段 JSON 响应,手写对应的 Java Bean 又慢又容易抄错字段。GsonFormat 可以把 JSON 字符串直接粘进去,自动生成字段和类型都对应好的实体类,嵌套结构也能处理。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="开发效率工具">开发效率工具<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="开发效率工具的直接链接" title="开发效率工具的直接链接" translate="no">​</a></h2>
<p>Lombok 是一个在 Java 开发过程中用注解的方式,简化了 JavaBean 的编写,避免了冗余和样板式代码而出现的插件,让编写的类更加简洁。</p>
<p>它的原理是在编译期通过注解处理器生成代码:<code>@Getter</code>/<code>@Setter</code> 生成读写方法,<code>@Builder</code> 生成建造者,<code>@Slf4j</code> 生成日志对象声明。源码里看不到这些方法,但编译出的 class 文件里都有。要注意 Lombok 需要两头配合——项目里引入依赖,IDEA 里装插件,少一头都会满屏报红。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="快捷键提示插件算是必装">快捷键提示插件(算是必装)<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#%E5%BF%AB%E6%8D%B7%E9%94%AE%E6%8F%90%E7%A4%BA%E6%8F%92%E4%BB%B6%E7%AE%97%E6%98%AF%E5%BF%85%E8%A3%85" class="hash-link" aria-label="快捷键提示插件(算是必装)的直接链接" title="快捷键提示插件(算是必装)的直接链接" translate="no">​</a></h2>
<p>Key Promoter X 是用于基于 IntelliJ 产品(如 IDEA,Android Studio 或 CLion)的插件,它有助于在工作时从鼠标操作中学习基本的键盘快捷键。当您在 IDE 内部的按钮上使用鼠标时,Key Promoter X 会显示您应该使用的键盘快捷键。这提供了一种简单的方法来学习如何用键盘按键代替繁琐的鼠标工作,并有助于过渡到更快的无鼠标开发。当前,它支持工具栏按钮,菜单按钮,工具窗口及其中的操作。</p>
<p>插件地址:<a href="https://plugins.jetbrains.com/plugin/9792-key-promoter-x" target="_blank" rel="noopener noreferrer" class="">https://plugins.jetbrains.com/plugin/9792-key-promoter-x</a></p>
<p>这类"教学型"插件的好处是被动学习:不需要专门背快捷键表,哪个操作用鼠标点得多,它就反复提醒哪个,自然就记住了最常用的那批。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）插件不是越多越好。每个插件都会占内存、拖慢索引和启动速度,IDEA 卡顿时先检查是不是插件装太多了,不常用的及时禁用。</p>
<p>2）Lombok 相关的编译报错,优先确认依赖和插件是否都装了,以及注解处理(Annotation Processing)是否已启用。</p>
<p>3）质量检查类插件(FindBugs、CheckStyle)在大项目上全量扫描会比较耗时,建议只对改动的模块或文件跑增量检查。</p>
<p>4）依赖冲突用 Maven Helper 排查后,exclude 要谨慎,确认被排除的版本确实不被其他模块需要,否则可能把问题从编译期推迟到运行期。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/17/idea-chang-yong-cha-jian#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这十个工具大致覆盖了几类日常需求:质量与规范(SonarQube、FindBugs、CheckStyle)、依赖管理(Maven Helper)、编码效率(Lombok、GsonFormat、Translation、CodeGlance)、开发体验(JRebel、Key Promoter X)。不必一次装全,按自己项目的痛点挑着装,用顺手了再补,才是插件的正确打开方式。</p>]]></content:encoded>
            <category>Java</category>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[JVM GC 知识点]]></title>
            <link>https://tanzhuo.xyz/2022/10/17/jvm-gc</link>
            <guid>https://tanzhuo.xyz/2022/10/17/jvm-gc</guid>
            <pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[JVM 中常见的垃圾收集器主要包括 Serial、Parallel、ParNew、CMS、G1 以及 ZGC 等。不同收集器在并发能力、停顿时间、吞吐量以及适用场景上各不相同。]]></description>
            <content:encoded><![CDATA[<p>JVM 中常见的垃圾收集器主要包括 Serial、Parallel、ParNew、CMS、G1 以及 ZGC 等。不同收集器在 <strong>并发能力、停顿时间、吞吐量以及适用场景</strong> 上各不相同。下面对常见的几种垃圾收集器进行整理说明。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="serial-收集器">Serial 收集器<a href="https://tanzhuo.xyz/2022/10/17/jvm-gc#serial-%E6%94%B6%E9%9B%86%E5%99%A8" class="hash-link" aria-label="Serial 收集器的直接链接" title="Serial 收集器的直接链接" translate="no">​</a></h2>
<p>对应参数:<code>-XX:+UseSerialGC -XX:+UseSerialOldGC</code></p>
<p>Serial 收集器是 JVM 中最基础、历史最悠久的垃圾收集器,它采用 <strong>单线程方式执行垃圾回收</strong>。在进行垃圾回收时,JVM 会触发 <strong>STW(Stop The World)</strong>,即暂停所有用户线程,直到垃圾回收完成后才恢复业务线程运行。</p>
<p>在算法实现上,Serial 收集器在 <strong>新生代使用标记-复制算法(Copying)</strong>,而在 <strong>老年代使用标记-整理算法(Mark-Compact)</strong>。由于整个回收过程只有一个 GC 线程,因此实现简单且稳定,但在堆内存较大或 CPU 核心较多的情况下,垃圾回收效率会明显下降。</p>
<p>因此 Serial 收集器通常适用于 <strong>内存规模较小、CPU 核心较少的应用环境</strong>,例如早期的 Client 模式 JVM 或一些资源受限的系统。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="parallel-scavenge-收集器">Parallel Scavenge 收集器<a href="https://tanzhuo.xyz/2022/10/17/jvm-gc#parallel-scavenge-%E6%94%B6%E9%9B%86%E5%99%A8" class="hash-link" aria-label="Parallel Scavenge 收集器的直接链接" title="Parallel Scavenge 收集器的直接链接" translate="no">​</a></h2>
<p>对应参数:<code>-XX:+UseParallelGC -XX:+UseParallelOldGC</code></p>
<p>Parallel Scavenge 收集器是 Serial 收集器的 <strong>多线程版本</strong>,它的核心目标是 <strong>提高系统吞吐量(Throughput)</strong>。在垃圾回收时,会使用多个 GC 线程并行执行,从而提高整体回收效率。</p>
<p>默认情况下,GC 线程数量与 CPU 核数基本一致,也可以通过参数 <code>-XX:ParallelGCThreads</code> 手动指定线程数量。Parallel 收集器在算法上与 Serial 收集器基本一致,即 <strong>新生代使用标记-复制算法,老年代使用标记-整理算法</strong>。</p>
<p>由于 Parallel 收集器更关注 <strong>吞吐量而非停顿时间</strong>,因此非常适合 <strong>后台计算型服务或批处理系统</strong>。在一些堆内存规模中等(例如 3~4GB 左右)的应用场景中,该收集器可以获得较好的性能表现。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="parnew-收集器">ParNew 收集器<a href="https://tanzhuo.xyz/2022/10/17/jvm-gc#parnew-%E6%94%B6%E9%9B%86%E5%99%A8" class="hash-link" aria-label="ParNew 收集器的直接链接" title="ParNew 收集器的直接链接" translate="no">​</a></h2>
<p>对应参数:<code>-XX:+UseParNewGC</code></p>
<p>ParNew 收集器可以理解为 <strong>Serial 收集器的多线程版本</strong>,但它的主要用途并不是单独使用,而是 <strong>与 CMS 收集器配合使用</strong>。ParNew 负责 <strong>新生代的垃圾回收</strong>,而 CMS 则负责 <strong>老年代的垃圾回收</strong>。</p>
<p>在实现方式上,ParNew 采用 <strong>多线程并行回收机制</strong>,并且在新生代中仍然使用 <strong>标记-复制算法</strong>。由于 CMS 是一个以低停顿时间为目标的收集器,而 Serial 收集器是单线程的,因此在实际使用 CMS 时通常会搭配 ParNew 来提高新生代回收效率。</p>
<p>这种组合(ParNew + CMS)在 <strong>早期 Web 服务和对停顿时间较敏感的系统中使用较多</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cms-收集器">CMS 收集器<a href="https://tanzhuo.xyz/2022/10/17/jvm-gc#cms-%E6%94%B6%E9%9B%86%E5%99%A8" class="hash-link" aria-label="CMS 收集器的直接链接" title="CMS 收集器的直接链接" translate="no">​</a></h2>
<p>对应参数:<code>-XX:+UseConcMarkSweepGC</code></p>
<p>CMS(Concurrent Mark Sweep)收集器主要用于 <strong>老年代垃圾回收</strong>,其设计目标是 <strong>尽可能减少垃圾回收时的停顿时间</strong>。CMS 的一个重要特点是 <strong>允许 GC 线程与用户线程并发执行</strong>,从而减少应用暂停时间。</p>
<p>CMS 使用 <strong>标记-清除算法(Mark-Sweep)</strong>,并基于 <strong>三色标记法</strong> 来完成对象可达性分析。整个回收过程通常分为五个阶段:</p>
<ol>
<li class=""><strong>初始标记(Initial Mark)</strong>
触发 STW,仅标记 GC Roots 直接引用的对象,由于对象数量较少,因此执行速度很快。</li>
<li class=""><strong>并发标记(Concurrent Mark)</strong>
与用户线程并发执行,遍历整个对象图,标记所有可达对象。</li>
<li class=""><strong>重新标记(Remark)</strong>
再次触发 STW,用于修正并发标记期间由于对象引用变化而产生的偏差。</li>
<li class=""><strong>并发清理(Concurrent Sweep)</strong>
与用户线程并发执行,清理未被标记的对象。</li>
<li class=""><strong>并发重置(Concurrent Reset)</strong>
重置 CMS 的内部状态,为下一次 GC 做准备。</li>
</ol>
<p>CMS 流程图:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10----b8e31d638671b82e0bfc6e3e2f62f4b7.webp" width="1105" height="396" class="img_ev3q"></p>
<p>CMS 把回收过程从一个步骤拆分为多个步骤,标记和清理都做了拆分,并把最耗时的标记对象步骤放到与用户线程并发执行,从而让应用程序几乎感知不到 STW 的停顿,只有初始标记与重新标记两个阶段存在很短的 STW,其余流程都是并发进行的。所以 CMS 达到了 <strong>最短回收停顿</strong> 的目标。不过最短停顿时间不代表整个回收流程耗时更短,实际上 CMS 的整体回收时间是要大于其他垃圾收集器的,只是它的停顿时间是最小的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="g1-收集器">G1 收集器<a href="https://tanzhuo.xyz/2022/10/17/jvm-gc#g1-%E6%94%B6%E9%9B%86%E5%99%A8" class="hash-link" aria-label="G1 收集器的直接链接" title="G1 收集器的直接链接" translate="no">​</a></h2>
<p>对应参数:<code>-XX:+UseG1GC</code></p>
<p>G1(Garbage First)收集器是 <strong>JDK9 之后默认的垃圾收集器</strong>,主要用于 <strong>大内存服务器环境</strong>。与传统垃圾收集器按照新生代和老年代进行物理划分不同,G1 将整个堆划分为多个 <strong>Region(区域)</strong>。</p>
<p>每个 Region 可以根据需要动态地作为 <strong>Eden、Survivor 或 Old 区域</strong> 使用。G1 会统计每个 Region 中的垃圾比例,并优先回收 <strong>垃圾最多、回收收益最高的 Region</strong>,这也是 "Garbage First" 名称的来源。</p>
<p>G1 的另一个重要特点是 <strong>可预测的停顿时间模型</strong>。通过参数 <code>-XX:MaxGCPauseMillis</code> 可以设定期望的最大停顿时间,G1 会根据该目标自动调整回收策略。</p>
<p>整体回收流程包括 <strong>初始标记、并发标记、最终标记以及 Region 回收</strong> 等阶段。相比 CMS,G1 在减少内存碎片、支持大堆内存以及控制停顿时间方面表现更加优秀,因此在现代 JVM 环境中被广泛使用。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zgc-收集器">ZGC 收集器<a href="https://tanzhuo.xyz/2022/10/17/jvm-gc#zgc-%E6%94%B6%E9%9B%86%E5%99%A8" class="hash-link" aria-label="ZGC 收集器的直接链接" title="ZGC 收集器的直接链接" translate="no">​</a></h2>
<p>ZGC 是 <strong>JDK11 引入的一种低延迟垃圾收集器</strong>,其核心目标是 <strong>实现毫秒级的 GC 停顿时间</strong>。在大多数情况下,ZGC 的 STW 停顿时间可以控制在 <strong>10ms 以内</strong>,即使在 TB 级别堆内存下也能保持稳定。</p>
<p>ZGC 的实现依赖于几项关键技术,包括 <strong>染色指针(Colored Pointer)</strong>、<strong>读屏障(Load Barrier)</strong> 以及基于 Region 的 <strong>ZPage 内存管理机制</strong>。通过这些技术,ZGC 可以在大部分时间内与用户线程并发执行垃圾回收。</p>
<p>ZGC 的主要回收过程包括 <strong>并发标记、并发重定位以及并发引用修复</strong>。由于绝大多数工作都在并发阶段完成,因此对应用线程的影响非常小。</p>
<p>因此 ZGC 非常适用于 <strong>超大内存服务器、实时系统以及金融交易等对延迟极其敏感的应用场景</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常见-gc-收集器对比">常见 GC 收集器对比<a href="https://tanzhuo.xyz/2022/10/17/jvm-gc#%E5%B8%B8%E8%A7%81-gc-%E6%94%B6%E9%9B%86%E5%99%A8%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="常见 GC 收集器对比的直接链接" title="常见 GC 收集器对比的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>Serial</strong>:单线程回收,适合小型应用</li>
<li class=""><strong>Parallel</strong>:多线程回收,吞吐量优先</li>
<li class=""><strong>ParNew + CMS</strong>:低停顿方案(JDK8 常见)</li>
<li class=""><strong>G1</strong>:现代 JVM 默认收集器,适合大内存服务</li>
<li class=""><strong>ZGC</strong>:超低延迟垃圾收集器,适合 TB 级内存系统</li>
</ul>





































































<table><thead><tr><th>收集器</th><th>类型</th><th>线程模式</th><th>主要算法</th><th>停顿特点</th><th>适用场景</th></tr></thead><tbody><tr><td>Serial</td><td>新生代 + 老年代</td><td>单线程</td><td>复制 + 标记整理</td><td>STW 时间较长</td><td>小型应用、单核 CPU</td></tr><tr><td>Parallel Scavenge</td><td>新生代</td><td>多线程</td><td>复制算法</td><td>吞吐量优先</td><td>后台计算、批处理任务</td></tr><tr><td>Parallel Old</td><td>老年代</td><td>多线程</td><td>标记整理</td><td>吞吐量优先</td><td>高吞吐服务</td></tr><tr><td>ParNew</td><td>新生代</td><td>多线程</td><td>复制算法</td><td>停顿较短</td><td>配合 CMS 使用</td></tr><tr><td>CMS</td><td>老年代</td><td>并发</td><td>标记清除</td><td>低停顿</td><td>Web 服务、低延迟系统</td></tr><tr><td>G1</td><td>全堆(Region)</td><td>并发 + 并行</td><td>复制 + 标记整理</td><td>可预测停顿</td><td>大内存服务器</td></tr><tr><td>ZGC</td><td>全堆(Region)</td><td>高度并发</td><td>标记 + 重定位</td><td>&lt;10ms 停顿</td><td>超大堆内存、低延迟系统</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="扩展-gc-基本机制">扩展:Golang GC 基本机制<a href="https://tanzhuo.xyz/2022/10/17/jvm-gc#%E6%89%A9%E5%B1%95-gc-%E5%9F%BA%E6%9C%AC%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="扩展:Golang GC 基本机制的直接链接" title="扩展:Golang GC 基本机制的直接链接" translate="no">​</a></h2>
<p>Go 从 <strong>1.5 版本开始引入三色标记并发 GC(Tri-color Mark and Sweep)</strong>,并在后续版本持续优化,目前已经形成一套成熟的 <strong>并发标记 + 并发清理 + 写屏障(Write Barrier)</strong> 的 GC 体系。</p>
<p>Golang GC 的核心特点包括:</p>
<ul>
<li class=""><strong>三色标记算法(Tri-color Marking)</strong></li>
<li class=""><strong>并发标记(Concurrent Mark)</strong></li>
<li class=""><strong>写屏障(Write Barrier)保证对象引用一致性</strong></li>
<li class=""><strong>STW 时间极短(通常 &lt; 1ms)</strong></li>
<li class=""><strong>自动触发 GC(基于堆增长比例)</strong></li>
</ul>
<p>Golang GC 的主要流程如下:</p>
<ol>
<li class=""><strong>STW Start</strong>:暂停所有 Goroutine,初始化 GC Root。</li>
<li class=""><strong>并发标记(Concurrent Mark)</strong>:GC 与用户 Goroutine 并发执行,使用三色标记遍历对象引用。</li>
<li class=""><strong>标记终止(Mark Termination)</strong>:短暂 STW,确保所有对象标记完成。</li>
<li class=""><strong>并发清理(Concurrent Sweep)</strong>:回收未标记对象,与程序并发运行。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="jvm-gc-与-golang-gc-的核心差异">JVM GC 与 Golang GC 的核心差异<a href="https://tanzhuo.xyz/2022/10/17/jvm-gc#jvm-gc-%E4%B8%8E-golang-gc-%E7%9A%84%E6%A0%B8%E5%BF%83%E5%B7%AE%E5%BC%82" class="hash-link" aria-label="JVM GC 与 Golang GC 的核心差异的直接链接" title="JVM GC 与 Golang GC 的核心差异的直接链接" translate="no">​</a></h3>
<p>JVM 采用 <strong>多种垃圾收集器(Serial、CMS、G1、ZGC 等)并支持分代回收机制,通过不同算法与参数组合来适配不同场景</strong>;而 Golang 从设计之初就采用统一的三色标记并发 GC(Tri-color Mark &amp; Sweep)体系,不做分代,通过并发标记、写屏障和极短 STW 来实现低延迟回收,因此整体结构更简单、调优成本更低。</p>
<p>为什么 Go 不做分代 GC?因为 Go 团队认为:分代 GC 对延迟敏感系统收益不明显,但会增加运行时复杂度。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/17/jvm-gc#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>从 Serial 到 ZGC,JVM 垃圾收集器的演进主线其实就是在吞吐量和停顿时间之间不断权衡。单线程的 Serial 胜在简单稳定,Parallel 追求吞吐,ParNew + CMS 用并发换来了低停顿,G1 通过 Region 化把停顿变得可预测,ZGC 则借助染色指针和读屏障把停顿压到了毫秒级。选择收集器时不必追新,关键还是看应用的堆规模和延迟要求。对比 Golang 统一且不分代的 GC 设计,也能更清楚地看到两种运行时在复杂度与灵活性上的不同取舍。</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[StarVCenter 私有云部署]]></title>
            <link>https://tanzhuo.xyz/2022/10/17/si-you-yun-bu-shu</link>
            <guid>https://tanzhuo.xyz/2022/10/17/si-you-yun-bu-shu</guid>
            <pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[StarVCenter 是一套国产超融合云平台 IaaS 软件，将计算、存储、网络整合进同一硬件节点。本文介绍它的超融合特点、免费策略与高级功能，并整理部署前的准备与注意事项。]]></description>
            <content:encoded><![CDATA[<p>一套国产超融合云平台IaaS软件，StarVCenter还提供了超融合技术，将计算、存储和网络等资源整合到一个硬件节点中，形成一个完整的虚拟化基础设施。这种超融合架构可以提高资源利用率，减少部署和维护成本，同时还可以提供更高的性能和可靠性。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么关注私有云">为什么关注私有云<a href="https://tanzhuo.xyz/2022/10/17/si-you-yun-bu-shu#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%85%B3%E6%B3%A8%E7%A7%81%E6%9C%89%E4%BA%91" class="hash-link" aria-label="为什么关注私有云的直接链接" title="为什么关注私有云的直接链接" translate="no">​</a></h2>
<p>想在家里或小型机房搭一套自己的私有云，绕不开选型问题。商业虚拟化方案license费用不低，纯手工维护KVM又缺少统一的管理界面，虚拟机多起来之后创建、迁移、监控都很繁琐。介于两者之间的超融合软件正好补上这个空档：既有图形化的集中管理，又不需要单独采购集中式存储设备。StarVCenter 就是我在调研国产方案时看到的一个选择，社区版对个人和小规模场景免费，值得记录一下。</p>
<p>官网文档：<a href="http://www.starvcs.com/doc/vcenter/index.html" target="_blank" rel="noopener noreferrer" class="">StarVCenter帮助文档 · StarVCenter帮助文档 (starvcs.com)</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="超融合是怎么回事">超融合是怎么回事<a href="https://tanzhuo.xyz/2022/10/17/si-you-yun-bu-shu#%E8%B6%85%E8%9E%8D%E5%90%88%E6%98%AF%E6%80%8E%E4%B9%88%E5%9B%9E%E4%BA%8B" class="hash-link" aria-label="超融合是怎么回事的直接链接" title="超融合是怎么回事的直接链接" translate="no">​</a></h2>
<p>传统的虚拟化架构里，计算节点和存储设备是分开的：服务器负责跑虚拟机，数据放在集中式的存储阵列上，两者之间靠专用存储网络连接。这套架构性能和可靠性都不错，但设备贵、布线复杂，对小规模部署很不友好。</p>
<p>超融合（HCI）的思路是把存储也放回服务器本地：每台节点既出算力，也贡献本地磁盘，由软件层把各节点的磁盘聚合成一个分布式存储池，虚拟机的磁盘数据分片存放在多台节点上并保留副本。这样一台节点故障时数据不丢，扩容时加一台服务器就同时增加了算力和存储容量。</p>
<p>StarVCenter的超融合技术包括以下特点：</p>
<ol>
<li class="">硬件节点整合：将计算、存储和网络等资源整合到一个硬件节点中，形成一个完整的虚拟化基础设施。</li>
<li class="">简化部署：通过自动化部署和配置，简化了整个虚拟化环境的部署和维护。</li>
<li class="">高性能和可靠性：通过整合计算、存储和网络等资源，提高了系统的性能和可靠性。</li>
<li class="">灵活扩展：支持在线扩容和缩容，可以根据业务需要灵活调整资源。</li>
</ol>
<p>整体来看，StarVCenter的超融合技术可以帮助用户实现高效的虚拟化基础设施部署和管理，提高资源利用率和降低成本。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="免费策略与高级功能">免费策略与高级功能<a href="https://tanzhuo.xyz/2022/10/17/si-you-yun-bu-shu#%E5%85%8D%E8%B4%B9%E7%AD%96%E7%95%A5%E4%B8%8E%E9%AB%98%E7%BA%A7%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="免费策略与高级功能的直接链接" title="免费策略与高级功能的直接链接" translate="no">​</a></h2>
<p>StarVCenter追求极致好用，社区免费使用(不超过20台物理服务器)、社区免费技术支持、永久免费升级。对家庭实验室和小团队来说，20 台物理服务器的上限基本等于没有限制。</p>
<p>除了基本的虚拟化管理功能，StarVCenter还提供了一些高级功能，如：</p>
<ol>
<li class="">多云管理：支持管理多个云平台，包括公有云和私有云，提供了跨云平台的资源管理和调度功能。</li>
<li class="">容器管理：支持管理容器化应用，如Docker和Kubernetes，可以实现容器的部署、管理和监控。</li>
<li class="">自动化运维：支持自动化运维功能，如自动化部署、配置管理、故障诊断和性能优化等，提高运维效率和减少人为错误。</li>
<li class="">安全管理：提供了安全管理功能，包括访问控制、审计和日志管理等，保障虚拟化环境的安全性。</li>
</ol>
<p>项目白皮书：<a href="http://www.starvcs.com/white_paper.pdf" target="_blank" rel="noopener noreferrer" class="">http://www.starvcs.com/white_paper.pdf</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="部署前的准备">部署前的准备<a href="https://tanzhuo.xyz/2022/10/17/si-you-yun-bu-shu#%E9%83%A8%E7%BD%B2%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87" class="hash-link" aria-label="部署前的准备的直接链接" title="部署前的准备的直接链接" translate="no">​</a></h2>
<p>真要动手部署之前，有几件事最好先确认：</p>
<ol>
<li class="">BIOS 里开启 CPU 的硬件虚拟化支持（Intel VT-x 或 AMD-V），这是所有虚拟化平台的前提条件。</li>
<li class="">提前做好网络规划。超融合架构里管理流量、业务流量和存储流量最好分开，至少要想清楚各用哪块网卡、哪个网段，事后调整很麻烦。</li>
<li class="">磁盘规划也要提前想：系统盘和数据盘分开，参与分布式存储的盘不要提前分区或做 RAID，具体要求以官方文档为准。</li>
</ol>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>第一次接触这类平台，建议先在虚拟机里做嵌套虚拟化的实验部署，把安装流程和管理界面摸熟，再往物理服务器上装。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/10/17/si-you-yun-bu-shu#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class="">社区版的免费额度是 20 台物理服务器以内，超出规模需要联系官方，选型时要把未来的扩容预期算进去。</li>
<li class="">超融合的可靠性来自多副本，单节点部署体验不到它的核心价值，故障域和副本机制都需要至少三台节点才有意义。</li>
<li class="">分布式存储对磁盘和网络质量比较敏感，跑重要数据前先做好备份，不要把鸡蛋都放在一个还没验证过的存储池里。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/17/si-you-yun-bu-shu#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>StarVCenter 提供了一条国产化的超融合路线：计算、存储、网络收进同一批节点，部署和运维都比传统三层架构简单，社区版的免费策略对小规模场景也足够友好。</p>
<p>目前作者家里只有一台浪潮刀架式服务器，待我有多台加架式服务器时，就尝试使用 StarVCenter 来部署私有云 😒，再配合K8S不是美滋滋吗。</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Zookeeper 可视化UI工具]]></title>
            <link>https://tanzhuo.xyz/2022/10/16/zookeeper-ke-shi-hua-uigong-ju</link>
            <guid>https://tanzhuo.xyz/2022/10/16/zookeeper-ke-shi-hua-uigong-ju</guid>
            <pubDate>Sun, 16 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[整理几款常用的 Zookeeper 可视化工具：收费的 Zookeeper Assistant、开源的 PrettyZoo，以及 zktools 和 zkui，附使用感受与选择建议。]]></description>
            <content:encoded><![CDATA[<p>推荐几款常用的 Zookeeper UI 工具，以帮助我们提高工作效率。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么需要一个-ui-工具">为什么需要一个 UI 工具<a href="https://tanzhuo.xyz/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E4%B8%80%E4%B8%AA-ui-%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="为什么需要一个 UI 工具的直接链接" title="为什么需要一个 UI 工具的直接链接" translate="no">​</a></h2>
<p>Zookeeper 自带的命令行客户端 <code>zkCli.sh</code> 能满足基本操作，但用起来并不顺手：查看节点树要一层层 <code>ls</code>，看节点数据要单独 <code>get</code>，节点多了之后很难对整体结构有直观印象。日常排查注册中心、分布式锁、配置节点这类问题时，一个能把 Znode 树状结构、节点数据和 stat 元信息直接展示出来的图形工具，可以省下不少敲命令的时间。</p>
<p>Zookeeper 的数据模型本身很简单：一棵由 Znode 组成的树，每个节点可以存少量数据，附带版本号、ACL、创建/修改时间等 stat 信息。所以这类 UI 工具的核心能力也大同小异，主要就是节点的增删改查、数据编辑和连接管理，差别更多体现在界面体验和维护活跃度上。下面是我接触过的几款。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zookeeper-assistant收费">Zookeeper Assistant（收费）<a href="https://tanzhuo.xyz/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#zookeeper-assistant%E6%94%B6%E8%B4%B9" class="hash-link" aria-label="Zookeeper Assistant（收费）的直接链接" title="Zookeeper Assistant（收费）的直接链接" translate="no">​</a></h2>
<p><a href="http://www.redisant.cn/za" target="_blank" rel="noopener noreferrer" class="">ZooKeeper Assistant - ZooKeeper可视化管理与监控工具</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-1665904032647-77776a146fa2d03b97094cfb91e011db.webp" width="1550" height="925" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-17-1aeb7611ecabc744bb394677184f3ed7.webp" width="1600" height="681" class="img_ev3q"></p>
<p>这是 Redisant 家的桌面客户端，界面风格和他们家其他产品一脉相承：左侧节点树、右侧数据面板，节点数据和元信息一目了然。</p>
<p>对于收费来说，个人感觉是没必要的，ZK的功能并不复杂，主要就是Znode节点信息的CRUD，不过看得出来他们的UI界面是用心做了的，对于这点就值得点赞。如果公司愿意报销、又追求开箱即用的体验，可以考虑；个人使用的话，下面的免费方案已经够用。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prettyzoo开源免费">PrettyZoo（开源免费）<a href="https://tanzhuo.xyz/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#prettyzoo%E5%BC%80%E6%BA%90%E5%85%8D%E8%B4%B9" class="hash-link" aria-label="PrettyZoo（开源免费）的直接链接" title="PrettyZoo（开源免费）的直接链接" translate="no">​</a></h2>
<p>这款本人也在使用，免费，UI又比较耐看。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-1665904111228-14856e4b6a0f693574abc8ea019fd310.webp" width="1331" height="747" class="img_ev3q"></p>
<p>github地址：<a href="https://github.com/vran-dev/PrettyZoo" target="_blank" rel="noopener noreferrer" class="">vran-dev/PrettyZoo: 😉 Pretty nice Zookeeper GUI, Support Win / Mac / Linux Platform (github.com)</a></p>
<p>它是基于 JavaFX 开发的桌面应用，Windows、Mac、Linux 三个平台都有安装包。除了基本的节点树浏览和数据编辑，还能查看节点的 stat 信息，也内置了命令行终端，图形界面覆盖不到的操作可以直接敲命令补上。对我来说，"免费 + 跨平台 + 界面不丑 + 还在维护"这几点加起来，就足够把它设为默认选择了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zktools">zktools<a href="https://tanzhuo.xyz/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#zktools" class="hash-link" aria-label="zktools的直接链接" title="zktools的直接链接" translate="no">​</a></h2>
<p>太久没有维护，github上已经是4年前的产物，不过还是感谢作者的付出。</p>
<p><a href="https://github.com/zhangyawei117/zktools" target="_blank" rel="noopener noreferrer" class="">GitHub - zhangyawei117/zktools: zk可视化工具zktools</a></p>
<p>早年可选项不多的时候它帮过不少人，现在有更活跃的替代品，一般不建议新用户再入手，列在这里主要是致敬。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zkui">zkui<a href="https://tanzhuo.xyz/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#zkui" class="hash-link" aria-label="zkui的直接链接" title="zkui的直接链接" translate="no">​</a></h2>
<p>基于Web应用开发的ZKUI工具，最后一次更新也是2年前了，个人基本上很少去使用。</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="1110" height="378" class="img_ev3q"></p>
<p>github地址：<a href="https://github.com/DeemOpen/zkui" target="_blank" rel="noopener noreferrer" class="">https://github.com/DeemOpen/zkui</a></p>
<p>和前面几款桌面客户端不同，zkui 是部署在服务端的 Web 应用，团队成员通过浏览器访问，适合"一处部署、多人共用"的场景。代价是需要自己维护一个服务，还要考虑登录和权限问题；如果只是个人日常查看节点，桌面客户端更省事。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）连接生产环境的 ZK 时要格外小心。这类工具删节点、改数据往往就是一次点击，没有二次确认的心理负担，误删一个注册中心节点影响的可能是一批服务。建议生产环境只用来"看"，写操作还是走脚本或变更流程。</p>
<p>2）留意 ACL。如果集群配置了 ACL 鉴权，工具连上后可能只能看到部分节点或读不到数据，这不是工具的 bug，先确认连接时是否带了正确的认证信息。</p>
<p>3）长期不维护的工具（如上面的 zktools、zkui）在新版本 Zookeeper 或新版 JDK 上可能出现兼容性问题，遇到诡异报错时先怀疑版本，别急着怀疑集群。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>几款工具里，我的选择很明确：日常个人使用直接上 PrettyZoo，免费、跨平台、界面耐看且仍在维护；追求商业软件体验、预算充足的可以看看 Zookeeper Assistant；需要团队共享一个入口的再考虑 Web 版的 zkui。工具本身都不复杂，挑一个顺手的，把省下来的时间花在真正的问题上就好。</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Zookeeper 集群搭建]]></title>
            <link>https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun</link>
            <guid>https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun</guid>
            <pubDate>Sat, 15 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[在 Debian 11 上用 Zookeeper 3.8.0 搭建 5 节点集群：从 Znode 与 ZAB 协议的基本概念，到 zoo.cfg 配置、myid 设置与 Leader 选举实验。]]></description>
            <content:encoded><![CDATA[<p>这篇记录在 Debian 上搭建 5 节点 Zookeeper 集群的完整过程，先梳理 Znode、ZAB 协议这些基础概念，再动手配置并做一次 Leader 宕机选举实验。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="部署环境">部署环境<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E9%83%A8%E7%BD%B2%E7%8E%AF%E5%A2%83" class="hash-link" aria-label="部署环境的直接链接" title="部署环境的直接链接" translate="no">​</a></h2>
<p>操作系统 Debian 11.5.0</p>
<p>运行环境 OpenJDK-1.8.0_332 64-Bit Server VM</p>
<p>Zookeeper版本：3.8.0</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="官方介绍">官方介绍<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E5%AE%98%E6%96%B9%E4%BB%8B%E7%BB%8D" class="hash-link" aria-label="官方介绍的直接链接" title="官方介绍的直接链接" translate="no">​</a></h2>
<p>Zookeeper官方文档地址（下载的zip压缩包中\docs目录也自带文档）：<a href="https://zookeeper.apache.org/doc/r3.8.0/index.html" target="_blank" rel="noopener noreferrer" class="">ZooKeeper: Because Coordinating Distributed Systems is a Zoo (apache.org)</a></p>
<p>ZooKeeper 是一种用于分布式应用程序的分布式开源协调服务。它公开了一组简单的基元，分布式应用程序可以基于这些基元来实现更高级别的同步、配置维护以及组和命名服务。它被设计为易于编程，并使用类似文件系统目录树结构的数据模型。它以 Java 运行，并提供 Java 和 C 的绑定。</p>
<p>ZooKeeper 的实现非常重视高性能、高可用、严格有序的访问。高性能意味着它可以在大型分布式系统中使用；可靠性使其不会成为单点故障；严格的排序意味着可以在客户端上实现复杂的同步基元。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-zkservice-7e54d04876edac10ed3249727ed99fb4.webp" width="600" height="185" class="img_ev3q"></p>
<p>从官方的介绍中可以得出结论，Zookeeper主要作用于 <strong>meta 信息存储、配置信息同步</strong>，是一个<strong>高性能、高可用、没有单点故障</strong>的应用服务。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="个人理解">个人理解<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E4%B8%AA%E4%BA%BA%E7%90%86%E8%A7%A3" class="hash-link" aria-label="个人理解的直接链接" title="个人理解的直接链接" translate="no">​</a></h2>
<p>中文翻译：动物园管理员。Apache 的项目大都用动物做 Logo 与命名，所以 Zookeeper 的 Logo 像一个铲屎官，还拿着一把铲子（doge）。Zookeeper本身架构设计得也很简单，容易上手。</p>
<p><img decoding="async" loading="lazy" src="data:image/gif;base64,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" width="79" height="112" class="img_ev3q"></p>
<p>Apache Zookeeper 也是 Apache Hadoop 整个架构中的一员，在Hadoop架构中担任统一配置管理、域名服务、重点数据存储等功能的中间件。一句话概括：Zookeeper = 文件系统 + 通知机制。</p>
<p>Zookeeper（以下简称ZK）本身存储数据的方式类似于Key-Value形式，只不过ZK中数据是以Znode方式存储，类似于Linux中的文件系统管理：Key都以文件路径的方式表达，有多级目录。这和Redis中的Key很像，只不过Redis中的Key习惯用:符号分级，ZK完全使用Linux中的/符号分级。ZK的数据存储在内存中，并持久化到硬盘，在内存中维护Znode的树状数据结构（文件系统）。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据结构">数据结构<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84" class="hash-link" aria-label="数据结构的直接链接" title="数据结构的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="442" height="253" class="img_ev3q"></p>
<p>ZK的Value没有提供丰富的数据结构（不像Redis提供了list、set、hashmap、zset、string等多种结构），ZK的Value只有String类型。ZK的定位本来就不是数据库，它设计出来只是为了存储架构中的关键配置信息，并提供通知与同步能力，<strong>所以每个Znode最大存储数据为1M</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="znode类型">Znode类型<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#znode%E7%B1%BB%E5%9E%8B" class="hash-link" aria-label="Znode类型的直接链接" title="Znode类型的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-持久节点">1) 持久节点<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#1-%E6%8C%81%E4%B9%85%E8%8A%82%E7%82%B9" class="hash-link" aria-label="1) 持久节点的直接链接" title="1) 持久节点的直接链接" translate="no">​</a></h3>
<p>持久节点创建后，只要不人为执行Delete命令删除，则一直存在。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-临时节点">2) 临时节点<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#2-%E4%B8%B4%E6%97%B6%E8%8A%82%E7%82%B9" class="hash-link" aria-label="2) 临时节点的直接链接" title="2) 临时节点的直接链接" translate="no">​</a></h3>
<p>临时节点依赖连接客户端的会话：创建后，如果客户端一直保持连接，则临时节点一直存在；如果客户端连接断开，则清除临时节点。有没有感觉到它的作用？特别适合做注册中心的功能——Dubbo启动则连接，宕机则删除Znode节点，并通知其他客户端。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-顺序节点">3) 顺序节点<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#3-%E9%A1%BA%E5%BA%8F%E8%8A%82%E7%82%B9" class="hash-link" aria-label="3) 顺序节点的直接链接" title="3) 顺序节点的直接链接" translate="no">​</a></h3>
<p>创建顺序节点时会自动在节点路径后面追加10位数字作为计数器。</p>
<p>例如我创建 /node 顺序节点，则 file path 是：/node0000000001。</p>
<p>下次再次创建 /node 节点，会在计数器上 +1 并创建新的Znode节点（旧的/node0000000001不会删除，仍然保留），此时 file path 是：/node0000000002。</p>
<p>但当计数器的大小超过2147483647时，将会发生溢出。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-临时顺序节点">4) 临时顺序节点<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#4-%E4%B8%B4%E6%97%B6%E9%A1%BA%E5%BA%8F%E8%8A%82%E7%82%B9" class="hash-link" aria-label="4) 临时顺序节点的直接链接" title="4) 临时顺序节点的直接链接" translate="no">​</a></h3>
<p>临时节点的特性 + 顺序节点的特性。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zk特性">ZK特性<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#zk%E7%89%B9%E6%80%A7" class="hash-link" aria-label="ZK特性的直接链接" title="ZK特性的直接链接" translate="no">​</a></h2>
<ul>
<li class="">顺序一致性 - 来自客户端的更新将按其发送顺序应用。</li>
<li class="">原子性 - 更新成功或失败。无部分结果。</li>
<li class="">单个系统映像 - （全局数据一致）集群中每个Server都保存着相同的数据副本，无论Client连接到哪个Server，数据都是一致的【虽然理论上是一致的，但是在数据同步过程中还是会出现某些时刻不一致，不过可以通过sync命令解决】。</li>
<li class="">可靠性 - 应用更新后，它将从该时间开始一直存在，直到客户端覆盖更新。</li>
<li class="">及时性 - 保证系统的客户端视图在特定时间范围内是最新的。</li>
</ul>
<p><strong>其中原子性是通过ZAB协议进行保证的，ZAB协议的全称是 Zookeeper Atomic Broadcast（Zookeeper原子广播）。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zab-协议">ZAB 协议<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#zab-%E5%8D%8F%E8%AE%AE" class="hash-link" aria-label="ZAB 协议的直接链接" title="ZAB 协议的直接链接" translate="no">​</a></h2>
<p>ZAB有两种模式：一种是消息广播模式，另一种是崩溃恢复模式。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="消息广播模式">消息广播模式<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E6%B6%88%E6%81%AF%E5%B9%BF%E6%92%AD%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="消息广播模式的直接链接" title="消息广播模式的直接链接" translate="no">​</a></h3>
<p>在 Zookeeper 集群中数据副本的传递策略采用消息广播模式。Zookeeper 中的数据副本同步方式与2PC相似但并不相同：2PC要求协调者必须等待所有参与者全部反馈ACK确认消息后，再发送 commit 消息，要求所有参与者要么全成功要么全失败，因此2PC会产生严重的阻塞问题。</p>
<p>而 Zookeeper 中 Leader 等待 Follower 的 ACK 反馈是指：只要半数以上的 Follower 成功反馈即可，不需要收到全部的 Follower 反馈，这样可以减少等待时间。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="崩溃恢复模式">崩溃恢复模式<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E5%B4%A9%E6%BA%83%E6%81%A2%E5%A4%8D%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="崩溃恢复模式的直接链接" title="崩溃恢复模式的直接链接" translate="no">​</a></h3>
<p>一旦 Leader 服务器出现崩溃，或者由于网络原因导致 Leader 服务器失去了与过半 Follower 的联系，就会进入崩溃恢复模式，重新选举出新的 Leader。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="集群搭建5节点">集群搭建（5节点）<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA5%E8%8A%82%E7%82%B9" class="hash-link" aria-label="集群搭建（5节点）的直接链接" title="集群搭建（5节点）的直接链接" translate="no">​</a></h2>
<p>ZK的集群最少需要3个节点，并且节点个数需要是<strong>奇数</strong>，可以容忍宕机的数量是<strong>节点数/2</strong>：3台可以容忍1台宕机继续提供服务，5台可以容忍2台宕机继续提供服务，遵循的是<strong>过半节点正常，整个集群则可以继续对外提供服务</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="zk集群中的三种角色">ZK集群中的三种角色<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#zk%E9%9B%86%E7%BE%A4%E4%B8%AD%E7%9A%84%E4%B8%89%E7%A7%8D%E8%A7%92%E8%89%B2" class="hash-link" aria-label="ZK集群中的三种角色的直接链接" title="ZK集群中的三种角色的直接链接" translate="no">​</a></h3>
<p>Leader（领导者）：整个集群中只有一个节点可以选举为Leader，并且只有Leader可以进行写操作。（这样的设计注定ZK的写入性能会成为瓶颈，不过大部分都是内存操作，且每个Znode节点数据量很小，仍然可以保持较高的写入性能。）</p>
<p>Follower（跟随者）：Follower 在集群中可以有多个，要和Leader保持心跳连接，该节点只能读不能写，在Leader节点宕机时需要参与新的Leader选举投票。</p>
<p>Observer（观察者）：Observer节点在整个集群中是最边缘的存在，只能读，不能参与Leader选举。为什么需要Observer这样的角色呢？因为ZK集群的写入操作需要半数以上的Follower同步确认才能写入成功，当ZK集群中节点越多时，写入性能就越低。为了在提升ZK集群读能力的同时不影响写入性能，才设计了Observer这样的节点。Observer只负责同步Leader中的数据，并提供读操作。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-428604b1d2f3ba5ce2ecc4fde939121b-fad0baa9cebc4c2505c42de855a2494b.webp" width="500" height="350" class="img_ev3q"></p>
<p>根据官方给出的ZK集群数量性能图可以看到，随着节点数量增加，ZK集群处理的请求数也有上升。但个人觉得准确点说应该是读能力上升，写能力会下降：毕竟节点越多，Leader写入数据时需要通过ZAB协议同步其他Follower节点确认，只有半数Follower节点同步数据成功才算写入成功，节点数量增加必然导致写能力下降。这种设计也说明ZK不适合大量写入的场景，官方给出的读写比例最好为7:3或8:2。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="配置-zoocfg教程跳过了下载和解压">配置 zoo.cfg（教程跳过了下载和解压）<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E9%85%8D%E7%BD%AE-zoocfg%E6%95%99%E7%A8%8B%E8%B7%B3%E8%BF%87%E4%BA%86%E4%B8%8B%E8%BD%BD%E5%92%8C%E8%A7%A3%E5%8E%8B" class="hash-link" aria-label="配置 zoo.cfg（教程跳过了下载和解压）的直接链接" title="配置 zoo.cfg（教程跳过了下载和解压）的直接链接" translate="no">​</a></h3>
<p>解压zip压缩包后，进入conf文件夹下，将 zoo_sample.cfg 重命名为 zoo.cfg，或 copy 一份出来命名为 zoo.cfg。</p>
<p>修改 zoo.cfg 中的部分默认配置，最重要的是修改 dataDir 文件夹路径。ZK默认存放在tmp路径下，但Linux系统会不定时清空该临时目录，所以我们需要改到其他文件夹路径下。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-1665826644086-c156c215baba24939267268b3a9cf3ea.jpg" width="1294" height="892" class="img_ev3q"></p>
<p>修改完成之后记得保存。我设置的地址为 /opt/zookeeper/data，后续几个节点都要设置。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="创建-myid-文件">创建 myid 文件<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E5%88%9B%E5%BB%BA-myid-%E6%96%87%E4%BB%B6" class="hash-link" aria-label="创建 myid 文件的直接链接" title="创建 myid 文件的直接链接" translate="no">​</a></h3>
<p>在修改后的 dataDir 路径文件夹下创建 myid 文件，并在文件中写入当前节点的唯一标识ID。</p>
<p><img decoding="async" loading="lazy" src="data:image/jpeg;base64,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" width="494" height="49" class="img_ev3q"></p>
<p>后续几个节点也都要设置自身节点对应的标识ID，不能重复。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="配置集群节点列表">配置集群节点列表<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E9%85%8D%E7%BD%AE%E9%9B%86%E7%BE%A4%E8%8A%82%E7%82%B9%E5%88%97%E8%A1%A8" class="hash-link" aria-label="配置集群节点列表的直接链接" title="配置集群节点列表的直接链接" translate="no">​</a></h3>
<p>在每个节点的 zoo.cfg 中加入 server 节点的地址：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-1-0293760e7e0428371702a80c7559a9d5.png" width="1113" height="1010" class="img_ev3q"></p>
<p>server.1 中的 1 代表着myid中的标识，后面的值可以写节点ip也可以写hostname，其中2888端口用于同步数据，3888端口用于选举投票。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="启动所有节点">启动所有节点<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E5%90%AF%E5%8A%A8%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9" class="hash-link" aria-label="启动所有节点的直接链接" title="启动所有节点的直接链接" translate="no">​</a></h3>
<p>如果只启动了半数以下节点，则集群不会提供任何服务：</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="1082" height="182" class="img_ev3q"></p>
<p>此时抛出异常无法连接ZK，所以需要把所有节点全部启动。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="验证集群状态">验证集群状态<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E9%AA%8C%E8%AF%81%E9%9B%86%E7%BE%A4%E7%8A%B6%E6%80%81" class="hash-link" aria-label="验证集群状态的直接链接" title="验证集群状态的直接链接" translate="no">​</a></h3>
<p>所有节点都成功启动后：</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="790" height="174" class="img_ev3q"></p>
<p>可以通过 zkServer.sh 的 status 命令查看当前节点角色以及启动状态。</p>
<p>通过 zkCli.sh 连接集群，并执行命令测试是否正常：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-5-0820db64e27c2089db99d00fd216ab45.png" width="1133" height="646" class="img_ev3q"></p>
<p>再到其他节点进行命令测试：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-6-ac15ca49fe2fa89961cabb52a29c41b5.png" width="1097" height="574" class="img_ev3q"></p>
<p>一切正常，至此最简单的ZK集群已经搭建成功。本次搭建基于最简单的流程，后续可以通过修改配置继续调优整个集群细节，<strong>并添加ssl通讯与密码加强整个集群的安全性</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="风险实验">风险实验<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E9%A3%8E%E9%99%A9%E5%AE%9E%E9%AA%8C" class="hash-link" aria-label="风险实验的直接链接" title="风险实验的直接链接" translate="no">​</a></h3>
<p><strong>我们把测试力度继续加深，停掉其中的 Leader 节点，查看集群是否会自动选举出新的 Leader 节点。</strong></p>
<p>找到 Leader 节点后关闭该节点服务。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-15-d408ba5e077788935ad4ebe8f7cdcd02.png" width="1023" height="267" class="img_ev3q"></p>
<p>当前 Leader 节点为 debian4 服务器，我们立即执行 zkServer.sh stop 命令关闭其 ZK 服务。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-1665900721220-61d412e62856fbc99e948132720c2acf.jpg" width="1010" height="363" class="img_ev3q"></p>
<p>通过 zkServer.sh status 命令查看，debian5 服务器已被选举为新的 Leader 节点，表示集群选举一切正常。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-16-eea82f99208fce0d17c1684ccbddce2b.png" width="1060" height="272" class="img_ev3q"></p>
<p>官方配置参数：<a href="https://zookeeper.apache.org/doc/r3.8.0/zookeeperAdmin.html#sc_zkMulitServerSetup" target="_blank" rel="noopener noreferrer" class="">动物园管理员：因为协调分布式系统是一个动物园 (apache.org)</a></p>
<p><a href="https://tanzhuo.xyz/zookeeper-ke-shi-hua-uigong-ju/" target="_blank" rel="noopener noreferrer" class="">Zookeeper 可视化UI工具</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/15/da-jian-zookeeperji-qun#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这次搭建走的是最基础的流程：改 dataDir、写 myid、在 zoo.cfg 中登记所有 server 节点，然后逐台启动。集群能否对外服务的核心规则只有一条——过半节点存活。风险实验也验证了这一点：停掉 Leader 后，剩余节点很快选举出新的 Leader，服务不中断。理解了 Znode 模型和 ZAB 协议的过半确认机制，这些配置项背后的设计意图就都说得通了。生产环境使用前，还应补上 ssl 与鉴权配置。</p>]]></content:encoded>
            <category>Java</category>
            <category>技术</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Debian 安装 Containerd]]></title>
            <link>https://tanzhuo.xyz/2022/10/15/debian-an-zhuang</link>
            <guid>https://tanzhuo.xyz/2022/10/15/debian-an-zhuang</guid>
            <pubDate>Sat, 15 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[在 Debian 11 上安装 Containerd 的完整流程:下载 cri-containerd-cni 发行包、配置环境变量与国内镜像加速器,最后用 systemd 启动服务并设置开机自启。]]></description>
            <content:encoded><![CDATA[<p>Containerd 官方网址：<a href="https://containerd.io/" target="_blank" rel="noopener noreferrer" class="">containerd – An industry-standard container runtime with an emphasis on simplicity, robustness and portability</a></p>
<!-- -->
<p>官方安装教程：<a href="https://github.com/containerd/containerd/blob/main/docs/getting-started.md" target="_blank" rel="noopener noreferrer" class="">containerd/docs/getting-started.md (github.com)</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安装环境">安装环境<a href="https://tanzhuo.xyz/2022/10/15/debian-an-zhuang#%E5%AE%89%E8%A3%85%E7%8E%AF%E5%A2%83" class="hash-link" aria-label="安装环境的直接链接" title="安装环境的直接链接" translate="no">​</a></h2>
<p>Debian 11.5.0 版本</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="下载-containerd-发行包">下载 Containerd 发行包<a href="https://tanzhuo.xyz/2022/10/15/debian-an-zhuang#%E4%B8%8B%E8%BD%BD-containerd-%E5%8F%91%E8%A1%8C%E5%8C%85" class="hash-link" aria-label="下载 Containerd 发行包的直接链接" title="下载 Containerd 发行包的直接链接" translate="no">​</a></h2>
<p>下载地址：<a href="https://github.com/containerd/containerd/releases" target="_blank" rel="noopener noreferrer" class="">https://github.com/containerd/containerd/releases</a></p>
<p>由于 containerd 需要调用 runc,所以我们也需要先安装 runc。不过 containerd 提供了一个包含相关依赖的压缩包 cri-containerd-cni,直接用它可以省去逐个安装的麻烦。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-9-f8bc3132a7d5be8680b45c4f5bb2f749.webp" width="1291" height="1099" class="img_ev3q"></p>
<p>执行命令下载:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">wget</span><span class="token plain"> https://github.com/containerd/containerd/releases/download/v1.6.8/cri-containerd-cni-1.6.8-linux-amd64.tar.gz</span><br></div></code></pre></div></div>
<p>如果国内下载速度过慢,可以尝试此地址(国内代理站点):</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">wget</span><span class="token plain"> https://download.fastgit.org/containerd/containerd/releases/download/v1.6.8/cri-containerd-cni-1.6.8-linux-amd64.tar.gz</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="解压压缩包到根目录">解压压缩包到根目录<a href="https://tanzhuo.xyz/2022/10/15/debian-an-zhuang#%E8%A7%A3%E5%8E%8B%E5%8E%8B%E7%BC%A9%E5%8C%85%E5%88%B0%E6%A0%B9%E7%9B%AE%E5%BD%95" class="hash-link" aria-label="解压压缩包到根目录的直接链接" title="解压压缩包到根目录的直接链接" translate="no">​</a></h2>
<p>压缩包中已经按系统层级组织好了目录文件,直接解压到 <code>/</code> 根目录即可:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">tar</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-C</span><span class="token plain"> / </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-xzf</span><span class="token plain"> cri-containerd-cni-1.6.8-linux-amd64.tar.gz</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="设置环境变量">设置环境变量<a href="https://tanzhuo.xyz/2022/10/15/debian-an-zhuang#%E8%AE%BE%E7%BD%AE%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F" class="hash-link" aria-label="设置环境变量的直接链接" title="设置环境变量的直接链接" translate="no">​</a></h2>
<p>将 <code>/usr/local/bin</code> 和 <code>/usr/local/sbin</code> 追加到 <code>~/.bashrc</code> 文件的 <code>PATH</code> 环境变量中:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">vim</span><span class="token plain"> ~/.bashrc</span><br></div></code></pre></div></div>
<p>添加以下内容:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 让 shell 能找到 containerd 相关的可执行文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">export</span><span class="token plain"> </span><span class="token assign-left variable environment constant" style="color:hsl(29, 54%, 61%)">PATH</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token environment constant" style="color:hsl(29, 54%, 61%)">$PATH</span><span class="token plain">:/usr/local/bin:/usr/local/sbin</span><br></div></code></pre></div></div>
<p>添加后执行命令使其生效:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">source</span><span class="token plain"> ~/.bashrc</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="生成-containerd-默认配置文件">生成 containerd 默认配置文件<a href="https://tanzhuo.xyz/2022/10/15/debian-an-zhuang#%E7%94%9F%E6%88%90-containerd-%E9%BB%98%E8%AE%A4%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6" class="hash-link" aria-label="生成 containerd 默认配置文件的直接链接" title="生成 containerd 默认配置文件的直接链接" translate="no">​</a></h2>
<p>配置文件可以放到 etc 文件夹下:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-p</span><span class="token plain"> /etc/containerd</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 将默认配置导出为 config.toml</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">containerd config default </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> /etc/containerd/config.toml</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="配置国内镜像加速器地址">配置国内镜像加速器地址<a href="https://tanzhuo.xyz/2022/10/15/debian-an-zhuang#%E9%85%8D%E7%BD%AE%E5%9B%BD%E5%86%85%E9%95%9C%E5%83%8F%E5%8A%A0%E9%80%9F%E5%99%A8%E5%9C%B0%E5%9D%80" class="hash-link" aria-label="配置国内镜像加速器地址的直接链接" title="配置国内镜像加速器地址的直接链接" translate="no">​</a></h3>
<p>生成默认配置文件之后,我们手动配置国内镜像加速器地址:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">vim</span><span class="token plain"> /etc/containerd/config.toml</span><br></div></code></pre></div></div>
<p>找到以下这行
<strong>[plugins."io.containerd.grpc.v1.cri".registry.mirrors]</strong></p>
<p>大约 153 行处:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-11-405c4770b2814e518247e3a124317568.png" width="1136" height="798" class="img_ev3q"></p>
<p>添加以下内容并注意缩进:</p>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># docker.io 与 k8s.gcr.io 分别指向对应的国内镜像源</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token table class-name" style="color:hsl(29, 54%, 61%)">plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key property" style="color:hsl(355, 65%, 65%)">endpoint</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token string" style="color:hsl(95, 38%, 62%)">"https://kvuwuws2.mirror.aliyuncs.com"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token table class-name" style="color:hsl(29, 54%, 61%)">plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token key property" style="color:hsl(355, 65%, 65%)">endpoint</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token string" style="color:hsl(95, 38%, 62%)">"https://registry.aliyuncs.com/k8sxio"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><br></div></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-12-8d68b3bb6dbc5662e9ee1c379201e1a5.png" width="1165" height="662" class="img_ev3q"></p>
<p>配置之后保存文件。</p>
<p>配置中 root 是存放容器信息的位置,可自行进行更改:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-image-13-8de1c0fc48a088a3650e2f3bdeaf9831.png" width="1032" height="392" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="启动-containerd-服务并设置开机自启">启动 containerd 服务并设置开机自启<a href="https://tanzhuo.xyz/2022/10/15/debian-an-zhuang#%E5%90%AF%E5%8A%A8-containerd-%E6%9C%8D%E5%8A%A1%E5%B9%B6%E8%AE%BE%E7%BD%AE%E5%BC%80%E6%9C%BA%E8%87%AA%E5%90%AF" class="hash-link" aria-label="启动 containerd 服务并设置开机自启的直接链接" title="启动 containerd 服务并设置开机自启的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># --now 表示在设置开机自启的同时立即启动服务</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">systemctl </span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">enable</span><span class="token plain"> containerd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--now</span><br></div></code></pre></div></div>
<p>验证一下服务是否启动成功:</p>
<p><img decoding="async" loading="lazy" src="data:image/jpeg;base64,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" width="926" height="234" class="img_ev3q"></p>
<p>至此 containerd 服务已经成功安装并启动。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/15/debian-an-zhuang#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>在 Debian 上安装 containerd,整个流程其实就是四步:下载官方的 cri-containerd-cni 发行包、解压到根目录、配置 PATH 与默认配置文件、用 systemd 启动服务。这个压缩包里已经带了 runc 和 CNI 插件,省去了分别安装依赖的麻烦。国内环境建议顺手配置镜像加速器,拉取镜像的体验会好很多。后续如果要对接 Kubernetes,这套安装方式也可以直接复用。</p>]]></content:encoded>
            <category>Docker</category>
        </item>
        <item>
            <title><![CDATA[常见的CI/CD工具]]></title>
            <link>https://tanzhuo.xyz/2022/10/14/chang-jian-de-ci-cd</link>
            <guid>https://tanzhuo.xyz/2022/10/14/chang-jian-de-ci-cd</guid>
            <pubDate>Fri, 14 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[盘点几款常见的 CI/CD 工具：Jenkins、GitLab CI、CircleCI、Buddy、Jenkins X 与阿里云云效，简述各自的特点与适用场景。]]></description>
            <content:encoded><![CDATA[<p>CI/CD 把持续集成与持续交付/部署结合在一起，目标是让软件交付更快、更稳。这篇整理一下市面上几款常见的 CI/CD 工具，方便选型时参考。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tanzhuo.xyz/2022/10/14/chang-jian-de-ci-cd#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>CI/CD是一种软件开发流程，它将持续集成（Continuous Integration）和持续交付/部署（Continuous Delivery/Deployment）两个概念结合起来，旨在加速软件的交付和部署，提高软件质量和稳定性。</p>
<p>持续集成指的是将开发人员提交的代码自动集成到主干代码库中，然后运行一系列的测试和检查，以确保代码的质量和稳定性。这样可以避免代码冲突、降低代码合并的复杂度，同时也可以及时发现和解决代码问题，提高代码的可维护性和可测试性。</p>
<p>持续交付/部署则是指将经过测试和检查的代码自动部署到生产环境中，以实现快速的软件交付和部署。它需要借助自动化工具和流程来实现，包括自动化测试、自动化构建、自动化部署等，从而减少人为操作的错误和延迟，提高软件交付的速度和质量。</p>
<p>CI/CD可以帮助开发团队更快地响应市场需求和用户反馈，减少开发周期和成本，同时提高团队的协作效率和代码管理的可靠性。实现CI/CD需要借助一系列的工具和技术，包括版本控制工具、自动化构建工具、自动化测试工具、容器化技术、云计算等。目前市场上有很多CI/CD工具和平台，如Jenkins、GitLab CI、Travis CI、CircleCI等，它们提供了丰富的功能和插件，支持多种语言和框架的开发。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="circleci">CircleCI<a href="https://tanzhuo.xyz/2022/10/14/chang-jian-de-ci-cd#circleci" class="hash-link" aria-label="CircleCI的直接链接" title="CircleCI的直接链接" translate="no">​</a></h2>
<p>闭源收费，开源免费：开源项目可以免费使用。它的价格取决于所需并发构建实例的数量，单个实例是免费的。</p>
<p><a href="https://circleci.com/" target="_blank" rel="noopener noreferrer" class="">Continuous Integration and Delivery</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="jenkins">Jenkins<a href="https://tanzhuo.xyz/2022/10/14/chang-jian-de-ci-cd#jenkins" class="hash-link" aria-label="Jenkins的直接链接" title="Jenkins的直接链接" translate="no">​</a></h2>
<p>免费，最常见的CI/CD工具之一，基于Java开发。</p>
<p><a href="https://www.jenkins.io/zh/" target="_blank" rel="noopener noreferrer" class="">Jenkins</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="buddy">Buddy<a href="https://tanzhuo.xyz/2022/10/14/chang-jian-de-ci-cd#buddy" class="hash-link" aria-label="Buddy的直接链接" title="Buddy的直接链接" translate="no">​</a></h2>
<p>账号注册后免费使用14天，UI界面较新，功能丰富且上手简单。</p>
<p><a href="https://buddy.works/" target="_blank" rel="noopener noreferrer" class="">Buddy: The DevOps Automation Platform</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gitlab">GitLab<a href="https://tanzhuo.xyz/2022/10/14/chang-jian-de-ci-cd#gitlab" class="hash-link" aria-label="GitLab的直接链接" title="GitLab的直接链接" translate="no">​</a></h2>
<p>社区版免费。GitLab不仅是一个代码仓库管理工具，其中也内置了CI/CD能力，可以直接编写流水线脚本，完成编译、打包、发布等任务。</p>
<p><a href="https://about.gitlab.com/" target="_blank" rel="noopener noreferrer" class="">The One DevOps Platform</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="jenkins-x">Jenkins X<a href="https://tanzhuo.xyz/2022/10/14/chang-jian-de-ci-cd#jenkins-x" class="hash-link" aria-label="Jenkins X的直接链接" title="Jenkins X的直接链接" translate="no">​</a></h2>
<p>为了更好地融入Kubernetes集群，整合了Helm、Draft、GitOps，以及Nexus、chartmuseum、monocular等诸多系统和工具，从而实现自动构建编译环境、生成容器镜像、流水线、自动化部署，并通过简单的Review实现不同环境间的自动发布。</p>
<p><a href="https://www.jenkins.io/zh/projects/jenkins-x/" target="_blank" rel="noopener noreferrer" class="">Jenkins X</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="云效">云效<a href="https://tanzhuo.xyz/2022/10/14/chang-jian-de-ci-cd#%E4%BA%91%E6%95%88" class="hash-link" aria-label="云效的直接链接" title="云效的直接链接" translate="no">​</a></h2>
<p>阿里云建立的一站式云原生开发平台，内置git仓库、Bug管理、任务分配、流水线发布等功能，集成度非常高，并且打通了云上所有资源，适合企业进行快速开发与部署。</p>
<p><a href="https://www.aliyun.com/product/yunxiao?channel=yy_sem_key_yunxiao&amp;utm_content=se_1009966124&amp;ref=tanzhuo.xyz" target="_blank" rel="noopener noreferrer" class="">阿里云云效-DevOps自动化运维-企业级DevOps平台-项目协作工具-阿里云</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/14/chang-jian-de-ci-cd#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这几款工具没有绝对的优劣，更多是场景匹配的问题。个人或开源项目可以优先考虑CircleCI这类对开源免费的托管服务；团队已经用GitLab管理代码的话，直接用内置的GitLab CI最省事。Jenkins胜在生态和插件数量，自建流水线的自由度最高；如果基础设施已经跑在Kubernetes上，Jenkins X会更贴合云原生的工作流。国内企业若资源都在阿里云上，云效的一站式集成也值得一试。</p>]]></content:encoded>
            <category>CI/CD</category>
        </item>
        <item>
            <title><![CDATA[Debian apt 添加阿里源]]></title>
            <link>https://tanzhuo.xyz/2022/10/14/debian-tian-jia-a-li-yuan-apt</link>
            <guid>https://tanzhuo.xyz/2022/10/14/debian-tian-jia-a-li-yuan-apt</guid>
            <pubDate>Fri, 14 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[把 Debian 默认 apt 源替换为阿里云镜像:备份并编辑 sources.list、写入阿里源地址、apt-get update 刷新索引,并说明源格式与换源时的常见坑。]]></description>
            <content:encoded><![CDATA[<p>新装的 Debian 默认走官方源,在国内下载软件包经常只有几十 KB/s,装个东西半天卡在进度条上。把 apt 源换成阿里云的国内镜像,是拿到机器后我一般会做的第一件事,这里把步骤和注意点记一下。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么要换源">为什么要换源<a href="https://tanzhuo.xyz/2022/10/14/debian-tian-jia-a-li-yuan-apt#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%8D%A2%E6%BA%90" class="hash-link" aria-label="为什么要换源的直接链接" title="为什么要换源的直接链接" translate="no">​</a></h2>
<p>apt 安装软件时,会从 <code>/etc/apt/sources.list</code> 里配置的仓库地址下载包和索引。官方源的服务器在境外,国内访问延迟高、带宽小,<code>apt-get update</code> 和 <code>apt-get install</code> 都会很慢,甚至超时失败。阿里云、清华、中科大等镜像站会定期从官方源同步完整仓库,包内容一致,只是地址换成了国内节点,速度能有量级的提升。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="源列表的格式">源列表的格式<a href="https://tanzhuo.xyz/2022/10/14/debian-tian-jia-a-li-yuan-apt#%E6%BA%90%E5%88%97%E8%A1%A8%E7%9A%84%E6%A0%BC%E5%BC%8F" class="hash-link" aria-label="源列表的格式的直接链接" title="源列表的格式的直接链接" translate="no">​</a></h2>
<p>动手之前先看懂 sources.list 里每一行的含义,格式是固定的:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">deb [仓库地址] [发行版代号] [组件...]</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>deb</code> 表示二进制包仓库,<code>deb-src</code> 是对应的源码仓库,平时只装软件的话 <code>deb-src</code> 不配也可以;</li>
<li class="">发行版代号对应 Debian 的版本,比如 <code>stretch</code> 就是 Debian 9,换源时代号必须和自己系统的版本一致,可以用 <code>cat /etc/os-release</code> 确认;</li>
<li class="">组件里 <code>main</code> 是官方主仓库,<code>contrib</code> 和 <code>non-free</code> 包含依赖非自由软件或本身非自由的包,比如一些驱动和固件,一般都带上;</li>
<li class=""><code>stretch/updates</code> 是安全更新仓库,<code>stretch-updates</code> 是常规更新,<code>stretch-backports</code> 提供从新版本移植回来的软件包。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="操作步骤">操作步骤<a href="https://tanzhuo.xyz/2022/10/14/debian-tian-jia-a-li-yuan-apt#%E6%93%8D%E4%BD%9C%E6%AD%A5%E9%AA%A4" class="hash-link" aria-label="操作步骤的直接链接" title="操作步骤的直接链接" translate="no">​</a></h2>
<p>1）进入源文件列表</p>
<p>vim /etc/apt/sources.list</p>
<p>改之前建议先备份一份,出问题可以直接还原:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 备份原有源配置</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">cp</span><span class="token plain"> /etc/apt/sources.list /etc/apt/sources.list.bak</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 编辑源列表</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">vim</span><span class="token plain"> /etc/apt/sources.list</span><br></div></code></pre></div></div>
<p>2）添加阿里源地址</p>
<p>把原有内容注释掉或清空,写入下面几行(以 stretch 即 Debian 9 为例):</p>
<p>deb <a href="http://mirrors.aliyun.com/debian/" target="_blank" rel="noopener noreferrer" class="">http://mirrors.aliyun.com/debian/</a> stretch main non-free contrib<br>
deb-src <a href="http://mirrors.aliyun.com/debian/" target="_blank" rel="noopener noreferrer" class="">http://mirrors.aliyun.com/debian/</a> stretch main non-free contrib<br>
deb <a href="http://mirrors.aliyun.com/debian-security" target="_blank" rel="noopener noreferrer" class="">http://mirrors.aliyun.com/debian-security</a> stretch/updates main<br>
deb-src <a href="http://mirrors.aliyun.com/debian-security" target="_blank" rel="noopener noreferrer" class="">http://mirrors.aliyun.com/debian-security</a> stretch/updates main<br>
deb <a href="http://mirrors.aliyun.com/debian/" target="_blank" rel="noopener noreferrer" class="">http://mirrors.aliyun.com/debian/</a> stretch-updates main non-free contrib<br>
deb-src <a href="http://mirrors.aliyun.com/debian/" target="_blank" rel="noopener noreferrer" class="">http://mirrors.aliyun.com/debian/</a> stretch-updates main non-free contrib<br>
deb <a href="http://mirrors.aliyun.com/debian/" target="_blank" rel="noopener noreferrer" class="">http://mirrors.aliyun.com/debian/</a> stretch-backports main non-free contrib<br>
deb-src <a href="http://mirrors.aliyun.com/debian/" target="_blank" rel="noopener noreferrer" class="">http://mirrors.aliyun.com/debian/</a> stretch-backports main non-free contrib</p>
<p>方便复制的纯文本版本:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">deb http://mirrors.aliyun.com/debian/ stretch main non-free contrib</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">deb-src http://mirrors.aliyun.com/debian/ stretch main non-free contrib</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">deb http://mirrors.aliyun.com/debian-security stretch/updates main</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">deb-src http://mirrors.aliyun.com/debian-security stretch/updates main</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">deb http://mirrors.aliyun.com/debian/ stretch-updates main non-free contrib</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">deb-src http://mirrors.aliyun.com/debian/ stretch-updates main non-free contrib</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">deb http://mirrors.aliyun.com/debian/ stretch-backports main non-free contrib</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">deb-src http://mirrors.aliyun.com/debian/ stretch-backports main non-free contrib</span><br></div></code></pre></div></div>
<p>3）更新源</p>
<p>保存退出后刷新本地的包索引,让新配置生效:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">apt-get</span><span class="token plain"> update</span><br></div></code></pre></div></div>
<p>这一步会从阿里源重新拉取所有仓库的索引文件,输出里应该能看到 <code>mirrors.aliyun.com</code> 的地址。之后再执行 <code>apt-get install</code>,下载就走国内镜像了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/10/14/debian-tian-jia-a-li-yuan-apt#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class="">代号一定要和系统版本对应。把 stretch 的源写到别的版本上,轻则 update 报 404,重则混装不同版本的包把依赖搞乱。其他版本只需把上面所有 <code>stretch</code> 换成对应代号。</li>
<li class="">较新的 Debian 版本里,安全源的写法从 <code>代号/updates</code> 改成了 <code>代号-security</code>,照抄旧文章会遇到找不到仓库的报错,以镜像站首页给出的配置为准。</li>
<li class="">如果 update 时报 GPG 签名相关的错误,通常是缺少对应的密钥或系统时间不对,先检查时间再排查密钥,不要用跳过验证的方式硬装。</li>
<li class="">内网或对稳定性要求高的环境,改完源后可以先 <code>apt-get update</code> 加装一个小包验证一下,确认没问题再做大规模安装或升级。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/10/14/debian-tian-jia-a-li-yuan-apt#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>换源本身就三步:备份并编辑 <code>/etc/apt/sources.list</code>,写入镜像地址,<code>apt-get update</code> 刷新索引。关键在于代号和系统版本要对得上,改前留好备份,出问题随时能退回去。这套思路对清华源、中科大源同样适用,换个域名而已。</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[JUC复习篇（二）锁的认识]]></title>
            <link>https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er</link>
            <guid>https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er</guid>
            <pubDate>Sat, 27 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[JUC 复习第二篇：整理公平锁、可重入锁、读写锁等常见锁概念，并介绍 CAS 算法的原理与 ABA 问题，以及 AQS 的定位。]]></description>
            <content:encoded><![CDATA[<p>锁是并发编程绕不开的话题。这一篇把常见锁的概念集中过一遍，从公平锁、读写锁到 CAS 与 AQS，作为 JUC 复习的第二篇。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="锁的基本概念">锁的基本概念<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#%E9%94%81%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="锁的基本概念的直接链接" title="锁的基本概念的直接链接" translate="no">​</a></h2>
<p>通过锁机制，能够保证在多线程环境中，在同一时间空间中，只能有一个线程进入临界区代码，从而保证临界区中操作数据的一致性。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常见的锁">常见的锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#%E5%B8%B8%E8%A7%81%E7%9A%84%E9%94%81" class="hash-link" aria-label="常见的锁的直接链接" title="常见的锁的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-公平锁">1) 公平锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#1-%E5%85%AC%E5%B9%B3%E9%94%81" class="hash-link" aria-label="1) 公平锁的直接链接" title="1) 公平锁的直接链接" translate="no">​</a></h3>
<p>防止某个线程出现饿死现象，也就是防止某个线程分配不到 CPU 时间片导致指令一直无法执行。保证公平需要额外维护线程状态，更多的线程上下文切换，开销更大，吞吐量有所下降。ReentrantLock 可通过初始化参数设置为公平锁，Synchronized 默认是非公平锁并且不能变为公平锁。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-非公平锁">2) 非公平锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#2-%E9%9D%9E%E5%85%AC%E5%B9%B3%E9%94%81" class="hash-link" aria-label="2) 非公平锁的直接链接" title="2) 非公平锁的直接链接" translate="no">​</a></h3>
<p>直接进行计算资源抢夺，容易出现线程饿死现象。但吞吐量更高，线程切换不那么频繁，谁先抢到 CPU 时间片即可执行。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-可重入锁">3) 可重入锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#3-%E5%8F%AF%E9%87%8D%E5%85%A5%E9%94%81" class="hash-link" aria-label="3) 可重入锁的直接链接" title="3) 可重入锁的直接链接" translate="no">​</a></h3>
<p>又称为递归锁，可以再次获取已经获取到的锁对象，可以一定程度地避免死锁现象。ReentrantLock 和 Synchronized 都是可重入锁。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-不可重入锁">4) 不可重入锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#4-%E4%B8%8D%E5%8F%AF%E9%87%8D%E5%85%A5%E9%94%81" class="hash-link" aria-label="4) 不可重入锁的直接链接" title="4) 不可重入锁的直接链接" translate="no">​</a></h3>
<p>线程在运行中获取到锁之后，在同步代码块中再次获取这把锁将会导致死锁，不可重入锁只允许在同步代码块中被获取一次。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-互斥锁">5) 互斥锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#5-%E4%BA%92%E6%96%A5%E9%94%81" class="hash-link" aria-label="5) 互斥锁的直接链接" title="5) 互斥锁的直接链接" translate="no">​</a></h3>
<p>当一个线程占用资源时，其他线程会被挂起，不会占用 CPU 资源，锁被释放时，CPU 去调度挂起的线程。适合不会被高频操作的资源，否则频繁调度线程的效率会较低。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-读写锁">6) 读写锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#6-%E8%AF%BB%E5%86%99%E9%94%81" class="hash-link" aria-label="6) 读写锁的直接链接" title="6) 读写锁的直接链接" translate="no">​</a></h3>
<p>ReentrantReadWriteLock 读写锁由读锁和写锁两部分构成，读锁写锁互斥，如果只读共享资源用读锁加锁，如果需要修改共享资源则用写锁加锁，适合读多写少场景。当写锁没有被线程持有时，多个线程可以并发地持有读锁，但是当写锁被线程持有后，其他线程获取读锁和写锁的操作都会阻塞。</p>
<p>**写优先：**如果一直有写线程获取写锁，读线程也会被「饿死」。</p>
<p>**读优先：**一直有读线程获取读锁，那么写线程将永远获取不到，造成写线程「饿死」。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-公平读写锁">7) 公平读写锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#7-%E5%85%AC%E5%B9%B3%E8%AF%BB%E5%86%99%E9%94%81" class="hash-link" aria-label="7) 公平读写锁的直接链接" title="7) 公平读写锁的直接链接" translate="no">​</a></h3>
<p>公平读写锁比较简单的一种方式是：用队列把获取锁的线程排队，不管是写线程还是读线程都按照先进先出的原则加锁即可。这样读线程仍然可以并发，一定程度缓解「饿死」的现象。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-戳记锁">8) 戳记锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#8-%E6%88%B3%E8%AE%B0%E9%94%81" class="hash-link" aria-label="8) 戳记锁的直接链接" title="8) 戳记锁的直接链接" translate="no">​</a></h3>
<p>StampedLock 是比 ReentrantReadWriteLock 更快的一种锁，支持乐观读、悲观读锁和写锁。和 ReentrantReadWriteLock 不同的是，StampedLock 支持多个线程申请乐观读的同时，还允许一个线程申请写锁。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="看待并发同步的角度">看待并发同步的角度<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#%E7%9C%8B%E5%BE%85%E5%B9%B6%E5%8F%91%E5%90%8C%E6%AD%A5%E7%9A%84%E8%A7%92%E5%BA%A6" class="hash-link" aria-label="看待并发同步的角度的直接链接" title="看待并发同步的角度的直接链接" translate="no">​</a></h2>
<p>以下的「锁」不是具体的锁实现，更多是看待并发同步的角度或锁的状态。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-乐观锁">1) 乐观锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#1-%E4%B9%90%E8%A7%82%E9%94%81" class="hash-link" aria-label="1) 乐观锁的直接链接" title="1) 乐观锁的直接链接" translate="no">​</a></h3>
<p>可用版本号或者 CAS 算法实现，假定冲突的概率很低。它的工作方式是：对资源做修改时认为只有自己在做修改，修改完成后，再去校验，如果不对再修改。另外虽然叫锁，但是乐观锁全程并没有加锁，所以它也叫无锁编程。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-悲观锁">2) 悲观锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#2-%E6%82%B2%E8%A7%82%E9%94%81" class="hash-link" aria-label="2) 悲观锁的直接链接" title="2) 悲观锁的直接链接" translate="no">​</a></h3>
<p>默认有多个线程对资源进行修改，每一次修改都要加锁。一般数据库本身锁的机制都是基于悲观锁的机制实现的。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-分段锁">3) 分段锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#3-%E5%88%86%E6%AE%B5%E9%94%81" class="hash-link" aria-label="3) 分段锁的直接链接" title="3) 分段锁的直接链接" translate="no">​</a></h3>
<p>分段锁的设计目的就是细化锁的操作，例如当操作不需要更新整个数组时，仅针对数组中的一个元素更新时，我们仅给元素加锁即可。JDK1.7 中 ConcurrentHashMap 就是利用分段锁的形式实现并发操作，通过对 Segment 进行加锁实现更高的并发效率，但在 JDK1.8 中已经使用 CAS 算法代替掉了。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-偏向锁">4) 偏向锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#4-%E5%81%8F%E5%90%91%E9%94%81" class="hash-link" aria-label="4) 偏向锁的直接链接" title="4) 偏向锁的直接链接" translate="no">​</a></h3>
<p>指一段同步代码一直被一个线程访问，那么该线程就会自动获取这个锁，以降低获取锁的代价。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-轻量级锁">5) 轻量级锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#5-%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81" class="hash-link" aria-label="5) 轻量级锁的直接链接" title="5) 轻量级锁的直接链接" translate="no">​</a></h3>
<p>当前锁是偏向锁并且被另外一个线程访问时，偏向锁就会升级为轻量级锁，其他线程会通过自旋的方式尝试获取锁，不会阻塞其他线程。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-重量级锁">6) 重量级锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#6-%E9%87%8D%E9%87%8F%E7%BA%A7%E9%94%81" class="hash-link" aria-label="6) 重量级锁的直接链接" title="6) 重量级锁的直接链接" translate="no">​</a></h3>
<p>当前锁是轻量级锁，另一个线程自旋到一定次数还没获取到锁，轻量级锁就会升级为重量级锁，会阻塞其他线程。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-自旋锁">7) 自旋锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#7-%E8%87%AA%E6%97%8B%E9%94%81" class="hash-link" aria-label="7) 自旋锁的直接链接" title="7) 自旋锁的直接链接" translate="no">​</a></h3>
<p>指尝试获取锁的线程不会立即阻塞，而是采用循环的方式去尝试获取锁。但会消耗更多的 CPU 资源，也就是不停地空转。一般会设置自旋次数，达到指定次数后将会挂起线程。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-死锁">8) 死锁<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#8-%E6%AD%BB%E9%94%81" class="hash-link" aria-label="8) 死锁的直接链接" title="8) 死锁的直接链接" translate="no">​</a></h3>
<p>死锁不是一种锁，而是一种现象：两个正在执行的线程相互需要获取对方已持有的锁才能继续执行指令时，就出现了死锁现象，导致程序卡死，无法继续向下执行。死锁可通过 <code>jstack</code> 指令进行排查：先通过 <code>jps -l</code> 指令获取到应用程序 pid 进程号，然后通过 <code>jstack &lt;进程号&gt;</code> 进行死锁排查，也可通过 <code>jconsole</code> 指令打开 UI 工具查看死锁信息。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cas算法是什么">CAS算法是什么<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#cas%E7%AE%97%E6%B3%95%E6%98%AF%E4%BB%80%E4%B9%88" class="hash-link" aria-label="CAS算法是什么的直接链接" title="CAS算法是什么的直接链接" translate="no">​</a></h2>
<p>CAS 全称 Compare And Swap，顾名思义，即<strong>比较与交换</strong>。该算法是一种无锁算法，可以在不加锁的前提下保证线程安全，即在没有线程被阻塞的情况下实现变量同步，因此属于<strong>非阻塞同步</strong>的范畴。</p>
<p>CAS 算法涉及到三个操作数：内存值 <strong>V</strong>、比较值 <strong>A</strong>、交换值 <strong>B</strong>。其具体的操作步骤是，当且仅当 V 等于 A 时，CAS 算法通过原子方式用 B 将 V 替换，否则不执行任何操作。一般情况下，CAS 算法是一个<strong>自旋</strong>操作，即不断地重试直到成功为止。</p>
<p><strong>CAS 算法有如下特点：ABA 问题，存在自旋开销，只保证一个共享变量的原子操作。</strong></p>
<ul>
<li class="">ABA 问题。若变量 V 初次读取时为 A，在准备赋值时检查其仍为 A，但这不能说明它没有被其他线程修改过，因为在这段时间它可能被改为其他值，然后又改回 A，此时 CAS 算法就会误认为它从未修改过。该问题被称为 CAS 算法的 ABA 问题。在实际使用场景中需判断，若 ABA 问题对该使用场景的影响不大，可以不作处理。否则，可以采取对每次修改添加版本号标识的方式解决 ABA 问题，或直接采用悲观锁。</li>
<li class="">存在自旋开销。前面已经提到，CAS 算法是一个自旋操作，拥有不撞南墙不回头的精神，在失败时会不断重试直到成功为止。但这种自旋如果长时间存在，将会对 CPU 造成很大的负担，这也就是所谓的自旋开销。</li>
<li class="">只保证一个共享变量的原子操作。CAS 算法只对单个共享变量有效，当操作跨多个共享变量时 CAS 算法将不能保证同步。一种解决方式是，将这些共享变量封装为一个对象之后再使用 CAS 算法进行处理。</li>
</ul>
<p>底层的汇编指令是 <code>lock cmpxchgl</code>，其中 lock 前缀特别重要，其指令说明如下：</p>
<p>1）确保对内存的读-改-写操作原子执行。在 Pentium 及 Pentium 之前的处理器中，带有 lock 前缀的指令在执行期间会锁住总线，使得其他处理器暂时无法通过总线访问内存，很显然这会带来昂贵的开销。从 Pentium 4、Intel Xeon 及 P6 处理器开始，intel 在原有总线锁的基础上做了一个很有意义的优化：如果要访问的内存区域（area of memory）在 lock 前缀指令执行期间已经在处理器内部的缓存中被锁定（即包含该内存区域的缓存行当前处于独占或已修改状态），并且该内存区域被完全包含在单个缓存行（cache line）中，那么处理器将直接执行该指令。由于在指令执行期间该缓存行会一直被锁定，其它处理器无法读/写该指令要访问的内存区域，因此能保证指令执行的原子性。这个操作过程叫做缓存锁定（cache locking），缓存锁定将大大降低 lock 前缀指令的执行开销，但是当多处理器之间的竞争程度很高或者指令访问的内存地址未对齐时，仍然会锁住总线。</p>
<p>2）禁止该指令与之前和之后的读和写指令重排序。</p>
<p>3）把写缓冲区中的所有数据刷新到内存中。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="aqs是什么">AQS是什么<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#aqs%E6%98%AF%E4%BB%80%E4%B9%88" class="hash-link" aria-label="AQS是什么的直接链接" title="AQS是什么的直接链接" translate="no">​</a></h2>
<p>Abstract Queued Synchronizer 抽象队列同步器，JUC 中所有的 Lock 接口实现类均基于 AQS 抽象类进行实现。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/08/27/jucfu-xi-pian-er#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这一篇把常见的锁概念做了一次集中梳理：公平与非公平、可重入与不可重入是锁的行为特性，读写锁与 StampedLock 面向读多写少的场景，而乐观锁、悲观锁、偏向锁到重量级锁的升级更多是看待并发同步的角度与锁的状态。CAS 是 JUC 无锁编程的基石，理解它的三个操作数与 ABA 问题很有必要。至于 AQS，它是 JUC 中各类 Lock 实现的共同骨架，其内部实现值得单独展开一篇来写。</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[JUC复习篇（一）线程基础]]></title>
            <link>https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi</link>
            <guid>https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi</guid>
            <pubDate>Fri, 26 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[JUC 复习第一篇：从 JVM 内存模型与线程栈讲起，梳理线程模型、线程的五种状态，以及线程池的创建方式与拒绝策略。]]></description>
            <content:encoded><![CDATA[<p>Java JUC 章节的知识点繁多，为了更好地整理我掌握的知识点，故此通过书写博客的方式，从底层原理开始梳理，总结出自己对并发编程的理解。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="什么是并发编程">什么是并发编程<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#%E4%BB%80%E4%B9%88%E6%98%AF%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B" class="hash-link" aria-label="什么是并发编程的直接链接" title="什么是并发编程的直接链接" translate="no">​</a></h2>
<p>原因还得从计算机的发展历史说起。根据摩尔定律，CPU 算力随时间越来越高，但摩尔定律很快接近物理极限，单核心 CPU 算力遇到瓶颈。人们为了追求更高的算力，转向多核心 CPU 架构设计，多核心与超线程技术使得 CPU 算力大幅提高。原理相当于有多个人同时执行不同的指令，整体效率当然比一个人执行得更高。（注意是整体效率更高，不意味着使用多线程就一定比单线程应用执行指令效率更高）</p>
<p>为了更好地运用多核心 CPU 的计算资源（压榨 CPU），于是产生了多线程开发。虽然多线程在某些场景下确实提高了程序整体效率，但它<strong>并不是银弹</strong>：随之而来的还有线程安全、锁机制、死锁、线程上下文切换开销等一系列问题。</p>
<p><strong>那么说了这么多线程的描述，到底什么是线程呢？</strong></p>
<p>线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中，是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流，一个进程中可以并发多个线程，每条线程并行执行不同的任务，而一个进程中至少有一个线程执行。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="线程在-jvm-中的位置">线程在 JVM 中的位置<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#%E7%BA%BF%E7%A8%8B%E5%9C%A8-jvm-%E4%B8%AD%E7%9A%84%E4%BD%8D%E7%BD%AE" class="hash-link" aria-label="线程在 JVM 中的位置的直接链接" title="线程在 JVM 中的位置的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-56-6dc9d01d54aecf1fb5b2c41ecf1908a7.webp" width="1006" height="603" class="img_ev3q"></p>
<p>通过 JVM 内存模型图，我们可以知晓线程 Thread 处于整个 JVM 中的具体位置。在 VM Stack 虚拟机栈空间中，每一个线程都有自己的线程栈，线程栈中存放着许多栈帧 Stack Frame，栈帧中又存放着 LVA、OS、FD 等信息。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="lva局部变量数组local-variable-array">LVA：局部变量数组（Local Variable Array）<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#lva%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E6%95%B0%E7%BB%84local-variable-array" class="hash-link" aria-label="LVA：局部变量数组（Local Variable Array）的直接链接" title="LVA：局部变量数组（Local Variable Array）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">栈帧的局部变量部分是由从索引 0 开始的字节数组构成。</li>
<li class="">它包含了所有的方法参数和局部变量。</li>
<li class="">在数组中的每个插槽或条目大小是 4 个字节。</li>
<li class="">int、float 和引用类型在数组中全部占用一个条目或插槽，即 4 字节大小。</li>
<li class="">double 和 long 类型的数据在数组中占用 2 个连续条目，即总共 8 字节大小。</li>
<li class="">byte、short 和 char 类型在存储之前会被转化为 int 类型并占用 1 个插槽，即 4 字节大小。</li>
<li class="">但是存储 boolean 类型值的方式在不同的 JVM 中有不同的实现。在大多数 JVM 的实现中，boolean 类型在局部变量数组中占用一个插槽。</li>
<li class="">参数会首先被放置入局部变量数组中，顺序是它们在方法中声明的顺序。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="os操作数栈operand-stack">OS：操作数栈（Operand Stack）<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#os%E6%93%8D%E4%BD%9C%E6%95%B0%E6%A0%88operand-stack" class="hash-link" aria-label="OS：操作数栈（Operand Stack）的直接链接" title="OS：操作数栈（Operand Stack）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">JVM 使用操作数栈作为运行的工作空间，或者说用来存储计算的中间结果。</li>
<li class="">操作数栈也像局部变量数组一样组织为一个数组。但它不是使用索引来访问的，而是由一些指令来访问，这些指令可以把值推进栈中，也可以把值从栈中弹出并做一些我们需要的操作。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="fd栈帧数据区frame-data">FD：栈帧数据区（Frame Data）<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#fd%E6%A0%88%E5%B8%A7%E6%95%B0%E6%8D%AE%E5%8C%BAframe-data" class="hash-link" aria-label="FD：栈帧数据区（Frame Data）的直接链接" title="FD：栈帧数据区（Frame Data）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">栈帧数据区包含了所有的符号引用（常量池解析）和普通方法的 return 位置，该 return 与特定的方法关联，用来跳转回去。</li>
<li class="">它也包含了一个到异常表的引用，可以在异常发生时提供 catch 代码块的信息。</li>
</ul>
<p>在 JDK1.8 中，默认的线程栈空间为 1MB，可通过 <code>-XX:ThreadStackSize=256k</code> 调整线程栈内存空间大小。如果线程栈空间不足，则会抛出异常 StackOverflowError。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="线程栈">线程栈<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#%E7%BA%BF%E7%A8%8B%E6%A0%88" class="hash-link" aria-label="线程栈的直接链接" title="线程栈的直接链接" translate="no">​</a></h2>
<p>**线程栈：**每次线程执行函数时都会压入一个栈帧，线程栈遵循先进后出，先进在栈底，后进在栈顶，从栈顶往下执行。例如：A 函数调用 B 函数，B 函数又调用 C 函数，栈结构如下图：</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="457" height="471" class="img_ev3q"></p>
<p>栈结构简单，且不需要垃圾回收器，因为栈帧执行完成后即刻销毁内存。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="线程模型">线程模型<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="线程模型的直接链接" title="线程模型的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="一对一线程模型即一个lwp对应一个klt">一对一线程模型（即一个LWP对应一个KLT）<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#%E4%B8%80%E5%AF%B9%E4%B8%80%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B%E5%8D%B3%E4%B8%80%E4%B8%AAlwp%E5%AF%B9%E5%BA%94%E4%B8%80%E4%B8%AAklt" class="hash-link" aria-label="一对一线程模型（即一个LWP对应一个KLT）的直接链接" title="一对一线程模型（即一个LWP对应一个KLT）的直接链接" translate="no">​</a></h3>
<p>JDK1.8 中使用的就是这种模型。</p>
<p>**KLT：内核级线程（Kernel Level Thread）**这种线程实现是指直接由操作系统内核来完成线程支持、线程切换和操纵调度器进行调度，即线程映射到各个处理器。</p>
<p>**LWP：轻量级进程（Light Weight Process）**程序一般不会直接去使用内核线程，而是通过内核线程的一种高级接口 LWP 来调度操作内核线程。LWP 与普通进程的区别在于它只有一个最小的执行上下文和调度程序所需的统计信息，这也是它之所以被称为轻量级的原因，并且它与 KLT 之间的关系是 1:1 的存在。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-58-eb43fb8e4e480589dae73d43e0fd3a74.webp" width="951" height="636" class="img_ev3q"></p>
<p>优点：</p>
<p>1）实现简单，可适用大部分多线程场景，目前主流的 JVM 都使用此方案。</p>
<p>缺点：</p>
<p>1）用户线程的阻塞和唤醒直接映射到内核线程中，而且随着线程数量的增加，线程之间频繁切换导致 CPU 开销变大。JDK1.8 中引入了 CAS 算法来避免线程之间的频繁切换与线程加锁，确实大幅度提高了 JVM 的并发性能。</p>
<p>2）操作系统内核可以创建的线程数有限，应用程序创建过多的线程，会大幅度降低系统的性能，CPU 大部分时间片都会被过多的线程切换所消耗。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="多对多线程模型即在lwp上构建虚拟用户线程ut">多对多线程模型（即在LWP上构建虚拟用户线程UT）<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#%E5%A4%9A%E5%AF%B9%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B%E5%8D%B3%E5%9C%A8lwp%E4%B8%8A%E6%9E%84%E5%BB%BA%E8%99%9A%E6%8B%9F%E7%94%A8%E6%88%B7%E7%BA%BF%E7%A8%8But" class="hash-link" aria-label="多对多线程模型（即在LWP上构建虚拟用户线程UT）的直接链接" title="多对多线程模型（即在LWP上构建虚拟用户线程UT）的直接链接" translate="no">​</a></h3>
<p>目前 GO 语言与 JDK19 版本所使用的就是这种模型。</p>
<p>**UT：用户线程（User Thread）**建立在用户空间，系统内核不能感知用户线程的存在，线程创建、销毁、切换开销小。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-59-36418701a1fc2ee67b61c7e5c35ed456.webp" width="796" height="657" class="img_ev3q"></p>
<p>多对多模型，又叫作两级线程模型，它充分吸收各线程模型的优点且尽量规避它们的缺点。在此模型下用户线程与内核线程是多对多（M : N，通常 M &gt;= N）的映射模型。</p>
<p>优点：</p>
<p>1）应用程序自身去管理 UT 的相互切换，而底层 CPU 不必频繁地切换线程，可以更连续地执行当前线程，从而提高程序效率。</p>
<p>2）UT 的调度优先级可以通过程序自身把控。</p>
<p>3）极大提高并发量，通过此模型开启 UT 过万也可以很好地提供服务，天生适合高并发场景。</p>
<p>缺点：</p>
<p>1）实现比较复杂。比如 Go 语言采用的 GMP 线程模型就是这种多对多的方式实现的，这也是使用 goroutine 可以实现高并发的原因之一。目前 Java 的 Loom 项目也在这方面进行探索，在 JDK19 中已经实现，大家可以尝试使用。<a href="https://openjdk.org/jeps/425" target="_blank" rel="noopener noreferrer" class="">JEP 425：虚拟线程（预览版） (openjdk.org)</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="线程的5种状态">线程的5种状态<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#%E7%BA%BF%E7%A8%8B%E7%9A%845%E7%A7%8D%E7%8A%B6%E6%80%81" class="hash-link" aria-label="线程的5种状态的直接链接" title="线程的5种状态的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-62-fbfa9c8007363de3c757b9ae4a73abee.webp" width="1019" height="670" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-新建new">1) 新建（New）<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#1-%E6%96%B0%E5%BB%BAnew" class="hash-link" aria-label="1) 新建（New）的直接链接" title="1) 新建（New）的直接链接" translate="no">​</a></h3>
<p>线程对象被创建后，就进入了新建状态。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-就绪runnable">2) 就绪（Runnable）<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#2-%E5%B0%B1%E7%BB%AArunnable" class="hash-link" aria-label="2) 就绪（Runnable）的直接链接" title="2) 就绪（Runnable）的直接链接" translate="no">​</a></h3>
<p>也被称为"可执行状态"。线程对象被创建后，其它线程调用了该对象的 start() 方法，从而来启动该线程，例如 thread.start()。处于就绪状态的线程，随时可能被 CPU 调度执行，但具体什么时候执行此线程由 CPU 自身决定。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-运行running">3) 运行（Running）<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#3-%E8%BF%90%E8%A1%8Crunning" class="hash-link" aria-label="3) 运行（Running）的直接链接" title="3) 运行（Running）的直接链接" translate="no">​</a></h3>
<p>线程获取 CPU 权限进行执行。需要注意的是，线程只能从就绪状态进入到运行状态。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-阻塞blocked">4) 阻塞（Blocked）<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#4-%E9%98%BB%E5%A1%9Eblocked" class="hash-link" aria-label="4) 阻塞（Blocked）的直接链接" title="4) 阻塞（Blocked）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">(01) 等待阻塞 -- 通过调用线程的 wait() 方法，让线程等待某工作的完成。可通过 notify() 方法唤醒该线程，唤醒之后线程进入就绪状态。</li>
<li class="">(02) 同步阻塞 -- 线程在获取 synchronized 同步锁失败（因为锁被其它线程所占用）时，它会进入同步阻塞状态。</li>
<li class="">(03) 其他阻塞 -- 通过调用线程的 sleep() 或 join() 或发出了 I/O 请求时，线程会进入到阻塞状态。当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时，线程重新转入就绪状态。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-死亡dead">5) 死亡（Dead）<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#5-%E6%AD%BB%E4%BA%A1dead" class="hash-link" aria-label="5) 死亡（Dead）的直接链接" title="5) 死亡（Dead）的直接链接" translate="no">​</a></h3>
<p>线程执行完了或者因异常退出了 run() 方法，该线程结束生命周期。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="java线程池的应用">Java线程池的应用<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E5%BA%94%E7%94%A8" class="hash-link" aria-label="Java线程池的应用的直接链接" title="Java线程池的应用的直接链接" translate="no">​</a></h2>
<p>从上面的线程状态可以得知，不停地创建和销毁线程，将会在新建与死亡两个状态中消耗更多的计算资源。为了更好地使用线程，于是便出现了管理线程资源的线程池。在线程池中我们会先创建好指定数量的线程，待需要使用的时候，从中取出线程运行，运行完对应任务后又扔回线程池中，这样就很好地避免了重复的创建和销毁步骤，从而提高程序的效率。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-60-11f1b3fb7fc52f39dfcec972cc9df124.webp" width="894" height="682" class="img_ev3q"></p>
<p>优点：</p>
<p>1）更好地管理应用程序中的线程数量、任务数量。</p>
<p>2）加快了任务执行速度，即缩短了请求响应时间。（没有创建和销毁步骤）</p>
<p>3）可使用多线程并行计算提高效率，以及其他的增强功能。</p>
<p>4）复用线程，降低了系统计算资源的开销。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="线程池的创建方式">线程池的创建方式<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E5%88%9B%E5%BB%BA%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="线程池的创建方式的直接链接" title="线程池的创建方式的直接链接" translate="no">​</a></h2>
<ul>
<li class="">通过 ThreadPoolExecutor 手动创建线程池。</li>
<li class="">通过 Executors 执行器自动创建线程池。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7种创建线程池方式">7种创建线程池方式<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#7%E7%A7%8D%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B%E6%B1%A0%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="7种创建线程池方式的直接链接" title="7种创建线程池方式的直接链接" translate="no">​</a></h3>
<p>1）Executors.newFixedThreadPool：创建一个固定大小的线程池，可控制并发的线程数，超出的线程会在队列中等待。
2）Executors.newCachedThreadPool：创建一个可缓存的线程池，若线程数超过处理所需，缓存一段时间后会回收，若线程数不够，则新建线程。
3）Executors.newSingleThreadExecutor：创建单个线程数的线程池，它可以保证先进先出的执行顺序。
4）Executors.newScheduledThreadPool：创建一个可以执行延迟任务的线程池。
5）Executors.newSingleThreadScheduledExecutor：创建一个单线程的可以执行延迟任务的线程池。
6）Executors.newWorkStealingPool：创建一个抢占式执行的线程池（任务执行顺序不确定）。
7）ThreadPoolExecutor：手动创建线程池的方式，它创建时最多可以设置 7 个参数：<strong>核心数，线程最大数，阻塞队列类型（有界，无界），存活时间，时间单位，线程工厂，拒绝策略</strong>。</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token keyword" style="color:hsl(286, 60%, 67%)">int</span><span class="token plain"> corePoolSize</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">                    </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 核心线程数</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">int</span><span class="token plain"> maximumPoolSize</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">                 </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 最大线程数</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">long</span><span class="token plain"> keepAliveTime</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">                  </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 非核心线程的空闲存活时间</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token class-name" style="color:hsl(29, 54%, 61%)">TimeUnit</span><span class="token plain"> unit</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">                       </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 存活时间的单位</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token class-name" style="color:hsl(29, 54%, 61%)">BlockingQueue</span><span class="token generics punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token generics class-name" style="color:hsl(29, 54%, 61%)">Runnable</span><span class="token generics punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"> workQueue</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">   </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 阻塞队列（有界 / 无界）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token class-name" style="color:hsl(29, 54%, 61%)">ThreadFactory</span><span class="token plain"> threadFactory</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain">         </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 线程工厂</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token class-name" style="color:hsl(29, 54%, 61%)">RejectedExecutionHandler</span><span class="token plain"> handler     </span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 拒绝策略</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>线程池的创建推荐使用最后一种 ThreadPoolExecutor 的方式，因为使用它可以明确线程池的运行规则，规避资源耗尽的风险。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="拒绝策略">拒绝策略<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#%E6%8B%92%E7%BB%9D%E7%AD%96%E7%95%A5" class="hash-link" aria-label="拒绝策略的直接链接" title="拒绝策略的直接链接" translate="no">​</a></h2>
<p>拒绝策略共有 4 种：</p>
<p>1）ThreadPoolExecutor.AbortPolicy：默认拒绝策略，拒绝任务并抛出异常。
2）ThreadPoolExecutor.CallerRunsPolicy：使用调用线程直接运行任务。
3）ThreadPoolExecutor.DiscardPolicy：直接拒绝任务，不抛出错误。
4）ThreadPoolExecutor.DiscardOldestPolicy：触发拒绝策略后，只要还有任务新增，就一直丢弃阻塞队列中最老的任务，并将新的任务加入。</p>
<p><strong>线程最大执行任务数 = 线程数 + 队列长度。</strong></p>
<p>如果线程池最大线程为 10 个线程，队列长度为 20，则表明该线程池最大同时接受 30 个任务，超出的任务则会使用对应的拒绝策略。当然也可以自己实现接口 RejectedExecutionHandler 来处理拒绝流程。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/08/26/jucfu-xi-pian-yi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这一篇从 JVM 内存模型入手，梳理了线程栈的内部结构、一对一与多对多两种线程模型的取舍，以及线程的五种状态。理解了线程创建与销毁的开销，也就理解了线程池存在的意义。实际开发中建议用 ThreadPoolExecutor 手动创建线程池，明确核心参数与拒绝策略，避免 Executors 默认配置带来的资源风险。这些基础概念是后续理解锁机制与 JUC 各类同步器的前提。</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Kubernetes 部署 Istio]]></title>
            <link>https://tanzhuo.xyz/2022/08/22/kubernetes</link>
            <guid>https://tanzhuo.xyz/2022/08/22/kubernetes</guid>
            <pubDate>Mon, 22 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[介绍 Istio 的架构与核心组件，并记录在 Kubernetes 集群中使用 Helm 安装 Istio、部署 Bookinfo 示例应用的过程。]]></description>
            <content:encoded><![CDATA[<p>kubernetes 已经解决了运维上的大部分问题，拥有优秀的自动运维机制：动态扩容、调度、镜像管理、容器宕机重启、计算节点监控等。但 k8s 在应用层上的监控与流量管理、应用之间相互调用的协调、注册中心、配置中心等功能并未提供，于是便出现了基于 Service Mesh 架构的代表产品 Istio。</p>
<!-- -->
<p>Istio 是 Google、IBM、Lyft 三家联合开发的框架，主要作用是在 k8s 环境中对微服务之间的流量进行控制、连接、保护、设置和观测，使得我们开发微服务时可以抛弃掉繁琐、复杂、维护性低、强耦合的底层微服务 SDK。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="istio架构">Istio架构<a href="https://tanzhuo.xyz/2022/08/22/kubernetes#istio%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="Istio架构的直接链接" title="Istio架构的直接链接" translate="no">​</a></h2>
<p><strong>在架构上 Istio 分为数据平面与控制平面。</strong></p>
<p>数据平面的核心思维就是 Sidecar（边车模式）：在同一个 pod 中添加 proxy 代理容器，接管我们的业务容器流量，在不对业务代码有任何入侵的前提下，实现各种功能。例如：负载均衡、流量加密、监控、策略（灰度发布、蓝绿发布）、熔断、降级、远程服务调用、配置中心、注册中心等常用的微服务功能（但不负责分布式事务的管理，这个按道理确实也不属于 Istio 应该接管的范畴）。</p>
<p>控制平面则负责管理和配置代理来路由流量。此外控制平面配置 Mixer 以实施策略和收集遥测数据进行展示，提供分析服务调用链路、服务拓扑图等功能。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-50-29cab4357ba6d65501863969da0b1a2a.webp" width="1057" height="715" class="img_ev3q"></p>
<p>由于业务代码不再依赖具体的微服务 SDK，例如 SpringCloud、Dubbo、Motan、ServiceComb 等强耦合性框架，我们可以根据业务场景选择对应的开发语言，让不同语言的开发团队进行业务开发，提高整体效率，把琐碎的注册、配置与远程调用的管理全部交给 Istio。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="组件介绍">组件介绍<a href="https://tanzhuo.xyz/2022/08/22/kubernetes#%E7%BB%84%E4%BB%B6%E4%BB%8B%E7%BB%8D" class="hash-link" aria-label="组件介绍的直接链接" title="组件介绍的直接链接" translate="no">​</a></h2>
<p>citadel：负责身份认证和证书管理的核心安全组件。</p>
<p>galley：负责配置管理的组件，如验证配置文件格式和内容的正确性，并将这些配置信息提供给 pilot 和 mixer。</p>
<p>pilot：控制中枢，包含了服务发现和规则转化与下发。</p>
<p>proxy：由 C++ 开发的 Envoy 与 Pilot-agent 实现，提供了动态服务发现、负载均衡、TLS、熔断、健康检查、流量拆分、灰度等功能，另外生成遥测数据，为微服务提供可观测能力。</p>
<p>Ingressgateway：入口处的 gateway，即网格外访问网格内的服务就是通过这个 gateway 进行的。</p>
<p><strong>详细中文官网文档：</strong><a href="https://istio.io/latest/zh/docs/" target="_blank" rel="noopener noreferrer" class=""><strong>Istio / 文档</strong></a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="部署流程">部署流程<a href="https://tanzhuo.xyz/2022/08/22/kubernetes#%E9%83%A8%E7%BD%B2%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="部署流程的直接链接" title="部署流程的直接链接" translate="no">​</a></h2>
<p>我这里使用最简单的 Helm 安装。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1为什么用-helm">1）为什么用 Helm<a href="https://tanzhuo.xyz/2022/08/22/kubernetes#1%E4%B8%BA%E4%BB%80%E4%B9%88%E7%94%A8-helm" class="hash-link" aria-label="1）为什么用 Helm的直接链接" title="1）为什么用 Helm的直接链接" translate="no">​</a></h3>
<p>Helm 在 K8S 集群中处于包管理工具的角色。声明 K8S 的各种资源，例如 Service、Pod、Ingress 时，都需要编写 Yaml 文件。但随着微服务增多，加上 redis 集群、mysql 高可用集群、Hadoop 集群等，需要编写的 Yaml 文件越来越多，并且有写错的风险。更重要的是，如果随着业务增长在其他地域也部署了 K8S 集群，异地多活场景下又需要再次部署一批 Pod。这个时候 Helm 就可以很好地解决此类问题：Helm 中的 Chart 包含了一个完整服务所需要的所有 Yaml 文件配置。</p>
<p>Helm 的优势：</p>
<ul>
<li class="">实现 yaml 文件的高效复用。</li>
<li class="">可以将大量 yaml 文件作为一个整体进行管理。</li>
<li class="">实现应用级别的版本管理。</li>
</ul>
<p>之后我们也可以搭建企业内部的 Chart 仓库。这样企业需要什么服务，不用再进行繁琐的 Yaml 文件编写，只需要通过 Helm 执行 Chart，即可在 K8S 集群部署对应 Pod，得到对应的功能服务。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2安装-helm">2）安装 Helm<a href="https://tanzhuo.xyz/2022/08/22/kubernetes#2%E5%AE%89%E8%A3%85-helm" class="hash-link" aria-label="2）安装 Helm的直接链接" title="2）安装 Helm的直接链接" translate="no">​</a></h3>
<p>使用 Helm 需要先进行安装（官方网址）：<a href="https://helm.sh/docs/" target="_blank" rel="noopener noreferrer" class="">掌舵|文档 (helm.sh)</a></p>
<p>安装时注意 Helm 与 Kubernetes 的版本支持：<a href="https://helm.sh/zh/docs/topics/version_skew/" target="_blank" rel="noopener noreferrer" class="">Helm | Helm版本支持策略</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3安装-istio">3）安装 Istio<a href="https://tanzhuo.xyz/2022/08/22/kubernetes#3%E5%AE%89%E8%A3%85-istio" class="hash-link" aria-label="3）安装 Istio的直接链接" title="3）安装 Istio的直接链接" translate="no">​</a></h3>
<p><strong>开始执行安装：<a href="https://istio.io/latest/zh/docs/setup/install/helm/" target="_blank" rel="noopener noreferrer" class="">Istio / 使用 Helm 安装</a></strong></p>
<p>为 Istio 组件创建命名空间 <code>istio-system</code>：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">$ kubectl create namespace istio-system</span><br></div></code></pre></div></div>
<p>安装 Istio base chart，它包含了 Istio 控制平面用到的集群范围的资源：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 官网没有贴出对应chart仓库，我这里进行了补充</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">$ helm repo </span><span class="token function" style="color:hsl(207, 82%, 66%)">add</span><span class="token plain"> istio https://istio-release.storage.googleapis.com/charts</span><br></div></code></pre></div></div>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">$ helm </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> istio-base istio/base </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-n</span><span class="token plain"> istio-system</span><br></div></code></pre></div></div>
<p>安装 Istio discovery chart，它用于部署 <code>istiod</code> 服务：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">$ helm </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> istiod istio/istiod </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-n</span><span class="token plain"> istio-system </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--wait</span><br></div></code></pre></div></div>
<p>（可选项）安装 Istio 的入站网关：</p>
<p><strong>Linux 内核必须是 4.11 以上，可自行查看 Linux 内核版本是否符合要求。</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 创建入口网关的命名空间，并开启 sidecar 自动注入</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">$ kubectl create namespace istio-ingress</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">$ kubectl label namespace istio-ingress istio-injection</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">enabled</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">$ helm </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> istio-ingress istio/gateway </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-n</span><span class="token plain"> istio-ingress </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--wait</span><br></div></code></pre></div></div>
<p>部署好 Istio ingress 之后，即可部署 Istio 官方 demo 项目 Bookinfo 微服务，用于体验 Istio 的各种能力。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bookinfo">Bookinfo<a href="https://tanzhuo.xyz/2022/08/22/kubernetes#bookinfo" class="hash-link" aria-label="Bookinfo的直接链接" title="Bookinfo的直接链接" translate="no">​</a></h2>
<p>官网地址：<a href="https://istio.io/latest/zh/docs/examples/bookinfo/" target="_blank" rel="noopener noreferrer" class="">Istio / Bookinfo 应用</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-noistio-6757dcc885fcdd61bb4011d88efc5dd7.svg" width="662" height="453" class="img_ev3q"></p>
<p>Bookinfo 微服务使用了 Istio 进行边车部署，使用了 4 种不同的语言程序，并且 Java 语言开发的 Reviews 服务提供了 3 个不同版本的应用，可以很好地体现出 Istio 在多版本控制、流量管理、迁移、整合方面的能力。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/08/22/kubernetes#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Istio 把服务治理从业务代码里剥离出来：数据平面用 Sidecar 接管流量，控制平面统一下发路由与策略，业务方不再绑定某个语言的微服务 SDK。部署上借助 Helm，三条 install 命令就能装好 base、istiod 和入口网关，比手写一堆 Yaml 省事得多。装完之后跑一遍官方的 Bookinfo 示例，多语言、多版本的流量管理能力基本就能体验到了。后续再结合自身业务，逐步把灰度发布、熔断这类能力用起来。</p>]]></content:encoded>
            <category>K8S</category>
        </item>
        <item>
            <title><![CDATA[kubectl 常用命令]]></title>
            <link>https://tanzhuo.xyz/2022/08/21/k</link>
            <guid>https://tanzhuo.xyz/2022/08/21/k</guid>
            <pubDate>Sun, 21 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[整理日常运维中高频使用的 kubectl 命令:资源查看、创建与删除、节点维护、配置导出,并补充强制删除 Pod、drain 驱逐节点等操作的注意事项。]]></description>
            <content:encoded><![CDATA[<p>日常和 Kubernetes 集群打交道,绝大部分操作都绕不开 kubectl。命令本身不难,难的是需要用的时候想不起来——尤其是节点维护、强制删除这类低频但关键的操作。这篇把我平时用得最多的命令按场景整理一遍,忘了就回来翻。</p>
<!-- -->
<p>kubectl 的所有操作本质上都是向 kube-apiserver 发 REST 请求:查看是 GET,应用配置是对期望状态的声明式更新,删除则是把对象从 etcd 中移除,再由各控制器去收敛实际状态。理解这一点,很多命令的行为就好解释了。</p>
<p>先从最基础的开始,不确定参数怎么写时,帮助信息永远是第一入口:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看帮助信息,也可以针对子命令,如 kubectl get --help</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--help</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="集群与节点查看">集群与节点查看<a href="https://tanzhuo.xyz/2022/08/21/k#%E9%9B%86%E7%BE%A4%E4%B8%8E%E8%8A%82%E7%82%B9%E6%9F%A5%E7%9C%8B" class="hash-link" aria-label="集群与节点查看的直接链接" title="集群与节点查看的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看集群节点数及各节点状态(Ready / NotReady)</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl get nodes</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看 node 节点详细信息:pod 信息以及硬件资源占用情况</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 排查节点资源不足、Pod 无法调度时常用</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl describe </span><span class="token function" style="color:hsl(207, 82%, 66%)">node</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">节点名称</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><br></div></code></pre></div></div>
<p><code>describe node</code> 的输出里,Conditions、Allocated resources 和 Events 三段信息量最大,节点异常时优先看这几处。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="命名空间与-pod-查看">命名空间与 Pod 查看<a href="https://tanzhuo.xyz/2022/08/21/k#%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4%E4%B8%8E-pod-%E6%9F%A5%E7%9C%8B" class="hash-link" aria-label="命名空间与 Pod 查看的直接链接" title="命名空间与 Pod 查看的直接链接" translate="no">​</a></h2>
<p>Kubernetes 用命名空间做资源隔离,查询类命令大多需要用 <code>-n</code> 指定命名空间,不指定则默认 default:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看命名空间</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl get namespace</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看对应命名空间下 pod 信息</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl get pod </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-n</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">命名空间</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看所有命名空间下的 pod,-A 是 --all-namespaces 的缩写</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl get pods </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-A</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看所有命名空间下的 pod(完整参数写法为 --all-namespaces)</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl get pod --all-namespace</span><br></div></code></pre></div></div>
<p>定位到具体 Pod 之后,排查问题主要靠 describe 和 logs 两个命令,一个看事件,一个看日志:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 显示一个或多个资源对象的详细信息</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 调度失败、镜像拉取失败等原因都在 Events 里</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl describe</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 输出 pod 资源对象中一个容器的日志</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 多容器 Pod 需要用 -c 指定容器名</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl logs</span><br></div></code></pre></div></div>
<p>一个经验:Pod 起不来先 describe 看事件,起来了但行为不对再 logs 看日志,顺序反了往往白翻半天。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="资源的创建与删除">资源的创建与删除<a href="https://tanzhuo.xyz/2022/08/21/k#%E8%B5%84%E6%BA%90%E7%9A%84%E5%88%9B%E5%BB%BA%E4%B8%8E%E5%88%A0%E9%99%A4" class="hash-link" aria-label="资源的创建与删除的直接链接" title="资源的创建与删除的直接链接" translate="no">​</a></h2>
<p>kubectl 管理资源有两种风格:<code>create</code> 是命令式,直接告诉集群创建什么;<code>apply</code> 是声明式,把 yaml 里描述的期望状态提交给集群,已存在则做增量更新。日常维护配置文件推荐用 apply,同一份文件可以反复执行。</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 通过 yaml/json 文件或者标准输入创建一个资源对象</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl create</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 应用 pod 配置文件,设置资源;文件已应用过则做增量更新</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl apply </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-f</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">pod.yaml</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 取消应用 pod 配置文件,删除资源</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl delete </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-f</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">pod.yaml</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 删除指定命名空间下的 Deployment</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 注意:删除 Deployment 会级联删除它管理的 ReplicaSet 和 Pod</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl delete Deployments </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-n</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">命名空间</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">Deployment名称</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><br></div></code></pre></div></div>
<p>批量清理和强制删除的几个命令,破坏性依次递增:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 强制删除状态为 Terminating 的 pod</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --grace-period=0 表示跳过优雅终止等待</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl delete pod </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">podname</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-n</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">namespace</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--force</span><span class="token plain"> --grace-period</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 删除某个 namespace 下所有 pod</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 由 Deployment 等控制器管理的 Pod 会被自动重建</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl delete </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--all</span><span class="token plain"> pods </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--namespace</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">namespace</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 清除掉 namespace,其下所有资源一并级联删除</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl delete ns </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">namespace</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><br></div></code></pre></div></div>
<p>还有两个修改类的命令,用来给资源打标签和临时改配置:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 设置资源标签,标签是 Service 选择后端、节点亲和性调度的依据</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl label</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 使用默认编辑器编辑服务器上定义的资源对象,保存即生效</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl edit</span><br></div></code></pre></div></div>
<p><code>edit</code> 适合应急调试,但改动不会同步回本地 yaml 文件,改完记得把变更落回配置文件,否则下次 apply 会被覆盖。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="节点维护">节点维护<a href="https://tanzhuo.xyz/2022/08/21/k#%E8%8A%82%E7%82%B9%E7%BB%B4%E6%8A%A4" class="hash-link" aria-label="节点维护的直接链接" title="节点维护的直接链接" translate="no">​</a></h2>
<p>下线节点做维护(升级内核、换硬件)的标准流程是先 cordon 再 drain:cordon 只是把节点标记为不可调度,存量 Pod 不受影响;drain 在此基础上把节点上已有的 Pod 驱逐走,由控制器在其他节点重建。</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 驱逐节点:标记节点不可调度,新 Pod 不再调度到该节点</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl cordon </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">节点名称</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 驱逐节点 pod</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl drain</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 驱逐该节点所有的 pod</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --ignore-daemonsets:跳过 DaemonSet 管理的 Pod(它们本就每节点一份,驱逐了也会重建)</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --delete-local-data:连带删除使用 emptyDir 本地数据的 Pod</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl drain </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">节点名称</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> --delete-local-data </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--force</span><span class="token plain"> --ignore-daemonsets</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 维护完成后从集群中删除掉 node 节点</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl delete nodes </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">节点名称</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="导出资源配置">导出资源配置<a href="https://tanzhuo.xyz/2022/08/21/k#%E5%AF%BC%E5%87%BA%E8%B5%84%E6%BA%90%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="导出资源配置的直接链接" title="导出资源配置的直接链接" translate="no">​</a></h2>
<p>线上手动改过的资源,想把当前状态固化成 yaml 文件时,可以用 <code>-o yaml</code> 加重定向导出:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 把现有的 pod 导出 yaml 配置文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl get deployment </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-n</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">命名空间</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">pod名称</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-o</span><span class="token plain"> yaml </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">文件名称</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain">.yaml</span><br></div></code></pre></div></div>
<p>导出的 yaml 会带上 status、resourceVersion、uid 这类集群运行时字段,拿去别处 apply 之前最好先清理掉,只留 spec 相关的部分。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/08/21/k#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1)<code>--all-namespace</code> 这个写法容易记错,完整参数是 <code>--all-namespaces</code>(带 s),日常直接用缩写 <code>-A</code> 最省事。</p>
<p>2)<code>--force --grace-period=0</code> 强制删除只是让 API Server 立即移除对象,容器进程未必真的退出了。对有状态服务慎用,可能导致同一实例"脑裂"式地跑两份。</p>
<p>3)删除 namespace 是级联操作,其下的 Deployment、Service、ConfigMap 全部一起消失,执行前先 <code>kubectl get all -n &lt;namespace&gt;</code> 确认一遍里面还有什么。</p>
<p>4)<code>drain</code> 时如果不加 <code>--ignore-daemonsets</code>,遇到 DaemonSet 管理的 Pod 命令会直接报错中断;<code>--delete-local-data</code> 在较新版本中已更名为 <code>--delete-emptydir-data</code>,旧参数报错时换新名字即可。</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p>drain 会真实驱逐业务 Pod,生产环境执行前确认副本数足够、其他节点有余量,避免服务容量骤降。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/08/21/k#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这份清单覆盖了查看、创建删除、节点维护、配置导出四类高频场景。命令记不全没关系,记住两条主线就够了:查询排障走 <code>get → describe → logs</code>,节点下线走 <code>cordon → drain → delete node</code>。其余的,<code>--help</code> 随时可查。</p>]]></content:encoded>
            <category>K8S</category>
        </item>
        <item>
            <title><![CDATA[K8S Kubeadm 搭建流程]]></title>
            <link>https://tanzhuo.xyz/2022/08/20/k8s</link>
            <guid>https://tanzhuo.xyz/2022/08/20/k8s</guid>
            <pubDate>Sat, 20 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[记录用 kubeadm 搭建 Kubernetes 1.24 高可用集群的完整流程：前置环境准备、Containerd 运行时、集群初始化、CNI 网络组件，以及踩过的坑。]]></description>
            <content:encoded><![CDATA[<p>把最近用 kubeadm 搭建 K8S 集群的流程，以及途中遇到的部分问题，作为笔记记录下来。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="部署环境">部署环境<a href="https://tanzhuo.xyz/2022/08/20/k8s#%E9%83%A8%E7%BD%B2%E7%8E%AF%E5%A2%83" class="hash-link" aria-label="部署环境的直接链接" title="部署环境的直接链接" translate="no">​</a></h2>
<ul>
<li class="">搭建版本：kubernetes-1.24.4（HA）</li>
<li class="">容器运行时：containerd-1.6.8</li>
<li class="">操作系统：CentOS7</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前置条件">前置条件<a href="https://tanzhuo.xyz/2022/08/20/k8s#%E5%89%8D%E7%BD%AE%E6%9D%A1%E4%BB%B6" class="hash-link" aria-label="前置条件的直接链接" title="前置条件的直接链接" translate="no">​</a></h2>
<p>1）集群节点计算资源必须是 2h-cpu 2g-ram 以上的机器，节点内网或公网需要可达。</p>
<p>2）每个机器的 hostname、mac 地址不能相同，product_uuid 需要唯一；通过 hostname 可以访问对应节点，需要配置 /etc/hosts 文件。</p>
<p>3）kubernetes 1.24 版本后默认的容器运行时不再使用 Docker，改为 Containerd 容器运行时，如果需要使用 docker 则需要安装 <a href="https://github.com/Mirantis/cri-dockerd" target="_blank" rel="noopener noreferrer" class="">cri-dockerd</a>。</p>
<p>4）HA 架构必须存在 3 个 master 控制平面节点，高可用分为外部 etcd 与堆叠 etcd 两种拓扑，架构如下图。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="外部etcd">外部etcd<a href="https://tanzhuo.xyz/2022/08/20/k8s#%E5%A4%96%E9%83%A8etcd" class="hash-link" aria-label="外部etcd的直接链接" title="外部etcd的直接链接" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-46-2b17f77d2252cf1f1b37c1c98c81e317.png" width="1281" height="879" class="img_ev3q"></p>
<p>优点：失去控制平面实例或者 etcd 成员的影响较小，不会像堆叠的 HA 拓扑那样影响集群冗余。</p>
<p>缺点：需要更多机器节点，要 3 台服务器单独部署 etcd HA 集群。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="堆叠etcd">堆叠etcd<a href="https://tanzhuo.xyz/2022/08/20/k8s#%E5%A0%86%E5%8F%A0etcd" class="hash-link" aria-label="堆叠etcd的直接链接" title="堆叠etcd的直接链接" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-45-18ff9d55a0109ad3d07f388f439d385a.webp" width="1307" height="837" class="img_ev3q"></p>
<p>优点：这种拓扑将控制平面和 etcd 成员耦合在同一节点上。相对使用外部 etcd 集群，设置起来更简单，而且更易于副本管理。</p>
<p>缺点：堆叠集群存在耦合失败的风险。如果一个节点发生故障，则 etcd 成员和控制平面实例都将丢失，并且冗余会受到影响。你可以通过添加更多控制平面节点来降低此风险。</p>
<p><strong>load balancer：</strong> 内部负载均衡器，需要保证自身 HA。为 worker 节点反向代理 apiServer，提供统一访问 apiServer 的 ip，并且可以感知 master 的可用性。</p>
<p>5）禁用 Linux 交换分区。（交换分区：内存不足时使用硬盘存储，会降低系统性能，好处是可以存储更多内存数据，并且内存占满也不会导致服务器马上出现卡顿）</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 设置 CentOS 禁用交换分区：</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 永久关闭：</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 1、vim /etc/fstab 进入配置文件注释掉下面这个配置</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">/dev/mapper/cl-swap     swap     swap    defaults    </span><span class="token number" style="color:hsl(29, 54%, 61%)">0</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 2、注释掉并保存</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 临时关闭：</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 执行 swapoff -a 命令</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">swapoff </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-a</span><br></div></code></pre></div></div>
<p>6）关闭防火墙或者开启官网指定端口</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看防火墙状态</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">firewall-cmd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--state</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 1、打开防火墙端口 master 节点需要 6443 2379-2380 10250 10251 10252</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">firewall-cmd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--permanent</span><span class="token plain"> --add-port</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">6443</span><span class="token plain">/tcp</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">firewall-cmd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--permanent</span><span class="token plain"> --add-port</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">2379</span><span class="token plain">-2380/tcp</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">firewall-cmd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--permanent</span><span class="token plain"> --add-port</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">10250</span><span class="token plain">/tcp</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">firewall-cmd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--permanent</span><span class="token plain"> --add-port</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">10251</span><span class="token plain">/tcp</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">firewall-cmd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--permanent</span><span class="token plain"> --add-port</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">10252</span><span class="token plain">/tcp</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 打开防火墙端口 node-worker 节点需要 10250 30000-32767</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">firewall-cmd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--permanent</span><span class="token plain"> --add-port</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">10250</span><span class="token plain">/tcp</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">firewall-cmd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--permanent</span><span class="token plain"> --add-port</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">30000</span><span class="token plain">-32767/tcp</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 防火墙重载配置</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">firewall-cmd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--reload</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 检测端口是否已经配置成功</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">firewall-cmd </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--zone</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">public --list-ports</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 2、也可选择关闭防火墙 ！不安全 但可以避免很多问题，比如后续的CNI网络组件所需端口</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 停止firewall</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">systemctl stop firewalld.service</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 禁止firewall开机启动</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">systemctl disable firewalld.service</span><br></div></code></pre></div></div>
<p>7）配置 yum 源信息 /etc/yum.repos.d/kubernetes.repo（官方源有可能无法使用，需要科学上网，所以此处使用国内阿里源）</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">cat</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;&lt;</span><span class="token string" style="color:hsl(95, 38%, 62%)">EOF</span><span class="token string bash punctuation" style="color:hsl(220, 14%, 71%)"> </span><span class="token string bash punctuation operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token string bash punctuation" style="color:hsl(220, 14%, 71%)"> /etc/yum.repos.d/kubernetes.repo</span><span class="token string" style="color:hsl(95, 38%, 62%)"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">[kubernetes]</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">name=Kubernetes</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">enabled=1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">gpgcheck=1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">repo_gpgcheck=1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">EOF</span><br></div></code></pre></div></div>
<p>8）开启内核转发与网桥过滤，安装 ipset、ipvsadm，并启用对应内核模块</p>
<p>官方文档：<a href="https://kubernetes.io/zh-cn/docs/setup/production-environment/container-runtimes/" target="_blank" rel="noopener noreferrer" class="">容器运行时 | Kubernetes</a></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 声明需要加载的内核模块</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">cat</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;&lt;</span><span class="token string" style="color:hsl(95, 38%, 62%)">EOF</span><span class="token string bash punctuation" style="color:hsl(220, 14%, 71%)"> </span><span class="token string bash punctuation operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token string bash punctuation" style="color:hsl(220, 14%, 71%)"> </span><span class="token string bash punctuation function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token string bash punctuation" style="color:hsl(220, 14%, 71%)"> </span><span class="token string bash punctuation function" style="color:hsl(207, 82%, 66%)">tee</span><span class="token string bash punctuation" style="color:hsl(220, 14%, 71%)"> /etc/modules-load.d/k8s.conf</span><span class="token string" style="color:hsl(95, 38%, 62%)"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">overlay</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">br_netfilter</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">EOF</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> modprobe overlay</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> modprobe br_netfilter</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 开启网桥过滤与内核转发</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">cat</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;&lt;</span><span class="token string" style="color:hsl(95, 38%, 62%)">EOF</span><span class="token string bash punctuation" style="color:hsl(220, 14%, 71%)"> </span><span class="token string bash punctuation operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token string bash punctuation" style="color:hsl(220, 14%, 71%)"> </span><span class="token string bash punctuation function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token string bash punctuation" style="color:hsl(220, 14%, 71%)"> </span><span class="token string bash punctuation function" style="color:hsl(207, 82%, 66%)">tee</span><span class="token string bash punctuation" style="color:hsl(220, 14%, 71%)"> /etc/sysctl.d/k8s.conf</span><span class="token string" style="color:hsl(95, 38%, 62%)"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">net.bridge.bridge-nf-call-ip6tables = 1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">net.bridge.bridge-nf-call-iptables = 1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">net.ipv4.ip_forward = 1</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">vm.swappiness = 0</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">EOF</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 使配置生效</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">sysctl</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--system</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 安装 ipset ipvsadm</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">yum </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-y</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> ipset ipvsadm</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-p</span><span class="token plain"> /etc/sysconfig/modules/</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 声明 ipvs 所需内核模块</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">cat</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> /etc/sysconfig/modules/ipvs.modules </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;&lt;</span><span class="token string" style="color:hsl(95, 38%, 62%)">EOF</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">#!/bin/bash</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">modprobe -- ip_vs</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">modprobe -- ip_vs_rr</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">modprobe -- ip_vs_wrr</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">modprobe -- ip_vs_sh</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">modprobe -- nf_conntrack</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token string" style="color:hsl(95, 38%, 62%)">EOF</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 授予执行权限并运行</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">chmod</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">755</span><span class="token plain"> /etc/sysconfig/modules/ipvs.modules </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&amp;&amp;</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">bash</span><span class="token plain"> /etc/sysconfig/modules/ipvs.modules</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看是否生效</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">lsmod </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">grep</span><span class="token plain"> ip_vs</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 控制台输出</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">ip_vs_sh               </span><span class="token number" style="color:hsl(29, 54%, 61%)">16384</span><span class="token plain">  </span><span class="token number" style="color:hsl(29, 54%, 61%)">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">ip_vs_wrr              </span><span class="token number" style="color:hsl(29, 54%, 61%)">16384</span><span class="token plain">  </span><span class="token number" style="color:hsl(29, 54%, 61%)">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">ip_vs_rr               </span><span class="token number" style="color:hsl(29, 54%, 61%)">16384</span><span class="token plain">  </span><span class="token number" style="color:hsl(29, 54%, 61%)">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">ip_vs                 </span><span class="token number" style="color:hsl(29, 54%, 61%)">180224</span><span class="token plain">  </span><span class="token number" style="color:hsl(29, 54%, 61%)">6</span><span class="token plain"> ip_vs_rr,ip_vs_sh,ip_vs_wrr</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">nf_conntrack          </span><span class="token number" style="color:hsl(29, 54%, 61%)">176128</span><span class="token plain">  </span><span class="token number" style="color:hsl(29, 54%, 61%)">1</span><span class="token plain"> ip_vs</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">nf_defrag_ipv6         </span><span class="token number" style="color:hsl(29, 54%, 61%)">24576</span><span class="token plain">  </span><span class="token number" style="color:hsl(29, 54%, 61%)">2</span><span class="token plain"> nf_conntrack,ip_vs</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">libcrc32c              </span><span class="token number" style="color:hsl(29, 54%, 61%)">16384</span><span class="token plain">  </span><span class="token number" style="color:hsl(29, 54%, 61%)">2</span><span class="token plain"> nf_conntrack,ip_vs</span><br></div></code></pre></div></div>
<p>9）将 SELinux 设置为 permissive 模式（相当于将其禁用）</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> setenforce </span><span class="token number" style="color:hsl(29, 54%, 61%)">0</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安装容器运行时">安装容器运行时<a href="https://tanzhuo.xyz/2022/08/20/k8s#%E5%AE%89%E8%A3%85%E5%AE%B9%E5%99%A8%E8%BF%90%E8%A1%8C%E6%97%B6" class="hash-link" aria-label="安装容器运行时的直接链接" title="安装容器运行时的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="containerd">Containerd<a href="https://tanzhuo.xyz/2022/08/20/k8s#containerd" class="hash-link" aria-label="Containerd的直接链接" title="Containerd的直接链接" translate="no">​</a></h3>
<p>参考：<a href="https://blog.csdn.net/weixin_39246554/article/details/120930966" target="_blank" rel="noopener noreferrer" class="">实战：centos7上containerd的安装-20211023_一念一生～one的博客-CSDN博客_centos安装containerd</a></p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p>在 Containerd 1.6 后的版本中，需要升级本地依赖 libseccomp 到 2.4 以上，不然可能出现无法启动容器的情况。本人亲自踩坑，错误信息非常不好找，并且 CentOS yum 源中最高只有 2.3.1，只能自己从官方下载并安装。</p></div></div>
<p>相关命令：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看当前 libseccomp 版本</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">rpm</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-qa</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">grep</span><span class="token plain"> libseccomp</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 卸载旧版本</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">rpm</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-e</span><span class="token plain"> libseccomp-devel-2.3.1-4.el7.x86_64 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--nodeps</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">rpm</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-e</span><span class="token plain"> libseccomp-2.3.1-4.el7.x86_64 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--nodeps</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 下载新版本 libseccomp</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">wget</span><span class="token plain"> http://rpmfind.net/linux/centos/8-stream/BaseOS/x86_64/os/Packages/libseccomp-2.5.1-1.el8.x86_64.rpm</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 安装 libseccomp</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">rpm</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-ivh</span><span class="token plain"> libseccomp-2.5.1-1.el8.x86_64.rpm</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="集群初始化">集群初始化<a href="https://tanzhuo.xyz/2022/08/20/k8s#%E9%9B%86%E7%BE%A4%E5%88%9D%E5%A7%8B%E5%8C%96" class="hash-link" aria-label="集群初始化的直接链接" title="集群初始化的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1安装-kubeadmkubectlkubelet-三大组件">1）安装 kubeadm、kubectl、kubelet 三大组件<a href="https://tanzhuo.xyz/2022/08/20/k8s#1%E5%AE%89%E8%A3%85-kubeadmkubectlkubelet-%E4%B8%89%E5%A4%A7%E7%BB%84%E4%BB%B6" class="hash-link" aria-label="1）安装 kubeadm、kubectl、kubelet 三大组件的直接链接" title="1）安装 kubeadm、kubectl、kubelet 三大组件的直接链接" translate="no">​</a></h3>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 安装组件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> yum </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-y</span><span class="token plain"> kubelet-1.24.4 kubeadm-1.24.4 kubectl-1.24.4 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--disableexcludes</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">kubernetes</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 设置kubelet开机自启</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> systemctl </span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">enable</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--now</span><span class="token plain"> kubelet</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2初始化平面控制节点">2）初始化平面控制节点<a href="https://tanzhuo.xyz/2022/08/20/k8s#2%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B9%B3%E9%9D%A2%E6%8E%A7%E5%88%B6%E8%8A%82%E7%82%B9" class="hash-link" aria-label="2）初始化平面控制节点的直接链接" title="2）初始化平面控制节点的直接链接" translate="no">​</a></h3>
<p>使用 kubeadm 初始化节点，命令为 kubeadm init 【参数】。</p>
<p><strong>更多参数可以查看官网文档：<a href="https://kubernetes.io/zh-cn/docs/reference/setup-tools/kubeadm/" target="_blank" rel="noopener noreferrer" class="">Kubeadm | Kubernetes</a></strong></p>
<p>此处我使用我本地环境的配置参数，生产环境只需要把对应 ip 地址改为线上内网 ip 即可。</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 可使用命令初始化控制平面</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> kubeadm init </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">--control-plane-endpoint </span><span class="token string" style="color:hsl(95, 38%, 62%)">"k8s-cluster:6443"</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">--apiserver-advertise-address</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">192.168</span><span class="token plain">.5.27 </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">--apiserver-bind-port</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">6443</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">--image-repository registry.aliyuncs.com/google_containers </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">--kubernetes-version v1.24.4 </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">--cert-dir</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">/etc/kubernetes/pki </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">--service-cidr</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">10.96</span><span class="token plain">.0.0/12 </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">--pod-network-cidr</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token number" style="color:hsl(29, 54%, 61%)">172.24</span><span class="token plain">.0.0/16 </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">--upload-certs</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 也可使用初始化文件 先生成初始化文件，再根据自身场景修改其参数</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubeadm config print init-defaults  </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> kubeadm-config.yaml</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubeadm init </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--config</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> kubeadm-config.yaml --upload-certs</span><br></div></code></pre></div></div>
<p>关键参数说明：</p>
<p>--control-plane-endpoint：填写内部 <strong>load balancer</strong> 的 ip 地址或者内网的 DNS 域名信息。worker 节点通过此域名或 ip 访问集群内部的 master 节点，从而确保 master 高可用。</p>
<p>--apiserver-advertise-address：填写当前控制平面的内网 ip 地址。</p>
<p>--pod-network-cidr：pod 内网网段 ip。</p>
<p>--service-cidr：service 内网网段 ip。</p>
<p>--image-repository：镜像地址，设置为国内的，我这里使用的 ali 源，拉取速度提升。</p>
<p><strong>初始化时，kubeadm 需要拉取 k8s.gcr.io/pause 镜像，由于国内被墙，很有可能无法成功下载，导致初始化失败。如果你初始化失败了则查看容器运行时的日志信息，如果发现无法拉取到对应镜像，说明就是此问题（被坑几天查找问题）。解决办法：</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看容器运行时日志</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">journalctl </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-xeu</span><span class="token plain"> containerd</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 由于k8s.gcr.io 需要连外网才可以拉取到，导致 k8s 的基础容器 pause 经常无法获取。</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 可使用 crictl 从国内镜像仓库拉取，再利用 ctr tag 解决问题</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 拉取国内镜像</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">crictl pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 进行tag打标</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">ctr </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-n</span><span class="token plain"> k8s.io image tag registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6 k8s.gcr.io/pause:3.6</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3初始化成功后的输出">3）初始化成功后的输出<a href="https://tanzhuo.xyz/2022/08/20/k8s#3%E5%88%9D%E5%A7%8B%E5%8C%96%E6%88%90%E5%8A%9F%E5%90%8E%E7%9A%84%E8%BE%93%E5%87%BA" class="hash-link" aria-label="3）初始化成功后的输出的直接链接" title="3）初始化成功后的输出的直接链接" translate="no">​</a></h3>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Your Kubernetes control-plane has initialized successfully</span><span class="token operator" style="color:hsl(207, 82%, 66%)">!</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">To start using your cluster, you need to run the following as a regular user:</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token function" style="color:hsl(207, 82%, 66%)">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-p</span><span class="token plain"> </span><span class="token environment constant" style="color:hsl(29, 54%, 61%)">$HOME</span><span class="token plain">/.kube</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">cp</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-i</span><span class="token plain"> /etc/kubernetes/admin.conf </span><span class="token environment constant" style="color:hsl(29, 54%, 61%)">$HOME</span><span class="token plain">/.kube/config</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">chown</span><span class="token plain"> </span><span class="token variable" style="color:hsl(207, 82%, 66%)">$(</span><span class="token variable function" style="color:hsl(207, 82%, 66%)">id</span><span class="token variable" style="color:hsl(207, 82%, 66%)"> </span><span class="token variable parameter variable" style="color:hsl(207, 82%, 66%)">-u</span><span class="token variable" style="color:hsl(207, 82%, 66%)">)</span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">:</span><span class="token variable" style="color:hsl(207, 82%, 66%)">$(</span><span class="token variable function" style="color:hsl(207, 82%, 66%)">id</span><span class="token variable" style="color:hsl(207, 82%, 66%)"> </span><span class="token variable parameter variable" style="color:hsl(207, 82%, 66%)">-g</span><span class="token variable" style="color:hsl(207, 82%, 66%)">)</span><span class="token plain"> </span><span class="token environment constant" style="color:hsl(29, 54%, 61%)">$HOME</span><span class="token plain">/.kube/config</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Alternatively, </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">if</span><span class="token plain"> you are the root user, you can run:</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:hsl(207, 82%, 66%)">KUBECONFIG</span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain">/etc/kubernetes/admin.conf</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">You should now deploy a pod network to the cluster.</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Run </span><span class="token string" style="color:hsl(95, 38%, 62%)">"kubectl apply -f [podnetwork].yaml"</span><span class="token plain"> with one of the options listed at:</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  https://kubernetes.io/docs/concepts/cluster-administration/addons/</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">You can now </span><span class="token function" style="color:hsl(207, 82%, 66%)">join</span><span class="token plain"> any number of the control-plane </span><span class="token function" style="color:hsl(207, 82%, 66%)">node</span><span class="token plain"> running the following </span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">command</span><span class="token plain"> on each as root:</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  kubeadm </span><span class="token function" style="color:hsl(207, 82%, 66%)">join</span><span class="token plain"> k8s-cluster:6443 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--token</span><span class="token plain"> v6rrcg.vnl78b94fo740xv9 </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        --discovery-token-ca-cert-hash sha256:5dd2edf3d668bd0608897a0097455c6a514a91fd0b1ad4d1044789d9f56123ce </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        --control-plane --certificate-key 4c9b2b3a2d19db4b4f58681e6cf264a0b7bb1eacf443ae90a50bd0e8c11e0c31</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Please note that the certificate-key gives access to cluster sensitive data, keep it secret</span><span class="token operator" style="color:hsl(207, 82%, 66%)">!</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">As a safeguard, uploaded-certs will be deleted </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">in</span><span class="token plain"> two hours</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"> If necessary, you can use</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token string" style="color:hsl(95, 38%, 62%)">"kubeadm init phase upload-certs --upload-certs"</span><span class="token plain"> to reload certs afterward.</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Then you can </span><span class="token function" style="color:hsl(207, 82%, 66%)">join</span><span class="token plain"> any number of worker nodes by running the following on each as root:</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubeadm </span><span class="token function" style="color:hsl(207, 82%, 66%)">join</span><span class="token plain"> k8s-cluster:6443 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--token</span><span class="token plain"> v6rrcg.vnl78b94fo740xv9 </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        --discovery-token-ca-cert-hash sha256:5dd2edf3d668bd0608897a0097455c6a514a91fd0b1ad4d1044789d9f56123ce </span><br></div></code></pre></div></div>
<p>通过以上输出信息，已经得知对应 node 节点加入集群的指令信息。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="节点加入集群">节点加入集群<a href="https://tanzhuo.xyz/2022/08/20/k8s#%E8%8A%82%E7%82%B9%E5%8A%A0%E5%85%A5%E9%9B%86%E7%BE%A4" class="hash-link" aria-label="节点加入集群的直接链接" title="节点加入集群的直接链接" translate="no">​</a></h2>
<p>kubeadm join 指令附带上 token 信息即可加入其他的 master 节点与 worker 节点。token 默认 6 小时生效，重新生成的 token 是 24 小时生效，如果 token 过期可使用命令重新生成：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># master平面节点加入执行：（获取证书加密信息）平面节点加入需要 token + certs 信息</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubeadm init phase upload-certs --upload-certs</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># worker节点加入生成token信息</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubeadm token create --print-join-command</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 执行查看当前已有token列表</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubeadm token list</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安装网络cni组件">安装网络CNI组件<a href="https://tanzhuo.xyz/2022/08/20/k8s#%E5%AE%89%E8%A3%85%E7%BD%91%E7%BB%9Ccni%E7%BB%84%E4%BB%B6" class="hash-link" aria-label="安装网络CNI组件的直接链接" title="安装网络CNI组件的直接链接" translate="no">​</a></h2>
<p>可根据自身企业业务场景与规模，从安全与性能上考虑，选择适合的 CNI 组件。注意：集群中只能有一种 CNI 组件。</p>
<p><strong>更多细节也可查看官方文档：</strong><a href="https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/" target="_blank" rel="noopener noreferrer" class="">使用 kubeadm 创建集群 | Kubernetes</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-47-e74df237f2373cb24825389fd1ff8792.png" width="1336" height="679" class="img_ev3q"></p>
<p>此处我选用常用的 CNI 组件：Calico。</p>
<p>Calico 官方网站：<a href="https://projectcalico.docs.tigera.io/getting-started/kubernetes/self-managed-onprem/onpremises" target="_blank" rel="noopener noreferrer" class="">为本地部署安装 Calico 网络和网络策略 (tigera.io)</a></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 首先，在群集上安装运算符。</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl create </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-f</span><span class="token plain"> https://raw.githubusercontent.com/projectcalico/calico/v3.24.0/manifests/tigera-operator.yaml</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 下载配置 Calico 所需的自定义资源</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">curl</span><span class="token plain"> https://raw.githubusercontent.com/projectcalico/calico/v3.24.0/manifests/custom-resources.yaml </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-O</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 自定义配置custom-resources.yaml文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 修改其中pod网络网段ip信息</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#  calicoNetwork:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    # Note: The ipPools section cannot be modified post-install.</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    ipPools:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - blockSize: 26</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#      cidr: 172.24.0.0/16 （修改此处与kubeadm时相同的pod网段）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#      encapsulation: VXLANCrossSubnet</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#      natOutgoing: Enabled</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#      nodeSelector: all()</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 创建清单以安装 Calico。</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl create </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-f</span><span class="token plain"> custom-resources.yaml</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 运行查看Calico相关pod运行状态，需要等待4分钟左右</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl get pod -all-namespace</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="部署控制平面">部署控制平面<a href="https://tanzhuo.xyz/2022/08/20/k8s#%E9%83%A8%E7%BD%B2%E6%8E%A7%E5%88%B6%E5%B9%B3%E9%9D%A2" class="hash-link" aria-label="部署控制平面的直接链接" title="部署控制平面的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="kuboard">kuboard<a href="https://tanzhuo.xyz/2022/08/20/k8s#kuboard" class="hash-link" aria-label="kuboard的直接链接" title="kuboard的直接链接" translate="no">​</a></h3>
<p><strong>官方文档：</strong><a href="https://www.kuboard.cn/" target="_blank" rel="noopener noreferrer" class="">https://www.kuboard.cn/</a></p>
<p>通过 docker 镜像直接一键启动容器，然后进行集群导入引导操作。</p>
<p>成功监控集群信息：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-48-a39e9ce841bd338e83876a2d26160fd8.webp" width="1600" height="783" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/08/20/k8s#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>到这一步，集群已经基本可用：kubeadm 完成控制平面初始化，节点通过 token 加入，Calico 提供 pod 网络，kuboard 负责可视化监控。整个流程里最容易踩的坑集中在两处：Containerd 的 libseccomp 版本过低，以及 pause 镜像因网络原因拉取失败，两者的报错都不直观，需要去容器运行时日志里找线索。后续还需要加入 NAS 文件服务，便于集群内部的文件存储，以及考虑是否引入 Istio 和网络入口 Ingress 的设置，根据企业自身进行合理配置。</p>
<p>下篇：<a href="https://tanzhuo.xyz/kubernetes/" target="_blank" rel="noopener noreferrer" class="">Kubernetes部署Istio (tanzhuo.xyz)</a></p>]]></content:encoded>
            <category>K8S</category>
        </item>
        <item>
            <title><![CDATA[HBase介绍以及对比MongoDB]]></title>
            <link>https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb</link>
            <guid>https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb</guid>
            <pubDate>Sat, 13 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[HBase 是面向列的可扩展分布式存储系统，可在廉价 PC 集群上支撑 PB 级数据。本文梳理其架构、读写流程、LSM 树结构与优缺点，并与 MongoDB 做适用场景对比。]]></description>
            <content:encoded><![CDATA[<p>HBase 全称 Hadoop Database，是一种高可靠性、高性能、面向列的可扩展分布式存储系统，可以在廉价的 PC 服务器上构建大规模结构化存储集群。</p>
<!-- -->
<p>HBase 的目标是存储和处理大量数据，仅使用标准硬件配置即可处理包含数千行和列的海量数据，可支撑 PB 级别的数据存储、亿级 QPS 的查询。</p>
<p>它适用于实时性要求不高的业务场景。HBase 存储的是 Byte 数组，不关心数据类型，从而允许动态、灵活的数据模型。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="架构解析">架构解析<a href="https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb#%E6%9E%B6%E6%9E%84%E8%A7%A3%E6%9E%90" class="hash-link" aria-label="架构解析的直接链接" title="架构解析的直接链接" translate="no">​</a></h2>
<p>HBase 由 HMaster 和 HRegionServer 组成，遵循主从服务器体系结构。HBase 将逻辑表划分为多个数据块 HRegion，并将它们存储在 HRegionServer 中。</p>
<p>HMaster 负责管理所有 HRegionServer。它本身不存储任何数据，只保存数据到 HRegionServer 的映射（元数据）。</p>
<p>集群中的所有节点由 Zookeeper 协调，用于处理 HBase 运行期间可能遇到的各种问题。HBase 的基本架构如下所示：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-ad2553610635ee0adc362fe27c2a7537-a4b0200d1bfb0f00802e6f2aaf28ab93.webp" width="640" height="370" class="img_ev3q"></p>
<p><strong>客户端：</strong> 使用 HBase 的 RPC 机制与 HMaster 和 HRegionServer 通信，提交请求并获得结果。管理类操作，客户端通过 HMaster 执行 RPC；数据读写操作，客户端通过 HRegionServer 执行 RPC。</p>
<p><strong>Zookeeper：</strong> 集群中每个节点的状态信息都注册到 Zookeeper，HMaster 借此可以随时感知每个 HRegionServer 的健康状态，同时也避免了 HMaster 的单点故障。</p>
<p><strong>HMaster：</strong> 管理所有 HRegionServer，告诉它们需要维护哪些 HRegion，并监视所有 HRegionServer 的运行状况。当新的 HRegionServer 向 HMaster 注册时，HMaster 告诉它等待数据分配；当某个 HRegion 失效时，HMaster 会把它负责的所有 HRegion 标记为未分配，再分配给其他 HRegionServer。HMaster 没有单点问题——HBase 可以启动多个 HMaster，通过 Zookeeper 的选举机制保证集群中始终有一个 HMaster 在运行，从而提高集群的可用性。</p>
<p><strong>HRegion：</strong> 当表的大小超过预设值时，HBase 会自动将表划分为不同的区域，每个区域包含表中所有行的一个子集。对用户来说，每个表就是一个数据集合，用主键（RowKey）加以区分；从物理上讲，一个表被分为多个块，每个块就是一个 HRegion，用「表名 + 开始/结束主键」来区分。一个 HRegion 保存表中一段连续的数据，完整的表数据则存储在多个 HRegion 中。</p>
<p><strong>HRegionServer：</strong> HBase 的所有数据通常存储在底层的 HDFS 中，用户通过 HRegionServer 访问这些数据。一般集群的一个节点上只运行一个 HRegionServer，每个 HRegion 也只由一个 HRegionServer 维护。HRegionServer 主要负责响应用户 I/O 请求，向 HDFS 文件系统读写数据，是 HBase 中的核心模块。HRegionServer 内部管理一系列 HRegion 对象，每个 HRegion 对应逻辑表中一段连续的数据。HRegion 由多个 HStore 组成，每个 HStore 对应逻辑表中一个列族的存储。可以看出，每个列族就是一个集中的存储单元，因此为了提高操作效率，最好把具有相同 I/O 特性的列放在同一个列族中。</p>
<p><strong>HStore：</strong> 它是 HBase 存储的核心，由 MemStore 和 StoreFile 组成。MemStore 是内存缓冲区，用户写入的数据会先放入 MemStore；当 MemStore 写满时，会 Flush 成一个 StoreFile（底层实现是 HFile）。当 StoreFile 文件数量增加到某个阈值时，会触发 Compact 合并操作，将多个 StoreFile 合并为一个，并在合并过程中执行版本合并和数据删除。因此可以看出，HBase 只是不断追加数据，所有的更新和删除都在后续的 Compact 过程中完成，用户的写入操作进入内存后即可立即返回，保证了 HBase 的写入性能。StoreFile 经过不断 Compact 会逐渐形成越来越大的文件；当单个 StoreFile 的大小超过某个阈值时，会触发 Split 操作，把当前 HRegion 拆分为 2 个 HRegion，父 HRegion 下线，HMaster 把两个子 HRegion 分配给相应的 HRegionServer，从而把原 HRegion 的负载压力分流到这两个 HRegion 上。</p>
<p><strong>HLog：</strong> 每个 HRegionServer 都有一个 HLog 对象，它是实现预写日志（Write-Ahead Log）的类。用户每次向 MemStore 写入数据时，也会把一份数据副本写入 HLog 文件。HLog 文件定期滚动并删除旧文件（其中的数据已持久化到 StoreFile）。当 HMaster 通过 Zookeeper 检测到某个 HRegionServer 意外终止时，会先处理遗留的 HLog 文件，按不同的 HRegion 拆分其中的日志数据，放入对应的 HRegion 目录，然后重新分配这些失效的 HRegion。接管这些 HRegion 的 HRegionServer 在加载过程中会发现有历史 HLog 需要处理，于是把 HLog 中的数据 Replay 到 MemStore，再刷新到 StoreFile，完成数据恢复。</p>
<p>HBase 基于 BigTable 模型，是一个稀疏的、长期存储（在 HDFS 上）、多维、排序的映射表。该表的索引是行关键字、列关键字和时间戳。HBase 的数据都是字符串，没有类型。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-bdc5394355d37057a381069b139db6c7-fffe9699725f0292d1ef21fd0831af44.webp" width="1200" height="234" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hbase-读写过程">HBase 读写过程<a href="https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb#hbase-%E8%AF%BB%E5%86%99%E8%BF%87%E7%A8%8B" class="hash-link" aria-label="HBase 读写过程的直接链接" title="HBase 读写过程的直接链接" translate="no">​</a></h2>
<p>下图是 HRegionServer 的数据存储关系图。如上所述，HBase 使用 MemStore 和 StoreFile 来存储对表的更新。数据在更新时首先写入 HLog 和 MemStore，MemStore 中的数据是排序的。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-4f79359da1203e9a7f16754087434d53-70190020f2acab2fae91ebe0a5b1484c.webp" width="640" height="394" class="img_ev3q"></p>
<p>当 MemStore 累积到某个阈值时，会创建一个新的 MemStore，旧的 MemStore 被加入 Flush 队列，由单独的线程刷新到磁盘上成为 StoreFile。同时，系统会在 Zookeeper 中记录一个 CheckPoint，表明该时间点之前的数据变更已经持久化。当系统发生意外时，MemStore 中的数据可能丢失，这种情况下就用 HLog 来恢复 CheckPoint 之后的数据。</p>
<p>StoreFile 是只读的，一旦创建便无法修改，因此 HBase 的更新其实是一种追加操作。当 HStore 中的 StoreFile 数量达到某个阈值时，会执行合并操作，把对相同 key 的修改合并成一个大的 StoreFile；当 StoreFile 的大小达到某个阈值时，又会把它拆分为两个 StoreFile。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="写操作流程">写操作流程<a href="https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb#%E5%86%99%E6%93%8D%E4%BD%9C%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="写操作流程的直接链接" title="写操作流程的直接链接" translate="no">​</a></h3>
<ul>
<li class="">步骤 1：客户端通过 Zookeeper 的调度向 HRegionServer 发送写数据请求，将数据写入 HRegion。</li>
<li class="">步骤 2：数据写入 HRegion 的 MemStore，直到 MemStore 达到预设阈值。</li>
<li class="">步骤 3：MemStore 中的数据被 Flush 成 StoreFile。</li>
<li class="">步骤 4：随着 StoreFile 文件数量增加到特定阈值，触发 Compact 合并操作，将多个 StoreFile 合并为一个，同时进行版本合并和数据删除。</li>
<li class="">步骤 5：StoreFile 通过不断的 Compact 操作，逐渐形成越来越大的 StoreFile。</li>
<li class="">步骤 6：单个 StoreFile 的大小超过阈值后，触发 Split 操作，把当前 HRegion 拆分为两个新的 HRegion。父 HRegion 下线，新拆分出的两个子 HRegion 由 HMaster 分配给相应的 HRegionServer，从而把原 HRegion 的压力分流出去。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="读操作流程">读操作流程<a href="https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb#%E8%AF%BB%E6%93%8D%E4%BD%9C%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="读操作流程的直接链接" title="读操作流程的直接链接" translate="no">​</a></h3>
<ul>
<li class="">步骤 1：客户端访问 Zookeeper，找到 -ROOT- 表，进而获得 .META. 表的信息。</li>
<li class="">步骤 2：从 .META. 表中查询目标数据所在的 HRegion 信息，找到对应的 HRegionServer。</li>
<li class="">步骤 3：通过 HRegionServer 获取需要查找的数据。</li>
<li class="">步骤 4：HRegionServer 的内存分为 MemStore 和 BlockCache 两部分，MemStore 主要用于写数据，BlockCache 主要用于读数据。读请求会先到 MemStore 中查数据，查不到再查 BlockCache，还查不到才访问 StoreFile，并把读取的结果放入 BlockCache。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hbase-使用场景">HBase 使用场景<a href="https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb#hbase-%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="HBase 使用场景的直接链接" title="HBase 使用场景的直接链接" translate="no">​</a></h2>
<p>半结构化或非结构化数据：对于字段无法预先定义好或者比较杂乱的数据结构，很难用固定 schema 建模，适合交给 HBase。如果随着业务增长需要存储更多字段，RDBMS 需要停机维护变更表结构，而 HBase 支持动态增加列。</p>
<p>记录非常稀疏：RDBMS 表的列数是固定的，值为空的列白白浪费存储空间；HBase 中为空的列不占用存储，既节省空间又提高读取性能。</p>
<p>多版本数据：根据 RowKey 和列标识定位到的值可以有任意数量的版本（时间戳不同），因此对需要存储变更历史的数据，用 HBase 非常方便。</p>
<p>大量数据：当数据量越来越大时，RDBMS 会逐渐扛不住，于是先做读写分离——一台主库负责写，多台从库负责读，服务器成本翻倍；压力继续增加，主库扛不住了，就开始分库，把几乎不相关的数据分开部署，一些 join 查询没法用了，需要引入中间层；数据量再进一步增加，单表记录越来越多，查询变得非常慢，又不得不分表（例如按 ID 取模拆成多个表）来减少单表记录数。经历过这个过程的人都知道其中的繁琐。</p>
<p>HBase 就简单多了：只需向集群添加新节点，HBase 就会自动水平拆分，并且与 Hadoop 的无缝集成保证了数据可靠性（HDFS）和高性能的海量数据分析能力（MapReduce）。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hbase-与-mapreduce">HBase 与 MapReduce<a href="https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb#hbase-%E4%B8%8E-mapreduce" class="hash-link" aria-label="HBase 与 MapReduce的直接链接" title="HBase 与 MapReduce的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-ed27cafbd33b8be27e8810b45de839e5--1--ed4338a1786827b76930ee4962011de8.webp" width="554" height="277" class="img_ev3q"></p>
<p>HBase 中 Table 与 Region 的关系，和 HDFS 中 File 与 Block 的关系有些相似。由于 HBase 提供了与 MapReduce 交互的 API，例如 TableInputFormat 和 TableOutputFormat，HBase 的数据表可以直接作为 Hadoop MapReduce 的输入和输出，方便了 MapReduce 应用的开发，且无需关注 HBase 系统本身的细节。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据结构分析">数据结构分析<a href="https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%88%86%E6%9E%90" class="hash-link" aria-label="数据结构分析的直接链接" title="数据结构分析的直接链接" translate="no">​</a></h2>
<p>HBase 使用 LSM（Log-Structured Merge）树，传统数据库 InnoDB 使用 B+ 树。</p>
<p><strong>LSM 树</strong> 的特点：</p>
<ol>
<li class="">侧重写入</li>
<li class="">读需要访问多棵树，IO 次数比 B+ 树多，波动大</li>
<li class="">写都是顺序 IO；随机读是随机 IO，顺序读是顺序 IO</li>
</ol>
<p>优点：</p>
<ol>
<li class="">写性能大幅度提高</li>
<li class="">不受 SSD 随机写入放大干扰</li>
<li class="">不受空间放大干扰</li>
</ol>
<p>缺点：</p>
<ol>
<li class="">读性能有牺牲，IO 次数比 B+ 树大</li>
<li class="">需要定期 Compaction，对整体网络/磁盘 IO 存在放大</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hbase-的优点">HBase 的优点<a href="https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb#hbase-%E7%9A%84%E4%BC%98%E7%82%B9" class="hash-link" aria-label="HBase 的优点的直接链接" title="HBase 的优点的直接链接" translate="no">​</a></h2>
<ol>
<li class="">数据按列存储，查询时只访问所涉及的列，大量降低系统 I/O；同一列数据类型一致、空值不存储，可以高效压缩存储。</li>
<li class="">Key/Value 的存储方式，意味着即便面临海量数据的增长，也几乎不会导致查询性能下降。</li>
<li class="">作为列式数据库（相对于传统的行式数据库），当单张表字段很多时，可以将不同的列（以 Region 为单位）分散到不同的服务实例上，分散负载压力。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hbase-的缺点">HBase 的缺点<a href="https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb#hbase-%E7%9A%84%E7%BC%BA%E7%82%B9" class="hash-link" aria-label="HBase 的缺点的直接链接" title="HBase 的缺点的直接链接" translate="no">​</a></h2>
<ol>
<li class="">原生不支持二级索引，只能通过主键访问；社区实现的二级索引与数据更新之间有时延，会带来头疼的一致性问题</li>
<li class="">宽表模型概念拗口，难于理解，并且要求提前建模，不够灵活</li>
<li class="">支持的编程语言种类少（Java、Thrift、RESTful API），没有 SQL，只能使用 API</li>
<li class="">集群结构复杂，有 8 种不同类型的节点</li>
<li class="">数据类型低级，只支持字节流，开发不友好</li>
<li class="">无一致性快照功能</li>
<li class="">需要定时 Compact，对持续读写场景影响很大</li>
<li class="">不支持表的关联操作，因此数据分析是 HBase 的弱项，常见的 group by 或 order by 只能通过编写 MapReduce 来实现</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/08/13/hbasehe-mongodb#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>HBase 适合特别大数据量的写入场景：写入数据后，再异步对数据进行分析处理。对应的业务场景是对实时性要求不高、但写入量特别大的场景，如硬件与车载系统的数据监控采集、用户画像等。它主要解决海量存储问题，后续集成数据分析组件，可以完成大数据分析、用户行为、实时推荐、风控等功能。</p>
<p>MongoDB 本文没有做过多介绍。在同等机器集群配置下，MongoDB 的写入能力不如 HBase；但 MongoDB 写入数据时会创建对应的索引，查询速度和可查询的维度都高于 HBase——HBase 查询数据的维度有限，从查询灵活度和各种条件下的查询效率而言，MongoDB 略胜一筹，更适合需要实时查询分析的场景。若要提高 MongoDB 的写入能力，则需要加入更多高性能的集群节点。实际选型时，还是要结合自己的业务场景来分析匹配。</p>]]></content:encoded>
            <category>DB</category>
        </item>
        <item>
            <title><![CDATA[Redis 缓存穿透，击穿，雪崩]]></title>
            <link>https://tanzhuo.xyz/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng</link>
            <guid>https://tanzhuo.xyz/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng</guid>
            <pubDate>Thu, 11 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[缓存穿透、击穿、雪崩是 Redis 缓存最常见的三类问题。这篇分别说清它们的成因与区别，并整理对应的解决方案：布隆过滤器、缓存空对象、互斥锁、均匀过期与双层缓存。]]></description>
            <content:encoded><![CDATA[<p>穿透、击穿、雪崩，是用 Redis 做缓存时绕不开的三类问题。名字相近，成因和应对却各不相同，这篇把它们逐个说清楚。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="缓存穿透">缓存穿透<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F" class="hash-link" aria-label="缓存穿透的直接链接" title="缓存穿透的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-33-93577c026a68c565599c164e0eb90088.webp" width="862" height="597" class="img_ev3q"></p>
<p>缓存穿透是指用户请求的数据在缓存中不存在即没有命中，同时在数据库中也不存在，导致用户每次请求该数据都要去数据库中查询一遍，然后返回空。</p>
<p>如果有恶意攻击者不断请求系统中不存在的数据，会导致短时间大量请求落在数据库上，造成数据库压力过大，甚至击垮数据库系统。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="解决方案">解决方案<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88" class="hash-link" aria-label="解决方案的直接链接" title="解决方案的直接链接" translate="no">​</a></h3>
<p><strong>1）布隆过滤器</strong></p>
<p>布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数，可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好得多，缺点是有一定的误识别率，且删除困难。</p>
<p>如果想判断一个元素是不是在一个集合里，一般的思路是把所有元素保存起来，再通过比较确定，链表、树等数据结构都是这种思路。但随着集合中元素的增加，需要的存储空间越来越大，检索速度也越来越慢（O(n) 或 O(log n)）。而散列表（哈希表）可以通过一个 Hash 函数把元素映射到位阵列（Bit array）中的一个点，这样只要看这个点是不是 1，就能知道集合中有没有它——这就是布隆过滤器的基本思想。</p>
<p><strong>2）返回空对象</strong></p>
<p>当缓存未命中、查询数据库也为空时，可以把返回的空对象写进缓存，这样下次请求该 key 时直接从缓存返回空对象，请求不会落到数据库。为了避免存储过多空对象，通常会给空对象设置一个过期时间。</p>
<p>这种方法存在两个问题：</p>
<ol>
<li class="">如果有大量的 key 穿透，缓存空对象会占用内存空间。</li>
<li class="">在 key 过期之前，这段时间内可能存在缓存和数据库数据不一致的场景。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="缓存击穿">缓存击穿<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF" class="hash-link" aria-label="缓存击穿的直接链接" title="缓存击穿的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-34-518f21065331aa6695c8f45bcbc2c799.webp" width="840" height="522" class="img_ev3q"></p>
<p>当某个热点 key 在过期的瞬间，有大量请求同时访问这个 key 的数据。由于缓存刚好失效，这些请求会同时打到持久化的数据库去查询，并把数据回写到缓存，导致数据库瞬间压力过大，这就是击穿。</p>
<p>此处可以理解 <strong>击穿</strong> 和 <strong>穿透</strong> 的区别：</p>
<p>击穿，是一个 key 非常热点，大量的访问都打在这个 key 上面，在 key 失效的瞬间，所有请求打在数据库上，就打出一个洞，击穿了。而穿透更多的是访问的数据不存在的情况，大量的请求访问的都是不存在的数据。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="解决方案-1">解决方案<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88-1" class="hash-link" aria-label="解决方案的直接链接" title="解决方案的直接链接" translate="no">​</a></h3>
<p><strong>1）使用互斥锁</strong></p>
<p>让第一个访问的线程去查库并回写缓存，其他线程等待回写完成后重新读取缓存即可。</p>
<p><strong>2）设置热点数据永不过期</strong></p>
<p>对访问极高的热点数据不设置过期时间，改由业务侧异步更新缓存内容。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="缓存雪崩">缓存雪崩<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9" class="hash-link" aria-label="缓存雪崩的直接链接" title="缓存雪崩的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-35-fdaac8501a626f8c372ae833bbdf7e6e.webp" width="800" height="520" class="img_ev3q"></p>
<p>缓存雪崩是指缓存中大批量的热点 key 同时到达过期时间，而查询数据量巨大，请求直接落到数据库上，引起数据库压力过大甚至宕机。和缓存击穿不同的是，缓存击穿指并发查同一条数据，缓存雪崩是不同数据都过期了，很多数据都查不到，从而都去查数据库。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="解决方案-2">解决方案<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88-2" class="hash-link" aria-label="解决方案的直接链接" title="解决方案的直接链接" translate="no">​</a></h3>
<p><strong>1）均匀过期</strong></p>
<p>设置不同的过期时间，让缓存失效的时间点均匀分布。通常可以为有效期增加随机值，或者统一规划有效期。</p>
<p><strong>2）加互斥锁</strong></p>
<p>跟缓存击穿的解决思路一致，同一时间只让一个线程构建缓存，其他线程阻塞排队。</p>
<p><strong>3）缓存永不过期</strong></p>
<p>跟缓存击穿的解决思路一致，缓存在物理上永远不过期，用一个异步线程更新缓存。</p>
<p><strong>4）双层缓存策略</strong></p>
<p>使用主备两层缓存：</p>
<ul>
<li class="">主缓存：有效期按照经验值设置，作为主读取的缓存，主缓存失效后从数据库加载最新值。</li>
<li class="">备份缓存：有效期长，获取锁失败时读取的缓存，主缓存更新时需要同步更新备份缓存。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>三个问题的本质都是请求绕过缓存直接打到数据库：穿透是查不存在的数据，击穿是单个热点 key 失效，雪崩是大批 key 同时失效。应对思路也有共性——要么在缓存层把无效请求拦下来（布隆过滤器、空对象），要么控制回源的并发（互斥锁），要么让失效时间可控（均匀过期、永不过期、双层缓存）。实际使用时按数据的热点程度和一致性要求组合选用即可。</p>]]></content:encoded>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[Redis 缓存一致性]]></title>
            <link>https://tanzhuo.xyz/2022/08/11/redis-huan-cun-yi-zhi-wen-ti</link>
            <guid>https://tanzhuo.xyz/2022/08/11/redis-huan-cun-yi-zhi-wen-ti</guid>
            <pubDate>Thu, 11 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[梳理 Redis 缓存与数据库不一致的成因，对比直接写缓存、单删、延时双删等方案在高并发下的表现与取舍。]]></description>
            <content:encoded><![CDATA[<p>只要在数据库前面加了一层缓存，「缓存里的数据和库里的数据不一样」这个问题就注定绕不开。这篇整理一下我对缓存一致性的理解，以及几种常见方案各自的坑。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="缓存一致性">缓存一致性<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7" class="hash-link" aria-label="缓存一致性的直接链接" title="缓存一致性的直接链接" translate="no">​</a></h2>
<p>首先我们先说说什么是缓存一致性问题，为什么要解决，以及方案是什么。</p>
<p>缓存一致性问题是指在使用缓存时，由于缓存数据与数据库数据的不一致性，可能会导致数据错误或者数据丢失等问题。这种问题在高并发场景下尤为常见，因为多个线程同时读写同一份数据时，很难保证数据的一致性。如何保证和Redis内存中的数据与DB数据库中的数据保持一致，并且不能出现数据不一致的情况（高并发场景中），如果出现数据不一致对于某些情况来说可能会出现大麻烦，缓存一致性的解决方案也很重要。</p>
<p>问题的根源在于：缓存和数据库是两个独立的存储，对它们的写入不是一个原子操作。无论先写哪个，两步之间都存在一个时间窗口，其他线程在这个窗口里读到的就可能是「一半新一半旧」的状态。再叠加线程调度的不确定性、数据库事务提交的时机、网络延迟，各种交错顺序都会出现。所以讨论一致性方案，本质上是在讨论：这个窗口能不能消除，消除不了的话，脏数据能存活多久、业务能不能接受。</p>
<p>以下是我列出常见的几种方案，以及它们在高并发场景下各自的问题。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="直接写入缓存-不推荐">直接写入缓存 （不推荐）<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#%E7%9B%B4%E6%8E%A5%E5%86%99%E5%85%A5%E7%BC%93%E5%AD%98-%E4%B8%8D%E6%8E%A8%E8%8D%90" class="hash-link" aria-label="直接写入缓存 （不推荐）的直接链接" title="直接写入缓存 （不推荐）的直接链接" translate="no">​</a></h2>
<p>思路最直观：更新数据库之后，紧接着把最新值写进 Redis，让缓存始终有值，读请求也不会穿透到数据库。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-40-749de52be98887e346de853674fc613e.webp" width="942" height="615" class="img_ev3q"></p>
<p>在3，4步执行时高并发场景下无法保证写入Redis数据的Java线程会进行顺序执行（因为CPU时间分片问题，并且加上分布式、微服务情况更加明显）。才会导致最新数据可能被其他线程中的旧数据覆盖，如果发生了之后后续没有其他线程执行，可能缓存数据一直会保持旧数据情况。</p>
<p>换句话说，线程 A 先写库、线程 B 后写库，但写 Redis 的顺序完全可能反过来变成 B 先 A 后——最终缓存里留下的是 A 的旧值。而且这种覆盖没有任何自愈机制：只要后面没有新的写操作来「冲刷」这个 Key，脏数据就会一直留在缓存里被反复读到。</p>
<p><strong>这里可能有人会直接使用分布式锁（单应用加JVM锁即可），加锁需要控制整个DB写入到Redis写入的流程，并且每个Key操作的函数范围需要自己把控，且效率肯定下降得厉害，不过也可以自己控制一下锁的粒度，加分布式锁这种方案比较适合强一致性场景，为了一致性牺牲性能（强一致性场景推荐）。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="写入数据库后删除缓存单删策略---不推荐">写入数据库后删除缓存（单删策略 - 不推荐）<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE%E5%BA%93%E5%90%8E%E5%88%A0%E9%99%A4%E7%BC%93%E5%AD%98%E5%8D%95%E5%88%A0%E7%AD%96%E7%95%A5---%E4%B8%8D%E6%8E%A8%E8%8D%90" class="hash-link" aria-label="写入数据库后删除缓存（单删策略 - 不推荐）的直接链接" title="写入数据库后删除缓存（单删策略 - 不推荐）的直接链接" translate="no">​</a></h2>
<p>既然「写缓存」会有覆盖问题，那就换个思路：更新数据库后只删缓存，让下一个读请求自己回源数据库、把新值加载回来。删除是幂等的，不存在新值被旧值覆盖的问题。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-41-579fa302b29cd904d2a261b4c31e3bf1.webp" width="1431" height="779" class="img_ev3q"></p>
<p>单删策略看似可以很好的避免写入被覆盖问题，但其他查询数据的线程加入到逻辑中就会出现问题，查询线程在获取数据库数据时，可能其他线程正在提交数据库事务，而此时该查询线程就会获取到提交事务前的旧数据而存入缓存中造成数据不一致问题。</p>
<p>具体的交错顺序是：读线程发现缓存未命中，去数据库查数据，此时查到的是写事务提交前的旧值；写线程随后提交事务并删除缓存；最后读线程才把手里的旧值写回缓存。删除动作发生在旧值回填之前，等于白删了，缓存里又是脏数据，而且同样没有自愈手段。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="延时双删策略-推荐">延时双删策略 (推荐)<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#%E5%BB%B6%E6%97%B6%E5%8F%8C%E5%88%A0%E7%AD%96%E7%95%A5-%E6%8E%A8%E8%8D%90" class="hash-link" aria-label="延时双删策略 (推荐)的直接链接" title="延时双删策略 (推荐)的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-44-8c4f7a424001dd9bbbe338d1daab955f.webp" width="1550" height="825" class="img_ev3q"></p>
<p>通过加入延迟队列，等待一定时间后再次删除该Key的策略，解决掉了单删遗留的问题，也没有进行加锁操作，所以缓存双删策略，我个人觉得更适合允许部分数据短时间不一致的应用场景。</p>
<p>第二次删除的意义就在于兜底：上面那种「旧值在删除之后才被回填」的交错，会被延迟到期的第二次删除清理掉。延迟时间的选取需要覆盖「读线程查库 + 写回缓存」这一整段耗时，业务里一般根据接口的实际耗时估一个偏大的值。代价也很明确——在延迟窗口内，读到的仍然可能是旧数据，所以它保证的是最终一致，而不是强一致。</p>
<p>除开以上的方案，还有很多方案可以选择：</p>
<ol>
<li class="">读写锁。在写操作时获取写锁，其他线程无法读写缓存，直到写操作完成。这种方案可以保证数据的一致性，但是可能会增加读操作的延迟和锁的竞争问题。</li>
<li class="">版本号控制。在缓存中存储数据版本号，每次更新数据时同时更新版本号。在读取数据时，比较版本号是否一致，如果不一致则重新加载数据。这种方案可以保证数据的一致性，但是需要增加数据的存储空间和读取的开销。</li>
<li class="">带超时的缓存。在缓存中存储数据时，设置缓存的过期时间，超过时间后缓存自动失效，需要重新加载数据。这种方案可以减少缓存数据与数据库数据不一致的可能性，但是可能会增加数据的存储空间和读取的开销。</li>
<li class="">数据库异步更新。在写入数据库时，不直接更新缓存，而是将更新操作异步发送到消息队列中，由消费者负责更新缓存。这种方案可以减少数据库的负载压力，但是可能会增加消息队列的复杂度和延迟。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ol>
<li class="">
<p>无论选哪种删除类方案，都建议给缓存加上过期时间做兜底。即使某次交错产生了脏数据，过期后也能自动回源修正，把不一致的存活时间限制在可控范围内。</p>
</li>
<li class="">
<p>延时双删的第二次删除如果放在内存里做（比如线程 sleep 后删除），应用重启就丢了。对可靠性有要求的话，用消息队列或延迟队列来投递删除任务，失败还能重试。</p>
</li>
<li class="">
<p>删除缓存本身也可能失败（网络抖动、Redis 短暂不可用）。删除失败要有重试机制，否则脏数据同样会长期驻留。</p>
</li>
<li class="">
<p>不要试图在普通业务里追求「缓存与数据库任意时刻完全一致」。缓存和数据库是两套存储，只要不引入锁或事务级的协调，强一致就做不到；先想清楚业务能容忍多久的不一致，再选方案。</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>缓存一致性没有银弹，本质是在一致性、性能、实现复杂度之间做取舍：直接写缓存会被并发覆盖且无法自愈；单删堵住了覆盖问题，却挡不住旧值回填；延时双删用一次延迟删除兜底，换来最终一致，适合能接受短暂脏读的大多数业务；真正的强一致场景，老老实实上锁，用性能换正确性。选型之前先回答一个问题——这份数据脏几百毫秒，业务到底疼不疼。</p>]]></content:encoded>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[CentOS7-开启网卡]]></title>
            <link>https://tanzhuo.xyz/2022/08/10/contos7-kai-qi-wang-qia</link>
            <guid>https://tanzhuo.xyz/2022/08/10/contos7-kai-qi-wang-qia</guid>
            <pubDate>Wed, 10 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[CentOS 7 最小化安装后网卡默认不启用。记录如何找到网卡配置文件、把 ONBOOT 改为 yes 并重启网络服务，让机器装完就能联网。]]></description>
            <content:encoded><![CDATA[<p>CentOS 7 刚装完机，ping 外网不通，ip addr 里也看不到 IP——这不是网络坏了，而是系统默认没有启用网卡。这篇笔记记录一下开启网卡的完整步骤和背后的原因。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="背景">背景<a href="https://tanzhuo.xyz/2022/08/10/contos7-kai-qi-wang-qia#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="背景的直接链接" title="背景的直接链接" translate="no">​</a></h2>
<p>CentOS 7 在最小化安装（Minimal Install）时，安装向导里的网络开关默认是关闭的。如果安装过程中没有手动打开它，装完系统后网卡虽然被内核识别了，但不会在开机时自动激活，也不会去请求 IP 地址。表现出来就是：机器本地一切正常，但 ping 不通网关、yum 装不了软件、ssh 也连不上。</p>
<p>解决办法很简单：找到对应网卡的配置文件，把"开机启用"这个开关打开。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="原理简述">原理简述<a href="https://tanzhuo.xyz/2022/08/10/contos7-kai-qi-wang-qia#%E5%8E%9F%E7%90%86%E7%AE%80%E8%BF%B0" class="hash-link" aria-label="原理简述的直接链接" title="原理简述的直接链接" translate="no">​</a></h2>
<p>CentOS 7 的网卡配置文件放在 <code>/etc/sysconfig/network-scripts/</code> 目录下，每块网卡对应一个 <code>ifcfg-</code> 开头的文件，后缀就是网卡名，比如 <code>ifcfg-ens33</code>、<code>ifcfg-eth0</code>。</p>
<p>决定网卡是否开机启用的，是文件里的 <code>ONBOOT</code> 参数：</p>
<ul>
<li class=""><code>ONBOOT=no</code>：开机不激活这块网卡，这是安装时没开网络开关的默认值；</li>
<li class=""><code>ONBOOT=yes</code>：开机自动激活，配合 <code>BOOTPROTO=dhcp</code> 就会自动向路由器请求 IP。</li>
</ul>
<p>所以整件事的本质，就是把 <code>ONBOOT</code> 从 <code>no</code> 改成 <code>yes</code>，再让网络服务重新读一遍配置。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实操步骤">实操步骤<a href="https://tanzhuo.xyz/2022/08/10/contos7-kai-qi-wang-qia#%E5%AE%9E%E6%93%8D%E6%AD%A5%E9%AA%A4" class="hash-link" aria-label="实操步骤的直接链接" title="实操步骤的直接链接" translate="no">​</a></h2>
<p>1）进入网卡资源文件夹</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">cd</span><span class="token plain"> /etc/sysconfig/network-scripts/</span><br></div></code></pre></div></div>
<p>2）查看需要开启的网卡名称</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># ip 命令支持前缀缩写，addres / addr 都等价于完整的 ip address</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">ip</span><span class="token plain"> addres</span><br></div></code></pre></div></div>
<p>输出里除了 <code>lo</code>（本地回环）之外的接口就是物理网卡，常见名字是 <code>ens33</code>、<code>ens160</code> 或 <code>eth0</code>。此时它的状态一般是 <code>DOWN</code>，且没有 inet 地址。</p>
<p>3）编辑对应的配置文件</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 文件名是 ifcfg- 加上一步查到的网卡名，例如网卡叫 ens33：</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">vi</span><span class="token plain"> ifcfg-ens33</span><br></div></code></pre></div></div>
<p>4）找到 <code>ONBOOT=no</code> 这一行，把 <code>no</code> 改成 <code>yes</code> 后保存文件</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-21-721dec15f4564323c825f9777afbf564.webp" width="652" height="278" class="img_ev3q"></p>
<p>如果是接在有 DHCP 的路由器下面，顺便确认一下 <code>BOOTPROTO=dhcp</code>，这样重启网络后会自动拿到 IP。</p>
<p>5）重启网络服务，让配置生效</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">service</span><span class="token plain"> network restart</span><br></div></code></pre></div></div>
<p>再执行一次 <code>ip addr</code>，网卡状态变成 <code>UP</code> 并且有了 inet 地址，就说明成功了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/08/10/contos7-kai-qi-wang-qia#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）配置文件名容易记错。目录里的文件是 <code>ifcfg-网卡名</code>（中间没有多余的连字符），vi 打开前可以先 <code>ls</code> 看一眼，避免手滑新建了一个空文件、改了半天不生效。</p>
<p>2）改的是"配置"不是"状态"。<code>ONBOOT=yes</code> 只决定开机行为，改完必须重启网络服务（或重启机器）才生效。临时想拉起网卡也可以用 <code>ifup ens33</code>，但不改配置文件的话，下次重启又会回到没网的状态。</p>
<p>3）虚拟机场景下拿不到 IP，先检查虚拟网络模式。VMware/VirtualBox 里如果桥接、NAT 配置不对，<code>ONBOOT</code> 改了也 ping 不通外网，这属于宿主机层面的问题，和本文的开关无关。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>装机时在安装向导的"网络和主机名"页面直接把网卡开关打开，装完就自带网络，可以省掉上面所有步骤。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/08/10/contos7-kai-qi-wang-qia#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>CentOS 7 装完没网，多数情况不是驱动或硬件问题，只是 <code>ONBOOT</code> 默认为 <code>no</code>。用 <code>ip addr</code> 确认网卡名，到 <code>/etc/sysconfig/network-scripts/</code> 下改对应 <code>ifcfg-</code> 文件的 <code>ONBOOT=yes</code>，再 <code>service network restart</code>，网络就通了。这是每台新装 CentOS 7 机器几乎都要过一遍的固定动作，记下来省得每次现查。</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Redis 主从，哨兵，集群搭建]]></title>
            <link>https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi</link>
            <guid>https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi</guid>
            <pubDate>Tue, 09 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[记录 Redis 主从、哨兵、集群三种模式的搭建流程与配置要点，分析各模式的优缺点，以及数据倾斜问题的成因与应对。]]></description>
            <content:encoded><![CDATA[<p>记录一下自己对 Redis 主从、哨兵、集群三种模式的搭建流程。Redis 的所有部署模式都不复杂，主要是配置文件的书写，以及各个模式的优缺点分析。</p>
<!-- -->
<p>官网：<a href="https://redis.io/" target="_blank" rel="noopener noreferrer" class="">Redis</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="部署环境">部署环境<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E9%83%A8%E7%BD%B2%E7%8E%AF%E5%A2%83" class="hash-link" aria-label="部署环境的直接链接" title="部署环境的直接链接" translate="no">​</a></h2>
<p>操作系统：CentOS7</p>
<p>Redis 版本：Redis7.0.5</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="主从模式">主从模式<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E4%B8%BB%E4%BB%8E%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="主从模式的直接链接" title="主从模式的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="515" height="326" class="img_ev3q"></p>
<p><strong>优点：</strong> 配置简单、快速，读写分离，完全分担了主节点的读压力，并且从节点还可“套娃”继续配置多层从节点。</p>
<p><strong>缺点：</strong> 非高可用，主节点宕机则失去写能力；数据冗余量大，每个从节点 100% 复制主节点数据。属于非高可用架构，生产环境一般不单独使用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="配置流程">配置流程<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E9%85%8D%E7%BD%AE%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="配置流程的直接链接" title="配置流程的直接链接" translate="no">​</a></h3>
<p>1）配置从节点 redis.conf 文件。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-2-cfff0cf45887fffb34a8f4514cf67b8c.png" width="800" height="396" class="img_ev3q"></p>
<p>主从节点密码最好保持一致。</p>
<p>2）配置主节点 redis.conf 文件，正常设置验证密码和远程连接信息即可。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-5-019e0afdc2f974fde073d6b3c8d367bd.png" width="951" height="260" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="829" height="131" class="img_ev3q"></p>
<p>配置效果：</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="516" height="186" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="542" height="179" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="552" height="173" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="哨兵模式">哨兵模式<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E5%93%A8%E5%85%B5%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="哨兵模式的直接链接" title="哨兵模式的直接链接" translate="no">​</a></h2>
<p>官方文档：<a href="https://redis.io/docs/manual/sentinel/" target="_blank" rel="noopener noreferrer" class="">通过 Redis Sentinel 实现高可用</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-12-52dc39f41e38d8ff5835cf731c4b255c.webp" width="762" height="526" class="img_ev3q"></p>
<p><strong>优点：</strong> 哨兵模式解决了主从集群中主节点宕机导致不可写入的问题。当主节点宕机时，sentinel 会监控到主节点已经不可用，从剩余可用从节点中选举出新的主节点，从而解决不可写问题。之前的主节点重新启动之后，将会以从节点身份加入集群。</p>
<p><strong>缺点：</strong> 依然存在大量数据冗余，并且哨兵节点不参与数据存储，计算资源存在浪费，<strong>存储能力得不到提升</strong>，<strong>写能力得不到提升</strong>，存在中心化。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="部署注意事项">部署注意事项<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E9%83%A8%E7%BD%B2%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" class="hash-link" aria-label="部署注意事项的直接链接" title="部署注意事项的直接链接" translate="no">​</a></h3>
<ul>
<li class="">哨兵节点本身不存储数据。</li>
<li class="">哨兵节点至少 3 个，并且必须为奇数个（基于 Raft 算法进行选举）。</li>
<li class="">哨兵节点必须独立部署且只跑哨兵进程，不要跑其他应用程序，避免哨兵进入 TILT 模式；其他应用存在硬件资源抢占问题，会导致哨兵监控可靠性下降。</li>
</ul>
<p><strong>哨兵模式基于主从模式。</strong> 1 主 2 从加 3 个哨兵已经是 6 个计算实例资源。在生产环境中哨兵必须单独部署，不能部署到主节点或从节点上，以保证整个集群的稳定性。</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="1023" height="72" class="img_ev3q"></p>
<p>此模式可用于生产环境，高可用架构，保留了 Redis 所有的功能。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="sentinel-节点配置流程">Sentinel 节点配置流程<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#sentinel-%E8%8A%82%E7%82%B9%E9%85%8D%E7%BD%AE%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="Sentinel 节点配置流程的直接链接" title="Sentinel 节点配置流程的直接链接" translate="no">​</a></h3>
<p>前提：主从模式已经配置好。</p>
<p>1）配置 sentinel.conf 文件，设置监听的 master 地址。哨兵会自动从主节点的 info 信息中读取对应从节点信息。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-13-6b1ea6129087baad8494b2a35524cd70.png" width="1115" height="296" class="img_ev3q"></p>
<p>2）保证所有主从节点密码一致。</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="1012" height="162" class="img_ev3q"></p>
<p>3）通过 redis-sentinel 命令启动哨兵。</p>
<p>4）spring-boot 配置哨兵连接。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-17-3bdd041211172d2edb77c33a0d6bfd06.webp" width="1087" height="487" class="img_ev3q"></p>
<p>如果哨兵节点数过多，可分行配置：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-20-b041c7b3f51196de178e10d5c3f7adf1.webp" width="689" height="497" class="img_ev3q"></p>
<p>哨兵对节点下线的判断分主观下线与客观下线，只有当足够数量的哨兵都认为主节点已经不可用时才会选举投票。<strong>在选举期间，整个集群无法对外提供服务</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="主观下线与客观下线">主观下线与客观下线<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E4%B8%BB%E8%A7%82%E4%B8%8B%E7%BA%BF%E4%B8%8E%E5%AE%A2%E8%A7%82%E4%B8%8B%E7%BA%BF" class="hash-link" aria-label="主观下线与客观下线的直接链接" title="主观下线与客观下线的直接链接" translate="no">​</a></h3>
<p>SDOWN（subjectively down），直译为“主观”失效，即当前 sentinel 实例认为某个 redis 服务为“不可用”状态。</p>
<p>ODOWN（objectively down），直译为“客观”失效，即多个 sentinel 实例都认为 master 处于 SDOWN 状态，此时 master 将处于 ODOWN。ODOWN 可以简单理解为 master 已经被集群确定为“不可用”，将会开启故障转移机制。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-18-597476c5a1db840af5dcda6d7bbf4db9.png" width="1107" height="910" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="集群模式">集群模式<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="集群模式的直接链接" title="集群模式的直接链接" translate="no">​</a></h2>
<p>官方文档：<a href="https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/" target="_blank" rel="noopener noreferrer" class="">使用 Redis 集群|进行扩展</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-22-e4af1f77ba6ebb2ec4dbd5b2942abc63.webp" width="1563" height="587" class="img_ev3q"></p>
<p>常见的集群方案有三种：</p>
<p>1）Twemproxy（Twitter 开源的 Redis 代理）</p>
<ul>
<li class="">没有友好的监控管理后台界面，不利于运维监控。</li>
<li class="">最大的痛点在于无法平滑地扩容/缩容。当因业务需要增加 Redis 实例时，运维工作量非常大。</li>
</ul>
<p>2）Codis（豌豆荚自主研发）</p>
<ul>
<li class="">有 UI 管理界面。</li>
<li class="">可以很好地动态添加节点。</li>
</ul>
<p>3）官方搭建方案 Redis Cluster</p>
<p><strong>优点：</strong> 集群模式相对于哨兵模式来说，去除了中心化思想，自动实现了负载均衡，并且数据存储的上限随着节点数的增加而增加。节点之间使用轻量级协议，减少带宽占用，支持动态扩展节点。集群采用哈希槽（hash slot）来处理键与实例之间的映射关系：集群中总共有 16384 个哈希槽，默认将 16384 个哈希槽分配给所有节点，每个实例节点分配一段哈希槽，类似数据分区；每个键按照 CRC16 算法得到哈希值再对 16384 取模，即 <strong>CRC16(key) mod 16384</strong>，通过最终结果得到 key 所在的哈希槽位置。</p>
<p>概括起来有三点：写能力得到提升，存储能力得到提升，去中心化。</p>
<p><strong>缺点：</strong> 存在数据存储倾斜与数据访问倾斜问题。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据倾斜问题">数据倾斜问题<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E6%95%B0%E6%8D%AE%E5%80%BE%E6%96%9C%E9%97%AE%E9%A2%98" class="hash-link" aria-label="数据倾斜问题的直接链接" title="数据倾斜问题的直接链接" translate="no">​</a></h3>
<p>数据在切片集群的多个实例上分布不均衡，大量数据集中到了一个或几个实例上，存储压力得不到均匀分摊，且热点数据访问总会落到固定实例上，导致实例压力增大，最终面临宕机风险。数据倾斜产生的主要原因有三个。</p>
<p>1）保存了 bigkey。</p>
<p>bigkey 的 value 值很大（String 类型），或者是 bigkey 保存了大量集合元素（集合类型），会导致这个实例的数据量增加，内存资源消耗也相应增加。而且，bigkey 的操作一般都会造成实例 IO 线程阻塞，如果 bigkey 的访问量比较大，就会影响到这个实例上的其它请求被处理的速度。</p>
<p>为了避免 bigkey 造成的数据倾斜，一个根本的应对方法是，在业务层生成数据时，尽量避免把过多的数据保存在同一个键值对中。如果 bigkey 正好是集合类型，还有一个方法，就是把 bigkey 拆分成很多个小的集合类型数据，分散保存在不同的实例上。</p>
<p>2）Slot 分配不均衡。</p>
<p>如果集群运维人员没有均衡地分配 Slot，就会有大量的数据被分配到同一个 Slot 中，而同一个 Slot 只会在一个实例上分布，这就会导致大量数据被集中到一个实例上，造成数据倾斜。</p>
<p>可以通过运维规范，在分配之前就避免把过多的 Slot 分配到同一个实例。如果是已经分配好 Slot 的集群，可以先查看 Slot 和实例的具体分配关系，判断是否有过多的 Slot 集中到了同一个实例；如果有的话，就将部分 Slot 迁移到其它实例，从而避免数据倾斜。</p>
<p>不同集群上查看 Slot 分配情况的方式不同：如果是 Redis Cluster，就用 CLUSTER SLOTS 命令；如果是 Codis，就可以在 codis dashboard UI 控制板上查看。</p>
<p>3）使用了 Hash Tag。</p>
<p>Hash Tag 是指加在键值对 key 中的一对花括号 {}。这对括号会把 key 的一部分括起来，客户端在计算 key 的 CRC16 值时，只对 Hash Tag 花括号中的 key 内容进行计算。</p>
<p>假设 key 是 user:profile:3231，我们把其中的 3231 作为 Hash Tag，此时 key 就变成了 user:profile:{3231}。当客户端计算这个 key 的 CRC16 值时，就只会计算 3231 的 CRC16 值；否则，客户端会计算整个 “user:profile:3231” 的 CRC16 值。</p>
<p>使用 Hash Tag 的好处是，如果不同 key 的 Hash Tag 内容都是一样的，那么这些 key 对应的数据会被映射到同一个 Slot 中，同时会被分配到同一个实例上。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-8eec74dc40b94404b7903c0c126bb753-1-3af83afc758a9b796c22bc133b63ec9b.webp" width="1600" height="389" class="img_ev3q"></p>
<p>Hash Tag 一般用在什么场景呢？其实，它主要是用在 Redis Cluster 和 Codis 中，支持事务操作和范围查询。因为 Redis Cluster 和 Codis 本身并不支持跨实例的事务操作和范围查询，当业务应用有这些需求时，就只能先把这些数据读取到业务层进行事务处理，或者是逐个查询每个实例，得到范围查询的结果。</p>
<p>可以使用 Hash Tag 把要执行事务操作或是范围查询的数据映射到同一个实例上，这样就能很轻松地实现事务或范围查询了。</p>
<p>但是，使用 Hash Tag 的潜在问题，就是大量的数据可能被集中到一个实例上，导致数据倾斜，集群中的负载不均衡。这就需要在范围查询、事务执行的需求和数据倾斜带来的访问压力之间进行取舍了。</p>
<p>我的建议是，如果使用 Hash Tag 进行切片的数据会带来较大的访问压力，就优先考虑避免数据倾斜，最好不要使用 Hash Tag 进行数据切片。因为事务和范围查询都还可以放在客户端来执行，而数据倾斜会导致实例不稳定，造成服务不可用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据访问倾斜的成因和应对方法">数据访问倾斜的成因和应对方法<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE%E5%80%BE%E6%96%9C%E7%9A%84%E6%88%90%E5%9B%A0%E5%92%8C%E5%BA%94%E5%AF%B9%E6%96%B9%E6%B3%95" class="hash-link" aria-label="数据访问倾斜的成因和应对方法的直接链接" title="数据访问倾斜的成因和应对方法的直接链接" translate="no">​</a></h3>
<p><strong>发生数据访问倾斜的根本原因，就是实例上存在热点数据</strong>（比如新闻应用中的热点新闻内容、电商促销活动中的热门商品信息，等等）。<strong>一旦热点数据被存在了某个实例中，这个实例的请求访问量就会远高于其它实例</strong>，面临巨大的访问压力。</p>
<p>热点数据通常是一个或几个数据，所以直接重新分配 Slot 并不能解决热点数据的问题。通常来说，热点数据以服务读操作为主，在这种情况下，可以采用热点数据多副本的方法来应对。</p>
<p>具体做法是，把热点数据复制多份，在每一个数据副本的 key 中增加一个随机前缀，让它和其它副本数据不会被映射到同一个 Slot 中。这样一来，热点数据既有多个副本可以同时服务请求，同时这些副本数据的 key 又不一样，会被映射到不同的 Slot 中。在给这些 Slot 分配实例时，也要注意把它们分配到不同的实例上，热点数据的访问压力就被分散到不同的实例上了。</p>
<p>注意：热点数据多副本方法只能针对只读的热点数据。如果热点数据是有读有写的话，就不适合采用多副本方法了，因为要保证多副本间的数据一致性，会带来额外的开销。对于有读有写的热点数据，就要给实例本身增加资源了，例如使用配置更高的机器，来应对大量的访问压力。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="集群使用注意事项">集群使用注意事项<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E9%9B%86%E7%BE%A4%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" class="hash-link" aria-label="集群使用注意事项的直接链接" title="集群使用注意事项的直接链接" translate="no">​</a></h3>
<p>Redis Cluster 节点之间会定期交换 Gossip 消息，以及做一些心跳检测。官方建议 Redis Cluster 节点数量不要超过 1000 个，当集群中节点数量过多时，会产生不容忽视的带宽消耗。</p>
<ul>
<li class="">消息发送频率：节点发现与其他节点最后通信时间超过 cluster-node-timeout/2 时，会直接发送 PING 消息。</li>
<li class="">消息数据量：slots 槽数组（2kb 空间）和整个集群 1/10 的状态数据（10 个节点状态数据约为 1kb）。</li>
<li class="">节点部署的机器规模：集群分布的机器越多且每台机器划分的节点数越均匀，则集群内整体的可用带宽越高。</li>
</ul>
<p><strong>Redis Cluster</strong> 集群的限制：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">key批量操作支持有限：例如mget,mset必须在一个slot</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">key事务和Lua支持有限：操作的key必须在一个节点</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">key是数据分区的最小粒度：不支持bigkey分区</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">不支持多个数据库：集群模式下只有一个db0</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">复制只支持一层：不支持树形复制结构</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Redis Cluster满足容量和性能的扩展性，很多业务'不需要'</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">大多数时客户端性能会'降低'</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">命令无法跨节点使用：mget,keys,scan,flush,sinter等</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Lua和事务无法跨节点使用</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">客户端维护更复杂：SDK和应用本身消耗(例如更多的连接池)</span><br></div></code></pre></div></div>
<p>很多场景 Redis Sentinel 已经够用了。</p>
<p>Codis 与 Redis Cluster 集群对比：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-23-492599c8b3e333924535a3320378b0d1.webp" width="1230" height="600" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-cluster-总结">Redis Cluster 总结<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#redis-cluster-%E6%80%BB%E7%BB%93" class="hash-link" aria-label="Redis Cluster 总结的直接链接" title="Redis Cluster 总结的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">1.Redis Cluster数据分区规则采用虚拟槽方式(16384个槽)，每个节点负责一部分槽和相关数据，实现数据和请求的负载均衡</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">2.搭建Redis Cluster划分四个步骤：准备节点，meet操作，分配槽，复制数据。</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">3.Redis官方推荐使用redis-trib.rb工具快速搭建Redis Cluster（redis5.0版本之前使用redis-trib，之后版本使用redis-cli命令）</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">4.集群伸缩通过在节点之间移动槽和相关数据实现，扩容时根据槽迁移计划把槽从源节点迁移到新节点，收缩时如果下线的节点有负责的槽需要迁移到其他节点，再通过cluster forget命令让集群内所有节点忘记被下线节点</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">5.使用smart客户端操作集群达到通信效率最大化，客户端内部负责计算维护键，槽以及节点的映射，用于快速定位到目标节点</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">6.集群自动故障转移过程分为故障发现和节点恢复。节点下线分为主观下线和客观下线，当超过半数节点认为故障节点为主观下线时，标记这个节点为客观下线状态。从节点负责对客观下线的主节点触发故障恢复流程，保证集群的可用性</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">7.开发运维常见问题包括：超大规模集群带宽消耗，pub/sub广播问题，集群倾斜问题，单机和集群对比等</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用-redis-cli-搭建-redis-cluster搭建版本-704">使用 redis-cli 搭建 Redis Cluster（搭建版本 7.0.4）<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E4%BD%BF%E7%94%A8-redis-cli-%E6%90%AD%E5%BB%BA-redis-cluster%E6%90%AD%E5%BB%BA%E7%89%88%E6%9C%AC-704" class="hash-link" aria-label="使用 redis-cli 搭建 Redis Cluster（搭建版本 7.0.4）的直接链接" title="使用 redis-cli 搭建 Redis Cluster（搭建版本 7.0.4）的直接链接" translate="no">​</a></h3>
<p>至少 6 节点，(1 主 + 1 从) × 3 = 6 节点。redis 5.0 版本之前使用 redis-trib 工具，之后版本使用 redis-cli 命令。</p>
<p>1）设置 redis.conf 文件。所有节点的密码相同，然后开启集群模式（更多配置细节查看官网文档）：</p>
<div class="language-properties codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-properties codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 开启集群模式</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key attr-name" style="color:hsl(29, 54%, 61%)">cluster-enabled</span><span class="token plain"> </span><span class="token value attr-value" style="color:hsl(95, 38%, 62%)">yes</span><br></div></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-24-f3c1ba172fed2ea618311e8117e78173.png" width="907" height="233" class="img_ev3q"></p>
<p>2）执行创建集群命令：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">redis-cli </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--cluster</span><span class="token plain"> create --cluster-replicas </span><span class="token number" style="color:hsl(29, 54%, 61%)">1</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">192.168</span><span class="token plain">.121.145:6379 </span><span class="token number" style="color:hsl(29, 54%, 61%)">192.168</span><span class="token plain">.121.143:6379 </span><span class="token number" style="color:hsl(29, 54%, 61%)">192.168</span><span class="token plain">.121.142:6379 </span><span class="token number" style="color:hsl(29, 54%, 61%)">192.168</span><span class="token plain">.121.141:6379 </span><span class="token number" style="color:hsl(29, 54%, 61%)">192.168</span><span class="token plain">.121.144:6379 </span><span class="token number" style="color:hsl(29, 54%, 61%)">192.168</span><span class="token plain">.121.140:6379 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-a</span><span class="token plain"> 【密码】</span><br></div></code></pre></div></div>
<p>其中 redis-cli --cluster 或者 ./redis-trib.rb 代表集群操作命令；create 代表创建集群；--replicas 1 或者 --cluster-replicas 1 指定集群中每个 master 的副本个数为 1，此时节点总数 ÷ (replicas + 1) 得到的就是 master 的数量。因此节点列表中的前 n 个就是 master，其它节点都是 slave 节点，随机分配到不同 master。</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p>输入命令后会提示确认集群 slot 分配，必须输入完整的 yes 而不是 y，不然会跳过 slot 自动分配，集群不可用。如果未分配，可以使用 <code>redis-cli --cluster fix 127.0.0.1:6379 -a 【密码】</code>（任意节点）命令进行 slot 修复。</p></div></div>
<p>通过日志输出看到，集群已经自动创建完毕。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-26-422b2f151de78e39201db4d10440f08f.webp" width="1473" height="430" class="img_ev3q"></p>
<p>3）查看当前集群节点信息：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">redis-cli </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-h</span><span class="token plain"> 【任意节点ip】 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-p</span><span class="token plain"> 【端口】 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-a</span><span class="token plain"> 【密码】 cluster nodes</span><br></div></code></pre></div></div>
<p>下图为节点信息（当前未分配 slot）：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-25-961e8f1293208ec3c1845e699dff0863.webp" width="1365" height="196" class="img_ev3q"></p>
<p>执行 slot 分配（可以看到每个 master 负责的 slot 槽）：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-28-05b75092278370a56d2f2cc44a0858fe.webp" width="1600" height="379" class="img_ev3q"></p>
<p>尝试插入值：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-27-23c5475a76cba619e960379c596931d2.webp" width="932" height="289" class="img_ev3q"></p>
<p>通过 UI 工具查看值是否存入，连接集群任意节点即可：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-29-5703e1fbebb16ffc84c54bbb1e248717.webp" width="555" height="553" class="img_ev3q"></p>
<p>可见 Redis 集群已经成功搭建。</p>
<p>通过 spring-boot 连接集群：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-08-image-30-a295ea9fd270c78888bafa3e889a27dd.webp" width="712" height="561" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>主从模式配置最简单，但主节点宕机后集群失去写能力，更多是作为其他模式的基础。哨兵模式在主从之上补齐了自动故障转移，很多业务场景用它已经够了。集群模式带来写能力和存储容量的水平扩展，但也引入了数据倾斜、跨节点命令受限等新问题。选型时先评估数据量和写压力，没必要一步到位直接上集群。</p>]]></content:encoded>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[关于Serverless架构]]></title>
            <link>https://tanzhuo.xyz/2022/07/04/serverless-de-si-kao</link>
            <guid>https://tanzhuo.xyz/2022/07/04/serverless-de-si-kao</guid>
            <pubDate>Mon, 04 Jul 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[从 FaaS 的运行模型讲起,梳理 Serverless 的按需计费与自动扩缩容优势,分析云厂商绑定和冷启动两个常见问题,并给出适用场景的判断思路。]]></description>
            <content:encoded><![CDATA[<p>最近在整理团队的部署方案时,重新审视了一遍 Serverless。这篇文章记录一下我对这种架构的理解:它解决了什么问题,背后的运行机制是怎样的,以及哪些场景适合用、哪些场景要慎重。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="背景">背景<a href="https://tanzhuo.xyz/2022/07/04/serverless-de-si-kao#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="背景的直接链接" title="背景的直接链接" translate="no">​</a></h2>
<p>写这篇文章的起因很简单:我们有不少低频任务——定时脚本、事件回调、偶发的文件处理——各自占着一台或半台服务器,大部分时间 CPU 是空闲的,但机器的钱和运维的时间一分不少。这类"资源常驻、负载稀疏"的矛盾,正是 Serverless 想要解决的问题。</p>
<p>Serverless(无服务器架构)近年来逐渐成为云计算领域的一个热门方向。它的核心理念是:<strong>开发者无需关心服务器的部署、运维和扩容,只需要专注于业务逻辑的开发。</strong></p>
<p>需要说明的是,"无服务器"并不是真的没有服务器,而是服务器对开发者不可见——它们仍然存在,只是由云厂商统一调度和维护,开发者面对的抽象层从"机器"变成了"函数"。</p>
<p>在传统架构中,一个系统上线通常需要准备服务器、部署环境、配置网络、监控资源以及处理扩容问题,这些工作都需要额外的运维成本。而在 Serverless 架构中,这些基础设施全部由云厂商负责管理,开发者只需要编写函数代码并部署即可运行。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="serverless上云">Serverless上云<a href="https://tanzhuo.xyz/2022/07/04/serverless-de-si-kao#serverless%E4%B8%8A%E4%BA%91" class="hash-link" aria-label="Serverless上云的直接链接" title="Serverless上云的直接链接" translate="no">​</a></h2>
<p>Serverless 通常以 <strong>云函数(Function as a Service,FaaS)</strong> 的形式提供。例如阿里云函数计算(FC)、AWS Lambda、腾讯云 SCF 等。开发者只需要上传代码,当有请求触发时,平台会自动运行函数,并根据实际的调用次数和运行时间进行计费。</p>
<p>它的运行模型可以简单概括为一条链路:<strong>事件源触发 → 平台调度实例 → 函数执行 → 实例回收</strong>。事件源可以是 HTTP 请求、消息队列、对象存储的文件变更,也可以是一个定时器。平台收到事件后,会在隔离的运行环境(通常是容器或轻量沙箱)中加载函数代码执行,执行结束后环境会被保留一小段时间以复用,长时间无调用则被回收。</p>
<p>这种模式带来的最大变化是:<strong>应用可以按需运行,而不是长期占用服务器资源。</strong></p>
<p>计费方式也随之改变:传统服务器按"持有时长"付费,不管有没有流量;FaaS 按"实际执行"付费,通常以调用次数和执行时间(乘以分配的内存规格)来计算。负载越稀疏,这种模式的成本优势越明显。</p>
<p>例如在一些业务场景中:</p>
<ul>
<li class="">定时任务处理</li>
<li class="">Webhook 事件触发</li>
<li class="">图片处理</li>
<li class="">数据转换</li>
</ul>
<p>这些任务并不需要持续运行的服务器,通过 Serverless 可以在事件触发时启动函数执行任务,从而节省大量资源成本。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="serverless的弹性特性">Serverless的弹性特性<a href="https://tanzhuo.xyz/2022/07/04/serverless-de-si-kao#serverless%E7%9A%84%E5%BC%B9%E6%80%A7%E7%89%B9%E6%80%A7" class="hash-link" aria-label="Serverless的弹性特性的直接链接" title="Serverless的弹性特性的直接链接" translate="no">​</a></h2>
<p>Serverless 的一个重要特点是 <strong>自动扩缩容能力</strong>。当系统请求量突然增加时,平台会自动启动更多函数实例来处理请求;当流量降低时,又会自动释放资源。整个过程无需人工干预,并且具备天然的分布式和容灾能力。</p>
<p>这种弹性背后的机制,是平台把"一次调用"作为调度的基本单位:每个函数实例同一时刻通常只处理有限的请求,请求多了就横向拉起更多实例,而不是在单个实例上堆压力。因为实例本身无状态、可以随时创建和销毁,扩容和容灾就成了平台层面的常规操作,不再需要业务方自己设计。</p>
<p>对于一些小型项目或创业团队来说,这种模式非常友好。传统模式下即使业务访问量很小,也需要长期维护服务器,而 Serverless 按调用次数计费,可以大幅降低前期的部署和运维成本。例如阿里云函数计算(FC)目前每个月提供约 <strong>100 万次免费调用额度</strong>,对于很多小型应用来说已经足够使用。</p>
<p>不过 Serverless 架构也并非完美,目前仍然存在一些需要权衡的问题。</p>
<p>其中最常见的就是 <strong>云厂商绑定问题</strong>。在 Serverless 模式下,函数往往会深度依赖云平台提供的服务,例如 OSS、RDS、消息队列、日志系统等。当系统大量使用这些能力之后,如果需要迁移到其他云厂商,往往需要对代码进行一定程度的改造。这种绑定不只发生在代码层面——触发器配置、权限体系、日志与监控的接入方式,各家云的做法都不一样,迁移成本比想象中更分散。</p>
<p>另外一个比较典型的问题是 <strong>冷启动(Cold Start)</strong>。由于 Serverless 的函数通常运行在容器或沙箱环境中,当函数长时间未被调用时,平台会释放资源。当新的请求到来时,需要重新启动运行环境,这个过程就会带来一定的延迟。</p>
<p>冷启动的耗时大致由几部分叠加而成:平台调度并拉起运行环境、加载函数代码和依赖、执行运行时和框架的初始化逻辑。运行时越重、依赖越多,这个过程越慢——这也是为什么同样的业务逻辑,用 Java 这类需要启动虚拟机的运行时,冷启动通常比脚本语言更明显。</p>
<p>在 HTTP 请求或者事件触发的场景中,第一次调用函数时可能会明显感觉到响应变慢。如果业务对延迟非常敏感,可以通过购买 <strong>预留实例(预热资源)</strong> 的方式来减少冷启动带来的影响,但这样也会增加一定的资源成本——本质上是用"部分常驻"换延迟,又回到了传统模式的成本曲线上,需要根据流量特征算一笔账。</p>
<p>总体来说,Serverless 更适合以下场景:</p>
<ul>
<li class="">事件驱动型应用</li>
<li class="">短时间运行的任务</li>
<li class="">不稳定流量的服务</li>
<li class="">初期规模较小的项目</li>
</ul>
<p>而对于一些需要长期运行、高性能或者高度定制化环境的系统,例如大型数据库服务、持续运行的计算任务等,传统的服务器架构仍然更加适合。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/07/04/serverless-de-si-kao#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>结合前面的分析,如果准备把业务迁到 Serverless 上,有几点建议提前想清楚:</p>
<p>1)<strong>把函数写成无状态的。</strong> 实例随时可能被回收,任何写在本地内存或本地磁盘的状态都不可靠,需要持久化的数据应该放到外部存储或缓存服务中。</p>
<p>2)<strong>注意执行时长限制。</strong> FaaS 平台一般对单次执行有超时上限,长耗时任务要么拆分成多个函数串联,要么考虑换用其他计算形态。</p>
<p>3)<strong>评估冷启动对链路的影响。</strong> 对外的同步 HTTP 接口对延迟敏感,冷启动的影响最直接;异步任务和定时任务则基本无感,可以优先迁移这一类。</p>
<p>4)<strong>控制对云服务的依赖深度。</strong> 完全不依赖云服务不现实,但可以在代码里把对 OSS、消息队列这类服务的调用收敛到独立的适配层,真要迁移时改造范围会小很多。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>一个务实的做法是先挑一两个非核心的异步任务(比如定时清理、图片压缩)迁上去试水,跑通计费、日志、告警这一整套流程后,再评估是否扩大使用范围。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/07/04/serverless-de-si-kao#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>总结来看,Serverless 并不是完全替代传统架构,而是一种新的计算模式。它的核心价值在于:<strong>让开发者更加专注于业务本身,而不是基础设施。</strong></p>
<p>它把"按需执行、自动伸缩"做成了平台能力,代价是接受冷启动、执行时长限制和一定程度的厂商绑定。是否采用,取决于业务的流量形态和延迟要求,而不是架构本身的新旧。</p>
<p>随着云计算的发展,越来越多的平台开始提供 Serverless 能力。未来的系统架构也很可能会逐渐演变为:</p>
<p><strong>Serverless + 微服务 + 容器化</strong> 的组合模式。</p>]]></content:encoded>
            <category>Java</category>
            <category>K8S</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Linux 系统不同发行版对比]]></title>
            <link>https://tanzhuo.xyz/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi</link>
            <guid>https://tanzhuo.xyz/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi</guid>
            <pubDate>Fri, 24 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[对比 Debian、CentOS、Ubuntu、openSUSE 几个常见 Linux 发行版的定位、包管理与稳定性差异，并记录我在生产环境中的选择理由。]]></description>
            <content:encoded><![CDATA[<p>linux系统有着不同的发行版本，不同的组织对其自己的发行版进行维护。此次笔记记录我认知的部分发行版的区别。</p>
<!-- -->
<p>为什么值得单独写一篇？因为选发行版这件事，往往在装机那一刻就决定了后面几年的运维体验：用什么包管理器、软件源里的版本有多新、系统能滚动更新还是只能大版本迁移、出了问题社区文档好不好查。这些差异平时感知不强，一到生产环境排障或者系统升级时就会集中爆发。所以把几个主流发行版的特点先梳理清楚，比出了问题再换系统要划算得多。</p>
<p>先补充一点背景：Linux 发行版本质上都是「Linux 内核 + 包管理系统 + 一套默认软件与配置」的组合。内核大家共享同一个上游，真正拉开差距的是包管理生态（Debian 系用 apt/dpkg，Red Hat 系用 yum/dnf/rpm，SUSE 系用 zypper/rpm）和发布策略——是追求稳定、软件版本冻结几年不动，还是追求新特性、频繁更新。理解了这两点，下面各个发行版的取舍就好懂了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="debian">Debian<a href="https://tanzhuo.xyz/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#debian" class="hash-link" aria-label="Debian的直接链接" title="Debian的直接链接" translate="no">​</a></h2>
<p>1、Debian 是目前我最喜欢使用的发行版，它的10版本非常稳定，并且系统占用非常小。是目前我在生产环境的选择。</p>
<p>Debian 的稳定来自它的发布机制：软件包要先经过 unstable、testing 两个阶段的长期打磨，才能进入 stable 分支；stable 一旦发布，软件版本基本冻结，只接收安全补丁和重要修复。代价是软件源里的版本偏旧，但换来的是服务器上极少出现「更新把系统更坏了」的情况。加上默认安装非常精简，不预装多余服务，内存和磁盘占用都很克制，这也是我把它放在生产环境的主要原因。</p>
<p><a href="https://www.debian.org/" target="_blank" rel="noopener noreferrer" class="">Debian -- The Universal Operating System</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="centos">CentOS<a href="https://tanzhuo.xyz/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#centos" class="hash-link" aria-label="CentOS的直接链接" title="CentOS的直接链接" translate="no">​</a></h2>
<p>2、CentOS 是 Red Hat 从商业收费操作系统的分支版本，在商业操作系统上进行了免费。功能以及稳定性目前也是非常不错，但肯定在某些地方有阉割。毕竟人家有一个收费的系统。并且 CentOS 目前已经停止了维护，原作者 Gregory Kurtzer 也启动了新项目的 Rocky Linux 操作系统。</p>
<p>展开说一下这段历史：CentOS 原本是把 RHEL（Red Hat Enterprise Linux）的开源代码重新编译、去掉商标后免费发布，所以能做到与 RHEL 高度兼容，很多企业拿它当免费的 RHEL 用。后来 Red Hat 调整策略，把 CentOS 转向了 CentOS Stream——从「RHEL 的下游稳定复刻」变成了「RHEL 的上游滚动预览」，这对追求稳定的生产用户来说等于变了味。于是社区出现了 Rocky Linux、AlmaLinux 这类接棒项目，继续走 RHEL 下游兼容的老路线。如果现有业务重度依赖 CentOS 生态，迁移到这些替代品通常比换到 Debian 系成本更低。</p>
<p><a href="https://www.centos.org/" target="_blank" rel="noopener noreferrer" class="">The CentOS Project</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ubuntu">Ubuntu<a href="https://tanzhuo.xyz/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#ubuntu" class="hash-link" aria-label="Ubuntu的直接链接" title="Ubuntu的直接链接" translate="no">​</a></h2>
<p>3、Ubuntu 是从 Debian 为基础上进行开发的操作系统，功能更加丰富，强大，但稳定性以及系统资源占用却远远不及 Debian 操作系统。</p>
<p>Ubuntu 由 Canonical 公司主导，在 Debian 的基础上做了更激进的选择：软件版本更新、驱动支持更全、桌面体验开箱即用，还提供固定周期的 LTS（长期支持）版本。它的优势场景是桌面和开发机——新硬件适配快，社区教程多，遇到问题几乎都能搜到现成答案。但也因为预装组件多、默认服务多，同样配置的机器上会比 Debian 吃掉更多资源；对只跑几个服务的服务器来说，这些「丰富」反而是负担。</p>
<p><a href="https://ubuntu.com/" target="_blank" rel="noopener noreferrer" class="">Enterprise Open Source and Linux | Ubuntu</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="opensuse">openSUSE<a href="https://tanzhuo.xyz/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#opensuse" class="hash-link" aria-label="openSUSE的直接链接" title="openSUSE的直接链接" translate="no">​</a></h2>
<p>4、openSUSE 据说是德国最受欢迎的操作系统，其稳定性应该非常不错，有机会我会试一试与Debian的区别。</p>
<p>openSUSE 背后是 SUSE 公司，走的是和 Red Hat 类似的「商业版 + 社区版」路线。它有两个特色值得一提：一是 YaST 这个图形化系统管理工具，把网络、分区、服务配置都集中到一处，对不爱手写配置文件的人很友好；二是提供 Leap（固定版本、对应商业版 SLE）和 Tumbleweed（滚动更新）两条线，可以按需选稳定或尝鲜。国内用它的人相对少，中文资料不如 Debian/Ubuntu 好找，这一点在选型时要考虑进去。</p>
<p><a href="https://www.suse.com/" target="_blank" rel="noopener noreferrer" class="">SUSE - Open Source Solutions for Enterprise Servers &amp; Cloud</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）不要在生产环境用停止维护的系统。没有安全更新的发行版，每一个新爆出的漏洞都只能裸奔，CentOS 的教训就在眼前。选型前先查清楚版本的支持周期。</p>
<p>2）跨系发行版迁移成本很高。apt 和 yum/dnf 的差异不只是命令名，软件包的命名、拆分方式、配置文件路径都不同，脚本和运维习惯要跟着改。同一系内迁移（如 CentOS 到 Rocky）则平滑得多。</p>
<p>3）「软件新」和「系统稳」大多数时候是矛盾的。想在 Debian stable 上用新版本软件，优先考虑官方 backports 源或容器方式，尽量不要混用第三方源硬升级，容易把依赖关系搞乱。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这几个发行版没有绝对的优劣，只有场景的匹配：追求稳定和低占用的服务器，我选 Debian；重度依赖 RHEL 生态的，可以看 Rocky Linux 这类 CentOS 的后继者；桌面和开发机，Ubuntu 的开箱即用体验更省心；openSUSE 则留待我后续实际上手对比。选定一个系，把它的包管理和发布节奏摸熟，比频繁换系统更有价值。</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[血源诅咒]]></title>
            <link>https://tanzhuo.xyz/2022/06/17/xie-yuan-zu-zhou</link>
            <guid>https://tanzhuo.xyz/2022/06/17/xie-yuan-zu-zhou</guid>
            <pubDate>Fri, 17 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[《血源诅咒》是 FromSoftware 制作的一款黑暗风格动作角色扮演游戏，其故事背景建立在浓厚的克苏鲁神话体系之上，整体世界观充满神秘、疯狂与未知。]]></description>
            <content:encoded><![CDATA[<p>《血源诅咒》是 FromSoftware 制作的一款黑暗风格动作角色扮演游戏，其故事背景建立在浓厚的 <strong>克苏鲁神话体系</strong>之上，整体世界观充满神秘、疯狂与未知。</p>
<!-- -->
<p>本作的主要舞台设定在一座位于遥远东方山区的古老城市——<strong>亚楠（Yharnam）</strong>。亚楠是一座极度封闭排外的城市，但却因为一种神秘的医疗技术 <strong>“血疗（Blood Healing）”</strong> 而闻名世界。据传这种技术可以治愈几乎所有疾病，因此吸引了无数病患、旅人以及研究者远道而来，希望借助血疗摆脱病痛。</p>
<p>然而，随着血疗的广泛使用，一种可怕的瘟疫开始在亚楠蔓延——<strong>兽化病（Beast Scourge）</strong>。感染者会逐渐失去理智，最终变成疯狂嗜血的野兽。整个城市因此陷入混乱与恐惧之中，而玩家所扮演的“猎人”，正是在这样的背景下来到亚楠。</p>
<p>随着剧情的推进，玩家会逐渐发现：<strong>兽化并不是简单的疾病，而是与更高层次的存在有关。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="上位者与宇宙的真相">上位者与宇宙的真相<a href="https://tanzhuo.xyz/2022/06/17/xie-yuan-zu-zhou#%E4%B8%8A%E4%BD%8D%E8%80%85%E4%B8%8E%E5%AE%87%E5%AE%99%E7%9A%84%E7%9C%9F%E7%9B%B8" class="hash-link" aria-label="上位者与宇宙的真相的直接链接" title="上位者与宇宙的真相的直接链接" translate="no">​</a></h2>
<p>在《血源诅咒》的世界中，远古时代存在着一类被称为 <strong>“上位者（Great Ones）”</strong> 的神秘生命。这些存在并非传统意义上的神明，而是来自宇宙深处的高维生命。它们拥有远超人类理解的智慧与力量，其形态往往不可名状。</p>
<p>在克苏鲁神话体系中，类似的存在通常被称为：</p>
<ul>
<li class="">旧神（Old Ones）</li>
<li class="">古神（Elder Gods）</li>
<li class="">外神（Outer Gods）</li>
</ul>
<p>这些上位者早在远古时代就已经存在于这个世界，但随着时间推移，它们的肉体逐渐消亡，只剩下强大的 <strong>精神与意识</strong> 仍然在宇宙中徘徊。</p>
<p>上位者渴望 <strong>繁衍后代</strong>，但由于自身形态与生命结构过于特殊，它们往往无法正常生育，因此会尝试借助人类或其他生命作为媒介，孕育新的“神子”。这也成为了整个故事最重要的起点。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-src-http___img2.ali213.net_picfile_News_2015_09_15_2015091553403282.jpg-refer-http___img2.ali213-7d8f63a869338c1cfc657c0e23b4b560.webp" width="1200" height="1696" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="苏美鲁文明的发现">苏美鲁文明的发现<a href="https://tanzhuo.xyz/2022/06/17/xie-yuan-zu-zhou#%E8%8B%8F%E7%BE%8E%E9%B2%81%E6%96%87%E6%98%8E%E7%9A%84%E5%8F%91%E7%8E%B0" class="hash-link" aria-label="苏美鲁文明的发现的直接链接" title="苏美鲁文明的发现的直接链接" translate="no">​</a></h2>
<p>在人类文明早期，一支名为 <strong>苏美鲁（Pthumeru）</strong> 的古老文明曾经在地下建立起高度发达的城市。苏美鲁人长期生活在地下遗迹中，对古老遗物与神秘力量进行了大量研究。在某次探索过程中，他们发现了一具 <strong>上位者的遗骸</strong>，并从中提取出一种特殊物质：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="古老之血old-blood">古老之血（Old Blood）<a href="https://tanzhuo.xyz/2022/06/17/xie-yuan-zu-zhou#%E5%8F%A4%E8%80%81%E4%B9%8B%E8%A1%80old-blood" class="hash-link" aria-label="古老之血（Old Blood）的直接链接" title="古老之血（Old Blood）的直接链接" translate="no">​</a></h3>
<p>这种血液拥有不可思议的力量：</p>
<ul>
<li class="">可以快速治愈疾病</li>
<li class="">可以强化人体</li>
<li class="">甚至能够提升人的精神感知</li>
</ul>
<p>但与此同时，它也会带来严重的副作用。长期接触古老之血的人类，会逐渐失去理性，最终发生 <strong>兽化</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="教会与血疗的兴起">教会与血疗的兴起<a href="https://tanzhuo.xyz/2022/06/17/xie-yuan-zu-zhou#%E6%95%99%E4%BC%9A%E4%B8%8E%E8%A1%80%E7%96%97%E7%9A%84%E5%85%B4%E8%B5%B7" class="hash-link" aria-label="教会与血疗的兴起的直接链接" title="教会与血疗的兴起的直接链接" translate="no">​</a></h2>
<p>在苏美鲁遗迹被重新发现之后，亚楠建立了一个强大的组织：<strong>治愈教会（Healing Church）</strong>。教会利用古老之血发展出了著名的 <strong>血疗技术</strong>，并迅速让亚楠成为世界闻名的医疗圣地。大量病患来到这里接受治疗，城市也因此繁荣起来。</p>
<p>然而，随着血疗使用越来越频繁，兽化病也开始在城市中爆发。为了解决这一问题，教会秘密建立了一个新的组织：<strong>猎人组织（Hunters）</strong>。猎人的职责只有一个：</p>
<blockquote>
<p>在夜晚猎杀那些变成野兽的人。</p>
</blockquote>
<p>这就是所谓的 <strong>“猎杀之夜（The Hunt）”</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="梦境与猎人的宿命">梦境与猎人的宿命<a href="https://tanzhuo.xyz/2022/06/17/xie-yuan-zu-zhou#%E6%A2%A6%E5%A2%83%E4%B8%8E%E7%8C%8E%E4%BA%BA%E7%9A%84%E5%AE%BF%E5%91%BD" class="hash-link" aria-label="梦境与猎人的宿命的直接链接" title="梦境与猎人的宿命的直接链接" translate="no">​</a></h2>
<p>玩家所扮演的猎人，在接受血疗之后，会被卷入一个神秘的空间：<strong>猎人梦境（Hunter’s Dream）</strong>。这里是上位者力量所创造的特殊领域，也是猎人不断复活的地方。每当猎人死亡，就会从梦境中重新醒来，再次进入亚楠继续狩猎。</p>
<p>但随着故事的推进，猎人会逐渐发现：亚楠的灾难、血疗的秘密、上位者的存在，以及梦境本身，都隐藏着更深层的真相。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="血源诅咒真正的主题">血源诅咒真正的主题<a href="https://tanzhuo.xyz/2022/06/17/xie-yuan-zu-zhou#%E8%A1%80%E6%BA%90%E8%AF%85%E5%92%92%E7%9C%9F%E6%AD%A3%E7%9A%84%E4%B8%BB%E9%A2%98" class="hash-link" aria-label="血源诅咒真正的主题的直接链接" title="血源诅咒真正的主题的直接链接" translate="no">​</a></h2>
<p>表面上看，《血源诅咒》讲述的是猎人与野兽的战斗。但在更深层次上，它真正探讨的是：</p>
<ul>
<li class="">人类对未知力量的 <strong>贪婪</strong></li>
<li class="">对神明与宇宙真相的 <strong>探索</strong></li>
<li class="">以及 <strong>理性与疯狂之间的界限</strong></li>
</ul>
<p>随着玩家不断深入世界的秘密，人类逐渐意识到一个残酷的事实：<strong>宇宙远比人类想象的更加广阔，也更加可怕。</strong></p>]]></content:encoded>
            <category>游戏</category>
        </item>
        <item>
            <title><![CDATA[Vue UI库]]></title>
            <link>https://tanzhuo.xyz/2022/06/14/vue-chang-jian-uiku</link>
            <guid>https://tanzhuo.xyz/2022/06/14/vue-chang-jian-uiku</guid>
            <pubDate>Tue, 14 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[整理几个 Vue 生态里常见的 UI 组件库：View Design、Element Plus、Vuetify、Ant Design Vue、Vuesax，附各自定位与适用场景。]]></description>
            <content:encoded><![CDATA[<p>做 Vue 项目时，选 UI 组件库往往是动手前的第一个决定：选对了能省下大量按钮、表单、弹窗的重复劳动，选错了后期换库的成本相当高。这里把我平时接触到的几个常见 Vue UI 库整理一下，记录各自的定位和印象，方便以后选型时翻回来看。</p>
<!-- -->
<p>先说一下选型时我一般关注的几点：组件覆盖面是否够用（表格、表单、日期选择这些高频组件的完成度）、文档和社区是否活跃、设计风格是否贴合产品定位，以及项目是否还在持续维护。下面按这个视角逐个过一遍。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="view-design">View Design<a href="https://tanzhuo.xyz/2022/06/14/vue-chang-jian-uiku#view-design" class="hash-link" aria-label="View Design的直接链接" title="View Design的直接链接" translate="no">​</a></h2>
<p>基于 Vue.js 3 的企业级 UI 组件库和前端解决方案，为数万开发者服务。</p>
<p>它的前身是 iView，在 Vue 2 时代就积累了不少用户，风格偏简洁的后台管理系统路线。除了组件库本身，官方还提供了配套的模板和工具，适合想要一整套后台方案而不只是零散组件的场景。</p>
<p><a href="https://www.iviewui.com/" target="_blank" rel="noopener noreferrer" class="">iView / View Design 一套企业级 UI 组件库和前端解决方案</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="element-ui">Element UI<a href="https://tanzhuo.xyz/2022/06/14/vue-chang-jian-uiku#element-ui" class="hash-link" aria-label="Element UI的直接链接" title="Element UI的直接链接" translate="no">​</a></h2>
<p>基于 Vue 3，面向设计师和开发者的组件库。</p>
<p>Element 系列大概是国内 Vue 项目里出现频率最高的组件库，Vue 3 对应的版本叫 Element Plus。它的优势在于中文文档完善、组件齐全、社区大，遇到问题基本都能搜到现成答案。做中后台系统时，它几乎是默认选项之一。</p>
<p><a href="https://element-plus.org/" target="_blank" rel="noopener noreferrer" class="">A Vue 3 UI Framework | Element Plus</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vuetify">Vuetify<a href="https://tanzhuo.xyz/2022/06/14/vue-chang-jian-uiku#vuetify" class="hash-link" aria-label="Vuetify的直接链接" title="Vuetify的直接链接" translate="no">​</a></h2>
<p>Vuetify是一个Vue UI库，具有精美的手工制作的材质组件。无需任何设计技能 — 创建出色应用程序所需的一切触手可及。</p>
<p>它严格遵循 Google 的 Material Design 规范，视觉风格统一且带有明显的 Material 特征。如果产品本身就打算走 Material 风格，或者面向海外用户，Vuetify 会比国内几个库更合适；反过来，如果想大幅定制成自己的视觉体系，改起来会比较费劲。</p>
<p><a href="https://vuetifyjs.com/" target="_blank" rel="noopener noreferrer" class="">Vuetify — A Material Design Framework for Vue.js</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ant-design-vue">Ant Design Vue<a href="https://tanzhuo.xyz/2022/06/14/vue-chang-jian-uiku#ant-design-vue" class="hash-link" aria-label="Ant Design Vue的直接链接" title="Ant Design Vue的直接链接" translate="no">​</a></h2>
<p>遵循 Ant Design 规范，开发了一个 Vue UI 库，其中包含一组用于构建丰富的交互式用户界面的高质量组件和演示。</p>
<p>Ant Design 本身是蚂蚁团队为 React 生态制定的设计体系，Ant Design Vue 是它在 Vue 侧的实现。好处是设计规范经过大量中后台产品验证，表格、表单这类复杂组件功能很全；如果团队里同时有 React 和 Vue 项目，用同一套设计语言也能降低沟通成本。</p>
<p><a href="https://www.antdv.com/" target="_blank" rel="noopener noreferrer" class="">Ant Design Vue — An enterprise-class UI components based on Ant Design and Vue.js</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vuesax">Vuesax<a href="https://tanzhuo.xyz/2022/06/14/vue-chang-jian-uiku#vuesax" class="hash-link" aria-label="Vuesax的直接链接" title="Vuesax的直接链接" translate="no">​</a></h2>
<p>样式动画很好，可惜已经停止维护。</p>
<p>Vuesax 的组件在配色和交互动画上确实比上面几家更活泼，适合视觉要求偏年轻化的项目。但停止维护意味着新版本 Vue 的兼容、bug 修复都没有保障，新项目不建议再选它，这里列出来主要是当个参考。</p>
<p><a href="https://vuesax.com/" target="_blank" rel="noopener noreferrer" class="">Vuesax - Framework for Vuejs</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/06/14/vue-chang-jian-uiku#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）维护状态要先查。像 Vuesax 这样停止维护的库，装进新项目就是给未来埋雷，选型前先看仓库的提交记录和 issue 响应情况。</p>
<p>2）Vue 2 和 Vue 3 的版本要分清。这几个库大多经历过 Vue 2 到 Vue 3 的迁移，包名和文档往往是两套（比如 Element UI 和 Element Plus），装错版本会直接跑不起来。</p>
<p>3）按需引入。UI 库整包体积都不小，正式项目里建议配置按需加载，只打包用到的组件。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>如果只是写 demo 或内部工具，直接选文档最顺手的那个就行，不必纠结；选型的成本应该花在会长期维护的正式项目上。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/06/14/vue-chang-jian-uiku#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>中后台系统闭眼选 Element Plus 或 Ant Design Vue，两者社区都够大；想要 Material 风格或面向海外就看 Vuetify；View Design 适合想要整套后台方案的团队；Vuesax 已停止维护，仅作参考。选型没有标准答案，贴合团队习惯和产品风格的就是好选择。</p>]]></content:encoded>
            <category>前端</category>
        </item>
        <item>
            <title><![CDATA[Linux 目录结构分析]]></title>
            <link>https://tanzhuo.xyz/2022/06/13/linux-mu-lu-jie-gou-fen-xi</link>
            <guid>https://tanzhuo.xyz/2022/06/13/linux-mu-lu-jie-gou-fen-xi</guid>
            <pubDate>Mon, 13 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[从 FHS 规范出发梳理 Linux 根目录下各个目录的职责：/bin、/etc、/var、/usr、/opt 等分别放什么、为什么这样分，以及几组容易混淆的目录该如何区分。]]></description>
            <content:encoded><![CDATA[<p>第一次登录 Linux 服务器，<code>ls /</code> 看到一排陌生的短名字目录，多半会有点懵。这篇笔记把根目录下常见目录的职责梳理一遍，弄清楚"什么东西该放在哪"。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么目录长这样">为什么目录长这样<a href="https://tanzhuo.xyz/2022/06/13/linux-mu-lu-jie-gou-fen-xi#%E4%B8%BA%E4%BB%80%E4%B9%88%E7%9B%AE%E5%BD%95%E9%95%BF%E8%BF%99%E6%A0%B7" class="hash-link" aria-label="为什么目录长这样的直接链接" title="为什么目录长这样的直接链接" translate="no">​</a></h2>
<p>Linux 的目录布局不是随意约定的，背后有一份叫 FHS（Filesystem Hierarchy Standard，文件系统层次结构标准）的规范。它规定了每类文件应该放在哪个目录下：可执行程序、配置、日志、临时文件各归各位。这样做的好处很直接——</p>
<ul>
<li class="">软件包管理器知道把文件装到哪里，卸载时也能干净地删掉；</li>
<li class="">运维排查问题时,不用猜配置和日志在哪,<code>/etc</code> 和 <code>/var/log</code> 基本不会落空;</li>
<li class="">不同发行版之间保持大体一致，经验可以迁移。</li>
</ul>
<p>想看自己机器上的根目录布局，一条命令就够：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看根目录下的一级目录</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">ls</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-l</span><span class="token plain"> /</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心目录逐个看">核心目录逐个看<a href="https://tanzhuo.xyz/2022/06/13/linux-mu-lu-jie-gou-fen-xi#%E6%A0%B8%E5%BF%83%E7%9B%AE%E5%BD%95%E9%80%90%E4%B8%AA%E7%9C%8B" class="hash-link" aria-label="核心目录逐个看的直接链接" title="核心目录逐个看的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1程序与命令">1）程序与命令<a href="https://tanzhuo.xyz/2022/06/13/linux-mu-lu-jie-gou-fen-xi#1%E7%A8%8B%E5%BA%8F%E4%B8%8E%E5%91%BD%E4%BB%A4" class="hash-link" aria-label="1）程序与命令的直接链接" title="1）程序与命令的直接链接" translate="no">​</a></h3>
<p>/bin：存放着常用的指令和二进制可执行程序，比如 <code>ls</code>、<code>cp</code>、<code>cat</code> 这些所有用户都会用到的基础命令。</p>
<p>/sbin：管理员用户才能执行的命令和程序文件夹，典型的如网络配置、磁盘分区、系统关机重启相关的工具。普通用户的 <code>PATH</code> 里通常不包含它。</p>
<p>/usr：用户应用程序和文件放置的文件夹目录。名字容易被误解成 "user"，实际是 Unix System Resources 的意思。系统安装的绝大多数软件都在这里，<code>/usr/bin</code>、<code>/usr/lib</code>、<code>/usr/share</code> 分别放程序、库和共享数据。不少现代发行版做了 usr 合并，<code>/bin</code> 其实是指向 <code>/usr/bin</code> 的软链接：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 在做了 usr 合并的发行版上可以看到软链接关系</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">ls</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-ld</span><span class="token plain"> /bin /sbin</span><br></div></code></pre></div></div>
<p>/opt：optional，"可选择"的意思，作用是安装第三方软件的地方。区别于 <code>/usr</code> 由包管理器接管，<code>/opt</code> 更适合放自带完整目录结构的商业软件或手动解压安装的大型软件，一个软件一个子目录，删起来也省事。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2配置与设备">2）配置与设备<a href="https://tanzhuo.xyz/2022/06/13/linux-mu-lu-jie-gou-fen-xi#2%E9%85%8D%E7%BD%AE%E4%B8%8E%E8%AE%BE%E5%A4%87" class="hash-link" aria-label="2）配置与设备的直接链接" title="2）配置与设备的直接链接" translate="no">​</a></h3>
<p>/etc：专用于存放系统的配置文件和子目录列表。改系统行为基本都是在这里动手，比如 <code>/etc/fstab</code>、<code>/etc/hosts</code>、<code>/etc/ssh/</code>。备份系统配置时，这个目录是重点。</p>
<p>/dev：Device（设备）的缩写，是 Linux 系统目前连接的外部设备。Linux 遵循"一切皆文件"的设计，外部设备的访问和文件访问的方式一样——磁盘是 <code>/dev/sda</code>，终端是 <code>/dev/tty</code>，还有 <code>/dev/null</code> 这种特殊设备。</p>
<p>/boot：存放启动 Linux 系统的内核文件，包括连接文件和镜像文件，引导加载器（如 GRUB）的配置也在这里。这个目录轻易不要动，删错文件系统可能起不来。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3用户数据">3）用户数据<a href="https://tanzhuo.xyz/2022/06/13/linux-mu-lu-jie-gou-fen-xi#3%E7%94%A8%E6%88%B7%E6%95%B0%E6%8D%AE" class="hash-link" aria-label="3）用户数据的直接链接" title="3）用户数据的直接链接" translate="no">​</a></h3>
<p>/home：用户文件夹，每个系统账号都会创建一个不同的文件夹名称，比如用户 alice 的家目录就是 <code>/home/alice</code>。</p>
<p>/root：管理员用户的主目录。注意它不在 <code>/home</code> 下面——这样即使 <code>/home</code> 挂载的分区出问题，root 用户依然能正常登录排障。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4运行时与易变数据">4）运行时与易变数据<a href="https://tanzhuo.xyz/2022/06/13/linux-mu-lu-jie-gou-fen-xi#4%E8%BF%90%E8%A1%8C%E6%97%B6%E4%B8%8E%E6%98%93%E5%8F%98%E6%95%B0%E6%8D%AE" class="hash-link" aria-label="4）运行时与易变数据的直接链接" title="4）运行时与易变数据的直接链接" translate="no">​</a></h3>
<p>/var：存放着经常变动的文件，例如系统日志等文件。<code>/var/log</code> 放日志，<code>/var/lib</code> 放服务的持久数据（数据库文件常在这里），<code>/var/cache</code> 放缓存。磁盘被打满时,第一个该检查的往往就是它:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 找出 /var 下占用空间最大的子目录</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">du</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-sh</span><span class="token plain"> /var/* </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">sort</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-rh</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">head</span><br></div></code></pre></div></div>
<p>/tmp：临时文件目录。存放程序运行中产生的临时文件，任何用户都可写。很多发行版会定期清理其中长时间未访问、未更改或未修改的文件（比如 10 天），所以别把需要保留的东西放在这里。</p>
<p>/run：临时文件目录，存储系统启动以来的临时信息，比如服务的 PID 文件和 socket 文件。当系统重启，该目录下的文件将会被清理掉——它通常挂载在内存上，天然易失。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="容易混淆的几组目录">容易混淆的几组目录<a href="https://tanzhuo.xyz/2022/06/13/linux-mu-lu-jie-gou-fen-xi#%E5%AE%B9%E6%98%93%E6%B7%B7%E6%B7%86%E7%9A%84%E5%87%A0%E7%BB%84%E7%9B%AE%E5%BD%95" class="hash-link" aria-label="容易混淆的几组目录的直接链接" title="容易混淆的几组目录的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>/tmp 与 /run</strong>：都是临时目录，但 <code>/tmp</code> 面向应用程序和用户，<code>/run</code> 面向系统服务的运行时状态；<code>/run</code> 重启必清空，<code>/tmp</code> 取决于发行版策略。</li>
<li class=""><strong>/opt 与 /usr/local</strong>：都用来装包管理器之外的软件。手动编译安装的软件习惯放 <code>/usr/local</code>（结构与 <code>/usr</code> 平行），整包解压的第三方软件放 <code>/opt</code>。</li>
<li class=""><strong>/root 与 /</strong>：前者是 root 用户的家目录，后者是整个文件系统的根，名字像但完全是两回事。</li>
</ul>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>排查"磁盘满了"这类问题时，优先看 <code>/var/log</code> 和 <code>/tmp</code>；找某个软件的配置时，优先去 <code>/etc</code> 下搜同名目录。记住目录职责，比记住具体路径更有用。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/06/13/linux-mu-lu-jie-gou-fen-xi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Linux 的目录结构本质上是按"文件的性质"分类：程序放 <code>/bin</code>、<code>/usr</code>，配置放 <code>/etc</code>，易变数据放 <code>/var</code>，临时文件放 <code>/tmp</code> 和 <code>/run</code>，用户数据放 <code>/home</code>。理解了这套分类逻辑，面对一台陌生的机器也能很快找到要找的东西。</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[CentOS 安装 Docker]]></title>
            <link>https://tanzhuo.xyz/2022/06/08/centos-an-zhuang-docker</link>
            <guid>https://tanzhuo.xyz/2022/06/08/centos-an-zhuang-docker</guid>
            <pubDate>Wed, 08 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[记录在 CentOS 上通过 yum 安装 Docker 的完整步骤：配置国内源、指定版本安装、启动服务，以及修改镜像存储路径、配置镜像加速和设置开机自启。]]></description>
            <content:encoded><![CDATA[<p>随着目前容器技术的流行，学习如何安装docker也是至关重要的。</p>
<!-- -->
<p>Docker 官方文档的安装步骤本身不复杂，但在国内网络环境下直接照搬往往会卡在下载速度上；另外生产环境通常需要固定 Docker 版本、调整镜像存储位置，这些细节官方文档讲得比较分散。这篇文章把我在 CentOS 上安装 Docker 的完整流程整理下来，包括安装后的几个常用配置，方便以后重装时直接照着敲。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安装步骤">安装步骤<a href="https://tanzhuo.xyz/2022/06/08/centos-an-zhuang-docker#%E5%AE%89%E8%A3%85%E6%AD%A5%E9%AA%A4" class="hash-link" aria-label="安装步骤的直接链接" title="安装步骤的直接链接" translate="no">​</a></h2>
<p>1）安装对应 yum 工具包</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># yum-utils 提供 yum-config-manager 工具，用于后面添加软件源</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># device-mapper-persistent-data 和 lvm2 是 devicemapper 存储驱动的依赖</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> yum </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-y</span><span class="token plain"> yum-utils </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  device-mapper-persistent-data </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  lvm2</span><br></div></code></pre></div></div>
<p>这一步装的都是基础工具：<code>yum-config-manager</code> 是下一步添加仓库要用的命令，另外两个包是存储驱动相关的依赖，一并装上省得后面报错。</p>
<p>2）设置国内源地址，加快docker下载速度</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 添加阿里云的 docker-ce 仓库，替代官方源</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> yum-config-manager </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  --add-repo </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo</span><br></div></code></pre></div></div>
<p>Docker 官方源在国内下载很慢甚至超时，换成阿里云的镜像仓库后，安装包的下载速度会正常很多。这条命令实际上是把一个 repo 文件写入 <code>/etc/yum.repos.d/</code> 目录，之后 yum 就能从这个仓库里检索 docker 相关的包。</p>
<p>3）列出对应版本包</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 列出仓库中所有可用的 docker-ce 版本，按版本号倒序排列</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">yum list docker-ce </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--showduplicates</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">sort</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-r</span><br></div></code></pre></div></div>
<p><code>--showduplicates</code> 参数会把仓库里所有历史版本都列出来，而不是只显示最新版。输出的第二列就是版本字符串，下一步安装指定版本时会用到。</p>
<p>4）安装对应版本docker</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># &lt;VERSION_STRING&gt; 替换为上一步查到的版本字符串</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> yum </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> docker-ce-</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">VERSION_STRING</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> docker-ce-cli-</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">VERSION_STRING</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> containerd.io</span><br></div></code></pre></div></div>
<p>例子：修改对应 docker 版本。</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 指定安装 20.10.9 版本，docker-ce 与 docker-ce-cli 版本保持一致</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> yum </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> docker-ce-20.10.9-3.el7 docker-ce-cli-20.10.9-3.el7 containerd.io</span><br></div></code></pre></div></div>
<p>这里一次装了三个包，分工不同：<code>docker-ce</code> 是守护进程（dockerd），<code>docker-ce-cli</code> 是命令行客户端，<code>containerd.io</code> 是底层的容器运行时，真正负责容器的创建和生命周期管理。如果不指定版本直接 <code>yum install docker-ce</code>，装的就是仓库里的最新版；生产环境建议固定版本，避免不同机器上版本不一致带来的差异。</p>
<p>5）启动docker服务</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> systemctl start </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><br></div></code></pre></div></div>
<p>启动后可以执行 <code>docker info</code> 或 <code>docker version</code> 确认守护进程正常运行，到这里安装就完成了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="扩展知识点">扩展知识点<a href="https://tanzhuo.xyz/2022/06/08/centos-an-zhuang-docker#%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86%E7%82%B9" class="hash-link" aria-label="扩展知识点的直接链接" title="扩展知识点的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1修改-docker-默认镜像存储地址修改-docker-镜像下载地址">1）修改 docker 默认镜像存储地址、修改 docker 镜像下载地址<a href="https://tanzhuo.xyz/2022/06/08/centos-an-zhuang-docker#1%E4%BF%AE%E6%94%B9-docker-%E9%BB%98%E8%AE%A4%E9%95%9C%E5%83%8F%E5%AD%98%E5%82%A8%E5%9C%B0%E5%9D%80%E4%BF%AE%E6%94%B9-docker-%E9%95%9C%E5%83%8F%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80" class="hash-link" aria-label="1）修改 docker 默认镜像存储地址、修改 docker 镜像下载地址的直接链接" title="1）修改 docker 默认镜像存储地址、修改 docker 镜像下载地址的直接链接" translate="no">​</a></h3>
<p>Docker 默认把镜像、容器数据都存放在 <code>/var/lib/docker</code> 目录下。很多服务器的根分区空间有限，镜像拉多了很容易把根分区撑满，所以一般会把存储目录挪到空间更大的数据盘上。同时，从 Docker Hub 拉取镜像在国内也偏慢，配置一个国内的镜像加速地址可以明显改善。这两项都通过 daemon.json 配置。</p>
<p>1、使用vim编辑 /etc/docker/daemon.json 文件</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 该文件默认不存在，vim 打开后保存即会创建</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">vim</span><span class="token plain"> /etc/docker/daemon.json</span><br></div></code></pre></div></div>
<p>2、输入 data-root 对应值，修改镜像存储地址，输入 registry-mirrors 对应值修改对应镜像下载地址，数组，可多个地址，用，号隔开。</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token property" style="color:hsl(355, 65%, 65%)">"data-root"</span><span class="token operator" style="color:hsl(207, 82%, 66%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"/home/docker-data"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token property" style="color:hsl(355, 65%, 65%)">"registry-mirrors"</span><span class="token operator" style="color:hsl(207, 82%, 66%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">[</span><span class="token string" style="color:hsl(95, 38%, 62%)">"https://docker.mirrors.ustc.edu.cn/"</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><br></div></code></pre></div></div>
<p>这里的加速地址用的是<a href="https://docker.mirrors.ustc.edu.cn/" target="_blank" rel="noopener noreferrer" class="">中科大镜像站</a>。<code>registry-mirrors</code> 是数组，可以配置多个地址，Docker 拉取镜像时会依次尝试。</p>
<p>daemon.json 文件编辑完成后执行 docker 重启命令</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">service</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> restart</span><br></div></code></pre></div></div>
<p>再执行 docker info 命令查看修改配置是否生效。生效后 <code>docker info</code> 输出里的 <code>Docker Root Dir</code> 会显示新的存储路径，<code>Registry Mirrors</code> 一栏会列出配置的加速地址。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2添加-docker-服务为开机自启">2）添加 docker 服务为开机自启<a href="https://tanzhuo.xyz/2022/06/08/centos-an-zhuang-docker#2%E6%B7%BB%E5%8A%A0-docker-%E6%9C%8D%E5%8A%A1%E4%B8%BA%E5%BC%80%E6%9C%BA%E8%87%AA%E5%90%AF" class="hash-link" aria-label="2）添加 docker 服务为开机自启的直接链接" title="2）添加 docker 服务为开机自启的直接链接" translate="no">​</a></h3>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 注册为系统服务，服务器重启后 docker 自动启动</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">systemctl </span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">enable</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><br></div></code></pre></div></div>
<p>服务器难免会有重启的时候，如果忘了这一步，重启后容器全都起不来，还得手动去启动 docker 服务，所以装完顺手就把自启配置上。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/06/08/centos-an-zhuang-docker#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class="">daemon.json 是严格的 JSON 格式，多一个逗号、少一个引号都会导致 docker 启动失败。改完配置如果重启报错，先检查这个文件的语法。</li>
<li class="">修改 <code>data-root</code> 只是换了新目录，原来 <code>/var/lib/docker</code> 下已有的镜像和容器数据不会自动迁移，需要的话要自己拷贝过去再重启。</li>
<li class="">指定版本安装时，<code>docker-ce</code> 和 <code>docker-ce-cli</code> 的版本字符串要保持一致，混着装可能出现客户端与守护进程不兼容的提示。</li>
<li class="">安装前如果机器上有旧版本的 docker（比如包名为 <code>docker</code> 或 <code>docker-engine</code> 的老包），建议先卸载干净再装 docker-ce，避免冲突。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/06/08/centos-an-zhuang-docker#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>整个安装流程可以概括为：装工具包、换国内源、查版本、装指定版本、启动服务五步。装完之后别急着用，先把镜像存储路径、镜像加速和开机自启这三项配置好，都是一次配置长期受益的事情。之后新机器初始化时，照着这份笔记从上往下执行一遍即可。</p>]]></content:encoded>
            <category>Docker</category>
        </item>
        <item>
            <title><![CDATA[Docker Compose 基本使用]]></title>
            <link>https://tanzhuo.xyz/2022/06/08/docker-compose-ji-ben-shi-yong</link>
            <guid>https://tanzhuo.xyz/2022/06/08/docker-compose-ji-ben-shi-yong</guid>
            <pubDate>Wed, 08 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[docker-compose 用一份 YAML 文件描述多容器应用的服务、网络与数据卷，一条命令完成编排与启动。本文记录它的安装方式、工作机制和日常常用命令。]]></description>
            <content:encoded><![CDATA[<p>我们安装 docker 之后，便会启动很多容器服务，但对应这些容器如何统一编排，网络组管理，启动顺序，挂载数据卷，成为了问题，于是 docker 团队开发了 docker-compose 组件来便于我们编排容器。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么需要编排">为什么需要编排<a href="https://tanzhuo.xyz/2022/06/08/docker-compose-ji-ben-shi-yong#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E7%BC%96%E6%8E%92" class="hash-link" aria-label="为什么需要编排的直接链接" title="为什么需要编排的直接链接" translate="no">​</a></h2>
<p>单个容器用 <code>docker run</code> 就能起，但真实的应用很少只有一个容器：一个 Web 服务往往还带着数据库、缓存、消息队列。这些容器之间有依赖关系，需要在同一个网络里互相访问，还各自要挂载数据卷。如果全靠手敲 <code>docker run</code>，参数一长就没人记得住，换台机器重新部署更是折磨。</p>
<p>docker-compose 解决的就是这个问题：把所有容器的启动参数固化成一份 YAML 文件，纳入版本管理，任何人拿到文件就能一条命令复现整套环境。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安装">安装<a href="https://tanzhuo.xyz/2022/06/08/docker-compose-ji-ben-shi-yong#%E5%AE%89%E8%A3%85" class="hash-link" aria-label="安装的直接链接" title="安装的直接链接" translate="no">​</a></h2>
<p>开源项目地址：</p>
<p><a href="https://github.com/docker/compose" target="_blank" rel="noopener noreferrer" class="">https://github.com/docker/compose</a></p>
<p>从项目的 Releases 页面下载对应系统架构的二进制文件即可，安装分两步。</p>
<p>1、下载文件后上传至服务器 /usr/local/bin 文件夹。</p>
<p>放在 /usr/local/bin 是因为这个目录默认在 PATH 里，放进去之后在任意路径下都能直接敲 <code>docker-compose</code> 命令。</p>
<p>2、添加文件执行权限</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 赋予可执行权限，否则 shell 会提示 Permission denied</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sudo</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">chmod</span><span class="token plain"> +x /usr/local/bin/docker-compose</span><br></div></code></pre></div></div>
<p>装完可以执行 <code>docker-compose version</code> 验证一下，能打印出版本信息就说明装好了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="工作机制">工作机制<a href="https://tanzhuo.xyz/2022/06/08/docker-compose-ji-ben-shi-yong#%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="工作机制的直接链接" title="工作机制的直接链接" translate="no">​</a></h2>
<p>docker-compose 只是一个二进制文件，可以直接运行在 linux 系统上，通过docker-compose 可以使用 yaml 文件来配置应用程序需要的所有容器。然后使用一个命令 docker-compose up -d，就可以从 yaml 文件配置中创建并启动所有服务。</p>
<p>它本身并不是另一套容器引擎，底层做的事和手动 <code>docker run</code> 完全一样——解析 YAML 之后调用 docker 的 API 去创建网络、卷和容器。几个关键机制：</p>
<p>1）<strong>项目（project）</strong>：compose 默认以 yaml 文件所在目录名作为项目名，同一项目下创建的容器、网络、卷都会带上这个前缀，互相隔离。</p>
<p>2）<strong>默认网络</strong>：<code>up</code> 的时候会自动创建一个桥接网络，把文件里定义的所有服务放进去。同一网络内的容器可以直接用服务名当主机名互相访问，不需要关心容器 IP。</p>
<p>3）<strong>声明式管理</strong>：再次执行 <code>up</code> 时，compose 会对比 yaml 和当前容器的状态，只重建有变化的服务，没变的原地不动。</p>
<p>一份典型的 docker-compose.yml 大致长这样：</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token key atrule" style="color:hsl(29, 54%, 61%)">version</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"3"</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">services</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">web</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">image</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> nginx            </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 使用的镜像</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">ports</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">"80:80"</span><span class="token plain">             </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 宿主机端口:容器端口</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">volumes</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> ./html</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">/usr/share/nginx/html   </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 挂载数据卷</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">depends_on</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain"> app                 </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 声明启动顺序：先起 app 再起 web</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">app</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">build</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> ./app            </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 也可以从 Dockerfile 现场构建</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">restart</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> always         </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 容器异常退出时自动拉起</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常用命令">常用命令<a href="https://tanzhuo.xyz/2022/06/08/docker-compose-ji-ben-shi-yong#%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4" class="hash-link" aria-label="常用命令的直接链接" title="常用命令的直接链接" translate="no">​</a></h2>
<p>以下命令都需要在 docker-compose.yml 所在目录下执行，或者用 <code>-f</code> 指定文件路径。</p>
<p>常用命令：</p>
<p>docker-compose up [启动容器 带 -d 后台启动]</p>
<p>docker-compose stop [停止容器]</p>
<p>docker-compose ps [查看当前所有编排服务]</p>
<p>docker-compose logs -f --tail=500 &lt;容器Name&gt; [查看容器实时日志]</p>
<p>docker-compose restart [重启容器]</p>
<p>docker-compose rm  [删除容器]</p>
<p>docker-compose build  [打包镜像]</p>
<p>几个补充说明：</p>
<ul>
<li class=""><code>up</code> 不带 <code>-d</code> 会前台运行并把所有容器日志打到当前终端，调试时很方便，按 Ctrl+C 即停止；日常部署基本都是 <code>up -d</code>。</li>
<li class=""><code>logs</code> 的 <code>--tail=500</code> 表示只看最后 500 行再开始跟踪，不加的话历史日志全刷出来，日志量大时终端会卡很久。</li>
<li class=""><code>stop</code> 只停容器不删除，<code>rm</code> 删除的是已停止的容器；如果想一次性停止并删除容器和网络，用 <code>docker-compose down</code>。</li>
<li class="">改了 Dockerfile 之后直接 <code>up -d</code> 不会重新构建镜像，要么先 <code>build</code> 再 <code>up</code>，要么直接 <code>up -d --build</code>。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/06/08/docker-compose-ji-ben-shi-yong#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）<strong>修改 yaml 后记得重新 up</strong>：改完配置只 <code>restart</code> 是不生效的，<code>restart</code> 只是重启旧容器；要让新配置落地必须再执行一次 <code>up -d</code>，让 compose 重建有变化的容器。</p>
<p>2）<strong>depends_on 只管启动顺序</strong>：它保证容器按顺序启动，但不保证依赖的服务真正就绪。比如数据库容器起来了、进程还没接受连接，应用这时去连就会报错，应用侧要有重连逻辑。</p>
<p>3）<strong>数据卷用相对路径时注意执行目录</strong>：yaml 里的相对路径是相对于 yaml 文件位置解析的，换目录执行时最好用 <code>-f</code> 显式指定文件，避免挂错位置。</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p><code>docker-compose down</code> 加上 <code>-v</code> 参数会连同数据卷一起删除，对有状态服务（数据库等）执行前务必确认卷里的数据可以丢弃。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/06/08/docker-compose-ji-ben-shi-yong#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>docker-compose 本质上是把一堆 <code>docker run</code> 参数固化成一份可版本管理的 YAML：一条 <code>up -d</code> 起整套服务，<code>logs</code>、<code>ps</code>、<code>restart</code> 覆盖日常运维。单机环境下它已经足够顺手，等服务规模大到需要多机调度时，再考虑 Kubernetes 这类编排系统也不迟。</p>]]></content:encoded>
            <category>Docker</category>
        </item>
        <item>
            <title><![CDATA[Linux 常用命令]]></title>
            <link>https://tanzhuo.xyz/2022/06/08/linux-chang-yong-ming-ling</link>
            <guid>https://tanzhuo.xyz/2022/06/08/linux-chang-yong-ming-ling</guid>
            <pubDate>Wed, 08 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Linux 日常运维命令速查：CPU、内存、文件操作与系统信息查询，附一份常用指令清单，方便随手翻阅。]]></description>
            <content:encoded><![CDATA[<p>日常工作里这些 Linux 命令翻来覆去地用，与其每次都去搜，不如整理成一篇速查笔记。按 CPU、内存、文件、系统几个维度归类，最后附一份常用指令清单。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cpu-相关命令">CPU 相关命令<a href="https://tanzhuo.xyz/2022/06/08/linux-chang-yong-ming-ling#cpu-%E7%9B%B8%E5%85%B3%E5%91%BD%E4%BB%A4" class="hash-link" aria-label="CPU 相关命令的直接链接" title="CPU 相关命令的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看 CPU 物理个数</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">grep</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">'physical id'</span><span class="token plain"> /proc/cpuinfo </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">sort</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-u</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">wc</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-l</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看 CPU 核心数量</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">grep</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">'core id'</span><span class="token plain"> /proc/cpuinfo </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">sort</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-u</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">wc</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-l</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看 CPU 线程数</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">grep</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">'processor'</span><span class="token plain"> /proc/cpuinfo </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">sort</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-u</span><span class="token plain"> </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">wc</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-l</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看 CPU 型号</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">dmidecode </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-s</span><span class="token plain"> processor-version</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看 CPU 的详细信息</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">cat</span><span class="token plain"> /proc/cpuinfo</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="内存相关命令">内存相关命令<a href="https://tanzhuo.xyz/2022/06/08/linux-chang-yong-ming-ling#%E5%86%85%E5%AD%98%E7%9B%B8%E5%85%B3%E5%91%BD%E4%BB%A4" class="hash-link" aria-label="内存相关命令的直接链接" title="内存相关命令的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看内存目前使用情况</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">free</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-h</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看内存占用最高的前十个进程</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">ps</span><span class="token plain"> aux </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">head</span><span class="token plain"> -1</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token function" style="color:hsl(207, 82%, 66%)">ps</span><span class="token plain"> aux </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token function" style="color:hsl(207, 82%, 66%)">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-v</span><span class="token plain"> PID </span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token function" style="color:hsl(207, 82%, 66%)">sort</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-rn</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-k</span><span class="token plain"> +4</span><span class="token operator" style="color:hsl(207, 82%, 66%)">|</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">head</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-10</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="文件命令">文件命令<a href="https://tanzhuo.xyz/2022/06/08/linux-chang-yong-ming-ling#%E6%96%87%E4%BB%B6%E5%91%BD%E4%BB%A4" class="hash-link" aria-label="文件命令的直接链接" title="文件命令的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">ls</span><span class="token plain">        </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 显示文件或目录</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">ls</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-l</span><span class="token plain">     </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 列出文件详细信息（list）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">ls</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-a</span><span class="token plain">     </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 列出当前目录下所有文件及目录，包括隐藏项（all）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">mkdir</span><span class="token plain">     </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 创建目录</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-p</span><span class="token plain">  </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 创建目录，若无父目录则一并创建（parent）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">cd</span><span class="token plain">        </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 切换目录</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">touch</span><span class="token plain">     </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 创建空文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">echo</span><span class="token plain">      </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 创建带有内容的文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">cat</span><span class="token plain">       </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看文件内容</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">cp</span><span class="token plain">        </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 拷贝</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">mv</span><span class="token plain">        </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 移动或重命名</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">rm</span><span class="token plain">        </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 删除文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-r</span><span class="token plain">     </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 递归删除，可删除子目录及文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-f</span><span class="token plain">     </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 强制删除</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">find</span><span class="token plain">      </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 在文件系统中搜索某文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">wc</span><span class="token plain">        </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 统计文本中行数、字数、字符数</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">grep</span><span class="token plain">      </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 在文本文件中查找某个字符串</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">rmdir</span><span class="token plain">     </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 删除空目录</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">tree      </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 树形结构显示目录，需要安装 tree 包</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">pwd</span><span class="token plain">       </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 显示当前目录</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="系统相关命令">系统相关命令<a href="https://tanzhuo.xyz/2022/06/08/linux-chang-yong-ming-ling#%E7%B3%BB%E7%BB%9F%E7%9B%B8%E5%85%B3%E5%91%BD%E4%BB%A4" class="hash-link" aria-label="系统相关命令的直接链接" title="系统相关命令的直接链接" translate="no">​</a></h2>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看系统信息</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">lsb_release </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-a</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查询内核版本</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">uname</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-r</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常用指令清单">常用指令清单<a href="https://tanzhuo.xyz/2022/06/08/linux-chang-yong-ming-ling#%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4%E6%B8%85%E5%8D%95" class="hash-link" aria-label="常用指令清单的直接链接" title="常用指令清单的直接链接" translate="no">​</a></h2>
<ol>
<li class="">cd：改变当前工作目录</li>
<li class="">ls：列出当前工作目录中的文件和子目录</li>
<li class="">mkdir：创建一个新目录</li>
<li class="">rm：删除文件或目录</li>
<li class="">cp：复制文件或目录</li>
<li class="">mv：移动文件或目录</li>
<li class="">touch：创建一个新文件或更改现有文件的访问和修改时间</li>
<li class="">cat：查看文件内容</li>
<li class="">grep：在文件中搜索特定字符串</li>
<li class="">find：在文件系统中查找文件</li>
<li class="">chmod：更改文件或目录的权限</li>
<li class="">chown：更改文件或目录的所有者</li>
<li class="">top：显示系统中当前正在运行的进程</li>
<li class="">ps：列出当前正在运行的进程</li>
<li class="">ssh：通过安全的 shell 连接到远程主机</li>
<li class="">scp：通过安全的 shell 将文件从一个主机复制到另一个主机</li>
<li class="">tar：创建或提取 tar 存档</li>
<li class="">gzip：压缩文件</li>
<li class="">unzip：解压缩文件</li>
<li class="">ping：测试与另一个主机的网络连接</li>
<li class="">ifconfig：显示和管理网络接口</li>
<li class="">route：显示和管理网络路由表</li>
<li class="">netstat：显示网络状态信息</li>
<li class="">iptables：管理防火墙规则</li>
<li class="">systemctl：管理系统服务</li>
<li class="">service：管理系统服务</li>
<li class="">du：显示目录和文件的磁盘使用情况</li>
<li class="">df：显示文件系统的磁盘使用情况</li>
<li class="">uname：显示系统信息</li>
<li class="">date：显示或更改系统日期和时间</li>
<li class="">history：显示以前执行的命令历史记录</li>
<li class="">man：查看命令的手册页</li>
<li class="">which：查找给定命令的路径</li>
<li class="">whereis：查找给定命令的路径和手册页</li>
<li class="">whoami：显示当前用户的用户名</li>
<li class="">su：切换到其他用户或超级用户</li>
<li class="">sudo：使用超级用户权限执行命令</li>
<li class="">ssh-keygen：生成 SSH 密钥对</li>
<li class="">ssh-copy-id：将 SSH 公钥复制到远程主机</li>
<li class="">scp：通过 SSH 安全地复制文件</li>
<li class="">curl：用于向服务器发送请求并获取响应</li>
<li class="">wget：从 Web 上下载文件</li>
<li class="">vim：强大的文本编辑器</li>
<li class="">nano：易用的文本编辑器</li>
<li class="">tar：创建或提取 tar 存档</li>
<li class="">gzip：压缩文件</li>
<li class="">bzip2：压缩文件</li>
<li class="">unzip：解压缩文件</li>
<li class="">zip：创建或提取 zip 存档</li>
<li class="">sshfs：通过 SSH 挂载远程文件系统</li>
<li class="">rsync：远程同步文件和目录</li>
<li class="">awk：用于处理和分析文本</li>
<li class="">sed：用于处理和转换文本</li>
<li class="">sort：对文本行进行排序</li>
<li class="">uniq：去除文本中的重复行</li>
<li class="">cut：从文本中提取列</li>
<li class="">paste：将文本列合并为单个文件</li>
<li class="">tee：将标准输出复制到文件和终端</li>
<li class="">diff：比较两个文件之间的差异</li>
<li class="">patch：从 diff 文件中应用补丁</li>
<li class="">head：显示文件的前几行</li>
<li class="">tail：显示文件的后几行</li>
<li class="">tar：创建或提取 tar 存档</li>
<li class="">gzip：压缩文件</li>
<li class="">bzip2：压缩文件</li>
<li class="">unzip：解压缩文件</li>
<li class="">zip：创建或提取 zip 存档</li>
<li class="">rpm：安装、删除和查询 RPM 包</li>
<li class="">dpkg：安装、删除和查询 Debian 包</li>
<li class="">ps：列出正在运行的进程</li>
<li class="">kill：停止正在运行的进程</li>
<li class="">top：显示系统中当前正在运行的进程</li>
<li class="">uptime：显示系统的运行时间和负载</li>
<li class="">free：显示系统内存使用情况</li>
<li class="">df：显示文件系统的磁盘使用情况</li>
<li class="">du：显示目录和文件的磁盘使用情况</li>
<li class="">mount：挂载文件系统</li>
<li class="">umount：卸载文件系统</li>
<li class="">chroot：更改根目录</li>
<li class="">ln：创建链接或硬链接</li>
<li class="">echo：输出文本或变量</li>
<li class="">export：设置环境变量</li>
<li class="">source：执行脚本并将其导入当前 shell</li>
<li class="">alias：创建别名</li>
<li class="">unalias：删除别名</li>
<li class="">jobs：列出当前正在运行的作业</li>
<li class="">bg：将作业放入后台运行</li>
<li class="">fg：将作业放回前台运行</li>
<li class="">nohup：在后台运行作业，即使关闭终端也不会停止</li>
<li class="">crontab：设置定时任务</li>
<li class="">at：在指定时间运行命令</li>
<li class="">screen：创建多个终端会话</li>
<li class="">tmux：创建多个终端会话</li>
<li class="">curlftpfs：通过 FTP 挂载远程文件系统</li>
<li class="">dig：查询 DNS 服务器</li>
<li class="">nslookup：查询 DNS 服务器</li>
<li class="">ping：测试与另一个主机的网络连接</li>
<li class="">traceroute：显示数据包在网络中的路径</li>
<li class="">tcpdump：捕获网络数据包</li>
<li class="">iftop：显示网络流量信息</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/06/08/linux-chang-yong-ming-ling#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>命令记不全很正常，关键是知道去哪查。这篇笔记按 CPU、内存、文件、系统几类整理了日常最常用的命令，配合 man 手册基本能覆盖大部分排查场景。后面遇到值得记录的新命令，会继续补充进来。</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Linux 挂载新磁盘]]></title>
            <link>https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan</link>
            <guid>https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan</guid>
            <pubDate>Wed, 08 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[记录在 Linux 服务器上挂载一块新磁盘的完整流程：fdisk 分区、mkfs 格式化、mount 挂载,以及写 /etc/fstab 实现开机自动挂载,附常见坑。]]></description>
            <content:encoded><![CDATA[<p>给服务器加了一块新硬盘,插上去之后系统里却看不到可用空间——这是很多人第一次扩容时都会遇到的场景。磁盘从"接上主板"到"能存文件",中间还隔着识别、分区、格式化、挂载四步。这篇笔记把整个流程走一遍,顺便解释每一步在做什么。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="背景为什么插上硬盘还不能直接用">背景:为什么插上硬盘还不能直接用<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#%E8%83%8C%E6%99%AF%E4%B8%BA%E4%BB%80%E4%B9%88%E6%8F%92%E4%B8%8A%E7%A1%AC%E7%9B%98%E8%BF%98%E4%B8%8D%E8%83%BD%E7%9B%B4%E6%8E%A5%E7%94%A8" class="hash-link" aria-label="背景:为什么插上硬盘还不能直接用的直接链接" title="背景:为什么插上硬盘还不能直接用的直接链接" translate="no">​</a></h2>
<p>Linux 里"一切皆文件",磁盘也不例外。新磁盘接入后,内核会把它识别成一个块设备,比如 <code>/dev/sdb</code> 或 <code>/dev/vdb</code>,但此时它只是一块"裸盘":</p>
<ul>
<li class="">没有分区表,系统不知道空间怎么划分;</li>
<li class="">没有文件系统,内核不知道数据按什么结构组织;</li>
<li class="">没有挂载点,用户态程序无法通过路径访问它。</li>
</ul>
<p>所以完整流程是:识别 → 分区 → 格式化 → 挂载,最后再配置开机自动挂载,缺一步都不行。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实操步骤">实操步骤<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#%E5%AE%9E%E6%93%8D%E6%AD%A5%E9%AA%A4" class="hash-link" aria-label="实操步骤的直接链接" title="实操步骤的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-磁盘连上主板很重要">1) 磁盘连上主板(很重要)<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#1-%E7%A3%81%E7%9B%98%E8%BF%9E%E4%B8%8A%E4%B8%BB%E6%9D%BF%E5%BE%88%E9%87%8D%E8%A6%81" class="hash-link" aria-label="1) 磁盘连上主板(很重要)的直接链接" title="1) 磁盘连上主板(很重要)的直接链接" translate="no">​</a></h3>
<p>物理机就是把 SATA 线和电源线接好;云服务器则是在控制台把云盘挂载到实例上。这一步看着像废话,但排查"系统看不到磁盘"时,硬件连接确实是最常见的原因。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-重启系统">2) 重启系统<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#2-%E9%87%8D%E5%90%AF%E7%B3%BB%E7%BB%9F" class="hash-link" aria-label="2) 重启系统的直接链接" title="2) 重启系统的直接链接" translate="no">​</a></h3>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">reboot</span><br></div></code></pre></div></div>
<p>老一些的系统或热插拔不生效时,重启一次让内核重新扫描硬件最省事。支持热插拔的环境不重启也能识别,但重启是最稳妥的兜底做法。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-查看磁盘有没有被检测到">3) 查看磁盘有没有被检测到<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#3-%E6%9F%A5%E7%9C%8B%E7%A3%81%E7%9B%98%E6%9C%89%E6%B2%A1%E6%9C%89%E8%A2%AB%E6%A3%80%E6%B5%8B%E5%88%B0" class="hash-link" aria-label="3) 查看磁盘有没有被检测到的直接链接" title="3) 查看磁盘有没有被检测到的直接链接" translate="no">​</a></h3>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">fdisk</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-l</span><br></div></code></pre></div></div>
<p><code>fdisk -l</code> 会列出所有块设备及其分区。新磁盘的典型特征是:有容量、没有分区(下方没有 <code>/dev/sdb1</code> 之类的条目)。记下这个设备名,后面每一步都要用。也可以用 <code>lsblk</code> 对照确认,它以树形展示磁盘和分区的关系,更直观。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-进入磁盘开始分区">4) 进入磁盘,开始分区<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#4-%E8%BF%9B%E5%85%A5%E7%A3%81%E7%9B%98%E5%BC%80%E5%A7%8B%E5%88%86%E5%8C%BA" class="hash-link" aria-label="4) 进入磁盘,开始分区的直接链接" title="4) 进入磁盘,开始分区的直接链接" translate="no">​</a></h3>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">fdisk</span><span class="token plain"> /dev/</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain">磁盘名称</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><br></div></code></pre></div></div>
<p>这里的 <code>{磁盘名称}</code> 替换成上一步看到的设备名,比如 <code>sdb</code>。<code>fdisk</code> 是交互式工具,进去之后常用的几个指令:</p>
<ul>
<li class=""><code>n</code>:新建分区</li>
<li class=""><code>p</code>:打印当前分区表</li>
<li class=""><code>d</code>:删除分区</li>
<li class=""><code>w</code>:把改动写入磁盘并退出</li>
<li class=""><code>q</code>:不保存退出</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-把磁盘分区设置好可以只分一个区">5) 把磁盘分区设置好,可以只分一个区<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#5-%E6%8A%8A%E7%A3%81%E7%9B%98%E5%88%86%E5%8C%BA%E8%AE%BE%E7%BD%AE%E5%A5%BD%E5%8F%AF%E4%BB%A5%E5%8F%AA%E5%88%86%E4%B8%80%E4%B8%AA%E5%8C%BA" class="hash-link" aria-label="5) 把磁盘分区设置好,可以只分一个区的直接链接" title="5) 把磁盘分区设置好,可以只分一个区的直接链接" translate="no">​</a></h3>
<p>如果整块盘就是拿来存数据的,直接 <code>n</code> 新建一个主分区,起止扇区全部回车用默认值,把整盘划成一个区即可,然后 <code>w</code> 保存退出。分多个区在如今的场景下意义不大,反而增加管理成本。</p>
<p>一点提醒:传统 MBR 分区表最大只支持 2TB,超过 2TB 的磁盘要用 GPT 分区表,这时应改用 <code>parted</code> 或 <code>gdisk</code> 来分区。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-格式化分区">6) 格式化分区<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#6-%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%88%86%E5%8C%BA" class="hash-link" aria-label="6) 格式化分区的直接链接" title="6) 格式化分区的直接链接" translate="no">​</a></h3>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">mkfs.ext4 /dev/</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain">分区名称</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><br></div></code></pre></div></div>
<p>注意这里操作的对象是<strong>分区</strong>(比如 <code>/dev/sdb1</code>)而不是整块磁盘。<code>mkfs.ext4</code> 会在分区上创建 ext4 文件系统,写入超级块、inode 表等元数据结构。ext4 是目前 Linux 发行版最通用的选择;如果发行版默认用 XFS(比如 CentOS 系),换成 <code>mkfs.xfs</code> 也一样。</p>
<p>格式化会清空分区上的所有数据,执行前务必确认设备名没敲错。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-挂载分区">7) 挂载分区<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#7-%E6%8C%82%E8%BD%BD%E5%88%86%E5%8C%BA" class="hash-link" aria-label="7) 挂载分区的直接链接" title="7) 挂载分区的直接链接" translate="no">​</a></h3>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">mount</span><span class="token plain"> /dev/</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain">分区名称</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><span class="token plain"> /</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain">某个文件夹</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><br></div></code></pre></div></div>
<p>挂载点就是一个普通目录,不存在的话先 <code>mkdir</code> 创建,比如 <code>mkdir /data</code>。挂载后,写入 <code>/data</code> 的文件实际就落在新磁盘上。可以用 <code>df -h</code> 验证,能看到分区、容量和挂载点的对应关系。</p>
<p>如果挂载点目录里原本有文件,挂载后会被"遮住"看不到,卸载(<code>umount</code>)后又会恢复——这不是丢数据,但容易造成困惑,建议用空目录做挂载点。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-设置开机自动挂载">8) 设置开机自动挂载<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#8-%E8%AE%BE%E7%BD%AE%E5%BC%80%E6%9C%BA%E8%87%AA%E5%8A%A8%E6%8C%82%E8%BD%BD" class="hash-link" aria-label="8) 设置开机自动挂载的直接链接" title="8) 设置开机自动挂载的直接链接" translate="no">​</a></h3>
<p><code>mount</code> 命令的效果只在本次开机有效,重启后挂载就没了。要持久化,需要编辑 <code>/etc/fstab</code>:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">vim</span><span class="token plain"> /etc/fstab</span><br></div></code></pre></div></div>
<p>在文件末尾添加一行:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">/dev/(磁盘分区) /（挂载目录） ext4（文件格式）defaults 0 0</span><br></div></code></pre></div></div>
<p>这行配置共六个字段,依次是:</p>
<ol>
<li class=""><strong>设备</strong>:要挂载的分区,如 <code>/dev/sdb1</code>;</li>
<li class=""><strong>挂载点</strong>:目录路径,如 <code>/data</code>;</li>
<li class=""><strong>文件系统类型</strong>:和格式化时保持一致,这里是 <code>ext4</code>;</li>
<li class=""><strong>挂载选项</strong>:<code>defaults</code> 表示读写、允许执行等一组默认选项;</li>
<li class=""><strong>dump 备份标志</strong>:<code>0</code> 表示不用 dump 工具备份;</li>
<li class=""><strong>fsck 检查顺序</strong>:<code>0</code> 表示开机不做文件系统检查,根分区一般是 <code>1</code>,数据盘可设 <code>2</code> 或 <code>0</code>。</li>
</ol>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p><code>/etc/fstab</code> 写错可能导致系统启动失败进入紧急模式。保存后先执行 <code>mount -a</code> 验证——它会按 fstab 挂载所有未挂载的条目,没有报错再重启。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>用 UUID 代替设备名更稳。</strong> <code>/dev/sdb</code> 这类名字取决于内核识别顺序,增减磁盘后可能变化,导致 fstab 挂错盘。用 <code>blkid</code> 查出分区的 UUID,fstab 第一列写成 <code>UUID=xxxx</code> 的形式,就不受设备名漂移影响。</li>
<li class=""><strong>格式化前确认设备名。</strong> <code>mkfs</code> 敲错目标是不可逆的,操作前用 <code>lsblk</code> 或 <code>fdisk -l</code> 再核对一遍,确认容量和预期一致。</li>
<li class=""><strong>区分磁盘和分区。</strong> 分区、格式化、挂载各自作用的对象不同:<code>fdisk</code> 操作整盘(<code>/dev/sdb</code>),<code>mkfs</code> 和 <code>mount</code> 操作分区(<code>/dev/sdb1</code>),混着用是新手最常见的报错来源。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/06/08/linux-gua-zai-ci-pan#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>挂载新磁盘的主线就是四步:<code>fdisk -l</code> 确认识别、<code>fdisk</code> 分区、<code>mkfs.ext4</code> 格式化、<code>mount</code> 挂载,最后写 <code>/etc/fstab</code> 让配置在重启后依然生效。理解了"裸盘—分区—文件系统—挂载点"这条链路,再遇到扩容、换盘之类的需求,照着链路逐层排查就够了。</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[K3S 集群搭建]]></title>
            <link>https://tanzhuo.xyz/2022/06/02/k3s-ji-qun-da-jian</link>
            <guid>https://tanzhuo.xyz/2022/06/02/k3s-ji-qun-da-jian</guid>
            <pubDate>Thu, 02 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[记录用 k3sup 在阿里云 6 台服务器上搭建 HA etcd 高可用 K3S 集群的过程：环境准备、master/worker 节点部署命令与 k3sup 常用参数说明。]]></description>
            <content:encoded><![CDATA[<p>最近学习了K8S集群的部署，期间使用了很多的部署方式，例如官方给出的 <a href="https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/" target="_blank" rel="noopener noreferrer" class=""><strong>kubeadm</strong></a> 工具，青云的KK工具一键部署，也有github上开源免费或收费的一键部署工具，都尝试过，但最后选择了<a href="https://github.com/alexellis/k3sup" target="_blank" rel="noopener noreferrer" class="">K3S-up</a> 来进行集群的部署。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么选择-k3s">为什么选择 K3S<a href="https://tanzhuo.xyz/2022/06/02/k3s-ji-qun-da-jian#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-k3s" class="hash-link" aria-label="为什么选择 K3S的直接链接" title="为什么选择 K3S的直接链接" translate="no">​</a></h2>
<p>相对于K8S来说，K3S系统容器数量少，轻量级，并且默认使用 containerd 作为容器运行时，内部的 ingress 使用的是 go 语言开发的 traefik，集成了 SQLite 代替 Etcd。但在多个 master 节点的场景下最好使用 Etcd 组件来保证数据一致性，从而实现 HA。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="环境规划">环境规划<a href="https://tanzhuo.xyz/2022/06/02/k3s-ji-qun-da-jian#%E7%8E%AF%E5%A2%83%E8%A7%84%E5%88%92" class="hash-link" aria-label="环境规划的直接链接" title="环境规划的直接链接" translate="no">​</a></h2>
<p>本次搭建选用阿里云 6 台共享型服务器，搭建 HA-etcd 高可用 k3s 集群：</p>
<ul>
<li class="">3 台 master（2核4g）</li>
<li class="">3 台 worker（2核4g）</li>
</ul>
<p>系统使用 Debian 10.10。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前提条件">前提条件<a href="https://tanzhuo.xyz/2022/06/02/k3s-ji-qun-da-jian#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6" class="hash-link" aria-label="前提条件的直接链接" title="前提条件的直接链接" translate="no">​</a></h2>
<ol>
<li class="">先关闭防火墙，并更新软件源：</li>
</ol>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">apt-get</span><span class="token plain"> update</span><br></div></code></pre></div></div>
<ol start="2">
<li class="">安装 cgroupfs-mount，然后重启机器：</li>
</ol>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token function" style="color:hsl(207, 82%, 66%)">apt-get</span><span class="token plain"> </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> cgroupfs-mount</span><br></div></code></pre></div></div>
<ol start="3">
<li class="">
<p>下载 K3s 部署软件。由于 <a href="https://github.com/k3s-io/k3s" target="_blank" rel="noopener noreferrer" class="">k3s</a> 资源在 github 上，国内服务器下载过慢，可以优先去 github 下载 k3s 安装二进制放入服务器中，从而免去下载耗时。</p>
</li>
<li class="">
<p>检查环境是否有问题：</p>
</li>
</ol>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">k3s </span><span class="token builtin class-name" style="color:hsl(29, 54%, 61%)">help</span><span class="token plain">          </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看基础命令</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">k3s check-config  </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 检测环境是否满足运行条件</span><br></div></code></pre></div></div>
<p>所有服务器均需重复以上 1-4 步骤。如果嫌麻烦，可以先把一台服务器执行好这些步骤后复制系统盘镜像，后续创建云服务器时直接选择该镜像即可。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="集群部署">集群部署<a href="https://tanzhuo.xyz/2022/06/02/k3s-ji-qun-da-jian#%E9%9B%86%E7%BE%A4%E9%83%A8%E7%BD%B2" class="hash-link" aria-label="集群部署的直接链接" title="集群部署的直接链接" translate="no">​</a></h2>
<ol>
<li class="">环境检查没有问题后，使用 k3sup 初始化第一个控制平面 master 节点：</li>
</ol>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># --cluster 表示以嵌入式 etcd 的集群模式启动</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">k3sup </span><span class="token function" style="color:hsl(207, 82%, 66%)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--ip</span><span class="token plain"> </span><span class="token comment" style="color:hsl(220, 10%, 40%)">#{your_service_ip} --user root --cluster --k3s-version v1.23.4+k3s1 --print-command</span><br></div></code></pre></div></div>
<ol start="2">
<li class="">其他 master 节点加入集群：</li>
</ol>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># --server 表示以 master 身份加入，--server-ip 指向第一台 master</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">k3sup </span><span class="token function" style="color:hsl(207, 82%, 66%)">join</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--ip</span><span class="token plain"> </span><span class="token comment" style="color:hsl(220, 10%, 40%)">#{next_service_ip} --user root --server-user root --server-ip #{first_service_ip} --server --k3s-version v1.23.4+k3s1 --print-command</span><br></div></code></pre></div></div>
<ol start="3">
<li class="">其他 worker 节点加入集群：</li>
</ol>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">k3sup </span><span class="token function" style="color:hsl(207, 82%, 66%)">join</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--user</span><span class="token plain"> root --server-ip </span><span class="token variable" style="color:hsl(207, 82%, 66%)">$SERVER1</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--ip</span><span class="token plain"> </span><span class="token variable" style="color:hsl(207, 82%, 66%)">$AGENT1</span><span class="token plain"> --print-command</span><br></div></code></pre></div></div>
<ol start="4">
<li class="">执行节点状态检查：</li>
</ol>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">kubectl get nodes</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="k3sup-常用参数">k3sup 常用参数<a href="https://tanzhuo.xyz/2022/06/02/k3s-ji-qun-da-jian#k3sup-%E5%B8%B8%E7%94%A8%E5%8F%82%E6%95%B0" class="hash-link" aria-label="k3sup 常用参数的直接链接" title="k3sup 常用参数的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><code>--cluster</code> — 使用嵌入式 etcd（嵌入式 HA）在集群模式下启动此服务器</li>
<li class=""><code>--skip-install</code> — 如果已经安装了 k3s，可以运行此命令来获取 kubeconfig</li>
<li class=""><code>--ssh-key</code> — 为远程登录指定 SSH 密钥路径</li>
<li class=""><code>--local-path</code> — 设置 kubeconfig 保存路径，默认值为 <code>./kubeconfig</code>，默认情况下此文件会被覆盖</li>
<li class=""><code>--merge</code> — 将配置合并到现有文件中而不是覆盖，例如将配置添加到默认的 kubectl 配置中：<code>--local-path ~/.kube/config --merge</code></li>
<li class=""><code>--context</code> — 设置 kubeconfig 上下文的名称，默认值为 <code>default</code></li>
<li class=""><code>--ssh-port</code> — 指定 SSH 端口，默认为 22</li>
<li class=""><code>--k3s-extra-args</code> — 传递给 k3s 安装程序的可选额外参数，需用引号括起来，例如 <code>--k3s-extra-args '--no-deploy traefik'</code> 或 <code>--k3s-extra-args '--docker'</code>；多个参数合并写在单引号内：<code>--k3s-extra-args '--no-deploy traefik --docker'</code></li>
<li class=""><code>--k3s-version</code> — 设置 k3s 的特定版本，例如 <code>v1.21.1</code></li>
<li class=""><code>--k3s-channel</code> — 按通道设置 k3s 版本，例如 <code>stable</code></li>
<li class=""><code>--ipsec</code> — 强制 k3s 使用 <code>--flannel-backend ipsec</code></li>
<li class=""><code>--print-command</code> — 打印出通过 SSH 发送到远程计算机的命令</li>
<li class=""><code>--datastore</code> — 将 SQL 连接字符串传递给 k3s 的 <code>--datastore-endpoint</code>，格式必须<a href="https://rancher.com/docs/k3s/latest/en/installation/ha/" target="_blank" rel="noopener noreferrer" class="">按 Rancher 文档中 k3s 要求的格式</a>填写</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="集群控制面板">集群控制面板<a href="https://tanzhuo.xyz/2022/06/02/k3s-ji-qun-da-jian#%E9%9B%86%E7%BE%A4%E6%8E%A7%E5%88%B6%E9%9D%A2%E6%9D%BF" class="hash-link" aria-label="集群控制面板的直接链接" title="集群控制面板的直接链接" translate="no">​</a></h2>
<p>k3s 可以和 kuboard 组合使用。kuboard 控制面板文档：<a href="https://kuboard.cn/overview/share-coder.html" target="_blank" rel="noopener noreferrer" class="">Kuboard介绍 | Kuboard</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2022/06/02/k3s-ji-qun-da-jian#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这样我们就拥有了一个 k3s 集群，并且可以横向扩展我们的业务规模了。整个流程用 k3sup 通过 SSH 远程完成，比手工执行 kubeadm 省事不少，也适合在国内网络环境下反复重建。K3S 本身轻量，配合嵌入式 etcd 就能做到 master 高可用，对小规模集群是个不错的选择。后续再集成我们的各种监控以及 service mesh 套件。</p>]]></content:encoded>
            <category>K8S</category>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[神界：原罪2]]></title>
            <link>https://tanzhuo.xyz/2022/01/13/shen-jie-yuan-zui-2</link>
            <guid>https://tanzhuo.xyz/2022/01/13/shen-jie-yuan-zui-2</guid>
            <pubDate>Thu, 13 Jan 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[绿维珑大陆中，近乎所有关于绿维珑诞生的书中都会讲述到七神与绿维珑的起源。本文梳理七神的诞生，以及神谕者与觉醒者的出现。]]></description>
            <content:encoded><![CDATA[<p>绿维珑大陆中，近乎所有关于绿维珑诞生的书中都会讲述到七神与绿维珑的起源。虽然无人能够肯定其事实真伪，但却在各个种族中故老相传。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七神的诞生">七神的诞生<a href="https://tanzhuo.xyz/2022/01/13/shen-jie-yuan-zui-2#%E4%B8%83%E7%A5%9E%E7%9A%84%E8%AF%9E%E7%94%9F" class="hash-link" aria-label="七神的诞生的直接链接" title="七神的诞生的直接链接" translate="no">​</a></h2>
<p>传说在位面、世界和所有内在事物诞生之前，除了纯粹且未被利用的能量外什么也没有——那是被称为源力的创造生命之原材料。与之相对的是空虚——毁灭和虚无。这两种截然相反的东西相互平衡地存在着。有平衡和摩擦的地方，生命扎根，宇宙诞生。然后，就像土地的植物寻求阳光一样，源力找到了它能创造的位面之路。同样地，空间迁移到宇宙的位面开始产生巨大的影响。而在许多位面上，源力和虚空都寻求占据上风。在源力取胜的地方，生命绽放。绿维珑就是一个这样的世界。</p>
<p>随着其强大的源力力量，使其自发地构想出具有力量的原始生命——元素神，以及目前所知神的最初实例。随着时间的推移，这些神或增长变强，或萎缩消失，而这些力量的改变都取决于他们造物的所作所为。比如很快就出现的一些原始信仰的神灵，战争之神和爱情之神，随着凡人对战争与爱情的了解而不断增强力量。</p>
<p>随着众神的成长，六个新兴的种族在后来被称之为“崇拜”的行为中，正式形成了这种源力的集中。从而出现了七位主神——每一位主神对应相应的种族（其中一个对应着巫师）。七神通过自己信徒的崇拜来获取源力，并利用它获得了力量——随着他们的神的力量，种族也变得更强大了。而那些无法竞争过的神或者力量较小的神，就在这一过程中销声匿迹了。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-src-http___img.kxdw.com_2018_0108_20180108110747256.jpg-refer-http___img.kxdw-13ed6fe85d129658cdc922a7dc7f55d5.webp" width="1920" height="1080" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="神谕者与觉醒者的出现">神谕者与觉醒者的出现<a href="https://tanzhuo.xyz/2022/01/13/shen-jie-yuan-zui-2#%E7%A5%9E%E8%B0%95%E8%80%85%E4%B8%8E%E8%A7%89%E9%86%92%E8%80%85%E7%9A%84%E5%87%BA%E7%8E%B0" class="hash-link" aria-label="神谕者与觉醒者的出现的直接链接" title="神谕者与觉醒者的出现的直接链接" translate="no">​</a></h2>
<p>随着七神的力量不断增长，他们之间的摩擦也愈发激烈。没有人愿意让步，而他们之间的斗争是由各自种族来证明。战火纷乱，在很长一段时间里没有什么事情能够让他们再度团结起来……直到虚空来临。</p>
<p>尽管现实世界的帷幕稳固，但它并不完美。虚空得以能够周期性找到一个缝隙入侵，威胁着种族与神明本身。七神知道他们必须联合起来来阻止虚空，于是创造了神谕者——一个孤独的凡人勇士。他们将自己神圣的力量给予他，使他成为一个超越死亡而成为神明化身的英雄。这个神谕者会击退虚空，将它驱逐出现实的帷幕，回到它自己的领域。就这样发生了很多次——一名神谕者会出现并击退虚空。最近一次拯救世界的神谕者是卢锡安，他与虚空多次战斗并获得胜利。但如今卢锡安已死，一位新的神谕者还未出现。</p>
<p>在第一次混乱战争中帮助击退虚空的凡人英雄后裔，一批被称为“觉醒者”的人不断出现。他们的血统允许觉醒者以凡人中罕见的机敏技巧来观察和操控源力。利用这些能力，觉醒者成为了唯一能够接受神之馈赠——蕴含七神全部力量的凡人。神性觉醒是在所有种族中出现，并以不同的方式在每个人身上显现。他们都有一种与生俱来的能力去观察源力，如果经过适当的训练，就能够吸收源力，用它来完成难以置信的创造与毁灭。</p>]]></content:encoded>
            <category>游戏</category>
        </item>
        <item>
            <title><![CDATA[Jenkins自动化发布]]></title>
            <link>https://tanzhuo.xyz/2021/11/19/jenkins</link>
            <guid>https://tanzhuo.xyz/2021/11/19/jenkins</guid>
            <pubDate>Fri, 19 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[记录用 Jenkins 搭建自动化发布流程:拉取代码、maven 构建、docker-compose 打包镜像推送私有仓库,再远程触发线上部署,附踩坑与清理建议。]]></description>
            <content:encoded><![CDATA[<p>手工发布做多了,每次打包、传包、重启一套流程下来既耗时间又容易出错。这篇记录我用 Jenkins 把整个发布过程串成流水线的配置步骤。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tanzhuo.xyz/2021/11/19/jenkins#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>在没有流水线之前,一次发布大概是这样:本地打包、scp 传到服务器、登上去停服务、替换 jar 包、再启动。步骤不复杂,但全靠人肉执行,环境配置改漏一个、包传错一台机器,都是线上事故的常见来源。把这些动作交给 Jenkins,一方面是省事,更重要的是让发布过程可重复、可追溯——每次构建都有记录,出问题能回滚到指定版本的镜像。</p>
<p>记录一下最近配置Jenkins的发布流程的部分步骤</p>
<p>整体流程：使用 Jenkins 进行线上发布并打包docker镜像上传至私有docker镜像仓库、并配置 docker login 线上推送/拉取镜像发布运行。</p>
<p>简单拆开看,这条流水线做了四件事:从 git 拉代码并替换环境配置、maven 构建出 jar 包、用 docker-compose 打成镜像推到私有仓库、最后远程通知目标服务器拉取新镜像运行。下面按这个顺序展开。</p>
<p>安装docker 拉取 Jenkins 镜像就不再描述，有很多现成的文章。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="插件集成">插件集成<a href="https://tanzhuo.xyz/2021/11/19/jenkins#%E6%8F%92%E4%BB%B6%E9%9B%86%E6%88%90" class="hash-link" aria-label="插件集成的直接链接" title="插件集成的直接链接" translate="no">​</a></h2>
<p>Jenkins 本体只是一个调度器,构建能力基本都靠全局工具配置和插件补齐。所以第一步是在「全局工具配置」里把构建要用到的组件配好,Jenkins 支持自动下载安装,也可以指向宿主机上已有的安装路径。</p>
<p>配置并安装maven、jdk、git、nodeJs、docker等基本组件</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2021-11-image-1-e2d082c744c33296646996dab9f2ca3a.webp" width="1587" height="886" class="img_ev3q"></p>
<p>工具配好之后,再到插件中心装对接外部系统的插件。这里的原则是按需安装:代码托管在 gitlab 就装 gitlab 插件,它能接收 gitlab 的 webhook 推送,实现提交代码后自动触发构建。</p>
<p>安装对应各种支持插件、如gitlab。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2021-11-image-15888d64c81502e58d0621da66a56419.webp" width="1600" height="165" class="img_ev3q"></p>
<p>nodejs 插件前端发布使用</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="1597" height="112" class="img_ev3q"></p>
<p>前后端可以走同一套 Jenkins,只是构建工具不同——后端用 maven,前端任务里指定 nodeJs 版本跑 npm build 即可。</p>
<p>最后还需要一个能连接远程服务器执行命令的插件。Jenkins 所在机器和线上运行的机器通常不是同一台,构建产物推到镜像仓库之后,得有办法通知目标服务器去拉取并重启,这一步就靠它通过 SSH 在远端执行脚本完成。</p>
<p>连接远程服务端并执行命令插件</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="1600" height="165" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="创建流水线任务">创建流水线任务<a href="https://tanzhuo.xyz/2021/11/19/jenkins#%E5%88%9B%E5%BB%BA%E6%B5%81%E6%B0%B4%E7%BA%BF%E4%BB%BB%E5%8A%A1" class="hash-link" aria-label="创建流水线任务的直接链接" title="创建流水线任务的直接链接" translate="no">​</a></h2>
<p>插件就绪后开始建任务。任务的本质是把一串构建步骤按顺序编排起来,每个步骤指定用哪个组件、执行什么命令,上一步的产物就是下一步的输入。</p>
<p>创建任务、并配置任务各个环节执行命令与使用组件</p>
<p>首先配置拉取git库地址与项目代码</p>
<p>后续替换源码中的环境值、如dev环境替换为pro nacos 连接地址与命令空间等</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2021-11-image-4-d082c74127085b930fc6786f03f16aff.png" width="1269" height="136" class="img_ev3q"></p>
<p>替换环境值这一步值得多说一句。开发环境和生产环境的配置中心地址、命名空间往往不同,如果代码仓库里默认写的是 dev 配置,构建生产包之前就要把它替换掉,否则服务上线后会连到测试环境的 nacos。</p>
<p>我使用的是最简单的sed -i 命令，该命令可以匹配正则表达式从而替换文本字符串。</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># -i 表示直接修改文件本身;s/旧值/新值/g 为正则替换,g 表示替换所有匹配</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">sed</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-i</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">'s/dev-nacos-addr/pro-nacos-addr/g'</span><span class="token plain"> src/main/resources/bootstrap.yml</span><br></div></code></pre></div></div>
<p>sed 的好处是零依赖、脚本里一行就能解决;缺点是替换关系散落在 Jenkins 任务配置里,配置项多了不好维护。项目规模上来之后,可以考虑用 maven profile 或配置中心的多环境能力来做,把环境差异收敛到一处。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="资源打包与推送">资源打包与推送<a href="https://tanzhuo.xyz/2021/11/19/jenkins#%E8%B5%84%E6%BA%90%E6%89%93%E5%8C%85%E4%B8%8E%E6%8E%A8%E9%80%81" class="hash-link" aria-label="资源打包与推送的直接链接" title="资源打包与推送的直接链接" translate="no">​</a></h2>
<p>使用maven 进行jdk build，构建项目jar包</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2021-11-image-5-7fe705d054ce0e93b99727213b30f198.webp" width="1475" height="788" class="img_ev3q"></p>
<p>打包完成之后使用shell命令把target包下的jar包cp到对应docker-compose文件下，此处需要提前写好docker-compose文件与对应服务的docker-file文件。</p>
<p>之所以要提前写好这两个文件:Dockerfile 描述单个服务怎么打成镜像(基础镜像、拷贝 jar、启动命令),docker-compose 则把多个服务的构建声明汇总在一起,这样一条命令就能批量出镜像,不用每个服务单独 docker build。</p>
<p>执行docker-compose进行批量build镜像</p>
<p>build完成之后执行docker tag 对镜像进行版本打标</p>
<p>打标是为了让每次发布的镜像都有唯一版本号,而不是全部叫 latest。用 Jenkins 内置的构建号做 tag 是个省事的办法,回滚时直接指定历史版本号即可。</p>
<p>在镜像打标完成后docker push上传到镜像仓库</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 用构建号给镜像打版本标,再推送到私有仓库</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> tag myapp:latest registry.example.com/myapp:</span><span class="token variable" style="color:hsl(207, 82%, 66%)">${BUILD_NUMBER}</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> push registry.example.com/myapp:</span><span class="token variable" style="color:hsl(207, 82%, 66%)">${BUILD_NUMBER}</span><br></div></code></pre></div></div>
<p>推送前记得 Jenkins 所在机器和目标服务器都要先 docker login 私有仓库,否则 push/pull 都会因为鉴权失败中断。</p>
<p>远程连接目标服务器或者k8s master 节点 执行job命令并给予镜像下载地址。</p>
<p>到这一步,构建机的工作就结束了。远程命令里只需要告诉目标服务器新镜像的完整地址,由它自己 pull 镜像并重启容器——构建和运行彻底分离,目标服务器上不需要装任何构建工具。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="历史资源删除">历史资源删除<a href="https://tanzhuo.xyz/2021/11/19/jenkins#%E5%8E%86%E5%8F%B2%E8%B5%84%E6%BA%90%E5%88%A0%E9%99%A4" class="hash-link" aria-label="历史资源删除的直接链接" title="历史资源删除的直接链接" translate="no">​</a></h2>
<p>打包之后上传到私服之后别忘记清空本地工作空间中无用的资源，以及docker镜像等资源，避免多次发布后出现资源占用过高问题。这样就可以形成一个闭环操作，可以多次发布项目版本。</p>
<p>清理可以直接作为流水线的最后一个步骤:删掉工作空间里的构建产物,再清掉本次构建出来的本地镜像。docker 的镜像是分层存储的,旧版本镜像不清理会一直堆积,几十次发布之后磁盘被占满,构建就会莫名失败。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2021/11/19/jenkins#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ol>
<li class="">
<p>Jenkins 跑在容器里时,想在任务里执行 docker 命令,需要把宿主机的 docker.sock 挂载进容器,否则容器内找不到 docker 守护进程。</p>
</li>
<li class="">
<p>私有仓库如果走 http 而不是 https,docker 默认会拒绝连接,需要在推拉两端的 docker 配置里把仓库地址加入 insecure-registries。</p>
</li>
<li class="">
<p>sed 替换配置时注意特殊字符转义,替换内容里带 <code>/</code> 时可以换用 <code>#</code> 之类的分隔符,避免和 sed 自身的分隔符冲突。</p>
</li>
<li class="">
<p>远程执行命令的凭据(SSH 私钥、仓库账号密码)用 Jenkins 的凭据管理来存,不要明文写在任务的 shell 脚本里。</p>
</li>
</ol>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>镜像 tag 尽量和 git 提交或构建号关联起来,排查线上问题时能快速对应到具体一次代码变更。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2021/11/19/jenkins#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>整条流水线跑通之后,发布就变成了点一次构建按钮(或提交代码自动触发):拉代码、替换配置、maven 打包、docker-compose 出镜像、打标推送、远程拉起新版本、清理现场,全程无人工介入。相比手工发布,收益不只是快,更在于每一步都固化在任务配置里,发布结果稳定可预期,回滚也只是换个镜像 tag 的事。</p>]]></content:encoded>
            <category>技术</category>
            <category>CI/CD</category>
        </item>
        <item>
            <title><![CDATA[巫师3：狂猎]]></title>
            <link>https://tanzhuo.xyz/2021/08/10/wu-shi-3</link>
            <guid>https://tanzhuo.xyz/2021/08/10/wu-shi-3</guid>
            <pubDate>Tue, 10 Aug 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[《巫师3：狂猎》延承《猎魔人》系列小说的世界观。本文简述天球交汇的背景设定、猎魔人的由来，以及游戏主线的开端。]]></description>
            <content:encoded><![CDATA[<p>游戏延承了《<a href="https://baike.baidu.com/item/%E7%8C%8E%E9%AD%94%E4%BA%BA/7252589?fromModule=lemma_inlink&amp;ref=tanzhuo.xyz" target="_blank" rel="noopener noreferrer" class="">猎魔人</a>》系列小说的世界观。在小说故事发生前1500年前发生的一场灾难，不同的维度之间的世界发生了碰撞，世界直接被连接并互相渗透。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="天球交汇">天球交汇<a href="https://tanzhuo.xyz/2021/08/10/wu-shi-3#%E5%A4%A9%E7%90%83%E4%BA%A4%E6%B1%87" class="hash-link" aria-label="天球交汇的直接链接" title="天球交汇的直接链接" translate="no">​</a></h2>
<p>这场灾难被称为“天球交汇”，它让很多来自各式各样来自不同世界中的生物被困在这个世界中，例如，食尸鬼和血棘尸魔和高阶吸血鬼，它们就是天球交汇所留下的残物。这些生物带有自己独特的生态，是过去时代的遗种。根据精灵的传说，人类因为自己之前的世界被毁灭，来到了这个世界。人类的祖先学会了如何驾驭混沌之力，最早的人类法师从此诞生，并对抗着其他来自不同世界的生物。但由于怪物四处散布且难以击败，在普通人不具备与怪物对抗的情况下，猎魔人由此孕育而生，他们是专门被普通人雇佣来猎杀怪物的变种人类，并以此维持生计，保护人们不受怪物威胁。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-src-http___img-baofun.zhhainiao.com_fs_9a80f29dd057d4656d3e1132ad4bc3e3.jpg-refer-http___img-baofun.zhhainiao-794c46ab1ff3d30afb6e83e764945a42.webp" width="1920" height="1080" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="游戏剧情">游戏剧情<a href="https://tanzhuo.xyz/2021/08/10/wu-shi-3#%E6%B8%B8%E6%88%8F%E5%89%A7%E6%83%85" class="hash-link" aria-label="游戏剧情的直接链接" title="游戏剧情的直接链接" translate="no">​</a></h2>
<p>杰洛特恢复记忆后，开始四处寻找叶奈法的踪迹。最终，叶奈法联系了数年未见的杰洛特，请杰洛特前往威洛拜的村庄碰头。然而当杰洛特赶到约定地点时，整座村庄已经被大军夷为平地，叶奈法也不见踪迹。途中杰洛特碰见维瑟米尔，在他的帮助下一同追踪叶奈法的踪迹。在路途中，杰洛特梦见了凯尔莫罕的遭遇，梦境警示杰洛特他的养女希里遭遇了某种问题。杰洛特一行于白果园驻留，叶奈法在白果园旅馆现身，身旁有尼弗迦德士兵护卫。这对旧情人尚未叙旧，叶奈法就坚决要求杰洛特尽快与她一同前往维吉马面见尼弗迦德皇帝。他们的队伍在途中遭遇狂猎袭击，但终归是抵达了目的地。在与尼弗迦德皇帝的会面中，杰洛特了解到希里已经从遥远的国度归来，正遭受生命危险，被狂猎追捕。游戏主线由此正式开始。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-src-http___img.3dmgame.com_uploads_images_news_20211007_1633573821_869066.jpg-refer-http___img.3dmgame-00debfd584cc583ff322591dbed76ddb.webp" width="1920" height="1200" class="img_ev3q"></p>]]></content:encoded>
            <category>游戏</category>
        </item>
        <item>
            <title><![CDATA[Java 逻辑引擎的构思]]></title>
            <link>https://tanzhuo.xyz/2021/06/16/javaluo-ji-yin-qing-de-gou-si</link>
            <guid>https://tanzhuo.xyz/2021/06/16/javaluo-ji-yin-qing-de-gou-si</guid>
            <pubDate>Wed, 16 Jun 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[一个把工厂、代理、策略、装饰模式与 SPI 思想封装进可视化逻辑引擎的构思：像 Kettle 一样拖拽组件编排业务逻辑，让后端开发少写重复代码。]]></description>
            <content:encoded><![CDATA[<p>在工作中多年之后，深知设计模式的重要性。设计模式的合理使用可以让复杂的业务逻辑实现得更加灵活，更好地支持多态、易扩展、易维护。那么能不能更进一步——做一款本身就内置了设计模式的逻辑引擎？这篇笔记记录一下这个构思。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="想解决什么问题">想解决什么问题<a href="https://tanzhuo.xyz/2021/06/16/javaluo-ji-yin-qing-de-gou-si#%E6%83%B3%E8%A7%A3%E5%86%B3%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98" class="hash-link" aria-label="想解决什么问题的直接链接" title="想解决什么问题的直接链接" translate="no">​</a></h2>
<p>后端业务开发有一个尴尬的现实：大量代码其实是同一套骨架的重复——接收参数、查询数据、按条件走不同分支、组装结果、落库。每次都要手写这套流程，设计模式用得好的人把它抽象得漂亮一点，用得不好的人写成一大坨 if-else。</p>
<p>问题不在于开发人员不懂设计模式，而在于每个项目都要把工厂、策略这些模式重新落地一遍，落地质量还参差不齐。如果这层抽象能沉淀到一个引擎里，业务开发就只需要关心真正的业务逻辑本身。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心构思">核心构思<a href="https://tanzhuo.xyz/2021/06/16/javaluo-ji-yin-qing-de-gou-si#%E6%A0%B8%E5%BF%83%E6%9E%84%E6%80%9D" class="hash-link" aria-label="核心构思的直接链接" title="核心构思的直接链接" translate="no">​</a></h2>
<p>我的设想是：设计一款专门为后端 Java 开发人员量身定制、本身就具备设计模式的 UI 逻辑引擎。开发人员只需要进行逻辑组件的组合，然后双击组件书写 SQL 或 Java 代码，把组件的成员变量在组件之间传递，并最终入库。</p>
<p>原理类似于目前的 Kettle 工具——Kettle 做的是数据抽取与转换的可视化编排，这个引擎做的则是业务逻辑的可视化编排。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2021-06-image-4c7820a68dee74f2dae3cf60411a78e5.webp" width="892" height="517" class="img_ev3q"></p>
<p>组件是最小的逻辑单元，每个组件只做一件事：一次查询、一次校验、一次计算或一次写入。复杂业务就是这些单元按顺序或条件组合出来的一张流程图。如果有这样的逻辑引擎，Java 开发势必能够大幅度减少重复工作量。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="设计模式如何支撑">设计模式如何支撑<a href="https://tanzhuo.xyz/2021/06/16/javaluo-ji-yin-qing-de-gou-si#%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%A6%82%E4%BD%95%E6%94%AF%E6%92%91" class="hash-link" aria-label="设计模式如何支撑的直接链接" title="设计模式如何支撑的直接链接" translate="no">​</a></h2>
<p>这个构思的灵感来自我在工作中实际使用的几种模式的组合：工厂模式、代理模式、策略模式、装饰模式，再加上 SPI 的思想。它们在引擎里各有分工：</p>
<ol>
<li class="">
<p>工厂模式：负责根据配置创建具体的逻辑组件实例，UI 上拖出来的每个节点，运行时都由工厂产出对应对象。</p>
</li>
<li class="">
<p>策略模式：每个逻辑组件就是一个策略实现，同一个流程节点可以替换不同实现而不影响整体编排。</p>
</li>
<li class="">
<p>代理模式：执行入口收敛到代理策略类，由它统一做参数传递、异常处理这类横切逻辑，再转发给真正的组件。</p>
</li>
<li class="">
<p>装饰模式：在不改动组件本身的前提下叠加能力，比如给某个组件包上日志、缓存的外壳。</p>
</li>
<li class="">
<p>SPI 思想：组件以约定接口对外暴露，新组件按接口实现并注册即可被引擎发现，引擎本身不需要改代码。</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="运行机制">运行机制<a href="https://tanzhuo.xyz/2021/06/16/javaluo-ji-yin-qing-de-gou-si#%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="运行机制的直接链接" title="运行机制的直接链接" translate="no">​</a></h2>
<p>整体流转是配置驱动的：通过 UI 组件生成配置文件，配置文件告诉代理策略类去调用其中对应组件逻辑单元的 .class 文件。在系统启动时加载所有的逻辑策略，确保这些 Spring Bean 都能正常加载进内存——把类加载和 Bean 初始化的成本留在启动期，运行期只做查表和转发。</p>
<p>后续执行全部通过代理策略类进行逻辑处理，组装每一个单独的 Bean，从而完成一个复杂的业务逻辑。</p>
<p>这样的实现也符合单一职责原则与开闭原则：每个组件职责单一，新增业务通过新增组件和改配置完成，而不是修改已有代码。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="和低代码平台的关系">和低代码平台的关系<a href="https://tanzhuo.xyz/2021/06/16/javaluo-ji-yin-qing-de-gou-si#%E5%92%8C%E4%BD%8E%E4%BB%A3%E7%A0%81%E5%B9%B3%E5%8F%B0%E7%9A%84%E5%85%B3%E7%B3%BB" class="hash-link" aria-label="和低代码平台的关系的直接链接" title="和低代码平台的关系的直接链接" translate="no">​</a></h2>
<p>可能在目前市场上，对应这个构思的产品就是低代码平台或者零代码平台了吧。不过定位上有个差别：低代码平台大多面向业务人员或全栈场景，试图连页面一起生成；而这个构思只面向后端 Java 开发者，不回避写 SQL 和 Java 代码，只是把编排和设计模式这层骨架交给引擎。</p>
<p>这也是我觉得它可行的原因——不追求"不写代码"，只追求"不写重复的结构代码"。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2021/06/16/javaluo-ji-yin-qing-de-gou-si#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这篇笔记本质上是一个想法的存档：把工厂、代理、策略、装饰与 SPI 组合成一个配置驱动的逻辑引擎，用可视化编排替代手写的流程骨架。它未必需要做成一个大而全的平台，哪怕先在一个项目里把"组件 + 配置 + 代理策略"这套机制跑通，也能验证这个方向的价值。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Vuetify UI库]]></title>
            <link>https://tanzhuo.xyz/2021/02/22/vuetify</link>
            <guid>https://tanzhuo.xyz/2021/02/22/vuetify</guid>
            <pubDate>Mon, 22 Feb 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Vuetify 是基于 Vue.js 的 Material Design 组件框架，介绍它的主要特点、文档质量与适用场景，以及选型时需要注意的地方。]]></description>
            <content:encoded><![CDATA[<p>Vuetify 是一个基于 Vue.js 的 Material Design 组件框架，提供了丰富的 UI 组件和样式，可以帮助开发者更快速地构建漂亮、响应式的 Web 应用程序。Vuetify 遵循 Material Design 规范，提供了大量标准化的组件，比如按钮、卡片、表格、表单、菜单、导航、对话框等，可以快速构建出具有一致性和美观性的用户界面。</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2023-05-image-5-c0296426bf2bb007f952c88106fb31f3.webp" width="1353" height="319" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么要关注-ui-组件库">为什么要关注 UI 组件库<a href="https://tanzhuo.xyz/2021/02/22/vuetify#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%85%B3%E6%B3%A8-ui-%E7%BB%84%E4%BB%B6%E5%BA%93" class="hash-link" aria-label="为什么要关注 UI 组件库的直接链接" title="为什么要关注 UI 组件库的直接链接" translate="no">​</a></h2>
<p>做前端项目时，界面上大量的工作其实是重复的：按钮、表单校验、弹窗、表格分页，每个项目都要来一遍。自己从零写不仅费时间，还很难保证视觉和交互的一致性。一套成熟的组件库把这些通用部分沉淀下来，让人把精力放在业务逻辑上，这是我持续留意各家 UI 库的原因。</p>
<p>Vue 生态里可选的组件库不少，Element、Ant Design Vue 走的是偏后台管理的设计语言，而 Vuetify 选择了另一条路：完整实现 Google 的 Material Design 规范。如果项目本身希望贴近 Material 风格，或者要和 Android 端保持视觉统一，Vuetify 是一个很自然的选项。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="主要特点">主要特点<a href="https://tanzhuo.xyz/2021/02/22/vuetify#%E4%B8%BB%E8%A6%81%E7%89%B9%E7%82%B9" class="hash-link" aria-label="主要特点的直接链接" title="主要特点的直接链接" translate="no">​</a></h2>
<p>Vuetify 的主要特点包括：</p>
<ol>
<li class="">基于 Vue.js：Vuetify 是一个基于 Vue.js 的组件框架，可以很好地与 Vue.js 应用程序集成，提供了丰富的组件和样式。组件通过 props 配置、通过事件和插槽扩展，写法和普通 Vue 组件没有区别，学习成本主要在熟悉各组件的属性上。</li>
<li class="">Material Design 风格：Vuetify 遵循 Material Design 规范，提供了大量标准化的组件和样式，可以帮助开发者构建出具有一致性和美观性的用户界面。规范不只是配色和圆角，还包括阴影层级、动效曲线、间距体系这些细节，跟着规范走，页面不容易做得杂乱。</li>
<li class="">响应式设计：Vuetify 提供了响应式设计，可以根据屏幕大小和设备类型自动调整布局和样式。它内置了一套栅格系统和断点（xs 到 xl），布局组件会根据视口宽度自动切换，移动端适配基本不需要额外写媒体查询。</li>
<li class="">插件化设计：Vuetify 使用插件化设计，可以很方便地扩展和定制组件，满足不同的业务需求。主题色、字体、组件默认行为都可以在初始化时统一配置，也支持按需引入组件来控制打包体积。</li>
<li class="">丰富的组件库：Vuetify 提供了丰富的 UI 组件和样式，包括按钮、卡片、表格、表单、菜单、导航、对话框等，可以快速构建出漂亮、响应式的 Web 应用程序。除了基础组件，日期选择器、数据表格这类实现起来比较繁琐的组件也都有覆盖。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="文档与生态">文档与生态<a href="https://tanzhuo.xyz/2021/02/22/vuetify#%E6%96%87%E6%A1%A3%E4%B8%8E%E7%94%9F%E6%80%81" class="hash-link" aria-label="文档与生态的直接链接" title="文档与生态的直接链接" translate="no">​</a></h2>
<p>vuetify也提供了完善的中文文档、目前来说我觉得他们的文档写得比较用心、组件的所有的属性与状态都是描述得相当清晰。每个组件页面都配有可交互的示例，属性、事件、插槽分栏列出，查起来很顺手。文档质量在选型时经常被低估——组件库用到后期，大部分时间其实是在查文档。</p>
<p>目前看到在阿里的开源项目有所使用。</p>
<p><a href="https://vuetifyjs.com/en/" target="_blank" rel="noopener noreferrer" class="">Vuetify — A Vue Component Framework</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2021/02/22/vuetify#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>选型前有几点值得先想清楚：</p>
<p>1）设计风格绑定较深。Material Design 的视觉特征很明显，如果设计稿走的是另一套风格，强行用 Vuetify 定制的成本可能比收益高，这种情况选风格更中性的库更合适。</p>
<p>2）注意打包体积。全量引入组件和样式后体积不小，正式项目建议按需引入，只打包用到的组件。</p>
<p>3）版本与 Vue 的对应关系。Vuetify 的大版本和 Vue 的大版本是绑定的，升级前要确认目标版本对当前 Vue 版本的支持情况，避免升到一半发现组件 API 变了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2021/02/22/vuetify#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Vuetify 的定位很清晰：在 Vue 里完整落地 Material Design。组件覆盖全、文档质量高、响应式开箱即用，适合认可 Material 风格、想快速把界面搭起来的项目；反过来，如果对视觉有强定制需求，就要掂量一下改主题的成本。</p>
<p>后续会在自己的前端项目中尝试使用。</p>]]></content:encoded>
            <category>前端</category>
        </item>
        <item>
            <title><![CDATA[Kettle工具使用]]></title>
            <link>https://tanzhuo.xyz/2021/02/04/kettlegong-ju-shi-yong</link>
            <guid>https://tanzhuo.xyz/2021/02/04/kettlegong-ju-shi-yong</guid>
            <pubDate>Thu, 04 Feb 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[记录使用 Kettle 做多数据源同步、迁移与转换的经验：核心概念、常用组件的用途，以及基于查询的 CDC 方式适用的场景与限制。]]></description>
            <content:encoded><![CDATA[<p>最近使用了Kettle这款ETL工具、对于多数据源进行数据之间的同步，迁移，转换，修正等功能进行了解与使用。</p>
<!-- -->
<p>做业务系统时经常会遇到这样的需求：老系统的数据要搬到新系统，两边表结构不一样；或者要把多个库的数据汇总到一个报表库里，字段名、状态码、数据类型都对不上。这类活如果全靠手写脚本，SQL 加导入导出程序写一堆，改一次需求就要改一次代码，维护起来很累。ETL 工具解决的就是这个问题——把抽取（Extract）、转换（Transform）、加载（Load）这三步流程化、可视化，Kettle 是这类工具里比较有代表性的一个开源实现。</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,UklGRroDAABXRUJQVlA4WAoAAAAQAAAAMQAAMQAAQUxQSGgAAAABV6CojRQ4uhc/cIMKPBARAffxizsE20iSnFx9DP8+KWASBiYZQCLS1RABf2Ei52yciP4rSNuA2bEQEZLWUb+7l16S6TxZ4fLzbGFQ2mzBqQ6cPuLs+gvWgIKBkZnDM3KqDLgZOOSAClZQOCAsAwAA0BEAnQEqMgAyAD5tMJVHJCKiISgYC1CADYlsAHfR9a/E/mSOgiIn+X6QG2A/Yz1Afsh1E/QA/UDrRPQA6TP9m/Rru6Sjd+QGTrfGZnveCMVWoKc7/j/FH9EewN+rnWN/Y72QDEolNNzahDO8qPxFbXAxj2MefhSQMCLmVU1CgXm8PRqgsSqhLagaO2MNxKPHEPVkQW5VwAD++lqX5r3UgAx08fl1ave1YE+zozZrmXs4+e+tNT2P+cN5v/mPJr///4si/huEJ6MKP//+FNcKa3pxU4083zi9/Qc/a21EofhRbdK3xpjdhrHpVx+8fEDP9aSI9BfA/+FXwqRpZQRdXEgAn6IRBvbe/h5mip64VKe8aDWCLQl/7vYH/To9rd8CEc57uZHjgX7jZpUVTOSlDOvNDjvPCsrcB6Y1HwivOqEruHjYDlH0uOn/FuypEc9nrjeWBlWLd5WX47r/rpzCnR0uoYuYHf8ZNUM0ZZrahBmoHQ3P7W7vmvx59ia3C+XXMpXVmHoozxzuJ2SQ7WHvRT+161QPjDffDLnepHz/5O05TBaM1/5a3MdMFP+MASCYF3pnbeEzJC8kuW70FyXzSJFqnuvO8QcTF1eXpoiz963ncn+GEa9sUs+YBLV9SvacjOS9JGeV7jhyjWH/6oYLZuju5asH4w0vntxE0vdk1PPo/WcDrj8X2X1wX5bBFKEiA93LUFgcuEwv+DelK46YH7mZgp1rQ/8vNnn9eClf1biG9phuNZXQbzGL9cEBD4N19sWzJSI0xym1O0/8kp2sn2vNGD90ZsddoGAaLs4RapLYPZSgNH8FwvHWw+3Y0zGAz2gxxL6X8WOafTavHjU0Valf7LqAav+G+ijxdYGptc8f4P/7nA6QRetS8pendUF01TwvEu0gmdD0mJsuYFwHydzcbv6SOhAtGAAzR59mbpkQRY/YY8o6M+Pgt3OJoZO08e6YGsedIEhL17v54+4i1oZ/nEdPWdYMEHemmSlTgu8krYLRGNcvfkIoNlpPkArKoqyUoSGZVvsB5/MsLeiPMyZNidQb/lLpbiKzL6pSbsJRqQOjIPPdu3gAAAA=" width="50" height="50" class="img_ev3q"></p>
<p>Kettle-水壶、顾名思义就是把各种数据源中的表数据都当做水流、从多个水流汇总、分流、解析的工具。它是一款开源的数据集成工具，它提供了丰富的数据处理功能，包括数据抽取、转换和加载（ETL）等。Kettle的核心是一个基于图形化界面的设计工具，用户可以通过简单的拖拽和连接操作来构建数据处理流程。Kettle还提供了强大的数据处理引擎，支持多线程和分布式处理，可以高效地处理大规模数据。同时，Kettle还支持多种数据来源和目标，包括关系型数据库、文件、Web服务等，可以方便地与各种数据源进行集成。Kettle还提供了丰富的插件机制，用户可以自定义开发插件，扩展Kettle的功能。总之，Kettle是一款功能强大、易用性好、可扩展性强的数据集成工具，广泛应用于数据仓库、商业智能、数据分析等领域。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="工作方式">工作方式<a href="https://tanzhuo.xyz/2021/02/04/kettlegong-ju-shi-yong#%E5%B7%A5%E4%BD%9C%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="工作方式的直接链接" title="工作方式的直接链接" translate="no">​</a></h2>
<p>Kettle 里有两个基本概念：转换（Transformation）和作业（Job）。转换负责具体的数据流处理，由一个个步骤（Step）用连线（Hop)串起来，数据以行为单位在步骤之间流动；作业则是更上层的调度单位，可以把多个转换按顺序或条件组织起来执行。图形化设计器里画出来的流程，保存后就是一份 XML 描述文件，既可以在设计器里直接跑，也可以交给命令行工具在服务器上定时执行。</p>
<p>每个步骤在运行时是独立线程，上游步骤产出一行数据就往下游推一行，整个流程是流水线式的，不需要等前一步全部处理完。这也是它能处理较大数据量的原因之一——数据不会一次性全部装进内存。</p>
<p>无需任何编程、只需要手动拖动配置组件。即可完成复杂的数据处理功能。Kettle对于CDC层面来说，是基于查询的方式进行数据的读取与转换，适合一次性的数据迁移与转换。不能用于实时性要求较高场景。</p>
<p>这里稍微展开一下：CDC（Change Data Capture，变更数据捕获）常见有两类实现思路。一类是基于日志的，比如解析 MySQL 的 binlog，数据库每发生一次变更就能近乎实时地捕获到；另一类是基于查询的，靠定时执行 SQL、比对时间戳或自增主键来找出变化的数据。Kettle 属于后者，它拿到的是查询那一刻的快照，两次查询之间的中间状态是感知不到的，删除操作也不容易发现。所以它适合做一次性迁移、定时批量同步这类场景，要做实时同步就得换基于日志的方案。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据迁移小例子">数据迁移小例子<a href="https://tanzhuo.xyz/2021/02/04/kettlegong-ju-shi-yong#%E6%95%B0%E6%8D%AE%E8%BF%81%E7%A7%BB%E5%B0%8F%E4%BE%8B%E5%AD%90" class="hash-link" aria-label="数据迁移小例子的直接链接" title="数据迁移小例子的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2021-02-image-fb2bb7146b43fcc0dff8c1e94bf83070.webp" width="1600" height="879" class="img_ev3q"></p>
<p>kettle 提供了相当多的组件可以应付不同场景的数据转移，导入，导出，值映射等功能。并可以数据导出excel文件。上图就是一个典型的迁移流程：从源库查出数据，经过中间几步清洗转换，最后写入目标库。下面按流程中出现的顺序说说这些常用组件。</p>
<p>表输入：从数据库中执行sql从而查询出导入数据。这是整个流程的起点，写一条 SELECT 语句，查询结果的每一行都会作为数据流的一行往下游传递。SQL 里可以用变量做参数化，方便同一个转换在不同环境下复用。</p>
<p>表输出：从Kettle中运行得到最终结果集向表中输出数据。它是流程的终点，把流入的每一行数据 INSERT 到目标表。可以配置批量提交的条数，批量写入比逐条提交快得多。</p>
<p>字段名称完善：可赛选数据列，设置列别名等。源表和目标表字段名往往不一致，比如老库叫 user_name 新库叫 username，在这一步统一改名，后面的步骤就不用再关心源表的命名了。不需要的列也可以在这里直接丢弃，减少后续步骤的处理量。</p>
<p>排序：可以根据数据字段进行排序。单独看用处不大，但它常常是为下一步服务的——Kettle 的合并类组件通常要求两路输入按关联字段有序，所以合并前一般要先各自排序。</p>
<p>数据合并：把两个不同来源的数据进行合并、类似于mysql join功能。两个表输入的数据流按指定字段关联到一起，跨库的表也能"join"，这是纯 SQL 做不到的。前提如上所说，两路数据都要先按关联字段排好序。</p>
<p>值映射：很多数据库状态值1,2,3的状态码 在新数据库中可能为 4,5,6 则可以使用值映射进行值替换。本质上是一张配置在组件里的对照表，源值到目标值一一对应，还可以设置默认值兜住没有匹配上的情况。</p>
<p>字段修正：修正数据源的字段名称与数据类型。以方便迁移到新数据源中。典型场景是老库用字符串存日期、新库是 datetime 类型，或者数值精度需要调整，都在这一步转换掉，避免写入目标库时报类型错误。</p>
<p>新增、更新：对目标数据源执行新增数据操作。如果已有对应id则进行更新操作。也就是常说的 upsert：按指定的关键字段去目标表查找，查不到就插入，查到了就更新。做增量同步时用它代替表输出，可以让转换重复执行而不产生重复数据。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2021/02/04/kettlegong-ju-shi-yong#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）中文乱码：数据库连接的字符集要和库本身一致，MySQL 连接参数里最好显式指定编码，否则迁移完发现中文全是问号就要返工重来。</p>
<p>2）合并前忘记排序：数据合并类组件依赖输入有序，漏了排序步骤不一定报错，但关联结果会不对，这种问题比报错更难发现，务必核对结果行数。</p>
<p>3）批量提交与事务：表输出默认的提交条数可以调大来提速，但要想清楚失败后怎么办——中途出错时已提交的数据不会回滚，重跑前要么清空目标表，要么改用"新增、更新"组件保证幂等。</p>
<p>4）大表迁移：一条不带条件的 SELECT 全表拉取，对源库压力不小，尽量在业务低峰执行，或按主键、时间分批跑。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2021/02/04/kettlegong-ju-shi-yong#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>合理使用Kettle 可以帮助我们简化对数据库的数据管理，在项目进行大版本变更时，数据库结构与新老数据做兼容处理时，Kettle就是不错的工具之一。它的定位很清楚：批处理式的数据搬运和清洗，图形化流程降低了维护成本，让这类一次性或周期性的数据任务不必再写一堆临时脚本。至于实时同步的需求，就交给基于日志的 CDC 方案去做，工具各司其职。</p>]]></content:encoded>
            <category>技术</category>
            <category>大数据</category>
        </item>
        <item>
            <title><![CDATA[只狼：影逝二度]]></title>
            <link>https://tanzhuo.xyz/2020/11/12/zhi-lang</link>
            <guid>https://tanzhuo.xyz/2020/11/12/zhi-lang</guid>
            <pubDate>Thu, 12 Nov 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[《只狼：影逝二度》是 From Software 制作的第三人称动作冒险游戏。本文梳理游戏背景的两条线：苇名一心盗国，以及樱龙与不死之力。]]></description>
            <content:encoded><![CDATA[<p>《只狼：影逝二度(Sekiro：Shadows Die Twice)》是一款由From Software制作的第三人称视角的动作冒险沙盒类游戏，玩家将操控一位忍者，拯救他的主人——拥有日本贵族血统的大能的皇子，并向他的天敌复仇。</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2022-10-src-http___img.3dmgame.com_uploads_images_news_20190201_1548990325_614408.jpg-refer-http___img.3dmgame-1338da6632be831416ebb589f3a13634.webp" width="1920" height="1080" class="img_ev3q"></p>
<p>游戏背景方面是两条线，一条是苇名一心盗国，一条是樱龙和不死之力。先说苇名一心盗国，苇名之地原是苇名族的驻地，后来被内府势力侵并。到了战国末期，剑圣苇名一心为了收回被占领的土地带领苇名众及苇名的庶家也就是同源不同姓的平田家击败了内府士兵，从初次遇见苇名一心孙子苇名弦一郎的对话中得知平田家的家主是弦一郎的叔父。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="苇名一心盗国">苇名一心盗国<a href="https://tanzhuo.xyz/2020/11/12/zhi-lang#%E8%8B%87%E5%90%8D%E4%B8%80%E5%BF%83%E7%9B%97%E5%9B%BD" class="hash-link" aria-label="苇名一心盗国的直接链接" title="苇名一心盗国的直接链接" translate="no">​</a></h2>
<p>平田家的从属忍者就是在战场上收养了主角只狼的巨型忍者枭，其特征是一身毛发披肩，繁体版译名为猫头鹰。而与此同时另一个无主的忍者猿猴也就是日后破旧寺庙的佛雕师，则在战场上将自己的饭团给了在当时还是小女孩的永真，但是猿猴并没有亲自抚养永真，而是寄托给了自己的好友药师道玄，原因是喝完酒的猿猴说跟着一个药师总比要跟着一个忍者好，后来医术精湛的永真成为了苇名一心的专属药师，同时也学习了苇名一心的剑术。从这点上也能看出枭和猿猴同为忍者的不同，猿猴更多的心存善意，枭则为了达成自己的目的更重视培养自己的臂膀。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="樱龙不死之力">樱龙不死之力<a href="https://tanzhuo.xyz/2020/11/12/zhi-lang#%E6%A8%B1%E9%BE%99%E4%B8%8D%E6%AD%BB%E4%B9%8B%E5%8A%9B" class="hash-link" aria-label="樱龙不死之力的直接链接" title="樱龙不死之力的直接链接" translate="no">​</a></h2>
<p>另一方面则是樱龙这条线，樱龙下半身是樱花树上半身是独臂的龙形象，源自于西方龙之乡而流落于苇名。游戏中的不死之力都直接或间接源自于樱龙，包括龙胤，附虫者和变若水，而这三种力量中龙胤最为正统，也是最接近樱龙本身的。樱龙的不死是需要力量来源的，从道具“噬神”的描述里可以看出樱龙的力量需要汲取苇名本土草木小神明的力量，而古老的苇名土地也大量孕育了这种可以被汲取力量来源的小神明，这也是为什么樱龙选择留在了苇名。所以龙胤之力也是同样，对使用者自身没有影响，但是却需要其身边人的生命力，也就是龙咳。</p>
<p>同时樱龙的力量也影响了水，而离樱龙最近的住在山巅的淤加美一族喝下了具有不死之力的水，崇拜樱龙并修建了源之宫，美其名曰仙乡。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2021-02-src-http___img.yxbao.com_article_image_201903_26_8620b32096.jpg-refer-http___img.yxbao-1-9750066dc196b2540a549f3da64fba4b.webp" width="1600" height="900" class="img_ev3q"></p>
<p>第一代龙胤拥有者就出自淤加美一族，即丈大人。丈和其侍从巴大人应该也都是女性，淤加美一族的主要战力是女性，以跳舞加攻击力（就是那个集齐三块可以跟鬼佛用技能点加攻击力的面具）和唤雷为主要攻击手段，男性则都为蛇身的奇行种，淤加美一族和源之宫放在后文细说。丈大人和巴大人选择离开家乡寻求断绝龙胤的方法，结交了苇名一心和苇名弦一郎，并教授他们唤雷术，同时也留下了很多笔记作为日后九郎和其忍者只狼断绝不死的重要线索。</p>]]></content:encoded>
            <category>游戏</category>
        </item>
        <item>
            <title><![CDATA[IntelliJ IDEA 2020 JRebel破解]]></title>
            <link>https://tanzhuo.xyz/2020/11/03/idea-2020-jrebelpo-jie</link>
            <guid>https://tanzhuo.xyz/2020/11/03/idea-2020-jrebelpo-jie</guid>
            <pubDate>Tue, 03 Nov 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[记录 JRebel 在 IDEA 中的激活流程,并梳理 JVM 热部署的能力边界与几种合规替代方案。]]></description>
            <content:encoded><![CDATA[<p>IDEA 安装好最新版的JRebel之后。剩下的就是激活这一步,顺手把流程记下来,也把热部署本身的原理和边界一起理清楚。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么会用到它">为什么会用到它<a href="https://tanzhuo.xyz/2020/11/03/idea-2020-jrebelpo-jie#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BC%9A%E7%94%A8%E5%88%B0%E5%AE%83" class="hash-link" aria-label="为什么会用到它的直接链接" title="为什么会用到它的直接链接" translate="no">​</a></h2>
<p>Java 开发里最消耗耐心的环节之一,是改一行代码就得重启整个服务。一个稍有规模的 Spring 应用,启动要走完类扫描、Bean 实例化、上下文刷新、连接池初始化几道工序,项目越大越慢。改文案、调一个 if 分支、修一个空指针,都要为此付出一次完整的启动时间。</p>
<p>JRebel 做的事情就是把这段等待砍掉:改完类文件直接生效,进程不重启,内存里的会话、缓存、连接都还在。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="热部署的边界在哪">热部署的边界在哪<a href="https://tanzhuo.xyz/2020/11/03/idea-2020-jrebelpo-jie#%E7%83%AD%E9%83%A8%E7%BD%B2%E7%9A%84%E8%BE%B9%E7%95%8C%E5%9C%A8%E5%93%AA" class="hash-link" aria-label="热部署的边界在哪的直接链接" title="热部署的边界在哪的直接链接" translate="no">​</a></h2>
<p>JVM 本身是带热替换能力的。调试器通过 JPDA 调用 JVMTI 的 <code>RedefineClasses</code>,可以把一个已加载类的字节码换掉——但只能换方法体。</p>
<p>一旦触及类的结构,就会失败:</p>
<ul>
<li class="">新增或删除方法</li>
<li class="">新增或删除字段</li>
<li class="">修改继承关系、实现的接口</li>
<li class="">修改注解、修改方法签名</li>
</ul>
<p>这类操作会直接报 <code>schema change not implemented</code>,只能重启。</p>
<p>JRebel 的做法不是走标准 HotSwap,而是在类加载阶段介入,对字节码做改写,并维护自己的一套版本映射,所以结构性变更也能生效。它还针对 Spring、Hibernate、MyBatis 这些框架做了适配——新增一个 <code>@Service</code>、改一段 XML 映射、加一个 Controller 方法,框架侧的元数据也会跟着刷新。这部分适配工作量不小,也是它收费的原因。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="激活步骤">激活步骤<a href="https://tanzhuo.xyz/2020/11/03/idea-2020-jrebelpo-jie#%E6%BF%80%E6%B4%BB%E6%AD%A5%E9%AA%A4" class="hash-link" aria-label="激活步骤的直接链接" title="激活步骤的直接链接" translate="no">​</a></h2>
<p>点击激活页面</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2020-11-image-3c1090b040f1384f1eb9111c5a5c4f67.webp" width="1600" height="991" class="img_ev3q"></p>
<p>选择在线服务器授权激活。</p>
<p>输入服务器授权URL地址</p>
<p>URL地址为:<a href="https://jrebel.qekang.com/5d45985a-26e1-4172-95ad-e75e551605a2" target="_blank" rel="noopener noreferrer" class="">https://jrebel.qekang.com/</a>{GUID}</p>
<p>其中的GUID需要生成(生成地址):</p>
<p><a href="https://www.guidgen.com/" target="_blank" rel="noopener noreferrer" class="">Generate GUIDs online</a></p>
<p>进入网站后生成GUID之后填写入URL地址中。</p>
<p>勾选同意协议,点击激活。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2020-11-image-1-092fcb5e72c89913544c4338f2d3f8f2.webp" width="1600" height="991" class="img_ev3q"></p>
<p>至此JRebel破解成功。</p>
<p>这里的 URL 是「服务器地址 + 一个 GUID」拼出来的完整串,不是只填域名。GUID 相当于一个客户端标识,自己生成一个新的即可。后面的邮箱可以随便填,授权服务器不做校验。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2020/11/03/idea-2020-jrebelpo-jie#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1)<strong>激活成功不等于生效</strong>。JRebel 需要以自己的 Run / Debug 按钮启动应用,走普通的 Run 不会挂载 agent,代码改了依然要重启。</p>
<p>2)<strong>rebel.xml 决定了它去哪里找 class</strong>。JRebel 靠这个文件把运行时的类路径映射回项目的编译输出目录。Maven 项目用 <code>jrebel-maven-plugin</code> 生成:</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">&lt;!-- 在 pom.xml 的 build/plugins 中加入,执行 mvn jrebel:generate 生成 rebel.xml --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">plugin</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">groupId</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain">org.zeroturnaround</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">groupId</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">artifactId</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain">jrebel-maven-plugin</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">artifactId</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">executions</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">execution</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">id</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain">generate-rebel-xml</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">id</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">phase</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain">process-resources</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">phase</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">goals</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">                </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;</span><span class="token tag" style="color:hsl(355, 65%, 65%)">goal</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain">generate</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">goal</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">            </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">goals</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">        </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">execution</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">executions</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&lt;/</span><span class="token tag" style="color:hsl(355, 65%, 65%)">plugin</span><span class="token tag punctuation" style="color:hsl(220, 14%, 71%)">&gt;</span><br></div></code></pre></div></div>
<p>IDEA 插件通常会自动处理这一步,多模块项目要确认每个模块都生成了。</p>
<p>3)<strong>依赖外网</strong>。这种在线授权方式每次启动都要连授权服务器,内网开发机、离线环境会直接卡在校验上。</p>
<p>4)<strong>升级容易失效</strong>。IDEA 大版本更新后插件可能不兼容,授权服务器本身也随时可能关停。</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p>用第三方授权服务器本质上是绕过 JRebel 的商业授权。个人折腾无妨,商业项目里这么做是有法律风险的,团队环境建议走正规 license。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="更省事的替代方案">更省事的替代方案<a href="https://tanzhuo.xyz/2020/11/03/idea-2020-jrebelpo-jie#%E6%9B%B4%E7%9C%81%E4%BA%8B%E7%9A%84%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88" class="hash-link" aria-label="更省事的替代方案的直接链接" title="更省事的替代方案的直接链接" translate="no">​</a></h2>
<p>如果只是想少重启几次,不一定非要 JRebel:</p>
<ul>
<li class=""><strong>DCEVM + HotSwapAgent</strong>:开源方案。DCEVM 是修改过的 JVM,把 HotSwap 的限制放宽到支持增删方法和字段;HotSwapAgent 在此之上补齐框架适配。能力接近 JRebel,代价是要换 JVM。</li>
<li class=""><strong>Spring Boot DevTools</strong>:用两个 ClassLoader 分离第三方依赖和业务代码,改动后只重启业务那一层。不是真热部署,但重启速度比冷启动快一大截,零成本零风险。</li>
<li class=""><strong>IDEA 原生 HotSwap</strong>:Debug 模式下重新编译单个类即可生效。只能改方法体,但应付日常调参、改日志足够了。</li>
<li class="">JRebel 官方也提供免费试用,评估阶段直接用官方的更省心。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2020/11/03/idea-2020-jrebelpo-jie#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>热部署工具解决的是「反馈循环太长」这一个问题。理解 JVM 只允许替换方法体这条硬边界,就能明白 JRebel 贵在哪、DevTools 为什么只能算折中。选哪个,看项目规模和能接受的代价。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[MySQL 事物死锁解析]]></title>
            <link>https://tanzhuo.xyz/2020/10/15/mysqlshi-wu-si-suo</link>
            <guid>https://tanzhuo.xyz/2020/10/15/mysqlshi-wu-si-suo</guid>
            <pubDate>Thu, 15 Oct 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[并发事务如何一步步走进相互等待的僵局：从加锁顺序推演死锁成因，讲清 InnoDB 基于 undo log 的回滚取舍与 wait-for graph 检测机制，最后给出降低死锁概率的业务实践。]]></description>
            <content:encoded><![CDATA[<p>在使用mysql数据库中，随着我们业务与功能模块的增加、数据库的事务数量会随之上升。在开发业务功能的场景中我们会经常开启事务操作数据，或者开启分布式全局事务操作数据时、事务过多，在并发进行的场景中往往会有几率产生事务死锁问题。</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,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" width="255" height="255" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么要聊这个问题">为什么要聊这个问题<a href="https://tanzhuo.xyz/2020/10/15/mysqlshi-wu-si-suo#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E8%81%8A%E8%BF%99%E4%B8%AA%E9%97%AE%E9%A2%98" class="hash-link" aria-label="为什么要聊这个问题的直接链接" title="为什么要聊这个问题的直接链接" translate="no">​</a></h2>
<p>死锁麻烦的地方在于它不是稳定复现的 bug：测试环境单线程跑一百遍都没事，一到线上并发高峰就冒出来，日志里只留下一句 <code>Deadlock found when trying to get lock</code>。如果不理解它的成因，很容易把它当成偶发异常忽略掉，直到某天业务高峰期集中爆发。这篇就把死锁从产生、检测到规避完整梳理一遍。</p>
<p>先补一点前置背景。InnoDB 的行锁是加在索引记录上的，一个事务更新某行数据时会拿到这行的排他锁（X 锁），并且一直持有到事务提交或回滚才释放——这是两阶段锁协议的要求。锁的持有时间等于事务的存活时间，这一点是后面所有分析的基础：事务越长，锁被别人撞上的窗口就越大。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="死锁是怎么产生的">死锁是怎么产生的<a href="https://tanzhuo.xyz/2020/10/15/mysqlshi-wu-si-suo#%E6%AD%BB%E9%94%81%E6%98%AF%E6%80%8E%E4%B9%88%E4%BA%A7%E7%94%9F%E7%9A%84" class="hash-link" aria-label="死锁是怎么产生的的直接链接" title="死锁是怎么产生的的直接链接" translate="no">​</a></h2>
<p>那么我们来分析一下事务死锁产生的原因：</p>
<p>两个 session 连接：一个session持有T1事务，另外一个session持有T2事务。</p>
<p>1.T1事务修改rows1</p>
<p>2.T2事务修改rows2</p>
<p>3.T2事务修改rows1</p>
<p>4.T2等待T1释放rows1的x锁</p>
<p>5.T1事务修改Rows2</p>
<p>6.T1等待T2释放rows2的x锁</p>
<p>7.相互等待死锁</p>
<p>关键就在第 3 步和第 5 步：两个事务以相反的顺序去碰对方已经锁住的行。T1 手里攥着 rows1 等 rows2，T2 手里攥着 rows2 等 rows1，谁也不肯先松手——因为松手意味着回滚。等待关系形成了一个环，这就是死锁的本质：<strong>资源的循环等待</strong>。</p>
<p>死锁在没有外力的干扰下，程序本身无力解决此问题。 必须借助人为或者另外的守护线程来解决此死锁问题。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="mysql-如何选择牺牲者">MySQL 如何选择牺牲者<a href="https://tanzhuo.xyz/2020/10/15/mysqlshi-wu-si-suo#mysql-%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E7%89%BA%E7%89%B2%E8%80%85" class="hash-link" aria-label="MySQL 如何选择牺牲者的直接链接" title="MySQL 如何选择牺牲者的直接链接" translate="no">​</a></h2>
<p>解决死锁的方法也很简单：释放其中一个事务，让其回滚即可。</p>
<p>但回滚有一个问题，回滚哪个事务更合理，mysql采用的是根据<strong>undo log</strong>中谁的条数更多，谁的权重越大，则舍弃权重更小的事务进行回滚，可以更好解决事务死锁。让其回滚的数据量尽量减少，以减少服务的性能牺牲。</p>
<p>这个选择逻辑不难理解：undo log 记录的是事务已经做过的修改，条数越多说明这个事务干的活越多，回滚它的代价就越大。所以 InnoDB 倾向于回滚那个"做得少"的事务，把重做的成本压到最低。被选中的事务会收到死锁错误，应用层拿到这个错误后可以选择重试整个事务——这也是为什么写代码时要把事务设计成可重试的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="死锁概率的估算">死锁概率的估算<a href="https://tanzhuo.xyz/2020/10/15/mysqlshi-wu-si-suo#%E6%AD%BB%E9%94%81%E6%A6%82%E7%8E%87%E7%9A%84%E4%BC%B0%E7%AE%97" class="hash-link" aria-label="死锁概率的估算的直接链接" title="死锁概率的估算的直接链接" translate="no">​</a></h2>
<p>系统中任何一个事务发生死锁的概率≈n2r4/4R2</p>
<p>n：事务中事务的数量n，数量越多发生死锁的概率越大（事务包裹事务数量）</p>
<p>r：每个事务操作的数量r，每个事务操作的数量越大，发生死锁的概率越大（事务操作的行数）</p>
<p>R： 操作数据的集合R，越小发生死锁的概率越大（不同事务却操作到某集合一样的数据-数据集合，修改的数据越分散，数据集合越多，x锁发生在同一条数据的机率越小）</p>
<p>从这个公式能读出优化的方向：r 的指数是 4，影响最剧烈——<strong>把大事务拆小、减少单个事务操作的行数，是降低死锁概率收益最高的手段</strong>。而 R 在分母上，意味着热点数据是死锁的温床：所有事务都挤着更新同一小撮行（比如一个计数器行、一个库存行），死锁概率会被急剧放大。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="主动检测wait-for-graph">主动检测：wait-for graph<a href="https://tanzhuo.xyz/2020/10/15/mysqlshi-wu-si-suo#%E4%B8%BB%E5%8A%A8%E6%A3%80%E6%B5%8Bwait-for-graph" class="hash-link" aria-label="主动检测：wait-for graph的直接链接" title="主动检测：wait-for graph的直接链接" translate="no">​</a></h2>
<p>mysql的主动事务检测：wait-for graph</p>
<p>在每个session的事务开始前可以通过算法提前得知是否有事务回路（两个事务相互影响对方x锁数据而形成回路） ，提前释放其中undo log权重较小的事务。</p>
<p>wait-for graph 的原理是把等待关系建模成一张有向图：每个事务是一个节点，"T2 在等 T1 的锁"就画一条 T2 指向 T1 的边。每当一个事务因为拿不到锁进入等待，InnoDB 就在图里加边并检查是否出现了环——有环即死锁，立刻挑出权重小的事务回滚，不用干等。</p>
<p>除了主动检测，InnoDB 还有一道兜底：锁等待超时。等待行锁超过 <code>innodb_lock_wait_timeout</code> 设定的时间后，等待的语句会报错返回。它不区分是真死锁还是单纯的锁竞争，只是保证事务不会无限期挂死。</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 查看最近一次死锁的详细信息（LATEST DETECTED DEADLOCK 段）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 包含两个事务各自持有的锁、等待的锁以及被回滚的一方</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">SHOW</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">ENGINE</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">INNODB</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">STATUS</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 查看锁等待超时时间（单位：秒）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">SHOW</span><span class="token plain"> VARIABLES </span><span class="token operator" style="color:hsl(207, 82%, 66%)">LIKE</span><span class="token plain"> </span><span class="token string" style="color:hsl(95, 38%, 62%)">'innodb_lock_wait_timeout'</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><br></div></code></pre></div></div>
<p>但在复杂的业务场景中 mysql的主动事务检测wait-for graph 没有生效时，死锁就已经产生也是经常发生的事情。</p>
<p>所以我们要梳理好业务流程，以及业务影响的数据范围。缩小死锁产生概率。不然很有可能面临人工处理死锁的场景。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2020/10/15/mysqlshi-wu-si-suo#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>结合前面的原理，实际开发中有几条经验值得留意：</p>
<p>1）<strong>统一加锁顺序</strong>。回看死锁的产生过程，根因是两个事务以相反顺序访问同一批行。如果所有业务代码都约定按同一顺序（比如按主键升序）更新数据，循环等待的环就画不起来。</p>
<p>2）<strong>事务要短</strong>。不要在事务里做 RPC 调用、发消息、等用户输入这类耗时操作，锁的持有时间被拉长，等于给死锁敞开窗口。</p>
<p>3）<strong>留意间隙锁</strong>。在可重复读隔离级别下，范围条件的更新和删除会加间隙锁，两个事务锁住相邻间隙再互相插入，同样会死锁——这种死锁光看行数据往往查不出来，需要读 <code>SHOW ENGINE INNODB STATUS</code> 里的锁信息。</p>
<p>4）<strong>应用层做好重试</strong>。死锁被检测到后总有一方被回滚，业务代码要能捕获死锁错误并重试整个事务，而不是只重试最后一条语句。</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p>死锁检测本身也有成本：热点行上大量事务排队时，每个新来的等待都要遍历等待图，并发越高检测开销越大。热点更新场景要从业务上想办法拆分热点，而不是指望数据库硬扛。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2020/10/15/mysqlshi-wu-si-suo#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>死锁的本质是加锁顺序交错形成的循环等待，InnoDB 用 wait-for graph 主动发现环路，再按 undo log 权重挑代价小的事务回滚兜底。但检测和回滚都只是补救，真正的功夫在事前：事务拆小、加锁顺序统一、热点数据打散，死锁概率才能压到可以忽略的水平。</p>]]></content:encoded>
            <category>技术</category>
            <category>DB</category>
        </item>
        <item>
            <title><![CDATA[开发IDEA插件记录]]></title>
            <link>https://tanzhuo.xyz/2020/08/25/kai-fa-ideacha-jian-ji-lu</link>
            <guid>https://tanzhuo.xyz/2020/08/25/kai-fa-ideacha-jian-ji-lu</guid>
            <pubDate>Tue, 25 Aug 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[自己动手开发一款 MyBatis SQL 日志解析插件的记录：为什么要做、参数补全的原理、IDEA 插件开发的学习曲线,以及上架插件市场前后的一些体会。]]></description>
            <content:encoded><![CDATA[<p>之前一直在使用一款插件叫 mybatis log plugin，它里面的 sql log 参数补全这个功能我比较喜欢，之前低版本好像并没有进行收费，但高版本之后它开始收费了。于是我打算自己开发一个类似的功能插件，取名叫 mybatis log analysis , 1.0.0版本的话首先会完成sql log 解析并自动参数补全，sql美化等简单功能。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么要做这个插件">为什么要做这个插件<a href="https://tanzhuo.xyz/2020/08/25/kai-fa-ideacha-jian-ji-lu#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%81%9A%E8%BF%99%E4%B8%AA%E6%8F%92%E4%BB%B6" class="hash-link" aria-label="为什么要做这个插件的直接链接" title="为什么要做这个插件的直接链接" translate="no">​</a></h2>
<p>用 MyBatis 写业务的人应该都有体会：控制台打印出来的 SQL 是带 <code>?</code> 占位符的预编译语句，参数单独打在另一行。排查问题时想把这条 SQL 拿到数据库客户端里直接执行，就得手工把参数一个个填回去——参数一多，这个过程既繁琐又容易填错位置。参数补全类插件解决的就是这件小而高频的事，所以当常用的那款开始收费后，我第一反应不是找替代品，而是想着这个功能本身并不神秘，不如自己写一个，顺便把 IDEA 插件开发这条路摸一遍。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="插件的功能规划">插件的功能规划<a href="https://tanzhuo.xyz/2020/08/25/kai-fa-ideacha-jian-ji-lu#%E6%8F%92%E4%BB%B6%E7%9A%84%E5%8A%9F%E8%83%BD%E8%A7%84%E5%88%92" class="hash-link" aria-label="插件的功能规划的直接链接" title="插件的功能规划的直接链接" translate="no">​</a></h2>
<p>1.0.0 版本先把最核心、最常用的部分做出来：</p>
<ol>
<li class="">
<p>sql log 解析并自动参数补全；</p>
</li>
<li class="">
<p>sql 美化（格式化输出，方便阅读长 SQL）。</p>
</li>
</ol>
<p>之后待时间充裕之后将会把其他的使用功能也开发出来，如xml java 链接跳转，一键生成 controller，service，entity，mapper，xml等功能点。</p>
<p>目前已经开发完成1.0.0版本 并提交到了 IDEA PLUGIN 库中。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参数补全的原理">参数补全的原理<a href="https://tanzhuo.xyz/2020/08/25/kai-fa-ideacha-jian-ji-lu#%E5%8F%82%E6%95%B0%E8%A1%A5%E5%85%A8%E7%9A%84%E5%8E%9F%E7%90%86" class="hash-link" aria-label="参数补全的原理的直接链接" title="参数补全的原理的直接链接" translate="no">​</a></h2>
<p>这个功能的原理并不复杂。MyBatis 在日志级别开到 DEBUG 时，会把一次查询拆成两行输出：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">==&gt;  Preparing: select * from user where id = ? and status = ?</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">==&gt; Parameters: 1024(Long), 1(Integer)</span><br></div></code></pre></div></div>
<p><code>Preparing</code> 行是带占位符的预编译 SQL，<code>Parameters</code> 行是按顺序排列的参数值，括号里附带了参数的 Java 类型。插件要做的事就是把两行配对起来：</p>
<ol>
<li class="">
<p>从日志文本里定位相邻的 <code>Preparing</code> 和 <code>Parameters</code> 行；</p>
</li>
<li class="">
<p>按逗号切分参数列表，根据括号里的类型决定拼接方式——字符串、日期类型需要加引号，数字类型直接替换；</p>
</li>
<li class="">
<p>按顺序把处理后的参数依次填回 SQL 中的 <code>?</code>，得到一条可以直接执行的完整语句；</p>
</li>
<li class="">
<p>最后走一遍 SQL 格式化，输出美化后的结果。</p>
</li>
</ol>
<p>核心逻辑就是文本解析加字符串替换，真正花时间的反而是插件与 IDE 的集成部分：怎么注册一个工具窗口、怎么监听控制台输出、怎么把动作挂到菜单上，这些都要跟 IntelliJ 平台的 API 打交道。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="开发过程中的感受">开发过程中的感受<a href="https://tanzhuo.xyz/2020/08/25/kai-fa-ideacha-jian-ji-lu#%E5%BC%80%E5%8F%91%E8%BF%87%E7%A8%8B%E4%B8%AD%E7%9A%84%E6%84%9F%E5%8F%97" class="hash-link" aria-label="开发过程中的感受的直接链接" title="开发过程中的感受的直接链接" translate="no">​</a></h2>
<p>在开发插件的时候，简单的功能实现起来很简单， 但想要实现比较复杂的功能就必须要对IDEA 提供的API 等方法有个比较深层次的了解，可是我在官方插件文档上并没有找到比较完善的API 文档。只有一些官方给出的demo代码。这样的话就不得不花点时间去看demo代码并阅读理解才行。感觉会将是一个不太友好的学习曲线。</p>
<p>这一点和平时做业务开发很不一样。业务框架的文档通常是「查 API、看示例、直接用」，而 IntelliJ 平台更多要靠三种方式摸索：翻官方 demo 仓库、直接读平台源码、以及去看其他开源插件是怎么实现类似功能的。遇到不知道该用哪个扩展点的时候，找一个功能相近的开源插件对照它的 <code>plugin.xml</code> 和入口类，往往比翻文档更快。</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,UklGRjwDAABXRUJQVlA4WAoAAAAQAAAAJwAAJwAAQUxQSE0BAAABkEPbtmlJp1yV2RXZto12d2Tbtm3btm3btm3bO7huxRExAfR3D0/7Xb6uMufiwDzE0HqVXIyUQirjNXbx0DuoYaI6lhA5D189f24Ny3ebTNA2nJq4fuuIZQ9w/Oc0xrXpsp27ltewVfJac6ahBRFRHWAhkdNCCNurSAYB1ykdsnvU8qoztBXfpj+Xwhal1rgztawV+X2H8jCVEgD4ejh7rtpPX4GFFUO3wf76pobmAuqzs3OtYoeFHK2TRe0A4OdHfZejHa05Xt9g+HETFw4NNTWNhPb3zMx1sRNQgpnaJFvJSJIUxb/Wd5kUt+qbLOd7A/pTpBrD4BmSTDgHk5VFCQdg9DhxfUbfg+ESnsdRGJ5J4ulmrlhIUDsT73xJOuSatg8BpNryp57LbqRu3+2ehr6kuXDqE6l7vZxJv2V09V6TF61cOmNAnSj67wYAVlA4IMgBAACwCgCdASooACgAPm0sj0WkIqEY6zYAQAbEtABCLq5vdJOb6gM4B/AP4h+p/v/+gDeAOe59kPypSZf3ehZERTjxOnMBk2ziN6XdwD6BbT97F+QvdMMMODQe9l9pVoQAAP7vsu/9CY9D/H/6LSdGqPYbmyfP+JREX5o3u2hV03pH/0d5f//WbfJkFm4N/8zb5MgrljB/VfLZwLK7fP0wIWH//5Ld0Rv8pUUGarqm0kYVjkGf7PLA5g2Ms/PfVM0W/SN+0Calu+YMZMkJoXQdFkI/7Iqku+nGuBN4WQ50URDcAFjxgB3HN6p0/0RQTuLBv/27q52VmPeH7xNLYQHXiwJVTFMO/FniMlqO+tr987/6/SCHufmvug2cJRj78+QxMyTj71VX+EYUOl/4vMtpFVOSVf6qqfTl00O14W+O22m2LsxX9TM3jblp+Z+jnkDSd8WbtTMw3krC5D9Q2yDaFjiHt6/M2t/RrjGi2N8O+6EQY0j0rI4Ba/47twysFmcA/VkC6A18f9JWCbPNb22IB9RIOiE4O8b8znyUVhGmNG/qeJsb5siXxrr7RHqEkHyUqCVE54fxQbOtD9oCkPhWfWZSO0/2XOlUAAA=" width="40" height="40" class="img_ev3q"></p>
<p>贴上已经开发出来的插件地址：<a href="https://plugins.jetbrains.com/plugin/14958-mybatis-log-analysis" target="_blank" rel="noopener noreferrer" class="">https://plugins.jetbrains.com/plugin/14958-mybatis-log-analysis</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2020/08/25/kai-fa-ideacha-jian-ji-lu#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class="">参数替换不能简单地做全局字符串替换。参数值本身可能包含逗号或问号，按逗号切分 <code>Parameters</code> 行、按顺序逐个替换 <code>?</code>，比一次性正则替换稳妥得多。</li>
<li class="">类型信息要用起来。<code>Parameters</code> 行括号里的类型决定了要不要加引号，忽略它拼出来的 SQL 在数据库里多半执行不了。</li>
<li class="">提交到 JetBrains 插件市场需要人工审核，插件的描述、图标、兼容的 IDE 版本范围都要在配置里写清楚，提前准备好这些能少来回折腾几次。</li>
</ul>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>插件开发文档不够用的时候，<a href="https://plugins.jetbrains.com/docs/intellij/welcome.html" target="_blank" rel="noopener noreferrer" class="">IntelliJ Platform SDK 文档</a>配合开源插件的源码对照着看，是目前体验最好的学习路径。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2020/08/25/kai-fa-ideacha-jian-ji-lu#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这个插件本身的逻辑很小，价值在于把一件高频的手工活自动化掉了。对我而言更大的收获是完整走了一遍 IDEA 插件从开发到上架的流程：功能实现只占一小部分，理解平台的扩展机制、适应靠读源码和 demo 学习的方式，才是这条路上真正的门槛。后续版本会继续把 xml 与 java 之间的跳转、代码一键生成这些功能补上。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[近几年的工作心得]]></title>
            <link>https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de</link>
            <guid>https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de</guid>
            <pubDate>Tue, 26 May 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[前言 不知不觉已经在互联网行业工作了几年。 回想刚开始学习技术的时候，每天都在写各种 Demo 项目。从最简单的 Hello World，到小游戏、爬虫程序、后台管理系统，每掌握一项新技术都会带来明显的成就感。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>不知不觉已经在互联网行业工作了几年。</p>
<!-- -->
<p>回想刚开始学习技术的时候，每天都在写各种 Demo 项目。从最简单的 <strong>Hello World</strong>，到小游戏、爬虫程序、后台管理系统，每掌握一项新技术都会带来明显的成就感。那段时间学习速度很快，也充满了热情。</p>
<p>随着时间推移，也逐渐意识到互联网行业一个很明显的特点：</p>
<p><strong>门槛不算特别高，但技术的天花板却非常高。</strong></p>
<p>技术世界的知识量极其庞大，当你学得越多，反而越会意识到自己知道的其实很有限。很多时候，我们只是站在巨人的肩膀上，看着更高的巨人。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="技术成长的早期阶段">技术成长的早期阶段<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E6%8A%80%E6%9C%AF%E6%88%90%E9%95%BF%E7%9A%84%E6%97%A9%E6%9C%9F%E9%98%B6%E6%AE%B5" class="hash-link" aria-label="技术成长的早期阶段的直接链接" title="技术成长的早期阶段的直接链接" translate="no">​</a></h2>
<p>在技术成长的早期阶段，大多数人都会通过几种方式来提升自己，例如学习新的技术框架、模仿优秀项目、阅读技术文章，以及不断搭建各种 Demo 项目。</p>
<p>尤其是在开源社区中，经常可以看到很多优秀的设计思想、架构模式和高质量的项目代码。通过阅读这些项目，可以快速理解很多工程实践。</p>
<p>不过，如果想真正提升技术能力，有一个非常重要的过程：</p>
<p><strong>独立完成一个完整的系统项目。</strong></p>
<p>从系统设计、编码实现，到部署上线和后期维护，这整个过程会让人对软件开发有更深入的理解。当第一次独立设计系统架构，并成功让系统稳定运行的时候，那种成就感是非常强烈的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="技术瓶颈的出现">技术瓶颈的出现<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E6%8A%80%E6%9C%AF%E7%93%B6%E9%A2%88%E7%9A%84%E5%87%BA%E7%8E%B0" class="hash-link" aria-label="技术瓶颈的出现的直接链接" title="技术瓶颈的出现的直接链接" translate="no">​</a></h2>
<p>随着工作经验不断增加，很多工程师都会逐渐遇到一个阶段：<strong>技术成长速度开始明显变慢</strong>。</p>
<p>在这个阶段，系统不再只是简单的功能开发，而是需要考虑更多复杂的问题，例如：</p>
<ul>
<li class="">系统安全</li>
<li class="">系统可靠性</li>
<li class="">高可用架构</li>
<li class="">性能优化</li>
<li class="">监控与运维</li>
<li class="">系统扩展能力</li>
<li class="">部署与资源管理</li>
</ul>
<p>当系统规模逐渐变大之后，架构也会变得越来越复杂，需要使用的技术也越来越多。很多人会在这个阶段产生焦虑，觉得自己的学习速度变慢，甚至担心被更年轻的工程师取代。</p>
<p>但实际上，这个阶段的突破往往不再依赖单纯的学习速度，而是需要 <strong>长期经验积累和大量项目实践</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="技术深度的重要性">技术深度的重要性<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E6%8A%80%E6%9C%AF%E6%B7%B1%E5%BA%A6%E7%9A%84%E9%87%8D%E8%A6%81%E6%80%A7" class="hash-link" aria-label="技术深度的重要性的直接链接" title="技术深度的重要性的直接链接" translate="no">​</a></h2>
<p>当技术能力达到一定阶段之后，真正重要的反而不再是“会多少技术”，而是是否形成了 <strong>自己的技术核心能力</strong>。</p>
<p>很多年轻工程师在编码能力上可能非常优秀，但在 <strong>系统整体设计、架构思考、复杂问题分析</strong> 方面，往往还缺乏足够的经验。这种差异，本质上来自长期的技术沉淀。</p>
<p>当技术成长到一定阶段，如果想继续提升能力，就需要不断扩展自己的知识边界，而不是只停留在单一领域。</p>
<p>例如在数据库领域，一个完整的技术体系就包括：</p>
<ul>
<li class="">关系型数据库</li>
<li class="">非关系型数据库</li>
<li class="">图数据库</li>
<li class="">数据存储结构</li>
<li class="">数据分片策略</li>
<li class="">存储引擎原理</li>
</ul>
<p>数据库往往是系统最核心的部分之一，因此理解数据库的内部原理，对系统架构设计是非常重要的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="软件架构的复杂性">软件架构的复杂性<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84%E7%9A%84%E5%A4%8D%E6%9D%82%E6%80%A7" class="hash-link" aria-label="软件架构的复杂性的直接链接" title="软件架构的复杂性的直接链接" translate="no">​</a></h2>
<p>当系统规模逐渐扩大之后，软件架构会涉及到很多不同的技术领域，例如：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="系统部署与资源管理">系统部署与资源管理<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2%E4%B8%8E%E8%B5%84%E6%BA%90%E7%AE%A1%E7%90%86" class="hash-link" aria-label="系统部署与资源管理的直接链接" title="系统部署与资源管理的直接链接" translate="no">​</a></h3>
<ul>
<li class="">Docker 容器</li>
<li class="">CPU / 内存 / 带宽限制</li>
<li class="">存储性能</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="系统架构设计">系统架构设计<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="系统架构设计的直接链接" title="系统架构设计的直接链接" translate="no">​</a></h3>
<ul>
<li class="">微服务架构</li>
<li class="">接口设计</li>
<li class="">系统扩展能力</li>
<li class="">性能优化</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="分布式系统">分布式系统<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F" class="hash-link" aria-label="分布式系统的直接链接" title="分布式系统的直接链接" translate="no">​</a></h3>
<ul>
<li class="">分库分表</li>
<li class="">异地多活</li>
<li class="">容灾设计</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="安全与数据">安全与数据<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E5%AE%89%E5%85%A8%E4%B8%8E%E6%95%B0%E6%8D%AE" class="hash-link" aria-label="安全与数据的直接链接" title="安全与数据的直接链接" translate="no">​</a></h3>
<ul>
<li class="">通信加密</li>
<li class="">安全策略</li>
<li class="">数据校验</li>
</ul>
<p>甚至很多看起来很细节的问题，例如数据库字段长度、数据存储字节数、API 参数设计，都可能会影响系统未来的扩展能力。</p>
<p>因此，一个优秀的架构师不仅需要技术能力强，更需要 <strong>广泛的知识储备和丰富的实践经验</strong>。架构设计在很大程度上决定了系统未来的发展空间。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="软件行业中的不同角色">软件行业中的不同角色<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E8%BD%AF%E4%BB%B6%E8%A1%8C%E4%B8%9A%E4%B8%AD%E7%9A%84%E4%B8%8D%E5%90%8C%E8%A7%92%E8%89%B2" class="hash-link" aria-label="软件行业中的不同角色的直接链接" title="软件行业中的不同角色的直接链接" translate="no">​</a></h2>
<p>在软件行业中，不同角色承担着不同职责。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="产品经理">产品经理<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E4%BA%A7%E5%93%81%E7%BB%8F%E7%90%86" class="hash-link" aria-label="产品经理的直接链接" title="产品经理的直接链接" translate="no">​</a></h3>
<p>主要决定软件要解决什么问题，以及产品未来的发展方向。一个优秀的产品经理不仅需要理解市场需求，还需要理解技术实现的成本和限制，否则很容易设计出难以落地的功能。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="项目经理">项目经理<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E9%A1%B9%E7%9B%AE%E7%BB%8F%E7%90%86" class="hash-link" aria-label="项目经理的直接链接" title="项目经理的直接链接" translate="no">​</a></h3>
<p>更偏向于项目管理，包括进度控制、任务分配和团队协作，需要对项目整体情况有清晰的掌控。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="技术工程师--架构师">技术工程师 / 架构师<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E6%8A%80%E6%9C%AF%E5%B7%A5%E7%A8%8B%E5%B8%88--%E6%9E%B6%E6%9E%84%E5%B8%88" class="hash-link" aria-label="技术工程师 / 架构师的直接链接" title="技术工程师 / 架构师的直接链接" translate="no">​</a></h3>
<p>主要负责技术方案设计和系统实现，需要在复杂的技术体系中找到合适的解决方案，并保证系统长期稳定运行。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="技术成长的转变">技术成长的转变<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E6%8A%80%E6%9C%AF%E6%88%90%E9%95%BF%E7%9A%84%E8%BD%AC%E5%8F%98" class="hash-link" aria-label="技术成长的转变的直接链接" title="技术成长的转变的直接链接" translate="no">​</a></h2>
<p>当技术成长到一定阶段之后，很多工程师会逐渐发现，自己已经不再只是一个技术的使用者，而是开始尝试理解更深层的问题。</p>
<p>例如：</p>
<ul>
<li class="">为什么系统要这样设计</li>
<li class="">为什么某种架构更适合当前业务</li>
<li class="">如何解决复杂系统中的瓶颈问题</li>
</ul>
<p>从最开始使用开源软件，到后来参与开源项目，甚至设计自己的技术框架或中间件。</p>
<p>这其实是很多工程师成长过程中的一个重要转变：</p>
<p><strong>从技术使用者，逐渐成为技术创造者。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="最后的思考">最后的思考<a href="https://tanzhuo.xyz/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#%E6%9C%80%E5%90%8E%E7%9A%84%E6%80%9D%E8%80%83" class="hash-link" aria-label="最后的思考的直接链接" title="最后的思考的直接链接" translate="no">​</a></h2>
<p>当人们回过头来看自己的职业经历时，或许会思考一个问题：</p>
<p>是什么支撑着自己在这个行业一直走下去？</p>
<p>是对技术知识的渴望？<br>
是年轻时的一腔热血？<br>
还是技术已经成为生活的一部分？</p>
<p>也许答案并不重要。</p>
<p>重要的是，在这个不断变化的技术世界里，始终保持：</p>
<p><strong>好奇心、学习能力，以及持续探索的热情。</strong></p>]]></content:encoded>
            <category>生活</category>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[分布式锁与 Spring 事物管理顺序问题]]></title>
            <link>https://tanzhuo.xyz/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti</link>
            <guid>https://tanzhuo.xyz/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti</guid>
            <pubDate>Tue, 31 Mar 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[一位网友用 zookeeper 分布式锁做售票扣减，高并发下票数对不上。根因是锁在事务提交前释放，下一个线程读到脏数据。记录推理过程与修复思路。]]></description>
            <content:encoded><![CDATA[<p>这个是网上的网友提问，我给他推理并解决了他的问题。故此记录一下 。</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2021-02-src-http___img2.sycdn.imooc.com_5b3b66960001738707500375.jpg-refer-http___img2.sycdn.imooc-3ae12e651fe4d27c6fb9f127d3888435.webp" width="750" height="375" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="问题背景">问题背景<a href="https://tanzhuo.xyz/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF" class="hash-link" aria-label="问题背景的直接链接" title="问题背景的直接链接" translate="no">​</a></h2>
<p>"加了分布式锁，数据还是不对"是很典型的一类并发问题。锁本身没有 bug，事务也确实回滚提交都正常，但两者组合在一起时，释放锁和提交事务的先后顺序稍有差错，串行化的保护就形同虚设。这类问题在压测之前很难暴露，排查时也容易把注意力放在锁的实现上，反而忽略了事务边界。这位网友遇到的正是这种情况。</p>
<p>他在使用zookeeper的临时顺序节点分布式锁来修改mysql中的数据值，此功能类似于售票机制，高并发下，要求售票数量保持一致。不可以数量多或少。</p>
<p>使用zookeeper的临时顺序节点分布式锁的时候，他锁住service层时，执行完整个service方法时，锁释放，但此时方式上标注的spring 管理的声明式事物却还未提交。这样的场景在并发情况下会导致数据问题。</p>
<p>理所当然他的数据库中数据会出现异常情况。100个线程同时执行1次一次拿1张，100张票应该为剩余0，而他的结果却并不是0，没有达到预期值。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="原因分析">原因分析<a href="https://tanzhuo.xyz/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#%E5%8E%9F%E5%9B%A0%E5%88%86%E6%9E%90" class="hash-link" aria-label="原因分析的直接链接" title="原因分析的直接链接" translate="no">​</a></h2>
<p>原理是：第一个线程执行update方法结束之后，但由于事物还未提交完成。分布式锁却已经先一步释放，下一个线程执行此方法时 select 获取的还是update之前的脏读数据。导致数据出现问题。因为spring声明式事物@Transactional(rollbackFor = Exception.class) 是基于aop，方法执行完成之后的再执行的环绕通知，但环绕通知提交事务时，zookeeper的锁却已经释放。这个先后顺序差异导致下一个线程读到脏数据，数据处理产生错误。</p>
<p>展开说一下这个执行顺序。声明式事务的本质是 Spring 为目标类生成了一个代理对象，事务的开启和提交都发生在代理层，包裹在业务方法的外面。而他的加锁、解锁代码写在业务方法内部。于是一次调用的实际时序是这样的：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">// 代理对象的调用时序（示意）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 1. 事务拦截器开启事务</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 2. 进入业务方法，获取 zookeeper 锁</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 3. select 查余票，update 扣减</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 4. 业务方法内释放锁          &lt;-- 锁在这里就没了</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">// 5. 业务方法返回，事务拦截器提交事务  &lt;-- 提交却在这里</span><br></div></code></pre></div></div>
<p>第 4 步和第 5 步之间存在一个窗口：锁已经释放，事务还没提交。下一个等锁的线程在这个窗口内拿到锁并执行 select，由于上一个事务未提交，在默认的读已提交或可重复读隔离级别下，它读到的是扣减之前的旧值，相当于两个线程基于同一份余票各扣了一次。锁的排他性没有被破坏，被破坏的是"锁内的读一定能看到上一个持锁者的写"这个隐含假设。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="解决办法">解决办法<a href="https://tanzhuo.xyz/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95" class="hash-link" aria-label="解决办法的直接链接" title="解决办法的直接链接" translate="no">​</a></h2>
<p>所以要修改 spring事物和分布式锁释放的先后顺序。先提交事务之后，再释放掉分布式锁或直接锁controller层就可以避免此问题。</p>
<p>两种做法本质相同，都是让锁的持有范围完全覆盖事务的生命周期：</p>
<p>1）在 service 外层（比如 controller 或一个不带事务的外层方法）加锁、解锁，事务方法作为被锁包住的内层调用，方法返回即事务已提交，之后才释放锁。</p>
<p>2）不用声明式事务，改用编程式事务（TransactionTemplate 或手动 commit），在代码里显式地先提交、再解锁，把顺序掌握在自己手里。</p>
<p>需要留意的是，如果把加锁逻辑挪到同一个类的另一个方法里，通过 this 自调用进入事务方法，事务注解不会生效——自调用不经过代理对象。要么拆到两个类，要么从容器里拿到自身的代理再调用。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="分布式锁不等于数据完整性">分布式锁不等于数据完整性<a href="https://tanzhuo.xyz/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E4%B8%8D%E7%AD%89%E4%BA%8E%E6%95%B0%E6%8D%AE%E5%AE%8C%E6%95%B4%E6%80%A7" class="hash-link" aria-label="分布式锁不等于数据完整性的直接链接" title="分布式锁不等于数据完整性的直接链接" translate="no">​</a></h2>
<p>这里再多说一句，分布式锁不能保证数据完整性，只能保证当前业务层service方法的唯一调用。</p>
<p>如果其他service方法中有mapper也在处理这一条数据，那么数据将还是会出现问题。</p>
<p>所以理应给表中添加乐观锁字段来保证数据的完整性。乐观锁可以很好的保持数据的完整性，并且并发性能高于他使用的service加锁。</p>
<p>乐观锁的做法是在表里加一个 version 字段，更新时带上读到的版本号做条件：</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 读的时候把 version 一起查出来</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 更新时以旧版本号为条件，同时把版本号加一</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">update</span><span class="token plain"> ticket</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">set</span><span class="token plain"> stock </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> stock </span><span class="token operator" style="color:hsl(207, 82%, 66%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">1</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> version </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> version </span><span class="token operator" style="color:hsl(207, 82%, 66%)">+</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">where</span><span class="token plain"> id </span><span class="token operator" style="color:hsl(207, 82%, 66%)">=</span><span class="token plain"> </span><span class="token comment" style="color:hsl(220, 10%, 40%)">#{id} and version = #{version};</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 受影响行数为 0 说明数据已被别人改过，重试或报错</span><br></div></code></pre></div></div>
<p>这条防线加在数据本身上，不管有多少个入口在改这行数据，最终一致性都由数据库保证。分布式锁解决的是"同一时刻只有一个人在干活"，乐观锁解决的是"就算多个人干了活，数据也不会错"，两者针对的层面不同，并不互相替代。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）加分布式锁时，先想清楚锁的边界和事务的边界谁包住谁。锁必须完全覆盖事务，"方法内加锁 + 方法上 @Transactional"是最容易写出来、也最容易出错的组合。</p>
<p>2）这类问题单元测试和低并发场景基本测不出来，只有并发压到锁释放与事务提交之间的时间窗口才会复现。上线前对扣减类接口做并发一致性验证是必要的。</p>
<p>3）排查数据不一致时，不要只盯着锁的实现是否正确，把事务的开启、提交时机也画进时序里，往往问题就在两者的交界处。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这个案例里锁和事务各自都工作正常，出问题的是两者的先后顺序：锁在事务提交前释放，给了下一个线程读脏数据的窗口。修复思路是让锁完全包住事务，或者干脆用编程式事务显式控制提交时机。更进一步，扣减类的数据正确性不应该只依赖入口处的互斥，给表加乐观锁字段，把最后一道防线放在数据库层面，才是更稳妥的做法。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[Java后台架构理解]]></title>
            <link>https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou</link>
            <guid>https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou</guid>
            <pubDate>Fri, 20 Mar 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[整理对 Java 后台架构的理解：从代码规范、模块拆分、中间件、数据库治理，到安全、团队协作与微服务演进的整体思考。]]></description>
            <content:encoded><![CDATA[<p>最近自己搭建了一些系统架构，因此整理了一下对于后台架构的一些理解和思考。</p>
<!-- -->
<p>在实际开发中，我们经常会思考：<strong>什么样的项目结构才算是一个合理的架构？</strong></p>
<p>有人认为，一个好的架构应该能够：</p>
<ul>
<li class="">减少开发人员的重复工作</li>
<li class="">结构清晰，易于理解</li>
<li class="">具备良好的稳定性</li>
<li class="">方便扩展与维护</li>
</ul>
<p>但实际上，这只是<strong>项目层面的架构</strong>。</p>
<p>随着业务的发展，系统往往会从<strong>单机项目逐渐演进为集群架构，最终发展为分布式系统</strong>。在这个过程中，会不断引入各种组件，例如：</p>
<ul>
<li class="">注册中心</li>
<li class="">配置中心</li>
<li class="">API网关</li>
<li class="">服务熔断与降级</li>
<li class="">分库分表</li>
<li class="">容灾机制</li>
<li class="">各种中间件</li>
</ul>
<p>同时系统部署也会逐渐演进为 <strong>Docker + K8S 的容器化体系</strong>，服务器数量也可能从最初的一两台，逐渐增长到几十台甚至上百台。此时系统已经不再是简单的项目，而是一个完整的<strong>系统架构体系</strong>。</p>
<p>架构设计既包含宏观层面的系统设计，也包含微观层面的代码设计。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="代码层面的架构设计">代码层面的架构设计<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E4%BB%A3%E7%A0%81%E5%B1%82%E9%9D%A2%E7%9A%84%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="代码层面的架构设计的直接链接" title="代码层面的架构设计的直接链接" translate="no">​</a></h2>
<p>在代码层面，架构设计需要统一开发规范，并提高开发效率。例如：</p>
<ul>
<li class="">DTO 自动参数校验（AOP）</li>
<li class="">Result 统一返回对象</li>
<li class="">代码自动生成</li>
<li class="">Service / Mapper / Controller 规范化结构</li>
<li class="">在线接口文档自动生成</li>
</ul>
<p>同时需要规范代码组织结构：严格控制包结构、统一类命名规范、合理划分模块。</p>
<p>代码写得好并不仅仅是效率高，而是<strong>别人能够快速理解代码的含义</strong>。良好的代码架构可以大幅降低维护成本。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="业务模块拆分与系统解耦">业务模块拆分与系统解耦<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E4%B8%9A%E5%8A%A1%E6%A8%A1%E5%9D%97%E6%8B%86%E5%88%86%E4%B8%8E%E7%B3%BB%E7%BB%9F%E8%A7%A3%E8%80%A6" class="hash-link" aria-label="业务模块拆分与系统解耦的直接链接" title="业务模块拆分与系统解耦的直接链接" translate="no">​</a></h2>
<p>在系统设计过程中，需要充分理解业务之间的关系，根据业务关系进行<strong>模块划分与解耦</strong>，并在模块独立性、代码复用率、系统复杂度这几个方面进行权衡。</p>
<p>合理的架构设计应该做到：</p>
<ul>
<li class="">不同业务模块互不干扰</li>
<li class="">功能模块可以独立维护</li>
<li class="">代码耦合度尽可能降低</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="中间件与系统架构能力">中间件与系统架构能力<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E4%B8%AD%E9%97%B4%E4%BB%B6%E4%B8%8E%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="中间件与系统架构能力的直接链接" title="中间件与系统架构能力的直接链接" translate="no">​</a></h2>
<p>随着系统规模增长，系统通常会引入各种基础设施组件，例如：</p>
<ul>
<li class="">MQ（消息队列）</li>
<li class="">Redis</li>
<li class="">NoSQL</li>
<li class="">ELK 日志系统</li>
<li class="">分布式锁</li>
<li class="">分布式事务</li>
</ul>
<p>架构设计需要熟悉每一种中间件的使用场景，知道在什么情况下引入哪些组件，并合理分配服务器资源。同时需要清楚每一个中间件的职责、每一个模块的边界，以及每一个服务之间的调用关系。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据库设计与sql治理">数据库设计与SQL治理<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AE%BE%E8%AE%A1%E4%B8%8Esql%E6%B2%BB%E7%90%86" class="hash-link" aria-label="数据库设计与SQL治理的直接链接" title="数据库设计与SQL治理的直接链接" translate="no">​</a></h2>
<p>数据库设计在架构中占据非常重要的位置，需要重点关注以下几个方面。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据库设计">数据库设计<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="数据库设计的直接链接" title="数据库设计的直接链接" translate="no">​</a></h3>
<ul>
<li class="">合理设计字段类型</li>
<li class="">统一字段命名规范</li>
<li class="">合理设计索引</li>
<li class="">适当增加冗余字段</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="sql质量控制">SQL质量控制<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#sql%E8%B4%A8%E9%87%8F%E6%8E%A7%E5%88%B6" class="hash-link" aria-label="SQL质量控制的直接链接" title="SQL质量控制的直接链接" translate="no">​</a></h3>
<p>需要严格审核系统中的每一条 SQL：</p>
<ul>
<li class="">是否正确使用索引</li>
<li class="">是否存在全表扫描</li>
<li class="">是否存在复杂难维护 SQL</li>
</ul>
<p>复杂 SQL 应尽量拆分，同时需要尽量减少 SQL 影响行数与死锁发生概率。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="系统性能与压力测试">系统性能与压力测试<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E7%B3%BB%E7%BB%9F%E6%80%A7%E8%83%BD%E4%B8%8E%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95" class="hash-link" aria-label="系统性能与压力测试的直接链接" title="系统性能与压力测试的直接链接" translate="no">​</a></h2>
<p>在系统上线之前，需要进行<strong>压力测试与性能测试</strong>，例如模拟高并发请求、测试服务器极限性能、评估系统 TPS。</p>
<p>同时需要优化：</p>
<ul>
<li class="">服务器配置</li>
<li class="">容器配置</li>
<li class="">JVM参数</li>
</ul>
<p>通过压力测试，可以更清晰了解系统的性能瓶颈。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="代码质量管理">代码质量管理<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F%E7%AE%A1%E7%90%86" class="hash-link" aria-label="代码质量管理的直接链接" title="代码质量管理的直接链接" translate="no">​</a></h2>
<p>低质量代码会严重影响系统稳定性，例如 CPU 异常升高、内存占用过高，甚至导致系统崩溃。</p>
<p>因此需要引入代码质量检测工具，例如：</p>
<ul>
<li class="">FindBugs</li>
<li class="">SonarQube</li>
</ul>
<p>通过静态代码分析工具，可以提前发现潜在问题。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="服务器安全设计">服务器安全设计<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AE%89%E5%85%A8%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="服务器安全设计的直接链接" title="服务器安全设计的直接链接" translate="no">​</a></h2>
<p>系统安全同样是架构设计的重要部分。常见安全措施包括：</p>
<ul>
<li class="">更换默认端口</li>
<li class="">内外网访问白名单</li>
<li class="">定期检查系统漏洞</li>
<li class="">接口参数加密</li>
</ul>
<p>接口层面可以采用 HTTPS 加密传输、对称 / 非对称加密、参数签名机制等手段。同时需要尽量减少开放端口、设置高强度密码、配置防暴力破解策略。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="项目管理与团队协作">项目管理与团队协作<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E4%B8%8E%E5%9B%A2%E9%98%9F%E5%8D%8F%E4%BD%9C" class="hash-link" aria-label="项目管理与团队协作的直接链接" title="项目管理与团队协作的直接链接" translate="no">​</a></h2>
<p>架构不仅仅是技术问题，还包括<strong>团队管理与开发流程</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="git-分支管理">Git 分支管理<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#git-%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86" class="hash-link" aria-label="Git 分支管理的直接链接" title="Git 分支管理的直接链接" translate="no">​</a></h3>
<p>合理控制 Git 分支：</p>
<ul>
<li class="">新功能开发使用 feature 分支</li>
<li class="">紧急 bug 修复使用 hotfix 分支</li>
<li class="">测试环境使用 test 分支</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="任务管理">任务管理<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E4%BB%BB%E5%8A%A1%E7%AE%A1%E7%90%86" class="hash-link" aria-label="任务管理的直接链接" title="任务管理的直接链接" translate="no">​</a></h3>
<p>合理分配开发任务，避免开发人员任务差异过大。可以使用禅道、Teambition 等工具进行团队协作与任务管理。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="自动化测试体系">自动化测试体系<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E4%BD%93%E7%B3%BB" class="hash-link" aria-label="自动化测试体系的直接链接" title="自动化测试体系的直接链接" translate="no">​</a></h2>
<p>随着系统规模扩大，仅依靠人工测试效率非常低，因此需要逐步建设<strong>自动化测试体系</strong>。</p>
<p>自动化测试可以：</p>
<ul>
<li class="">快速验证功能</li>
<li class="">提前发现问题</li>
<li class="">提高系统稳定性</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前后端分离架构">前后端分离架构<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="前后端分离架构的直接链接" title="前后端分离架构的直接链接" translate="no">​</a></h2>
<p>目前前后端分离已经成为主流架构模式。前端框架（例如 Vue）已经形成完整生态，涵盖页面渲染、参数校验、交互逻辑、动画效果。</p>
<p>前后端分离后，后端专注接口开发，前端专注页面交互，同时可以减少服务器处理静态资源的压力。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="静态资源与对象存储">静态资源与对象存储<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E4%B8%8E%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8" class="hash-link" aria-label="静态资源与对象存储的直接链接" title="静态资源与对象存储的直接链接" translate="no">​</a></h2>
<p>图片、视频、文件等大量静态资源，可以统一存储在对象存储中，例如 <strong>OSS</strong>。</p>
<p>上传流程可以改为前端直接上传 OSS，后端只负责保存资源地址与进行权限控制。这样可以大幅减少后端服务器压力。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="微服务与未来架构">微服务与未来架构<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%8E%E6%9C%AA%E6%9D%A5%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="微服务与未来架构的直接链接" title="微服务与未来架构的直接链接" translate="no">​</a></h2>
<p>系统在设计之初就应该考虑<strong>微服务架构</strong>，例如注册中心、配置中心、服务治理，并能够部署到 <strong>Kubernetes</strong> 进行横向扩展。</p>
<p>未来架构还可能进一步发展，例如 Service Mesh、云原生架构。虽然很多大厂已经落地，但对于中小公司来说，目前仍然以<strong>传统微服务架构</strong>为主。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="架构师需要具备的思考能力">架构师需要具备的思考能力<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E6%9E%B6%E6%9E%84%E5%B8%88%E9%9C%80%E8%A6%81%E5%85%B7%E5%A4%87%E7%9A%84%E6%80%9D%E8%80%83%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="架构师需要具备的思考能力的直接链接" title="架构师需要具备的思考能力的直接链接" translate="no">​</a></h2>
<p>一个成熟的架构师不仅仅是技术实现者，更是系统长期发展的规划者。在设计架构时，通常需要从几个角度进行思考：</p>
<ul>
<li class="">是否能够支撑业务未来发展</li>
<li class="">系统是否容易维护</li>
<li class="">技术方案是否稳定可靠</li>
<li class="">成本是否可控</li>
<li class="">团队是否能够长期维护</li>
</ul>
<p>好的架构往往不是一次设计完成的，而是在<strong>业务发展、技术演进和不断优化中逐渐形成的</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2020/03/20/javahou-tai-jia-gou#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>架构设计是一个从代码到系统、从技术到团队的完整体系。代码规范、模块拆分、中间件选型、数据库治理解决的是系统本身的问题，而安全设计、团队协作、自动化测试保障的是系统能长期健康地演进。没有放之四海皆准的架构，只有与当前业务规模和团队能力相匹配的架构。与其追求一步到位，不如保留演进的空间，让架构随着业务一起成长。</p>]]></content:encoded>
            <category>技术</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Java远程桌面实现]]></title>
            <link>https://tanzhuo.xyz/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian</link>
            <guid>https://tanzhuo.xyz/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian</guid>
            <pubDate>Tue, 28 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[用 Java 实现远程桌面的一次尝试：从 Robot 截屏效率、图片传输带宽，到 h.264 视频流与行程编码差量传输的取舍记录。]]></description>
            <content:encoded><![CDATA[<p>突发奇想，使用java是否可以实现远程共享桌面的功能呢？</p>
<!-- -->
<p>远程桌面这类工具平时用得多，比如向日葵、TeamViewer，但很少有人想过它背后到底怎么跑起来的。拆开看其实就三件事：抓取屏幕画面、把数据压小、通过网络送到对端再还原。这三步每一步都有效率问题，而 Java 在第一步就先天吃亏。这篇就记录一下我用 Java 折腾远程桌面时，在这三个环节上各踩了什么坑、最后选了什么方案。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="截屏效率是第一道坎">截屏效率是第一道坎<a href="https://tanzhuo.xyz/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#%E6%88%AA%E5%B1%8F%E6%95%88%E7%8E%87%E6%98%AF%E7%AC%AC%E4%B8%80%E9%81%93%E5%9D%8E" class="hash-link" aria-label="截屏效率是第一道坎的直接链接" title="截屏效率是第一道坎的直接链接" translate="no">​</a></h2>
<p>当然可以了，都是跑代码，但是java效率比较低，为啥，也不是因为java代码执行效率低下，而是因为出于Java自带的屏幕截屏工具效率过低，java提供了robot类来实现桌面截屏，2k屏幕一张jpg大小在200kb左右，一张截图我使用的rx2700x cpu 一张截图竟然需要30毫秒 距离1秒30帧率还是差距有的，更别提1秒60帧率。就算如果1秒60帧率 如何使用 java 高效的传输到各个远程端呢 ?</p>
<p>补充一下机制层面的原因。<code>java.awt.Robot</code> 的 <code>createScreenCapture</code> 走的是 AWT 的本地调用，底层会把整块屏幕的像素从显存复制到内存，再封装成 <code>BufferedImage</code>。这个过程是同步的全量拷贝，分辨率越高拷贝的数据量越大，2K 屏一帧的原始 RGB 数据就有十几 MB，光是这一次内存搬运就注定快不了。它的设计初衷是给 GUI 自动化测试偶尔截一张图用的，不是为持续高频采集准备的。</p>
<p>有人会说使用多线程截图，来提高帧率，这样的做法，我个人觉得不太科学，为啥？因为占用太高cpu资源，应该寻找高效并占用cpu资源较少的方法。</p>
<p>多线程还有一个隐性问题：截屏的瓶颈在系统调用和内存拷贝上，多个线程同时抢这条通道，吞吐未必线性上涨，CPU 占用倒是实打实翻倍。远程桌面是常驻后台的工具，把宿主机 CPU 吃满，本身就不可接受。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="图片直传行不通">图片直传行不通<a href="https://tanzhuo.xyz/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#%E5%9B%BE%E7%89%87%E7%9B%B4%E4%BC%A0%E8%A1%8C%E4%B8%8D%E9%80%9A" class="hash-link" aria-label="图片直传行不通的直接链接" title="图片直传行不通的直接链接" translate="no">​</a></h2>
<p>并且图片传输不可取，因为一张图片太大 ，200kb，1s 30张就是 6000kb 严重占用带宽。</p>
<p>算一下就知道这条路走不通：6000kb 每秒，换算成带宽接近 50Mbps，家用上行根本扛不住，更别说一台主机同时给多个远程端推流。而且 JPEG 每一帧都是独立压缩，前后两帧就算 99% 的像素没变，也照样把整张图编码一遍再发一遍，浪费在了大量重复信息上。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="视频流是正路">视频流是正路<a href="https://tanzhuo.xyz/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#%E8%A7%86%E9%A2%91%E6%B5%81%E6%98%AF%E6%AD%A3%E8%B7%AF" class="hash-link" aria-label="视频流是正路的直接链接" title="视频流是正路的直接链接" translate="no">​</a></h2>
<p>最好的方式使用视频流，使用开源的h.264视频格式，还有更好的h.265 但是我并没有找到对应的开源jar。h.265技术还处于收费阶段。h.264视频格式 为根据每一帧中的像素点变化来记录其数据。而不需要记录整个图片中绝大部分像素信息。所以h.264格式的视频体积远远小于图片传输的数据总和。更加利于网络传输。</p>
<p>这正是视频编码里帧间压缩的思路：编码器隔一段发一个完整的关键帧，中间的帧只记录相对前一帧的变化量。桌面画面恰好是这种压缩的理想场景——大部分时间只有鼠标和局部窗口在动，背景纹丝不动，帧间冗余极高，压缩比自然可观。</p>
<p>通过录制桌面的方式，有效推流到客户端，可以大大降低图片带来的带宽和截屏的效率问题，但是使用视频流的话，实时转码与流量都是一个不小的挑战。</p>
<p>实时转码的难点在于延迟和算力的平衡：压缩率调高，编码耗时就上去了，远程操作最怕的就是画面比手慢半拍；压缩率调低，带宽又顶不住。成熟的远程桌面软件一般靠硬件编码来解这个矛盾，纯 Java 想在软件层面追上，难度不小。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我最终的折中方案">我最终的折中方案<a href="https://tanzhuo.xyz/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#%E6%88%91%E6%9C%80%E7%BB%88%E7%9A%84%E6%8A%98%E4%B8%AD%E6%96%B9%E6%A1%88" class="hash-link" aria-label="我最终的折中方案的直接链接" title="我最终的折中方案的直接链接" translate="no">​</a></h2>
<p>最终我采用的是行程编码，并只传输 robot 截图 rgb 位图中有改变的数据。但行程编码在遇到屏幕像素变化太大的情况 数据量体积急剧上升。我认为如果真要用图片来进行传输桌面信息。需要突破java获取的屏幕像素数据效率以及数据传输的体积压缩最小化，才能保证远程桌面的帧率最高。</p>
<p>展开说一下这个方案。做法是把当前帧和上一帧的 RGB 数组逐像素比对，只把变化的像素连同位置信息编码后发出去，对端拿到差量数据后覆盖到本地缓存的画面上。行程编码（RLE）本身很简单：连续相同的值记成「值 + 重复次数」，静止画面下差量几乎为零，效果很好。但它的弱点也明显——一旦全屏滚动或者播放视频，几乎每个像素都在变，差量退化成全量，再叠加编码本身的开销，数据量反而可能超过直接发原图。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）Robot 截屏的耗时和分辨率直接挂钩，测试时用小分辨率看着帧率还行，换到 2K 屏立刻现原形，评估方案时要按目标分辨率来测。</p>
<p>2）差量传输必须处理好首帧和丢包：对端没有基准画面时差量毫无意义，需要先发一次全量帧；传输中途丢了一帧差量，后面的画面就会一直错位，要么用可靠传输，要么定期强制刷全量帧兜底。</p>
<p>3）别忽视对端的还原成本，把差量合并回位图再绘制到界面上同样吃 CPU，接收端太弱一样会卡。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这次尝试下来，结论比较清楚：Java 做远程桌面，瓶颈不在语言本身，而在 Robot 这条截屏路径的效率和纯软件压缩的开销。图片直传带宽爆炸，行程编码差量传输适合静态画面但扛不住剧烈变化，视频流才是工程上的正解，只是纯 Java 生态里缺少顺手的 h.264 开源实现。如果只是做个玩具验证原理，差量传输够用了；真要做产品，还是得借助本地编码库或硬件编码的能力。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[MySQL 亿级数据导出excel文档]]></title>
            <link>https://tanzhuo.xyz/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu</link>
            <guid>https://tanzhuo.xyz/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu</guid>
            <pubDate>Thu, 16 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[SaaS 系统亿级列表数据导出 excel 的落地方案:MQ 削峰异步消费、从库分担查询压力、id 游标分页配合 EasyExcel 落盘上传 OSS,7550w 数据约 20 分钟导出完成。]]></description>
            <content:encoded><![CDATA[<p>公司 SaaS 系统需要给用户提供列表数据导出 excel 文档的功能。但有的公司的列表数据如财务流水已经高达千万、亿级别,对于这样的数据导出,我们需要一个专门的解决方案。</p>
<!-- -->
<p>不然到了月底,几百家公司同时导出 excel,会导致 mysql QPS 急剧上升,服务器 cpu、ram 瞬间到达报警阈值,大批量导出可能会导致服务器不可用甚至面临宕机。(提示:对于财务等敏感数据,需要做文件加密或者临时文件授权下载操作)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="具体方案">具体方案<a href="https://tanzhuo.xyz/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu#%E5%85%B7%E4%BD%93%E6%96%B9%E6%A1%88" class="hash-link" aria-label="具体方案的直接链接" title="具体方案的直接链接" translate="no">​</a></h2>
<p>方案的出发点:大批量导出,不能影响正常业务运行。</p>
<p>单独部署一个数据库从库节点分担查询压力,再单独部署多组服务器跑 Springboot 项目作为 MQ 的消费者集群。所有的导出请求均投递至 MQ 队列中,Springboot 采用拉模式主动拉取队列中的导出 Excel 任务消息进行执行,合理利用 MQ 的削峰填谷与异步能力,并在 SpringBoot 项目中严格分配线程池资源。</p>
<p>具体的任务消费者通过亿级分页方案分批查询 mysql 列表数据,使用阿里 EasyExcel 将数据落盘到硬盘,输出完成后上传 oss(也可以走 oss 内网流传输,直接传到 oss 文件系统中),再删除本地硬盘文件。</p>
<p>这样的设计让导出功能完全脱离主业务,导出直接成为一个独立的功能模块,部署也是单独的服务器节点。就算导出项目因为大量突如其来的导出请求导致宕机(一般也不会,因为 MQ 可以很好地避免此问题),也不会影响正常主业务功能。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="资源限制与分页查询">资源限制与分页查询<a href="https://tanzhuo.xyz/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu#%E8%B5%84%E6%BA%90%E9%99%90%E5%88%B6%E4%B8%8E%E5%88%86%E9%A1%B5%E6%9F%A5%E8%AF%A2" class="hash-link" aria-label="资源限制与分页查询的直接链接" title="资源限制与分页查询的直接链接" translate="no">​</a></h2>
<p>配合线程池的资源限制,以及限制单客户最大同时导出文件数,严格控制 JVM 单次拉取的任务数量,保证服务的健壮性。</p>
<p>查询数据库做的是亿级分页方案:先按条件查出目标数据的 id,放弃 limit 的大偏移量用法,改用 id 游标,保证每次 sql 查询效率都在毫秒级别;同时合理选择查询字段(索引是必须的),保证每次 mysql 流出的数据在一个大小范围之内(100kb-500kb)。</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 用 id 游标代替大偏移量 limit,每次以上一批的最大 id 作为起点</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">select</span><span class="token plain"> id </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">from</span><span class="token plain"> 表 </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">where</span><span class="token plain"> 条件 </span><span class="token operator" style="color:hsl(207, 82%, 66%)">and</span><span class="token plain"> id </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><span class="token plain"> 上一批最大id </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">order</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">by</span><span class="token plain"> id </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">limit</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">5000</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><br></div></code></pre></div></div>
<p>再封装抽象工厂与接口,提供给开发人员实现各种功能列表的导出。开发人员无需关心具体如何分页分批获取数据、如何生成 excel、如何上传,只需要写好导出 sql 查询列表,专注数据查询即可。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实际效果">实际效果<a href="https://tanzhuo.xyz/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu#%E5%AE%9E%E9%99%85%E6%95%88%E6%9E%9C" class="hash-link" aria-label="实际效果的直接链接" title="实际效果的直接链接" translate="no">​</a></h2>
<p>之后经过测试,7550w 的数据导出共用时 20 多分钟(其实可以更快,合理添加索引,以及多线程进行任务分解查询导出),已经可以支持当前业务场景需求。</p>
<p>单次查询 sql 通讯 + 执行时间按 100 ms 估算,大致时间公式:(75500000 / 5000 × 100ms) / 1000 / 60 ≈ 25min</p>
<p>单客户导出的内存增长只有 20m 左右的振幅,因为 dataList 单次只获取 5000 条数据,数据输出硬盘之后 list 对象内存已被回收,所以内存增长很少,硬盘 io 每次 3-4M。多客户则需要进行线程资源限制与总导出任务数量限制,防止 JVM 出现 OOM。</p>
<p><strong>Excel 单个工作表只能写入 100w 左右的行数,超过后需要分工作簿处理,工作簿的分割需要根据计算机内存进行考虑。总计导出 1 亿条记录的话,单文件体积过大,后续还需要对 excel 文件进行切割存储后压缩,避免单文件过大,打开时内存占用太高,导致客户下载后无法打开文件。</strong></p>
<p>数据库每次吞吐数据也不能消耗太多性能,需要多批次地完成 7550w 条数据的导出。输出到硬盘的 excel 文件完成后 zip 压缩直接上传至 oss,最后提供 oss 资源路径到前端 app 或 web 浏览器,给予客户端下载。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="总结">总结<a href="https://tanzhuo.xyz/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>就算是一个数据导出功能,要把性能做到极致,需要关注的点也非常多:从 MQ 异步削峰、从库分担查询,到游标分页、内存控制与文件切割,再到部署层面的隔离,以及对未来业务增长横向扩容的考虑,方方面面都需要设计到位。</p>]]></content:encoded>
            <category>技术</category>
            <category>DB</category>
        </item>
        <item>
            <title><![CDATA[MySQL 亿级数据分页]]></title>
            <link>https://tanzhuo.xyz/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo</link>
            <guid>https://tanzhuo.xyz/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo</guid>
            <pubDate>Wed, 15 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[单表数据到了几百万,直接 limit 翻页会越翻越慢。记录一次账务表分页优化:子查询定位主键、延迟关联、explain 验证,把深分页查询压到可接受范围。]]></description>
            <content:encoded><![CDATA[<p>随着公司业务增大，数据量也是随之剧增。MySQL作为一款社区免费开源数据库。想要用它做几百万的数据分页。光靠limit是不靠谱的。当然不是诋毁mysql，mysql作为开源插拔式存储引擎数据库，已经是可以满足绝大部分的应用场景需求。使用mysql管理100tb也不是问题。但是使用方式却是一个问题。</p>
<!-- -->
<p>分页几乎是所有后台列表页的标配功能，数据量小的时候怎么写都行，问题往往在表涨到几百万行之后才暴露出来：前几页毫秒级返回，越往后翻越慢，翻到最后几页甚至直接超时。这篇文章记录的就是我在公司账务表上遇到的深分页问题，以及一步步优化的过程。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="limit-为什么会慢">limit 为什么会慢<a href="https://tanzhuo.xyz/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#limit-%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BC%9A%E6%85%A2" class="hash-link" aria-label="limit 为什么会慢的直接链接" title="limit 为什么会慢的直接链接" translate="no">​</a></h2>
<p>limit接受一个或两个数字参数。参数必须是一个整数常量。如果给定两个参数，第一个参数指定第一个返回记录行的偏移量，第二个参数指定返回记录行的最大数目。</p>
<p>关键在于偏移量的实现方式：MySQL 并不能"跳到"第 10 万行，它必须把前面的 offset 行全部读出来再丢弃，只返回最后那几条。也就是说 <code>limit 100000, 20</code> 实际读取了 100020 行数据。如果查询的字段不在索引里，每一行还要根据主键回表去聚簇索引取完整记录，读的行数越多，这个代价被放大得越厉害。</p>
<p>limit在偏移量小于10w时性能还勉强可以接受，但随着偏移量越来越大，性能急剧下降。</p>
<p>公司单表账务数据已经到达230w ，做分页limt  来查询最后一页的数据，怕是没有个20秒是查询不出来的。当然具体的时间也要根据是否有索引，字段数量，数据内容而定，查询条件而定。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="子查询先定位主键">子查询先定位主键<a href="https://tanzhuo.xyz/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#%E5%AD%90%E6%9F%A5%E8%AF%A2%E5%85%88%E5%AE%9A%E4%BD%8D%E4%B8%BB%E9%94%AE" class="hash-link" aria-label="子查询先定位主键的直接链接" title="子查询先定位主键的直接链接" translate="no">​</a></h2>
<p>为了解决分页效率问题，我采用方案是：先用子查询只查出主键 id，再拿 id 去取整行数据。</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 子查询只取主键 id，带上 where 条件</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 只扫描索引即可完成，不需要回表取整行</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">select</span><span class="token plain"> id </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">from</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">table</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">limit</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">100000</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token number" style="color:hsl(29, 54%, 61%)">20</span><br></div></code></pre></div></div>
<p>（带上where条件作为子查询）</p>
<p>由于主键id原本就是主键索引，所以limit的速度效率很高。并把条件字段加入复合索引，效率才会有质量的提升 。这一步快的原因在于：只查 id 时整个查询可以在索引上完成，索引每条记录很小，同样的 offset 扫描量，IO 成本比扫整行低得多。</p>
<p>但如果有排序，性能也会极具下降，目测30w数据排序id或时间需要1-2秒时间。</p>
<p>最好的方式是直接放弃 limit 的偏移量 使用where id &gt; xxx 来更快速的定位目标数据id位置 之后排序 再 limit 20 条数据即可 这样的效率百万级基本可以支撑。这种写法通常叫游标分页（或 keyset 分页）：每次翻页时把上一页最后一条的 id 带过来，<code>where id &gt; xxx</code> 直接在索引上定位起点，不管翻到第几页，扫描的行数都是固定的 20 条左右。代价是只能顺序翻页，不支持随意跳页，适合信息流、导出这类场景。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="用-id-列表取整行数据">用 id 列表取整行数据<a href="https://tanzhuo.xyz/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#%E7%94%A8-id-%E5%88%97%E8%A1%A8%E5%8F%96%E6%95%B4%E8%A1%8C%E6%95%B0%E6%8D%AE" class="hash-link" aria-label="用 id 列表取整行数据的直接链接" title="用 id 列表取整行数据的直接链接" translate="no">​</a></h2>
<p>先查询出需要查询的id，之后执行不带条件的in idList查询：</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 第二步：用上一步拿到的 id 列表取完整字段</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 不带 where 条件，排序照常保留</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">select</span><span class="token plain"> 字段 </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">from</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">table</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">where</span><span class="token plain"> id </span><span class="token operator" style="color:hsl(207, 82%, 66%)">in</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">(</span><span class="token plain">idList</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">)</span><br></div></code></pre></div></div>
<p>（不带条件，有排序还是排序，但排序没关系，因为数据已经很少，就算是外部排序也很快）</p>
<p>查询列表时只需要 where in id 即可，其他的条件在 select id 的时候已经加上，列表数据查询时就不需要进行任何条件，但是排序还是要加的 。</p>
<p>这样就可以极快的进行分页查询，并且如果列表字段不多，可以做覆盖索引，查询效率更上一层楼。但一般列表字段都比较多10几个，20几个都很正常，这些字段都加索引是不可取的，那么索引体积太大，新增数据，修改数据时效率将会因为去维护索引而降低，可能会反而降低表的性能。所以做查询也需要考虑字段个数，排序规则，条件个数，索引类型，哪些字段做索引来做权衡。</p>
<p>表拼接这样的操作在大数据量下基本不考虑使用。最好做冗余字段。join 在大表上意味着驱动表的每一行都要去被驱动表做一次查找，数据量上去之后放大效应很明显；把常用的关联字段冗余到主表里，用写入时多存一份换查询时少一次关联，在读多写少的列表场景里通常是划算的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="用-explain-验证执行计划">用 explain 验证执行计划<a href="https://tanzhuo.xyz/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#%E7%94%A8-explain-%E9%AA%8C%E8%AF%81%E6%89%A7%E8%A1%8C%E8%AE%A1%E5%88%92" class="hash-link" aria-label="用 explain 验证执行计划的直接链接" title="用 explain 验证执行计划的直接链接" translate="no">​</a></h2>
<p>优化不能靠感觉，改完 SQL 要看执行计划确认。</p>
<p>查询sql 通过 explain 查看执行过程，是否走了索引，索引类型是什么。是否进行了回表，扫描数据行数等等重要信息，只要掌握好了这些，你的数据库性能才会有质量上的提升。</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 在查询语句前加 explain 即可查看执行计划</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">-- 重点看 type（索引类型）、key（实际用到的索引）、</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">-- rows（预估扫描行数）、Extra（是否 Using index 覆盖索引）</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token keyword" style="color:hsl(286, 60%, 67%)">explain</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">select</span><span class="token plain"> id </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">from</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">table</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">where</span><span class="token plain"> 条件 </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">limit</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">100000</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token number" style="color:hsl(29, 54%, 61%)">20</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><br></div></code></pre></div></div>
<p>经过测试，这样的分页效率 100w 每页20条数据 基本可以在1s内请求下来数据，基本为600ms左右的请求时间。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="单表之外的路">单表之外的路<a href="https://tanzhuo.xyz/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#%E5%8D%95%E8%A1%A8%E4%B9%8B%E5%A4%96%E7%9A%84%E8%B7%AF" class="hash-link" aria-label="单表之外的路的直接链接" title="单表之外的路的直接链接" translate="no">​</a></h2>
<p>分页优化解决的是单表查询效率，但表还在持续增长，架构上也要提前留好后路。</p>
<p>如果单表数据量已经过500w，已经可以考虑进行水平分表。</p>
<p>对于业务逻辑来讲，为了增加单库性能，可以考虑 读写分离，主写，从读，多从等方式。</p>
<p>根据业务对库进行垂直拆分， 分离热数据，冷数据进行分配服务器资源。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1）子查询方案的前提是 where 条件字段有合适的复合索引，否则第一步 select id 本身就会退化成全表扫描，优化等于白做。</p>
<p>2）<code>where id &gt; xxx</code> 的游标分页要求排序键单调且唯一，如果按时间排序而时间有重复值，翻页时可能漏数据或重复，通常要用「时间 + id」联合排序兜底。</p>
<p>3）<code>in (idList)</code> 的列表长度就是每页条数，一般 20 条没有问题；但不要把这个写法推广到一次传几千个 id 的场景。</p>
<p>4）explain 给出的 rows 是估算值，统计信息不准时会有偏差，拿不准时结合慢查询日志和实际执行时间一起判断。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>线上改分页 SQL 之前，先在从库或测试库用真实数据量验证一遍执行计划，深分页问题在小数据量下是复现不出来的。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>深分页慢的根源是 limit 偏移量必须逐行扫描丢弃，数据量越大代价越高。解决思路是让扫描发生在最小的数据集上：先在索引里定位主键，再回表取整行；能用游标分页就放弃偏移量。改完用 explain 验证索引是否真的生效。单表撑不住时，再考虑分表、读写分离、冷热拆分这些架构手段。</p>]]></content:encoded>
            <category>技术</category>
            <category>DB</category>
        </item>
        <item>
            <title><![CDATA[浅谈logo设计]]></title>
            <link>https://tanzhuo.xyz/2019/12/31/qian-tan-logoshe-ji</link>
            <guid>https://tanzhuo.xyz/2019/12/31/qian-tan-logoshe-ji</guid>
            <pubDate>Tue, 31 Dec 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[从企业定位、色彩取舍、图形形态到商标注册，聊聊一个 logo 从设计到落地要考虑的事：它不只是好看的图形，更是品牌记忆的载体。]]></description>
            <content:encoded><![CDATA[<p>在企业没有声望的时候，建立一个 logo，给予人们一定的标识性认知；一旦企业知名度高了，就可以很好地把图形映射到人们的记忆中，加强企业知名度，可谓是锦上添花。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="logo-存在的意义">logo 存在的意义<a href="https://tanzhuo.xyz/2019/12/31/qian-tan-logoshe-ji#logo-%E5%AD%98%E5%9C%A8%E7%9A%84%E6%84%8F%E4%B9%89" class="hash-link" aria-label="logo 存在的意义的直接链接" title="logo 存在的意义的直接链接" translate="no">​</a></h2>
<p>logo 的核心价值在于降低记忆成本。人对图形的记忆远比对文字的记忆牢固：提到某个品牌，脑海里最先浮现的往往不是它的全称，而是那个图形符号。让人们通过图形记忆来记住公司形象品牌，这就是 logo 要完成的第一件事。</p>
<p>反过来说，一个难以辨认、和同行高度雷同的 logo，等于把这部分记忆成本又还给了用户。所以评价一个 logo 好不好，第一标准不是"好看"，而是"看一眼能不能记住、隔几天能不能认出来"。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="从企业定位出发">从企业定位出发<a href="https://tanzhuo.xyz/2019/12/31/qian-tan-logoshe-ji#%E4%BB%8E%E4%BC%81%E4%B8%9A%E5%AE%9A%E4%BD%8D%E5%87%BA%E5%8F%91" class="hash-link" aria-label="从企业定位出发的直接链接" title="从企业定位出发的直接链接" translate="no">​</a></h2>
<p>logo 的设计要从公司的企业文化、企业提供的服务角度出发。说得简单易懂一点：公司是干嘛的？公司需要的气质是复古沉稳，还是年轻充满活力？</p>
<p>这一步经常被跳过，很多人拿到需求就直接开始画图，结果改稿改到双方都疲惫。比较稳妥的做法是先把几个问题写下来：</p>
<ol>
<li class="">
<p>公司的核心业务是什么，目标用户是谁；</p>
</li>
<li class="">
<p>希望用户看到 logo 时联想到什么关键词，比如"专业""亲切""科技感"；</p>
</li>
<li class="">
<p>主要竞品的 logo 长什么样，如何做出区分。</p>
</li>
</ol>
<p>把这些回答清楚之后再动手，方向偏差会小很多。也正因为这套前期工作量不小，专业视觉设计师的价值才体现在这里——不少知名品牌光是 logo 的设计费用就高达几十万元，买的不是几笔线条，而是背后完整的定位推导。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="色彩的取舍">色彩的取舍<a href="https://tanzhuo.xyz/2019/12/31/qian-tan-logoshe-ji#%E8%89%B2%E5%BD%A9%E7%9A%84%E5%8F%96%E8%88%8D" class="hash-link" aria-label="色彩的取舍的直接链接" title="色彩的取舍的直接链接" translate="no">​</a></h2>
<p>对于一家科技企业、互联网企业来讲，年轻充满活力的气质几乎是必选项。颜色可以选择多样化一点，但总体颜色不能太多，否则略显花哨，要适量控制颜色种类。</p>
<p>饱和度上我倾向于收着用：颜色应该略淡一些，色彩饱和度不可太高。饱和度太高的大面积色块，会让人看着不够放松，越看心情越急躁。想让人的眼球看过去更舒适、更亲近，有一种放松的感觉，低饱和、少色相通常是更安全的选择。</p>
<p>另外还有一个容易被忽略的点：logo 最终会出现在各种场景里——网站、名片、印刷品、深色背景的 App 界面。设计时最好同时验证它在黑白单色下是否依然清晰，很多依赖渐变和多色搭配的方案，一转成单色就糊成一团。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="图形与字体">图形与字体<a href="https://tanzhuo.xyz/2019/12/31/qian-tan-logoshe-ji#%E5%9B%BE%E5%BD%A2%E4%B8%8E%E5%AD%97%E4%BD%93" class="hash-link" aria-label="图形与字体的直接链接" title="图形与字体的直接链接" translate="no">​</a></h2>
<p>图形形状要根据公司做的业务来定，也取决于公司想要的是字体型 logo，还是比较抽象一点的图形。</p>
<p>字体型 logo 直接把公司名做视觉化处理，好处是认知负担小，看到即读出；抽象图形则更依赖长期投放来建立联想，但一旦建立起来，辨识度和延展性都更强。互联网企业往往会在图形里加入一些科技元素，具体的形象还得和公司的企业形态产生共鸣。</p>
<p>这里有一个心态要摆正：logo 可能不是最好看的那一个，但最符合公司形象的 logo 足矣。为了炫技堆细节，反而会牺牲小尺寸下的辨识度——favicon 只有十几个像素，复杂的图形到那个尺寸基本不可辨认。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="别忘了商标注册">别忘了商标注册<a href="https://tanzhuo.xyz/2019/12/31/qian-tan-logoshe-ji#%E5%88%AB%E5%BF%98%E4%BA%86%E5%95%86%E6%A0%87%E6%B3%A8%E5%86%8C" class="hash-link" aria-label="别忘了商标注册的直接链接" title="别忘了商标注册的直接链接" translate="no">​</a></h2>
<p>设计做完只是一半，法律层面的保护同样重要。logo 定稿后一定要注册商标、登记著作权，不然可能出现侵权的情况——既可能是别人抢注了你的图形，也可能是你的设计无意中撞上了已注册的商标。注册商标本质上是为了更好地保护公司企业形象。</p>
<p>实际操作上，建议在设计定稿前就先做一轮商标检索，确认目标类别下没有近似商标，避免辛苦打磨出来的方案因为无法注册而推倒重来。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2019/12/31/qian-tan-logoshe-ji#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>一个 logo 从无到有，大致要经过定位分析、色彩与图形设计、多场景验证、商标注册这几步。它不需要惊艳所有人，只需要准确传达公司是谁、让人看一眼就能记住。把"符合企业形象"放在"好看"前面，多数取舍就有了答案。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[关于使用ELK的记录]]></title>
            <link>https://tanzhuo.xyz/2019/12/19/guan-yu-shi-yong-elk</link>
            <guid>https://tanzhuo.xyz/2019/12/19/guan-yu-shi-yong-elk</guid>
            <pubDate>Thu, 19 Dec 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[公司统计业务把 MySQL 集群查到了瓶颈,索引优化也无力回天,于是引入 ELK:Logstash 增量同步、Elasticsearch 聚合查询、Kibana 可视化,记录一下选型与增量同步的方案。]]></description>
            <content:encoded><![CDATA[<p>最近因为公司系统项目统计压力大 。mysql集群查询进入瓶颈，索引优化也不能快速响应统计结果，于是接入 Elasticsearch、Logstash、Kibana 三大开源套餐。</p>
<!-- -->
<p>先交代一下背景。统计类查询和普通的业务查询不是一回事:业务查询大多是按主键或索引取少量行,MySQL 很擅长;而统计查询往往要扫描大量数据做分组、聚合,B+ 树索引在这种场景下帮助有限。数据量上来之后,一条统计 SQL 跑几秒甚至几十秒,还会拖累同库的业务请求。这时候常见的思路就是把统计负载从 OLTP 数据库里剥离出去,交给专门做聚合分析的引擎。Elasticsearch 底层是倒排索引加列式的 doc values,天生适合过滤和聚合,ELK 这套组合也是社区里最容易上手的方案之一,所以值得记一笔。</p>
<p><a href="https://www.elastic.co/cn/" target="_blank" rel="noopener noreferrer" class="">https://www.elastic.co/cn/</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="选型与分工">选型与分工<a href="https://tanzhuo.xyz/2019/12/19/guan-yu-shi-yong-elk#%E9%80%89%E5%9E%8B%E4%B8%8E%E5%88%86%E5%B7%A5" class="hash-link" aria-label="选型与分工的直接链接" title="选型与分工的直接链接" translate="no">​</a></h2>
<p>关于  Logstash 其实还可以使用ali DataX 也是可以的 功能比 Logstash 还要更上一层楼。我采用的还是Logstash，因为ali DataX 我是后面才知道的，所以就没有去替换掉了。</p>
<p>两者定位有些差别:Logstash 是 Elastic 家族的数据管道,靠 jdbc input 插件定时拉取数据,配置文件写好就能跑,和 Elasticsearch 的输出对接是现成的;DataX 是阿里开源的异构数据源同步工具,插件覆盖的数据源更多,批量吞吐也更强。对于"MySQL 定时增量同步到 ES"这种单一链路,Logstash 已经够用,没必要为了换而换。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-12-image-1-60438a663c67bcd8701b19a0ae751a56.webp" width="1461" height="1034" class="img_ev3q"></p>
<p>三个组件的分工很清晰:</p>
<p>通过 Logstash抓取过滤mysql统计数据，增量同步到Elasticsearch中。</p>
<p>再通过项目java api 调用 Elasticsearch查询；</p>
<p>Kibana 可以web可视化集群，索引具体情况。</p>
<p>也就是说 Logstash 管数据进,Java 应用管数据出,Kibana 负责让人看得见集群和索引的健康状况,三者互不干扰。</p>
<p>Elasticsearch查询语句挺简单，但是感觉官网的例子不是很多，很多比较复杂的聚合需要自己摸索。比如多层嵌套的 aggregation、聚合结果再排序这类写法,文档里往往只给最基础的示例,实际业务里得靠 Kibana 的 Dev Tools 一点点调出来。</p>
<p>整个学习难度不是很高。入手很快</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="java-端的接入">Java 端的接入<a href="https://tanzhuo.xyz/2019/12/19/guan-yu-shi-yong-elk#java-%E7%AB%AF%E7%9A%84%E6%8E%A5%E5%85%A5" class="hash-link" aria-label="Java 端的接入的直接链接" title="Java 端的接入的直接链接" translate="no">​</a></h2>
<p>java api jar 使用的是 elasticsearch-rest-high-level-client</p>
<p>之后再根据业务场景自己封装了一下工厂，抽象了几层代码给开发人员使用。封装的目的很简单:不希望每个开发都去直接拼 <code>SearchSourceBuilder</code>、解析 <code>SearchResponse</code>,把常用的条件过滤、分页、聚合模式收敛成几个方法,业务代码只关心传参和拿结果。</p>
<p>api的查询和得到结果集方法可能是操作起来有点麻烦，但其实只要对着查询语句写的话。其实还是很好理解的。high level client 的 builder 结构和 Query DSL 的 JSON 结构基本是一一对应的,先在 Kibana 里把 DSL 调通,再翻译成 Java 代码,几乎不会出错。</p>
<p>总的感觉还是比较友善。所有的软件都是安装即可用，不过还是需要改一下配置，这个就不多说了。ip 端口，中文英文，密码等等。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="只同步统计需要的字段">只同步统计需要的字段<a href="https://tanzhuo.xyz/2019/12/19/guan-yu-shi-yong-elk#%E5%8F%AA%E5%90%8C%E6%AD%A5%E7%BB%9F%E8%AE%A1%E9%9C%80%E8%A6%81%E7%9A%84%E5%AD%97%E6%AE%B5" class="hash-link" aria-label="只同步统计需要的字段的直接链接" title="只同步统计需要的字段的直接链接" translate="no">​</a></h2>
<p>公司主要是做统计，于是通过 Logstash 抓取关键统计数据，公司几百万数据抓取关键统计数据只有100m不到。这也可以大大提升es 聚合统计的速度，所以不建议把整张表都抓取进来，只拿最主要的统计字段。</p>
<p>这一点值得展开说。Elasticsearch 的聚合是在内存和 doc values 上做的,索引越瘦,段文件越小,能缓存的比例就越高,聚合自然越快。而且 ES 不是数据库,不需要承担"存全量明细"的职责,明细永远以 MySQL 为准,ES 里只放统计维度和指标字段,坏了随时可以重建,心理负担也小很多。</p>
<p>所有流程如图：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2020-01-----8c8c2bbb2ecaab9ad0f92291cf1cef25.webp" width="1600" height="740" class="img_ev3q"></p>
<p>这也是我给我们公司员工写的流程图。</p>
<p>其实如果抓取数据量极大，可以在中间使用kafaka进行缓存再次筛选。由于我们公司数据量并没有这么高，也就不用过分去开销其他服务器资源。中间加一层消息队列的意义在于削峰和解耦:上游抓取和下游写入速率不一致时,队列能兜住突发流量,也方便在中间再挂一道清洗逻辑。但每多一个组件就多一份运维成本,量不到就不要上。</p>
<p>Kibana 现在已经很完善了，写官方的Query DSL 语句也可以，写SQL语句也可以，但官方还是推荐使用 Query DSL 语句进行聚合与查询</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2020-01-image-0c13f4afeeb7506820c66bd5ea02aec4.webp" width="1600" height="808" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="增量同步的实现">增量同步的实现<a href="https://tanzhuo.xyz/2019/12/19/guan-yu-shi-yong-elk#%E5%A2%9E%E9%87%8F%E5%90%8C%E6%AD%A5%E7%9A%84%E5%AE%9E%E7%8E%B0" class="hash-link" aria-label="增量同步的实现的直接链接" title="增量同步的实现的直接链接" translate="no">​</a></h2>
<p>关于增量同步，更新同步数据，我采用的方案是在抓取的数据表上设置data_version字段（乐观锁原理，数据版本号），一旦某条数据进行了修改或者是新增的数据，data_version这个值将会设置，我设置的是时间戳（公司业务原因），建议给此字段设置bigint类型，timestamp只能使用到2028年。</p>
<p>这样就给了数据一个版本，在同步增量与更新数据时，只会抓取data_version 改变过的数据。大大降低mysql服务器，Logstash，Elasticsearch的压力。</p>
<p>Logstash 可以记录上次抓取数据最后一条数据的 data_version 值。这样在下一次抓取数据时，带上 上一次的data_version 值，就可以通过where条件过滤出来有效数据。当然sql里面是要对data_version 进行排序的，因为Logstash 只会记录上次抓取时最后一条数据的data_version 值。</p>
<p>这背后就是 Logstash jdbc 插件的 tracking column 机制:插件把上次运行时最后一条记录的追踪列值持久化到本地文件,下次执行 SQL 时以参数形式带入 where 条件。所以排序是必须的——如果结果集不按 data_version 升序排,记录下来的"最后一条"就不是最大值,下一轮同步会漏数据。</p>
<p>不然每一次同步数据都要全表同步吗？那压力可想而知。应该没人会采用，全表同步只是第一次同步时会全表同步。之后都是增量与更新数据同步。</p>
<p>更新的数据之所以也能被同步到,是因为每条数据在 ES 里以固定的文档 id(通常就是 MySQL 主键)写入,同一条数据版本变了会被再次抓到,写入 ES 时就是一次覆盖更新,不会产生重复文档。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2019/12/19/guan-yu-shi-yong-elk#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1)追踪列的类型要想清楚。上面提到过,时间戳做版本号时字段建议用 bigint,避免 timestamp 类型的取值上限问题;另外时间戳精度不够时,同一秒内的多次修改可能在边界上漏掉,业务上要能接受或者改用自增序列。</p>
<p>2)物理删除同步不到。data_version 方案只能感知新增和修改,MySQL 里直接 delete 的行不会再出现在结果集里,ES 中会残留旧文档。要么业务上用逻辑删除标记,把删除也当成一次"修改"同步过去;要么定期重建索引兜底。</p>
<p>3)不要把 ES 当唯一存储。ES 里的统计索引应该随时可以从 MySQL 重放出来,这样映射设计错了、字段要加了,直接删索引重跑一遍 Logstash 即可,不用做复杂的在线迁移。</p>
<p>4)增量 SQL 一定要验证排序和边界条件。where 条件用大于还是大于等于、排序是否生效,建议先手工执行一遍 SQL,再对比 Logstash 两轮运行的记录值,确认没有漏抓和重复抓。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2019/12/19/guan-yu-shi-yong-elk#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这次接入 ELK,核心收益是把统计聚合从 MySQL 集群里剥离出来:Logstash 基于 data_version 做增量同步,只抓统计需要的字段;Elasticsearch 负责聚合查询,Java 端用 high level client 封装后给业务使用;Kibana 兼顾调试 DSL 和观察集群状态。整套方案没有引入多余的组件,按数据量的实际规模做取舍,够用就好。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[逐渐上瘾的游泳]]></title>
            <link>https://tanzhuo.xyz/2019/11/25/zhu-jian-shang-yin-de-you-yong</link>
            <guid>https://tanzhuo.xyz/2019/11/25/zhu-jian-shang-yin-de-you-yong</guid>
            <pubDate>Mon, 25 Nov 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[本来健身房就有泳池，竟然办了卡不去游几次感觉蛮可惜的。那就去游吧，请了教练带了3节课后，蛙泳基本已经掌握了。但是姿势还是不够标准。]]></description>
            <content:encoded><![CDATA[<p>本来健身房就有泳池，竟然办了卡不去游几次感觉蛮可惜的。</p>
<!-- -->
<p>那就去游吧，请了教练带了3节课后，蛙泳基本已经掌握了。但是姿势还是不够标准。</p>
<p><img decoding="async" loading="lazy" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABsSFBcUERsXFhceHBsgKEIrKCUlKFE6PTBCYFVlZF9VXVtqeJmBanGQc1tdhbWGkJ6jq62rZ4C8ybqmx5moq6T/wAALCAA3AEIBAREA/8QAGgAAAgMBAQAAAAAAAAAAAAAABAUAAQMCBv/EADQQAAIBAwIDBQUHBQAAAAAAAAECAwAEEQUhEjFRExQiQWEyM3GBsQYjQlKh0fAVJWJykf/aAAgBAQAAPwD0tZSyJDGXkcIo5knAFAnVhJkWdrNc/wCQGF/6a4Nxq7bjT4lHRpQTVd+1CIffaU5HmY3DfoK1XWLQwPIzFCg8UbDDj0xWDTaq6GeOGKJBusLbsw/ejrC6W9tlnQYzsQfI+Yoql2oXEtu0EdvwGWZ8DjG2AD0+VCS2eozXCzXUVvcqnsxByo+O4oy2v+Pt0nhNu1uA0gLBgAQTzHwrqPWNOcYF3GP9tvrRMNxDPnsZkkx+RgaG1DTLe+ALeCUezIvMfvQ9pe3EbSWl0gNyikxkcpQB9az+zsyxxNZygx3CMWKMMbHpTukeric6pYmB0Mni4UcbDqT8vpWz6o9qmb62kh22ZDxq3p6H40onNyshR2aLvz7oxBPDkAZ2yNj+lepMUZQIUUqOQI2oG606w7FpJYI1VAWLIOEgfEULp7alPZxIoEQxvPL4mIz5L+9dXeku0RmW4lkvEPEsjN59AOQFD2Ulxqd9bXXYCMW2VkfO5OOWP5zr0VKT4/tGOLlHb7b+ZNSxXvsjXcnii4sQIeQAyOLHU71ldxva3Et7P2U8EpCPxLho0OwA9N9+tEf0qKIA2s9xb45BJCV+YOaqSxvpY+xlvUljY+PMXCSOmxpkqhECqMADAFdUkvf7XqK3q57tN4ZgPI+R/nr1pjbxpcW8c/B7xQ/PqM0DrdirQzXiO6zRxkbHmPP9K0mFzb6TEbXhMkSKSoXIYAbihrZhqhje6u4nXIbsIhgEj82dz8Ke5FTNVmroPU7UXdjLDjxEZX4jlWdn2vc4OXu1+lFSxrLE0bjKuCpHUGgdJlZUaykb7238O/4k/Cw+VYalo6vKbmCFXY+8hOwf1B8jQ8aqbi2SxvLmMSsyujtkoVGcEHlXo8VMVKlSrpfeWQuWWSN+yuY/YkA5eh6isrbU+K57pdL2dz0XcNtzB8vnXK24OtmURKFEWMgAZYncn5U2q6qpUr//2Q==" width="66" height="55" class="img_ev3q"></p>
<p>但这并不是重点。重点是已经可以游过去，游过来啦，真tm好玩。哈哈哈。</p>
<p>现在每次下水都感觉全身放松，整个人什么都不需要思考。只管游，漂就好了。</p>
<p>学会之后呢，后面就是开始动作，姿势，速度的提升。基本上现在每周要至少去一次。</p>
<p>特别是游泳完之后的冲澡，才是真的爽。但是游完从泳池出水的一瞬间，那重力……</p>]]></content:encoded>
            <category>生活</category>
        </item>
        <item>
            <title><![CDATA[黑魂1]]></title>
            <link>https://tanzhuo.xyz/2019/11/18/hei-hun-1</link>
            <guid>https://tanzhuo.xyz/2019/11/18/hei-hun-1</guid>
            <pubDate>Mon, 18 Nov 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[黑暗之魂1，在远古时代，世界还未分化，四处都是灰色的岩石，高耸的大树及不朽古龙。但是有一天某一颗树底燃起了第一团火，所有的差异因此而生。]]></description>
            <content:encoded><![CDATA[<p>《黑暗之魂1》是一款由日本游戏公司FromSoftware开发的角色扮演游戏。游戏于2011年首次发布，随后在全球范围内获得了巨大的成功和声誉。作为系列的第一部作品，它的深度和难度一直被认为是游戏史上的经典之一。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="游戏简介">游戏简介<a href="https://tanzhuo.xyz/2019/11/18/hei-hun-1#%E6%B8%B8%E6%88%8F%E7%AE%80%E4%BB%8B" class="hash-link" aria-label="游戏简介的直接链接" title="游戏简介的直接链接" translate="no">​</a></h2>
<p>游戏的主角是一个被称为“不死者”的角色，他的任务是探索一个名为洛兰的王国，解决邪恶的魔法和魔物所带来的危机。在游戏中，玩家需要探索各个地区，与各种各样的敌人战斗，并解决各种谜题和难题。</p>
<p>游戏的特点之一是其高难度。玩家需要面对各种强大的敌人，包括巨型魔物和其他玩家的角色。游戏中的战斗需要玩家熟练掌握攻击、防御和躲避等技能，同时需要合理利用各种武器和装备。在游戏中，死亡并不是终点，而是玩家需要学习和成长的机会，玩家需要不断尝试和探索才能够取得游戏的最终胜利。</p>
<p>除了高难度之外，游戏的另一个特点是其深度和复杂性。游戏中有一个复杂的世界观，包括各种不同的角色和故事情节。玩家需要通过探索和解决谜题来了解这个世界，并逐渐理解它的背景和历史。</p>
<p>总的来说，《黑暗之魂1》是一款经典的角色扮演游戏，它的深度和难度一直被称为游戏史上的经典之一。如果你喜欢挑战和深度的游戏，那么这款游戏一定会让你爱不释手。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="游戏剧情">游戏剧情<a href="https://tanzhuo.xyz/2019/11/18/hei-hun-1#%E6%B8%B8%E6%88%8F%E5%89%A7%E6%83%85" class="hash-link" aria-label="游戏剧情的直接链接" title="游戏剧情的直接链接" translate="no">​</a></h2>
<p>在远古时代，世界还未分化，四处都是灰色的岩石，高耸的大树及不朽古龙。世界犹如混沌，没有光，没有暗，只有古龙以及古树，他们是不朽的，永恒的存在，但是有一天某一颗树底燃起了第一团火，所有的差异因此而生。</p>
<p>光与暗，冷与热，生与死。</p>
<p>有几只从黑暗中诞生的类人物种，受到火的引诱，并在火的周围找到了王的灵魂。最初的死者尼特、伊札里斯的魔女、太阳王葛温及他麾下的白银骑士和最后没人看到过的矮人。他们获得了王的力量，并挑战古龙。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2020-01-29e7e21cea9e2777e86ed69d33d1410a_r-72cbf3a7ba302233f749b96eee217ae7.webp" width="1280" height="720" class="img_ev3q"></p>
<p>但古龙是不朽的，它们拥有着不朽龙鳞，难以被打败。而在不朽古龙之中存在着一只异种，无鳞的白龙希斯。它并不像其他不朽古龙那样拥有不朽。希斯背叛了古龙，告诉了葛温王古龙不朽的秘密。</p>
<p>葛温王带领着白银骑士使用雷系法术-雷枪贯穿古龙的龙鳞。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2020-05-image-67c219e1dee3e1154c7afe8b84c84df9.webp" width="1076" height="518" class="img_ev3q"></p>
<p>伊扎里斯和她的女儿们则用熊熊烈焰烧尽了盘绕在空中的古树。尼特则向巨龙们施放了可怕的死亡毒雾，病痛折磨着古龙的肉体。</p>
<p>古龙败退。于是，火的时代就此开启。</p>
<p>神域（亚诺尔隆德）：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--2--e0e9a8b322c31089e7372f23097f2e5a.webp" width="1600" height="935" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="传火与不死人">传火与不死人<a href="https://tanzhuo.xyz/2019/11/18/hei-hun-1#%E4%BC%A0%E7%81%AB%E4%B8%8E%E4%B8%8D%E6%AD%BB%E4%BA%BA" class="hash-link" aria-label="传火与不死人的直接链接" title="传火与不死人的直接链接" translate="no">​</a></h2>
<p>但是火终有熄灭的时候，到时将只剩下黑暗。无数漫长岁月之后的今日，火即将熄灭，光明无法照耀人世，黑夜无止境的持续。而身受不死诅咒的不死人，开始出现于人群中。</p>
<p>不死人虽然不死，但随着死亡的次数增加，灵魂与人性会流逝。慢慢的会变为游魂，游魂只会本能的追求灵魂。但只要不死人不放弃自己的信念，那么他将会一直在火焰中复活。</p>
<p>不死人的出现，导致一些国家出现一定的混乱。为了处理这些不死人，便把他们统一关押到北方不死院，因为不死人无法被杀死，只能进行关押，隔离世间。而北方不死院便是关押不死人的监牢。</p>
<p>传闻北方不死院中会出现一个被命运选中的不死人。他会离开北方不死院，去罗德兰大陆巡礼。而神域亚诺尔隆德也就是在罗德兰这片大陆上。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--1--2-c35f5a7f14fcb7b2f2540c3721cb6773.webp" width="1400" height="875" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--2--1-530d65696c7ce5e59240ef68646f004c.webp" width="1600" height="923" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--3--5ff38817aa68232926a9d6be653d0456.webp" width="1446" height="906" class="img_ev3q"></p>
<p>葛温王为了延续初火，选择燃烧自己的灵魂，成功延续了魂世界火之时代的寿命。这一举动之后也便称为传火。传火后的葛温，已被初火燃烧得只剩下躯壳，人性与灵魂，均已烧得所剩无几。甚至已不能使用标志性的雷系法术。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg-2-779d5acc3a2fe4f1317924155d5b9796.jpg" width="1259" height="786" class="img_ev3q"></p>
<p>亚诺尔隆德大书库，这里也是白龙研究不朽结晶的地方：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-20191118192243_1-99351699dec759d9bc5a042b5495418e.webp" width="1600" height="900" class="img_ev3q"></p>
<p>白龙希斯：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-20191123132954_1-dfc9d5f4353f82e44fbe94c23aac2e48.webp" width="1600" height="900" class="img_ev3q"></p>
<p>白龙希斯没有不朽龙鳞，于是沉迷于寻找代替不朽龙鳞的不朽之物，结晶。虽然希斯成功的研究出了不朽结晶。但自身也被结晶侵蚀。双眼失明。下半身已被结晶同化。</p>
<p>初始火炉：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-20191123171440_1-c5d10f28aec4b3e67129843ae8dee376.webp" width="1600" height="900" class="img_ev3q"></p>
<p>老魔女，她并没有考虑传火，而是想自己研究一个新的初火，来延续火的时代。但是却忘记了要畏惧火焰，她这疯狂的举动导致实验失控，出现了混沌之火。自己与自己的儿女，子民，乃至整个国度都变成了混沌怪物的温床，成为了恶魔之城。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-20191123163221_1-01caa2a2108e13e383eb8b721b0e241d.webp" width="1600" height="900" class="img_ev3q"></p>]]></content:encoded>
            <category>游戏</category>
        </item>
        <item>
            <title><![CDATA[迁移到阿里云]]></title>
            <link>https://tanzhuo.xyz/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi</link>
            <guid>https://tanzhuo.xyz/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi</guid>
            <pubDate>Sun, 10 Nov 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[趁双 11 活动把博客从香港服务器迁到阿里云:Docker 整体搬迁、域名备案、certbot 自动续签 Let's Encrypt 证书,记录过程与踩过的坑。]]></description>
            <content:encoded><![CDATA[<p>以前使用香港服务器 4h 4g 90g 5m,150 元/月,感觉略贵。趁着双 11 阿里云活动,把博客整体迁到了国内,顺手把 SSL 证书续签也自动化了。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="背景">背景<a href="https://tanzhuo.xyz/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="背景的直接链接" title="背景的直接链接" translate="no">​</a></h2>
<p>之前博客跑在一台香港服务器上,配置 4 核 4G 内存、90G 硬盘、5M 带宽,每月 150 元。对一个个人博客来说,这个价格谈不上划算——流量不大,4 核基本闲置,钱主要花在了"免备案"这个便利上。</p>
<p>正好赶上双 11,阿里云活动价拿下一台 2 核 8G 内存、40G 硬盘、5M 带宽的国内云服务器,3 年 1399 元,算下来一个月 38 元左右。核数少了,但内存翻倍,对我这种"博客 + 若干自写小服务"的用法更合适。5M 带宽对静态页面和小型应用完全够用,一些小型企业站点也不过如此。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="迁移过程">迁移过程<a href="https://tanzhuo.xyz/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#%E8%BF%81%E7%A7%BB%E8%BF%87%E7%A8%8B" class="hash-link" aria-label="迁移过程的直接链接" title="迁移过程的直接链接" translate="no">​</a></h2>
<p>原来的博客就是用 Docker 部署的,这次迁移因此省了很多事。容器化部署的一个直接好处是:环境不用重装,把镜像和数据搬过去,起容器就完事。</p>
<p>整体思路分三步:</p>
<p>1)在新服务器上装好 Docker,拉取(或导入)原来的镜像;</p>
<p>2)打包旧服务器上的数据卷目录,传到新机器对应路径,保证挂载点一致;</p>
<p>3)确认容器在新机器上正常运行后,把域名解析切到新 IP。</p>
<p>数据卷的搬迁可以简单粗暴地用 tar 加 scp:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 旧服务器:打包数据卷目录</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">tar</span><span class="token plain"> czf blog-data.tar.gz /data/blog</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 传输到新服务器</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">scp</span><span class="token plain"> blog-data.tar.gz root@new-server:/data/</span><br></div></code></pre></div></div>
<p>只要目录结构和挂载配置不变,容器在新机器上起来后和原来没有任何区别。这也是我一直坚持用 Docker 部署个人服务的原因:迁移成本几乎为零。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="域名备案">域名备案<a href="https://tanzhuo.xyz/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#%E5%9F%9F%E5%90%8D%E5%A4%87%E6%A1%88" class="hash-link" aria-label="域名备案的直接链接" title="域名备案的直接链接" translate="no">​</a></h2>
<p>国内服务器和香港服务器最大的区别在这里:域名解析到国内 IP 并对外提供 Web 服务,必须先完成 ICP 备案,否则 80/443 端口的请求会被拦截。</p>
<p>备案在阿里云控制台上提交即可,填写主体信息、网站信息,上传证件,然后等管局审核。我这次审核花了大概 6 天。备案期间域名不能解析到新服务器,但服务器本身是可以正常使用的,可以先通过 IP 或 SSH 把环境全部调试好,备案一通过就切解析,中间几乎没有停机时间。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ssl-证书自动续签">SSL 证书自动续签<a href="https://tanzhuo.xyz/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#ssl-%E8%AF%81%E4%B9%A6%E8%87%AA%E5%8A%A8%E7%BB%AD%E7%AD%BE" class="hash-link" aria-label="SSL 证书自动续签的直接链接" title="SSL 证书自动续签的直接链接" translate="no">​</a></h2>
<p>这次迁移顺便把 SSL 证书的事情做彻底了。证书用的还是免费的 Let's Encrypt,它的特点是单张证书有效期只有 90 天,官方的意图就是逼你把续签自动化,而不是手工换证书。</p>
<p>我用 Docker 版的 certbot 来做这件事。certbot 容器负责申请和续签,证书目录和验证目录通过数据卷挂载出来,nginx 挂载同一份证书文件。续签命令大致长这样:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 检查并续签所有临近过期的证书</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> run </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">--rm</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-v</span><span class="token plain"> /data/certbot/conf:/etc/letsencrypt </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-v</span><span class="token plain"> /data/certbot/www:/var/www/certbot </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  certbot/certbot renew</span><br></div></code></pre></div></div>
<p><code>renew</code> 命令只会续签临近过期的证书,所以放进 crontab 里每天跑一次也没有副作用,续签成功后再让 nginx 重载一下配置即可。这样一套配下来,SSL 证书就算是"永久免费"了,基本可以放着不用管。</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,UklGRr4EAABXRUJQVlA4ILIEAAAwFQCdASpCADgAPm0ukkakIiGhKzSc6IANiWkAE994r+SvzD7QceBlD988sO8H3N6CH8i/u28EgA/Jf6b/nvzK8YD0A0inwf2APvA9OX/q8xn0X6mn9l/43SzH7aZwopbEeUejVHQjiUe7iMdyha3QTpAdFCtt4rtbVT9qW1RYvMH6wRspL4rmVOXNbl7Z1bRnHXm8NepN/qutf9g/29zGidS5/8UtWkqpzBUXMFrs/jHgAP7+/+zw7AL7vdNZlL76/MGiozejihWdcU61m+KBtQX9me8SE/TCGavVHNbu7r/h0XYeGplZVLVrS2ylS4Ab5wdzqxYCM/NcWDqmzwzG5ClAca+vnRrIt04XxlcTZvctOexMRY/tKL+pdJPGfHvFE8Yfe7qyR+v2cPi3IQQm+yx65VpN4K87H2iuflEkYl660x6us1VuoD6rYA7BfOuS7Vb7/MFZciEf7AV5plGB9nduxuIMIQU//vlXN9z8MFzt5MV35Eg+qXfBlAWn6cr6/rsnninHamwwHa/mfx9L6+Ed/q4yUhPUY4za5lUw7jdOqEYTLH3T4akIMAgUUFbfBoQ7VLBk1rK3jKvZoTiFIXKBwW2UbwvqzGO/4tl+t1fsheSujkSgqVabbswMGCxNRVgrqm4j4dgp8fZvMeOwycKB3LQAswDKlpftZUVneGvDSpPyQlka1a6W9fqg/+a0w7+wlO2irBx9pioAH8/HASrsChdk5Mmr7VoN89zqyb2gr+85BfXrUudW4px6MuNDZOXj2ysz2xoFFzP1Q6WhnBvg8XeUJXOsOfhJtcYFxskn+LT110h2N9yApVkX/aWCVN+AW0IerNo/Tfw4ftbMIis1b3kOAm2ZUW1VOs/n/TBo4Y4W2fHFG3t22Hvb5F2xqstBS8rt9hhGs+HGmuN+Cu0CgNI27vQKUyA6+ePpSCvdNEO/pJJpI2jVdPV4hMzRB1LRXWE88x4B/bOhc0cGIfs63M9PQ9OxrSTJe/KnQbixRsBBauV3IIG5uM1kL71oH4ICDz10qGLeN9jP71glzqci1i9wfPWm8UYKYcuoPvc1w2rvcKq9Ghj11kdPHGkCOE4H6Apaypkhfxs7bu5dYmJjgyPOwcsU2dctN2GdLk0CF9LJUzg5/TDFrCsJil6SssURKFtloAWtGOm6dHcpPTkaj70C7O5AkpxicIqPqfP3/Kv/RjCa81snx1IATp2jUn5XX+rcA4W7094u3z8pkjHo7pjL5k/NS1qOZBpgcSuRDRMKxYnAdBCb4/gCpHt+6ymmNsaLDyAkxrlNOk61up5xR5Yoj27w/ERnXF6aVBHEVP3xatyjUdWw4bDRv3i/rwFmuRhhl424ei/Xz2az88fSXEedvYgvx5QKrRcvNGAPvsP7pTpuWkIkX+sb/B5jgSOtzJNv/gSFeRTi+T1ZzydBxgNOuHzj/sNe4e8DS2aQTkrv4mewknz+o9GPYrMUrg3zKqa6ead5rhER7WDUtSYSFGmfhyqhVaIRNwH0+ss1mEr8HL1rKlC+DTNDvz/7da44gSVvUNPvKpaPjtwhL+j4m8kizTXqb/94anJuif/MHTQAuOvougbhSvEObhgAAA==" width="66" height="56" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="后续的打算">后续的打算<a href="https://tanzhuo.xyz/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#%E5%90%8E%E7%BB%AD%E7%9A%84%E6%89%93%E7%AE%97" class="hash-link" aria-label="后续的打算的直接链接" title="后续的打算的直接链接" translate="no">​</a></h2>
<p>这台机器有 8G 内存,只跑一个博客太浪费。我会陆续把自己写的一些服务部署上来,给我的 demo App 提供后台支持。而且 https 和备案都已经就绪——微信小程序的后台接口正好要求域名必须备案且走 https,这两个前置条件都满足了,以后写点个人小程序也不是不可以。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>1)香港服务器上部署 Docker 时遇到过各种神奇的现象,印象最深的是宿主机不支持 Docker 较新的存储驱动格式,我不得不重新格式化硬盘、改变文件系统的存储格式才能正常使用。Docker 的存储驱动对内核版本和文件系统参数有要求,老旧或非主流的宿主机环境容易在这里翻车。这些问题在阿里云上从未出现过,一帆风顺。</p>
<p>2)备案审核期间域名无法指向新服务器,提前把迁移调试工作做完,可以把切换窗口压到最短。</p>
<p>3)阿里云也不是完美的,有时候感觉网络波动蛮大,个人博客无所谓,对延迟敏感的服务要留意。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这次迁移花的时间主要在等备案,技术部分因为 Docker 的存在几乎没有成本。顺手把证书续签自动化之后,这台服务器基本进入了"放着不管"的状态。对个人开发者来说,便宜的国内服务器加上备案好的域名,能做的事情反而更多了。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[做点小demo]]></title>
            <link>https://tanzhuo.xyz/2019/10/24/hao-jiu-mei-xie-bo-ke-liao</link>
            <guid>https://tanzhuo.xyz/2019/10/24/hao-jiu-mei-xie-bo-ke-liao</guid>
            <pubDate>Thu, 24 Oct 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[最近又看腾讯云活动买了台3年600元的服务器 1核2g 2m 40g 虽然感觉还是偏贵，而且配置过于低。但我还是买了，买了之后感觉放着又没啥用。就自己开发了几个小demo来玩玩。已经完成第一个demo！~。]]></description>
            <content:encoded><![CDATA[<p>为啥很久没写了呢，其实不是因为我懒，而是因为我把博客后台密码忘记了，自己忘记自己博客后台密码还是阔以的。</p>
<!-- -->
<p>结果不得不上去服务器重置了一波。</p>
<p><img decoding="async" loading="lazy" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABsSFBcUERsXFhceHBsgKEIrKCUlKFE6PTBCYFVlZF9VXVtqeJmBanGQc1tdhbWGkJ6jq62rZ4C8ybqmx5moq6T/wAALCAA0AD4BAREA/8QAGgAAAgIDAAAAAAAAAAAAAAAAAwUBBAACBv/EAC0QAAIBAwMDAwMDBQAAAAAAAAECAwAEEQUSIRMxURRBYSJxkQYjgTNCUnLw/9oACAEBAAA/AOlqrd38NoQr5Zz2RRkmq3qNSuOYLdIV8ynn8VBh1jGfUwZ8BTUdXWIeZIIZV99hwaiTWOsohtEPqXONrjGz5NDufXWEYumuesF/qJjjHkU2ibqRow4BUGg3916SDco3SMdqL5NLDcxabL+7me7fBdicBB4zV6G+LMomj6SyY6cgbKv8exB+9MKUzawguxbQRdVt23JbaM/9xUSxJqJIZTbX0Q3DnkeDn3FBne6vrOWwbat2uM54Dr5FXNLulmj6W3ZLCAjofahSE3Gtxx4OyBC58bj2pLqcckeoTCRSCXLAn3GeK6FYRdaQkW0JviGBzgHHFbb5ZdMZ9rrMYjxgghse381y9tL0LiOXZu2MDtPGa6XUP2XivF4MRw/yhOD98d6HrFo7Bby34nh5/wBh4rTR4mkL30sqSSTgZCjG34qsmoQWWrXfVV2eRgqhV5xTE6nbhQZ0lgyeBLGRViO6t5W2pPEzH2VwTR6A1vA0nVMMZfIO4qM5+9HqCMjmufnnfRLyQLGZIJvqVf8AE01vtPgvU+tcPjhwORS2S0vreEws7XUGQdrDnAOcA9/ahz9KdpGiDTb36iyRxiRl4wVZTyB4raFELQQ2bywyb8uqy7sKO5cdgfincCSJCqyydRx3bbjP8USsqpf2Md6qq45U5q5VDUb70SoekZNxP920DA8+fiq+npDd9VrpEkudxDowH0AdgB4rS1t4xrszQoI0iiCkJwCx+3enVRUVlbUtuI1utUEE31RLCXC+2ckZ/FHOmWZjVOgoC9iOD+e9Kv09I5ublWYvuwxZuTn70/rKysr/2Q==" width="62" height="52" class="img_ev3q"></p>
<p>还好没把服务器登录密码忘了，忘记了没关系可以在购买服务商那里再重置一波，如果把登录服务商的密码也忘了，没关系可以用手机短信找回来。这样看来，我完全可以不用担心啥密码问题。</p>
<p>最近又看腾讯云活动买了台3年600元的服务器 1核2g 2m 40g，虽然感觉还是偏贵，而且配置过于低。但我还是买了，买了之后感觉放着又没啥用。就自己开发了几个小demo来玩玩。已经完成第一个demo！</p>
<p>所有demo都是开源在码云上的：</p>
<p>多人web在线聊天 demo例子：<a href="http://118.25.113.204/gga_parent/(%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%97%A9%E5%B7%B2%E4%B8%8B%E7%BA%BF)" target="_blank" rel="noopener noreferrer" class="">http://118.25.113.204/gga_parent/(服务器早已下线)</a></p>
<p>开发周期时间：4小时左右</p>
<p>以后有新想法还会继续出一些有趣的demo</p>]]></content:encoded>
            <category>生活</category>
        </item>
        <item>
            <title><![CDATA[接触 Flutter 框架]]></title>
            <link>https://tanzhuo.xyz/2019/09/18/flutter</link>
            <guid>https://tanzhuo.xyz/2019/09/18/flutter</guid>
            <pubDate>Wed, 18 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[记录初次接触 Flutter 的体会：Skia 渲染引擎、Widget 组件模型、Dart 语言与热重载，以及一套代码跑多端的跨平台能力。]]></description>
            <content:encoded><![CDATA[<p>Flutter是一款开源的移动应用开发框架，由Google开发和维护。Flutter采用Dart语言编写，具有高效的渲染引擎、丰富的组件库和快速的开发周期，可以用于构建高质量、跨平台的移动应用程序。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么关注跨平台方案">为什么关注跨平台方案<a href="https://tanzhuo.xyz/2019/09/18/flutter#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%85%B3%E6%B3%A8%E8%B7%A8%E5%B9%B3%E5%8F%B0%E6%96%B9%E6%A1%88" class="hash-link" aria-label="为什么关注跨平台方案的直接链接" title="为什么关注跨平台方案的直接链接" translate="no">​</a></h2>
<p>做移动端开发绕不开一个现实问题：同一个产品要覆盖 Android 和 iOS，往往意味着两套代码、两套技术栈、双份的维护成本。之前流行的混合方案要么基于 WebView 套壳，性能和体验有明显折损；要么像 React Native 那样把 JS 组件映射到平台原生控件，跨端一致性始终差一口气。</p>
<p>Flutter 走了另一条路：不依赖平台原生控件，UI 完全由自己绘制。这让它在"一套代码、多端一致"这件事上做得比前辈们更彻底，也是我愿意花时间上手它的原因。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="渲染引擎">渲染引擎<a href="https://tanzhuo.xyz/2019/09/18/flutter#%E6%B8%B2%E6%9F%93%E5%BC%95%E6%93%8E" class="hash-link" aria-label="渲染引擎的直接链接" title="渲染引擎的直接链接" translate="no">​</a></h2>
<p>Flutter的渲染引擎采用Skia图形库，可以实现高质量、高性能的绘图和动画效果。Flutter的组件库包含丰富的UI组件，可以轻松创建各种样式的应用程序，并支持自定义组件的开发。Flutter还提供了丰富的API和工具，支持快速的开发周期和高效的调试。</p>
<p>这里值得展开说一下自绘的含义：Flutter 不把界面翻译成各平台的原生控件，而是通过 Skia 直接在画布上把每个像素画出来。平台层只需要提供一块可供绘制的 Surface 和事件输入，剩下的布局、绘制、动画都由 Flutter 引擎自己完成。好处是同一份 UI 代码在不同系统、不同系统版本上渲染结果高度一致，不会因为某个平台控件行为不同而出现差异。</p>
<p><a href="https://flutter.cn/" target="_blank" rel="noopener noreferrer" class="">Flutter: 为所有屏幕创造精彩</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="跨平台能力">跨平台能力<a href="https://tanzhuo.xyz/2019/09/18/flutter#%E8%B7%A8%E5%B9%B3%E5%8F%B0%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="跨平台能力的直接链接" title="跨平台能力的直接链接" translate="no">​</a></h2>
<p>Flutter的跨平台特性也是其最大的优势之一，可以同时为Android和iOS开发应用程序，而且开发的应用程序可以在不同的平台上实现相同的用户体验。Flutter还支持Web和桌面应用程序的开发，可以为不同的应用场景提供解决方案。</p>
<p>也就是说，一套代码理论上可以覆盖 Android、iOS、Web，以及 Windows、macOS 这类桌面端。当然，不同目标平台的成熟度并不一样，移动端是 Flutter 最先打磨的主战场；但对于工具类、展示类应用，多端复用带来的成本节约是实打实的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dart-语言">Dart 语言<a href="https://tanzhuo.xyz/2019/09/18/flutter#dart-%E8%AF%AD%E8%A8%80" class="hash-link" aria-label="Dart 语言的直接链接" title="Dart 语言的直接链接" translate="no">​</a></h2>
<p>Flutter采用Dart语言作为开发语言，Dart是一种面向对象、静态类型的语言，具有强大的类型推断和异步编程支持，可以提高开发效率和代码质量。Flutter的Dart编译器将Dart代码编译为本地代码，可以在不同平台上实现高性能和快速的渲染。</p>
<p>Dart 对 Flutter 来说不只是"顺手选的语言"。它同时支持 JIT 和 AOT 两种编译模式：开发阶段用 JIT，代码改动可以即时生效，支撑起热重载；发布阶段用 AOT 编译成机器码，运行时不需要解释器，性能接近原生。一门语言同时满足开发效率和运行性能两个诉求，这在跨平台框架里并不多见。</p>
<p>写惯 Java 或 JavaScript 的人上手 Dart 几乎没有门槛，语法风格介于两者之间，async/await 的异步写法也很直观。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="widget-组件模型">Widget 组件模型<a href="https://tanzhuo.xyz/2019/09/18/flutter#widget-%E7%BB%84%E4%BB%B6%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="Widget 组件模型的直接链接" title="Widget 组件模型的直接链接" translate="no">​</a></h2>
<p>Flutter的UI框架采用了一种称为"Widget"的组件模型，Widget是Flutter中最基本的构建块，包含了UI元素和逻辑。Flutter中的所有UI元素都是Widget，包括文本、图像、按钮等。Flutter的Widget具有层次结构，可以嵌套和组合，可以自由地创建复杂的UI界面。</p>
<p>"一切皆 Widget"是 Flutter 的核心设计哲学。不仅按钮、文本是 Widget，连内边距（Padding）、居中（Center）、手势识别（GestureDetector）这些"行为"也都是 Widget。复杂界面不靠继承一个庞大的基类来实现，而是靠小 Widget 层层组合——组合优于继承在这里体现得很充分。</p>
<p>Widget 按是否持有可变状态分为两类：StatelessWidget 描述一段不变的 UI；StatefulWidget 配合 State 对象管理会变化的部分，状态一变，框架自动重建对应的 Widget 子树。理解了这一点，Flutter 的声明式 UI 思路基本就通了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="热重载">热重载<a href="https://tanzhuo.xyz/2019/09/18/flutter#%E7%83%AD%E9%87%8D%E8%BD%BD" class="hash-link" aria-label="热重载的直接链接" title="热重载的直接链接" translate="no">​</a></h2>
<p>Flutter的热重载功能可以在开发过程中实时预览UI变化，无需重新编译和部署应用程序，大大提高了开发效率和反馈速度。Flutter还提供了丰富的调试工具和可视化工具，可以帮助开发人员快速定位和解决问题。</p>
<p>热重载的原理是把改动后的源码增量注入到正在运行的 Dart 虚拟机里，然后重建 Widget 树——应用的当前状态得以保留。这意味着调一个深层页面的样式时，不用每次都从首页重新点进去，改完保存，界面直接刷新，调 UI 的体验和原生开发相比是质的提升。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2019/09/18/flutter#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>上手过程中有几点值得提前知道：</p>
<p>1）国内网络环境下，SDK 下载和 pub 依赖拉取可能会很慢甚至失败，需要配置国内镜像源（设置 <code>PUB_HOSTED_URL</code> 和 <code>FLUTTER_STORAGE_BASE_URL</code> 环境变量）。</p>
<p>2）Widget 嵌套写深了之后代码可读性下降明显，建议尽早养成拆分小 Widget 的习惯，而不是在一个 build 方法里堆几百行。</p>
<p>3）涉及平台原生能力（如蓝牙、推送）时，需要通过 Platform Channel 与原生代码通信，这部分仍然要求一定的 Android/iOS 原生知识。</p>
<p>4）热重载并非万能：改动涉及全局状态初始化、main 方法或枚举等场景时需要热重启（hot restart）甚至完整重新编译。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>第一次运行 <code>flutter doctor</code> 时按提示把环境缺失项逐一补齐，能省掉后面大量莫名其妙的构建报错。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2019/09/18/flutter#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>感觉Flutter框架确实很不错，提供了优良的组件，配合Dart语言开发入门非常快。简单写了几个demo之后已经可以正式进行app开发，一套代码跑Android、iOS、Windows桌面程序、Mac都可以。自绘渲染保证了多端一致性，Widget 组合的写法清晰直观，热重载让调 UI 不再痛苦。对想尝试跨平台开发的人来说，Flutter 是一个值得认真投入的选项。</p>]]></content:encoded>
            <category>技术</category>
            <category>前端</category>
        </item>
        <item>
            <title><![CDATA[学习前端技术]]></title>
            <link>https://tanzhuo.xyz/2019/09/18/xue-xi-liao-vue</link>
            <guid>https://tanzhuo.xyz/2019/09/18/xue-xi-liao-vue</guid>
            <pubDate>Wed, 18 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[学习 Vue.js 的一份笔记：双向数据绑定、组件化、Virtual DOM 与生命周期等核心概念，顺带聊聊 TypeScript 给前端开发带来的变化。]]></description>
            <content:encoded><![CDATA[<p>Vue.js 是一个渐进式 JavaScript 框架，用于构建用户界面。它的核心库只关注视图层，可以很容易地与其他库或现有项目集成。Vue.js 采用了 MVVM 模式，通过双向数据绑定将视图和数据同步起来，使得开发者可以更加方便地管理数据和视图。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么要学前端框架">为什么要学前端框架<a href="https://tanzhuo.xyz/2019/09/18/xue-xi-liao-vue#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%AD%A6%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6" class="hash-link" aria-label="为什么要学前端框架的直接链接" title="为什么要学前端框架的直接链接" translate="no">​</a></h2>
<p>写这篇笔记的起因很简单：作为一个平时以后端为主的开发者，绕不开的一个事实是，页面早晚要自己动手。传统的 jQuery 写法里，数据散落在 DOM 各处，改一个状态要手动找到对应的节点去更新，页面一复杂，代码就成了一团难以维护的面条。Vue.js 解决的正是这个问题——把「数据」和「视图」的同步交给框架，开发者只关心数据本身。加上它「渐进式」的定位，不要求整个项目推倒重来，可以先在一个页面、一个模块里引入试水，这对已有项目的团队非常友好。</p>
<p><a href="https://vuejs.org/" target="_blank" rel="noopener noreferrer" class="">Vue.js - The Progressive JavaScript Framework | Vue.js</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vuejs-的特点">Vue.js 的特点<a href="https://tanzhuo.xyz/2019/09/18/xue-xi-liao-vue#vuejs-%E7%9A%84%E7%89%B9%E7%82%B9" class="hash-link" aria-label="Vue.js 的特点的直接链接" title="Vue.js 的特点的直接链接" translate="no">​</a></h2>
<p>Vue.js 的特点：</p>
<p>1）双向数据绑定：Vue.js 通过双向数据绑定来实现数据和视图的自动同步更新，使得开发者可以更加方便地管理数据和视图。背后的机制是数据劫持加发布-订阅：框架会拦截对数据属性的读写，读的时候收集依赖，写的时候通知对应的视图更新。开发者改的是一个普通的 JavaScript 对象，页面就自动跟着变了，不用再手动操作 DOM。</p>
<p>2）组件化开发：Vue.js 支持组件化开发，可以将一个页面拆分成多个独立的组件，每个组件都有自己的数据和视图，可以方便地复用和维护。组件之间通过 props 向下传数据、通过事件向上通知，边界清晰。这和后端拆分模块、按职责划分类的思路是一致的，后端出身的人理解起来并不困难。</p>
<p>3）轻量级易上手：Vue.js 的 API 简单易懂，学习曲线较低，可以快速上手，并且与其他框架和库（如 React、Angular）兼容。官方文档是中文的，示例足够多，一个 script 标签引入就能开始写，不强制上构建工具，这一点对入门者很关键。</p>
<p>4）面向模板编程：Vue.js 支持面向模板编程，可以将 HTML 和 JavaScript 代码分离，使得代码更加清晰易读。模板本质上还是 HTML，只是多了插值和指令，写页面结构的时候所见即所得，逻辑则收敛在组件的 JavaScript 部分。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vuejs-技术细节">Vue.js 技术细节<a href="https://tanzhuo.xyz/2019/09/18/xue-xi-liao-vue#vuejs-%E6%8A%80%E6%9C%AF%E7%BB%86%E8%8A%82" class="hash-link" aria-label="Vue.js 技术细节的直接链接" title="Vue.js 技术细节的直接链接" translate="no">​</a></h2>
<p>Vue.js 技术细节：</p>
<p>1）Virtual DOM：Vue.js 使用 Virtual DOM 技术来实现高效的 DOM 操作，提高性能和用户体验。原理是先在内存里用 JavaScript 对象描述一份 DOM 树，数据变化时生成新的虚拟树，与旧树做 diff 比对，最后只把真正变化的部分应用到真实 DOM 上。直接操作真实 DOM 的开销很大，这层中间结构把「改哪里」的计算留在了内存中，代价低得多。</p>
<p>2）生命周期：Vue.js 组件有自己的生命周期，包括创建、挂载、更新和销毁等阶段，可以在不同的阶段执行不同的操作。比如发起接口请求一般放在创建完成之后，需要访问真实 DOM 的逻辑要等到挂载完成，定时器、事件监听这类资源则应当在销毁阶段清理。理解生命周期，是理解「我的代码到底在什么时机执行」的关键。</p>
<p>3）指令和事件：Vue.js 提供了一些指令和事件，如 v-if、v-for、v-on 等，可以方便地操作 DOM 元素和处理用户事件。v-if 控制元素是否渲染，v-for 负责列表循环，v-on 绑定事件回调。指令把常见的 DOM 操作声明在模板里，读模板就能看出页面的行为，比在 JavaScript 里到处 addEventListener 直观。</p>
<p>4）插件和组件库：Vue.js 生态系统丰富，有大量的插件和组件库可供使用，可以快速构建高质量的应用程序。路由、状态管理有官方方案，UI 层面也有成熟的组件库，中后台页面基本可以拼装出来，不必所有轮子都自己造。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="typescript-带来的变化">TypeScript 带来的变化<a href="https://tanzhuo.xyz/2019/09/18/xue-xi-liao-vue#typescript-%E5%B8%A6%E6%9D%A5%E7%9A%84%E5%8F%98%E5%8C%96" class="hash-link" aria-label="TypeScript 带来的变化的直接链接" title="TypeScript 带来的变化的直接链接" translate="no">​</a></h2>
<p>现在的前端技术已经变化太多，特别是 TypeScript 的融入后，我个人觉得前景很好。JavaScript 一直是弱类型语言，配合 TypeScript 之后有了固定语法，数据类型和 Class 的概念之后，前端逻辑更加清晰明了。加上vue的组件化，前端也可以大致分为 MVC 模式开发。可以通过 TypeScript ,Vue 结合 编写组件，复杂的业务逻辑处理层。这确实比以前的技术编写方式清晰了许多。</p>
<p>对写惯了强类型语言的人来说，这个变化的意义在于：接口的数据结构可以用类型定义描述出来，编辑器能做补全和检查，很多低级错误在编译阶段就暴露了，而不是等到浏览器里报 undefined。前端项目的规模越大，这层约束的价值越明显。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2019/09/18/xue-xi-liao-vue#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<p>学习过程中有几点值得提醒：</p>
<p>1）双向绑定不是万能的。数据劫持有其边界，某些对对象和数组的操作方式框架感知不到，遇到「数据改了页面没动」的情况，先查一下响应式的限制，而不是怀疑框架坏了。</p>
<p>2）不要在组件里直接操作 DOM。既然交给框架管理视图，就应当通过数据驱动页面变化，混用手动 DOM 操作容易和框架的更新机制打架，问题很难排查。</p>
<p>3）组件拆分要适度。拆得太碎，props 层层透传会很痛苦；不拆，单个组件又会膨胀。按「能独立复用、职责单一」来把握粒度比较合适。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2019/09/18/xue-xi-liao-vue#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>Vue.js 用双向数据绑定和组件化把前端开发从手工操作 DOM 中解放了出来，Virtual DOM 和生命周期则是理解它运行方式的两把钥匙。对后端开发者而言，配合 TypeScript 之后，前端代码也有了类型和分层的秩序感，上手成本比想象中低。这篇笔记先记录概念层面的认识，后续在实际项目里踩到的坑，再另行整理。</p>]]></content:encoded>
            <category>技术</category>
            <category>前端</category>
        </item>
        <item>
            <title><![CDATA[web项目部分框架技术]]></title>
            <link>https://tanzhuo.xyz/2019/07/22/zi-ji-da-jian-javawebxiang-mu-kuang-jia</link>
            <guid>https://tanzhuo.xyz/2019/07/22/zi-ji-da-jian-javawebxiang-mu-kuang-jia</guid>
            <pubDate>Mon, 22 Jul 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[整理平时工作与学习中接触过的 Java Web 技术栈：从 Spring 全家桶、ORM、消息队列，到容器编排、链路追踪与监控体系，逐项附上一句话定位，作为选型备忘。]]></description>
            <content:encoded><![CDATA[<p>以下技术都是平时工作中和自己学习中所了解并尝试使用的部分技术，作为记录。</p>
<!-- -->
<p>写这份清单的起因很简单：Java Web 的生态太大了，从一个单体项目起步，到微服务、容器化、监控告警，每个环节都有好几个可选方案。平时零散接触，过一阵子就只记得名字、想不起定位。所以把它们集中记在一处，每一项写清楚"是什么、解决什么问题"，以后做技术选型时可以直接翻这一页，而不是重新去搜。清单大致按"开发框架 → 数据层 → 微服务治理 → 基础设施 → 大数据与工具"的顺序排列。</p>
<p>SpringBoot ，Spring-cloud，Spring-cloud-alibaba，Spring全家桶。SpringBoot 用自动装配和起步依赖把配置量压到最低，是现在 Java 项目的默认起点；Spring Cloud 在它之上提供服务注册、配置、网关等微服务能力；Spring Cloud Alibaba 则把 Nacos、Sentinel、Seata 这些国内生态组件接入了同一套抽象。</p>
<p>MyBatis-Plus （乐观锁，自动分页，代码生成器，生成service，mapper，entity，controller，自定义生成模板）。它在 MyBatis 之上补齐了通用 CRUD，单表操作基本不用写 SQL；代码生成器配合自定义模板，新建一张表的增删改查几分钟就能搭出来。</p>
<p>JOOQ：JavaORM框架。思路和 MyBatis 相反——用 Java DSL 以类型安全的方式拼 SQL，编译期就能发现字段写错，适合 SQL 逻辑复杂、又想要强类型保障的场景。</p>
<p>Uid-generator ： 百度uid生成器 （分布式雪花算法全局唯一标示，Long型UID）。分库分表之后数据库自增主键没法用了，雪花算法用"时间戳 + 机器位 + 序列号"拼出趋势递增的 Long 型 ID，既全局唯一又对索引友好。</p>
<p>Xxl-job：分布式任务调度中心。解决的是单机定时任务的两个老问题：机器挂了任务就丢、多实例部署会重复执行。它把调度中心和执行器分离，自带管理界面、失败重试和分片执行。</p>
<p>Apache-Shiro, Spring-Security ：登录安全框架。两者都覆盖认证和授权，Shiro 轻量、上手快；Spring Security 功能更全、与 Spring 生态结合更深，但配置曲线也更陡。</p>
<p>Druid ali , HikariCP ：常用数据连接池。连接池的意义在于复用数据库连接、避免频繁握手。Druid 附带 SQL 监控和防注入统计页面，HikariCP 以性能见长，是 SpringBoot 的默认选择。</p>
<p>Bcrypt ：数据库用户密码加密方式，保证密码安全。它是慢哈希，且每次加盐结果不同，即使库被拖走也难以用彩虹表批量反解，比 MD5/SHA 直接存哈希靠谱得多。</p>
<p>JWT：临时令牌生成策略/规范。把用户信息签名后放进令牌本身，服务端不用存会话，天然适合多实例横向扩展；代价是签发后无法主动作废，只能靠短有效期或黑名单兜底。</p>
<p>Undertow ，Tomcat，Jboss，Weblogic  ：Servlet容器。Tomcat 最通用；Undertow 基于 NIO、内存占用小，常用来替换 SpringBoot 内嵌容器；JBoss、Weblogic 属于更重的商用应用服务器。</p>
<p>Eolinker，Swagger，Knife4j，Yapi : 在线api文档 自动化测试工具。核心价值是让文档从代码注解自动生成、跟随代码更新，避免"文档和接口对不上"这种前后端扯皮的经典问题；Knife4j 是 Swagger 的国产增强皮肤。</p>
<p>MySQL 8.0 ，PostgreSQL  : RDS 关系型数据库。业务数据的主存储。MySQL 生态和资料最丰富；PostgreSQL 功能更完整，JSON、GIS 这类场景表现更好。</p>
<p>Sqlite 嵌入式数据库。整个库就是一个文件，不需要独立进程，适合客户端本地存储和小工具。</p>
<p>Redis，MongoDB ： NoDB 非关系型数据库。Redis 基于内存，常用作缓存、分布式锁和计数器；MongoDB 是文档型数据库，模式灵活，适合结构多变的数据。</p>
<p>Dobbo  ，Feign ： 远程方法调用。都是让跨服务调用写起来像本地方法。Dubbo 走自定义协议 + 长连接，性能好；Feign 基于 HTTP，声明式接口，和 Spring Cloud 体系搭配更自然。</p>
<p>Sentinel ， Hystrix ： 限流，降级。微服务的自保手段：上游流量超出承载能力时先限流，依赖的下游挂了就快速失败并走降级逻辑，避免线程被拖死后故障沿调用链扩散——也就是所谓的雪崩。Hystrix 已停止新功能开发，新项目一般选 Sentinel。</p>
<p>Skywalking 链路追踪（华为开发已捐赠apache）。通过 Java Agent 字节码增强埋点，不侵入业务代码，就能把一次请求跨多个服务的完整调用链画出来，排查"慢在哪一环"时非常直观。</p>
<p>Zookeeper，Eureka，Consul ，Nacos ，Etcd ：注册,配置中心。注册中心解决"服务实例地址动态变化，调用方如何找到它"的问题；配置中心解决"改配置要重启"的问题。Nacos 把两者合一，是 Spring Cloud Alibaba 体系的默认选择；Etcd 则是 Kubernetes 的底层存储。</p>
<p>Seata： 阿里分布式事物解决方案。一次业务操作跨了多个库或多个服务时，本地事务保证不了整体一致性。Seata 的 AT 模式通过代理数据源自动记录回滚日志，对业务代码侵入很小。</p>
<p>ELK ：分布式日志抓取，数据聚合，分布式搜索引擎。即 Elasticsearch + Logstash + Kibana 的组合：采集各节点日志集中存储，再用全文检索和可视化界面查询。服务一多，没有集中式日志基本没法排查问题。</p>
<p>Micrometer , Prometheus , Grafana ：服务健康监控，数据监控，io监控等。Micrometer 在应用侧暴露指标，Prometheus 定时拉取并存成时序数据，Grafana 负责画图和告警面板，三者是目前最常见的监控组合。</p>
<p>Kafka，RabbitMQ ，RocketMQ : 常用消息队列，削峰，解耦，异步。生产者只管投递、消费者按自己的节奏处理，流量洪峰先落到队列里慢慢消化。Kafka 吞吐最高、偏日志和流处理场景；RabbitMQ 路由灵活；RocketMQ 在事务消息、延时消息上功能更全。</p>
<p>Nginx ，apache-http-service ，Traefik ： http服务器。除了静态资源托管，更多时候承担反向代理和七层负载均衡；Traefik 能自动发现容器并更新路由规则，和容器环境更配。</p>
<p>Ali-OSS，HUAWEI-OBS ： 第三方静态资源存储。图片、附件这类文件放对象存储而不是应用服务器磁盘，既省心又方便配合 CDN 加速。</p>
<p>Ali-NAS ：内网共享文件存储挂载，内网共享磁盘。多台机器需要读写同一份文件时挂 NAS，比自己搭 NFS 少不少运维工作。</p>
<p>Ali-SLB ： 4，7层负载均衡器。流量入口的第一层，四层转发 TCP、七层能按域名和路径分流，通常再往后才到 Nginx 和应用。</p>
<p>Findbugs，Sonarqube ： 静态代码检测。不运行代码就能扫出空指针隐患、资源未关闭这类问题；SonarQube 还能接进 CI，用质量门禁挡住不达标的提交。</p>
<p>PDMan ： 国人开发的数据库可视化工具。用来画 ER 图、维护表结构版本，导出建表 SQL，团队协作时比口头约定表结构靠谱。</p>
<p>Docker，Containerd，CRI-O : 常见几种容器运行时。容器把应用和依赖打成一个镜像，"在我机器上能跑"的问题基本消失。Containerd、CRI-O 是更底层的运行时，Kubernetes 通过 CRI 接口直接对接它们。</p>
<p>Docker-Compose  ：容器编排工具。一个 YAML 文件描述多个容器的镜像、端口、依赖关系，一条命令拉起整套本地环境，开发联调很方便。</p>
<p>Kubernetes ：容器编排，运维，资源管理，资源调度，一站式管理。核心思想是声明式：你描述期望状态，它负责调度、扩缩容和故障自愈，让实际状态不断向期望收敛。是目前生产容器编排的事实标准。</p>
<p>Istio：常用 service-mech 框架，可多语言协同开发后端架构，流量控制，降级，熔断。Service Mesh 把限流、熔断、灰度这些治理逻辑从业务代码下沉到 Sidecar 代理里，不同语言写的服务都能共享同一套治理能力。</p>
<p>Helm：K8S应用资源管理中间件。相当于 Kubernetes 的包管理器，把一堆 YAML 模板化成 Chart，一条命令完成安装、升级和回滚。</p>
<p>Ali-EsayExcel：阿里开源excel高效jar包。针对 POI 大文件容易内存溢出的问题做了流式读写优化，导入导出几十万行也比较稳。</p>
<p>Netty :  常用 JavaNIO 网络编程包。把 NIO 繁琐的 Selector、Buffer 细节封装成事件驱动的 Pipeline 模型，是很多 RPC 框架和中间件的网络层地基。</p>
<p>Disruptor ： 高性能内存队列（英国外汇交易公司LMAX开发）。用环形数组预分配内存、避免锁竞争和伪共享，单机线程间传递消息的延迟可以压得非常低。</p>
<p>Caffeine ： JVM本地高性能缓存组件。底层使用 Disruptor 开发。进程内缓存，访问不走网络，常和 Redis 组成两级缓存：本地挡热点，Redis 保共享。</p>
<p>Jenkins CI/CD中间件，自动化发布，流水线生产。把拉代码、编译、测试、打镜像、部署串成流水线，提交代码后自动跑完，减少手工发布的出错空间。</p>
<p>Apahce-Hdoop：分布式，大数据存储，与数据处理。Hadoop 的 HDFS 把大文件切块冗余存到多台机器，MapReduce 负责在数据所在节点就近计算，是大数据体系的老底座。</p>
<p>Apahce-Flink：流式处理框架，适合大数据中进行准实时计算。把计算下推到数据流上，事件到达即处理，支持状态管理和精确一次语义，实时报表、实时风控常用它。</p>
<p>MyCat2、Apache-Shardingsphere ：分库分表中间件。单表数据量大到查询扛不住时做水平拆分，中间件负责解析 SQL、路由到具体分片并合并结果，让应用尽量无感。</p>
<p>TiDB，KunlunBase：NewSQL 分布式关系型数据库。换一个思路解决同样的问题：数据库自身分布式化，兼容 MySQL 协议，扩容不再需要业务侧改造分片规则。</p>
<p>Arthas：阿里开源JVM诊断工具，火焰图生成，死锁诊断等。不重启、不改代码就能挂到线上 JVM 上看方法耗时、反编译类、追踪调用，排查线上疑难问题的救命工具。</p>
<p>Kettle，DataX，Canal：开源ETL工具，数据迁移，数据清洗。前两者做批量抽取转换；Canal 比较特别，它伪装成 MySQL 从库解析 binlog，能拿到实时增量数据，常用于缓存同步和异构数据同步。</p>
<p>Jmeter：常用接口测试，压测工具。上线前先压一轮，摸清接口的吞吐上限和响应时间分布，限流阈值才有依据，而不是拍脑袋。</p>
<p>Ansible：多服务器运维工具。基于 SSH、无需在目标机装 Agent，用 YAML 描述期望状态批量执行，几十台机器改一个配置不用逐台登录。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2019/07/22/zi-ji-da-jian-javawebxiang-mu-kuang-jia#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ul>
<li class="">清单不等于选型。同一层的组件（比如 Sentinel 和 Hystrix、Eureka 和 Nacos）只需要选一个，把清单里的东西都堆进项目只会增加维护成本。</li>
<li class="">优先跟着体系走。用了 Spring Cloud Alibaba，注册中心、限流、分布式事务就顺着选 Nacos、Sentinel、Seata，混搭不同体系的组件，兼容性问题要自己扛。</li>
<li class="">注意组件的维护状态。生态更替很快，像 Hystrix 这样进入维护模式的项目，新系统尽量避开，选社区仍在活跃迭代的替代品。</li>
<li class="">分布式组件都有运维成本。Kafka、Elasticsearch、Kubernetes 每一个都值得专人研究，小团队在引入前先想清楚谁来维护。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2019/07/22/zi-ji-da-jian-javawebxiang-mu-kuang-jia#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这份清单覆盖了一个 Java Web 项目从开发框架、数据存储、微服务治理到容器化和大数据的大致版图。记录它的目的不是全都用上，而是遇到具体问题时知道有哪些现成方案、各自的定位是什么。生态一直在变，这一页也会随着实际使用继续增补和修正。</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[黑魂3]]></title>
            <link>https://tanzhuo.xyz/2019/06/17/hei-hun-3</link>
            <guid>https://tanzhuo.xyz/2019/06/17/hei-hun-3</guid>
            <pubDate>Mon, 17 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[黑魂3的故事发生在洛斯里克王国。玩家需要阻止由火之纪元和前代黑暗印记持有者之间的冲突造成的末世。为此，玩家需要去面对薪王，也就是那些曾经传过火的英雄。在无数的英雄传火之后的今天，火焰已经极其微弱。]]></description>
            <content:encoded><![CDATA[<p>黑魂3的故事发生在洛斯里克王国。玩家需要阻止由火之纪元和前代黑暗印记持有者之间的冲突造成的末世。为此，玩家需要去面对薪王，也就是那些曾经传过火的英雄。</p>
<!-- -->
<p>在无数的英雄传火之后的今天，火焰已经极其微弱。既定的薪王深知传火是条不归路，也不愿传火。而找不到合适的人去传火，只能把以前传过火的薪王从坟墓中唤醒，让他们再赴使命。但是这几个薪王中的三个也都不愿完成自己的职责。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--5--f730f26f57da9cda2e75d95b98193de6.webp" width="1600" height="900" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="不愿传火的薪王">不愿传火的薪王<a href="https://tanzhuo.xyz/2019/06/17/hei-hun-3#%E4%B8%8D%E6%84%BF%E4%BC%A0%E7%81%AB%E7%9A%84%E8%96%AA%E7%8E%8B" class="hash-link" aria-label="不愿传火的薪王的直接链接" title="不愿传火的薪王的直接链接" translate="no">​</a></h2>
<p>深渊监视者们因为长期接触深渊的力量已经被其侵蚀，陷入了疯狂。他们不断自相残杀，无暇传火；艾尔德利奇预感到了火焰熄灭后的深海时代，在绝望中放弃了传火，并吞噬了很多人的性命，包括初代的神，变成了一堆腐烂物盘踞在初代王城里，迎接深海时代的到来；巨人尤姆因为当初没能守护自己的臣民免受火焰的伤害，都城被毁，陷入了深深的自责和悔恨，所以不愿有所作为。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="传火的重任">传火的重任<a href="https://tanzhuo.xyz/2019/06/17/hei-hun-3#%E4%BC%A0%E7%81%AB%E7%9A%84%E9%87%8D%E4%BB%BB" class="hash-link" aria-label="传火的重任的直接链接" title="传火的重任的直接链接" translate="no">​</a></h2>
<p>最后，传火的重任就落到了玩家扮演的灰烬身上。</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--8--7e3b2ec746b4ec84b0251d3e4e510493.webp" width="1600" height="900" class="img_ev3q"></p>
<p>身处绝望的薪王们不可能靠劝说就自动归位，所以为了拿到薪王们的力量，玩家就必须在流程中突破重重守卫，杀死他们，将残存的柴薪取回。最后，当五大薪王的柴薪都归位到传火祭祀场的宝座上，灰烬将他们的力量都吸入了体内。</p>
<p>然后，灰烬前往初始的火炉，也就是初火所在之地，击败守卫在此的"薪王们的化身"之后就有足够的力量来传火了。根据选择的不同，剧情将会走向传火、灭火、篡火以及秘密四个结局。</p>
<p>也许火时代即将终结。</p>
<p>此视频B站原作者已授权</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--9--c29bfaa65c9633129d026b8df89b2c03.webp" width="1600" height="1000" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-678476-a199110863b5af78377f27441618cc6e.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-693009-4add49a2cab1f8c2361539bb0a210a09.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--7--fedf2f396fed8ca3ad53e85e631f4ca5.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--4--9b091401291fb68eb68485ba7b52d7a2.jpg" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-timg-3-804faade1b8b9a0f8013db8676d09a7e.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-timg--1--1-c44283e12f92a435b0185c22e21617af.webp" width="1600" height="1024" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--6--aaff85418fc6da5d97fc3d556a6a52ba.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-timg--2--1-2b9c2c6186a9b739d1caa0c9cb9fd3c4.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-timg--3--32744fd45eb4f0b34cffa84c2c5409e2.webp" width="1600" height="716" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-11-timg--3--1-fdff78fdffe6f95b0a340454eb3dd7dc.webp" width="1600" height="1024" class="img_ev3q"></p>]]></content:encoded>
            <category>游戏</category>
        </item>
        <item>
            <title><![CDATA[关于给ghost博客系统加点插件]]></title>
            <link>https://tanzhuo.xyz/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian</link>
            <guid>https://tanzhuo.xyz/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian</guid>
            <pubDate>Thu, 13 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[给 Ghost 博客加评论功能的折腾记录：先接了 Disqus，配置生效后发现被墙，国内访客根本看不到；权衡代理方案后，最终换成了国内可用的 Valine。]]></description>
            <content:encoded><![CDATA[<p>本来想给 Ghost 集成 Disqus 这个国外的公共留言系统，这样我的博客就可以拥有留言功能了，但是由于懒，我决定还是用别人做好的东西。</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="背景">背景<a href="https://tanzhuo.xyz/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="背景的直接链接" title="背景的直接链接" translate="no">​</a></h2>
<p>Ghost 是一个很干净的博客系统，干净到连评论功能都没有内置。官方的态度也很明确：评论属于第三方服务的领域，博客系统只负责内容。这个设计我是认可的——评论系统涉及用户身份、反垃圾、数据存储，自己维护成本不低，交给专门的服务更省心。</p>
<p>所以问题就变成了：选哪家的评论服务，以及怎么把它接进 Ghost。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ghost-接入第三方评论的原理">Ghost 接入第三方评论的原理<a href="https://tanzhuo.xyz/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#ghost-%E6%8E%A5%E5%85%A5%E7%AC%AC%E4%B8%89%E6%96%B9%E8%AF%84%E8%AE%BA%E7%9A%84%E5%8E%9F%E7%90%86" class="hash-link" aria-label="Ghost 接入第三方评论的原理的直接链接" title="Ghost 接入第三方评论的原理的直接链接" translate="no">​</a></h2>
<p>Ghost 虽然没有插件市场，但留了两个口子：</p>
<p>1）<strong>Code Injection（代码注入）</strong>。后台设置里可以直接往全站的 <code>&lt;head&gt;</code> 或 <code>&lt;body&gt;</code> 末尾插一段 HTML/JS，不用改主题文件。适合快速试验一个评论服务。</p>
<p>2）<strong>修改主题模板</strong>。把评论服务的嵌入代码写进主题的 <code>post.hbs</code> 文章模板里，只在文章页加载。这是更正式的做法，评论区的位置也更可控。</p>
<p>第三方评论服务的接入方式基本都一样：在页面里放一个占位的 <code>div</code>，再引入一段 JS，脚本会从服务商的服务器拉取评论数据并渲染到占位元素里。评论数据存在服务商那边，和博客本身完全解耦。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="接入-disqus">接入 Disqus<a href="https://tanzhuo.xyz/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#%E6%8E%A5%E5%85%A5-disqus" class="hash-link" aria-label="接入 Disqus的直接链接" title="接入 Disqus的直接链接" translate="no">​</a></h2>
<p>Disqus 是国外用得最多的评论服务，Ghost 官方文档里也是拿它当例子的。我看了下官方文档，配置非常简单：注册账号、创建站点拿到 shortname、把嵌入代码贴进文章模板，就完事了。</p>
<p>配置好之后，果然事情并不简单。</p>
<p>配置确实生效了——用能访问外网的网络打开博客，评论区正常显示。但换成国内的普通网络一看，评论区的位置是一片空白。原因很直接：Disqus 的嵌入脚本和接口域名在国内被墙了，脚本压根加载不出来，评论区自然渲染不了。</p>
<p>我就无语了。</p>
<p>对一个读者主要在国内的中文博客来说，这等于评论功能没做。更麻烦的是，加载失败的脚本还可能拖慢页面：浏览器会在那里等到超时才放弃。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="代理方案的权衡">代理方案的权衡<a href="https://tanzhuo.xyz/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#%E4%BB%A3%E7%90%86%E6%96%B9%E6%A1%88%E7%9A%84%E6%9D%83%E8%A1%A1" class="hash-link" aria-label="代理方案的权衡的直接链接" title="代理方案的权衡的直接链接" translate="no">​</a></h2>
<p>被墙的服务不是完全没救，社区里流行的思路是自建反向代理：用自己的服务器（或者 CDN、Serverless 函数）中转 Disqus 的接口请求，前端再配合改写一下嵌入脚本的请求地址。这样国内用户的请求先到你的代理，再由代理去访问 Disqus。</p>
<p>只好有空了去 GitHub 上找一下别人写好的代理代码。毕竟自己写也要花费点时间，找现成的还是不错滴。</p>
<p>不过冷静下来想想，这个方案有几个绕不开的问题：</p>
<ul>
<li class="">代理本身要部署、要维护，等于为了一个评论区多养一个服务；</li>
<li class="">评论的登录授权流程也走 Disqus，代理要处理的接口不止一两个，容易漏；</li>
<li class="">Disqus 免费版本身还带广告。</li>
</ul>
<p>为了评论功能背上这些负担，性价比不高。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="换成-valine">换成 Valine<a href="https://tanzhuo.xyz/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#%E6%8D%A2%E6%88%90-valine" class="hash-link" aria-label="换成 Valine的直接链接" title="换成 Valine的直接链接" translate="no">​</a></h2>
<p>-2019-12-19 更新</p>
<p>我最后还是选择了一款国内可以直接用的评论系统 <a href="https://valine.js.org/" target="_blank" rel="noopener noreferrer" class="">Valine</a>，无广告，免费可用。</p>
<p>Valine 的思路和 Disqus 不太一样：它没有自己的后端服务，评论数据存在 LeanCloud 这类 BaaS 平台上，前端只需要引入一个 JS 文件、填上自己申请的应用 App ID 和 App Key 就能跑起来。没有强制登录，访客填个昵称就能留言，对个人博客来说足够轻量。</p>
<p>接入方式和前面说的一样，把初始化代码通过 Code Injection 或主题模板放进文章页即可。国内访问没有任何障碍，页面加载也不再被卡住。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_BuS1"><p>选评论系统之前，先用目标读者的网络环境实际打开一次页面。很多服务在开发者自己的网络下一切正常，换个环境就是另一回事。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这次折腾下来的经验很简单：选第三方服务，可用性要放在功能前面。Disqus 功能再全，读者打不开就等于零；Valine 功能朴素，但对国内的中文博客反而是更务实的选择。Ghost 的 Code Injection 机制让这种更换成本很低，试错一轮也就半天的事。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[使用 certbot 配置 docker nginx https]]></title>
            <link>https://tanzhuo.xyz/2019/06/13/li-yong-docker</link>
            <guid>https://tanzhuo.xyz/2019/06/13/li-yong-docker</guid>
            <pubDate>Thu, 13 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[certbot 申请的 Let's Encrypt 证书是软链接,挂载进 nginx 容器后无法读取。记录 docker volumes 与软链接的这个坑,以及正确的挂载方式。]]></description>
            <content:encoded><![CDATA[<p>环境：Docker-1.13.1 ，nginx-1.15.12，certbot</p>
<!-- -->
<p>给站点上 HTTPS 这件事,单独在物理机上配 nginx 并不难,教程一搜一大把。但当 nginx 跑在 docker 容器里时,证书文件要靠 volumes 挂载进容器,中间就多了一层文件系统的间接性。恰好 certbot 生成的证书文件又是软链接,这两件事撞在一起,就成了一个不查文档很难想明白的坑。这篇把当时的排查过程记下来。</p>
<p>由于现在没有https会被标识不安全网站，我就打算配置一下，去申请免费的<strong>Let's Encrypt</strong>证书，毕竟也不是很麻烦，但万万没想到给 docker nginx 容器中配置 ssl 证书竟然暗藏玄机。由于我是白手起家，docker文档从未看过，在此处被坑了一波，以后有机会还是有必要慢慢的看一下官方文档。</p>
<p><img decoding="async" loading="lazy" src="data:image/jpeg;base64,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" width="66" height="58" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="证书是怎么来的">证书是怎么来的<a href="https://tanzhuo.xyz/2019/06/13/li-yong-docker#%E8%AF%81%E4%B9%A6%E6%98%AF%E6%80%8E%E4%B9%88%E6%9D%A5%E7%9A%84" class="hash-link" aria-label="证书是怎么来的的直接链接" title="证书是怎么来的的直接链接" translate="no">​</a></h2>
<p>先说明一下我的情况，利用cartbot申请了免费的域名ssl证书之后，得到的</p>
<p>fullchain.pem，privkey.pem文件是个软链接文件。</p>
<p>这不是偶然,而是 certbot 的固定设计:真实的证书文件存放在 <code>/etc/letsencrypt/archive/&lt;域名&gt;/</code> 下,每次续期都会生成一组新文件;而 <code>/etc/letsencrypt/live/&lt;域名&gt;/</code> 下的 fullchain.pem、privkey.pem 只是指向 archive 里最新一版的软链接。这样 nginx 配置里可以固定写 live 路径,续期后不用改配置。在物理机上这套机制很省心,但放到容器里就出问题了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="挂载进容器">挂载进容器<a href="https://tanzhuo.xyz/2019/06/13/li-yong-docker#%E6%8C%82%E8%BD%BD%E8%BF%9B%E5%AE%B9%E5%99%A8" class="hash-link" aria-label="挂载进容器的直接链接" title="挂载进容器的直接链接" translate="no">​</a></h2>
<p>再用docker volumes 把ssl配置文件夹挂载到 nginx 容器内部，顺便配置nginx.conf（注意nginx容器的nginx.conf配置文件有点差异 nginx.conf会引入/etc/nginx/conf.d/下的*.conf文件，这个小细节需要注意，还有别忘记映射容器443端口）</p>
<p>站点配置大致长这样:</p>
<div class="language-nginx codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-nginx codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 放在 /etc/nginx/conf.d/ 下,会被主配置的 include 引入</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token directive keyword" style="color:hsl(286, 60%, 67%)">server</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token directive keyword" style="color:hsl(286, 60%, 67%)">listen</span><span class="token directive"> </span><span class="token directive number" style="color:hsl(29, 54%, 61%)">443</span><span class="token directive"> ssl</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token directive keyword" style="color:hsl(286, 60%, 67%)">server_name</span><span class="token directive"> example.com</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 这里写的是容器内的路径,必须和挂载后的实际位置对得上</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token directive keyword" style="color:hsl(286, 60%, 67%)">ssl_certificate</span><span class="token directive">     /etc/letsencrypt/live/example.com/fullchain.pem</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">    </span><span class="token directive keyword" style="color:hsl(286, 60%, 67%)">ssl_certificate_key</span><span class="token directive"> /etc/letsencrypt/live/example.com/privkey.pem</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑在哪里">坑在哪里<a href="https://tanzhuo.xyz/2019/06/13/li-yong-docker#%E5%9D%91%E5%9C%A8%E5%93%AA%E9%87%8C" class="hash-link" aria-label="坑在哪里的直接链接" title="坑在哪里的直接链接" translate="no">​</a></h2>
<p>这里就有个坑了。由于证书文件是个软链接文件，挂载进入容器之后是无法使用的。</p>
<p>nginx就会报错，错误信息为 无法找到该证书文件。但你进入容器又会发现证书文件确实在容器中，但是！此证书文件是无法读取的，跟权限无关，完全是因为docker volumes 本身只能映射硬链接信息。而软链接是无法映射的，留在容器中的只是一个软链接的空壳。</p>
<p>排查这类问题有个简单办法:进容器里 <code>ls -l</code> 看一眼,如果证书文件显示为指向某个路径的链接,再 <code>cat</code> 一下试试能不能读。读不出来,基本就是链接指向的目标在容器里不存在。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么会这样">为什么会这样<a href="https://tanzhuo.xyz/2019/06/13/li-yong-docker#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BC%9A%E8%BF%99%E6%A0%B7" class="hash-link" aria-label="为什么会这样的直接链接" title="为什么会这样的直接链接" translate="no">​</a></h2>
<p>其实也不难理解 因为软链接只是一个快捷方式，如果volumes 中有宿主机的快捷方式还可以访问宿主机硬链接那才有问题了。因为软链接可以在/home卷下,而软链接对应的硬链接文件其实在/usr卷下。但我配置的volumes 只映射了/home卷 是不能去访问/usr卷的数据的，这里必须要准确映射到对应的软文件，不能是软文件的目录。也不能是直接挂载软文件。</p>
<p>换个角度说:软链接本身只存了一条目标路径,docker 把链接原样放进容器后,容器会按这条路径在<strong>自己的</strong>文件系统里找目标。目标路径没被一起挂载进来,这条链接就成了死链。这也是容器隔离的应有之义——如果挂载一个快捷方式就能顺着它读到宿主机上任意位置的文件,那隔离就形同虚设了。</p>
<p><strong>挂载路径指向完整的证书软文件路径，才可以生效 。docker中的nginx启动时才能根据软文件对应的文件地址获取到证书文件。</strong></p>
<p>也就是说,关键在于让软链接指向的目标路径在容器内真实存在。对 certbot 来说,live 和 archive 两个目录要一起进容器,链接才解析得开:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 启动容器时把整个 letsencrypt 目录挂进去,</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># live 下的软链接和 archive 下的真实文件都在,链接才能正常解析</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"> run </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-d</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-p</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">80</span><span class="token plain">:80 </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-p</span><span class="token plain"> </span><span class="token number" style="color:hsl(29, 54%, 61%)">443</span><span class="token plain">:443 </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-v</span><span class="token plain"> /etc/letsencrypt:/etc/letsencrypt:ro </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  </span><span class="token parameter variable" style="color:hsl(207, 82%, 66%)">-v</span><span class="token plain"> /data/nginx/conf.d:/etc/nginx/conf.d </span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">  nginx</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="踩坑与注意">踩坑与注意<a href="https://tanzhuo.xyz/2019/06/13/li-yong-docker#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%B3%A8%E6%84%8F" class="hash-link" aria-label="踩坑与注意的直接链接" title="踩坑与注意的直接链接" translate="no">​</a></h2>
<ol>
<li class="">
<p>nginx 官方镜像的主配置只做全局设置,站点配置靠 include <code>/etc/nginx/conf.d/*.conf</code>,自己的 server 块要放对位置,否则改了也不生效。</p>
</li>
<li class="">
<p>443 端口容易忘。容器内 nginx 监听了 443,宿主机没映射,浏览器照样连不上,而 nginx 日志里看不出任何异常。</p>
</li>
<li class="">
<p>Let's Encrypt 证书有效期只有 90 天,续期后 archive 目录下会多出新一版文件,live 下的软链接随之更新。如果当初图省事把解析后的真实文件路径写死在配置里,续期后路径就对不上了——这也是更推荐挂载整个目录、配置里引用 live 路径的原因。续期后记得让容器内的 nginx 重新加载配置。</p>
</li>
<li class="">
<p>容器里看到文件"存在"不代表能读。对着软链接 <code>ls</code> 是看不出问题的,<code>cat</code> 一下或者直接看 nginx 的报错更直接。</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2019/06/13/li-yong-docker#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这个坑的本质是两个各自合理的设计撞在了一起:certbot 用软链接把"固定路径"和"会变的证书文件"解耦,docker volumes 又只忠实地搬运链接本身、不追踪链接目标。理解了软链接"只存路径"这一点,问题就不神秘了——把链接和它指向的目标一起挂进容器,让路径在容器内闭环,证书自然就能读到。也算是用一次踩坑补上了没看 docker 文档的课。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[关于健身的心理活动]]></title>
            <link>https://tanzhuo.xyz/2019/06/09/jian-shen-zhen-tmlei</link>
            <guid>https://tanzhuo.xyz/2019/06/09/jian-shen-zhen-tmlei</guid>
            <pubDate>Sun, 09 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[每次去健身前一天： 啊，挺好的，又可以锻炼身体了也！真棒👍 离健身还有几个小时的时候： 有点不怎么想去呀！还是家里耍着舒服。算了，说好的要去，去就去吧。 健身中： 我是sb 健身完： 我是sb]]></description>
            <content:encoded><![CDATA[<p>每次去健身前一天：</p>
<!-- -->
<p>啊，挺好的，又可以锻炼身体了也！真棒👍</p>
<p>离健身还有几个小时的时候：</p>
<p>有点不怎么想去呀！还是家里耍着舒服。算了，说好的要去，去就去吧。</p>
<p>健身中：</p>
<p>我是sb</p>
<p>健身完：</p>
<p>我是sb</p>]]></content:encoded>
            <category>生活</category>
        </item>
        <item>
            <title><![CDATA[Factorio（异星工厂）]]></title>
            <link>https://tanzhuo.xyz/2019/06/07/factorio-yi-xing-gong-han</link>
            <guid>https://tanzhuo.xyz/2019/06/07/factorio-yi-xing-gong-han</guid>
            <pubDate>Fri, 07 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Factorio 是一款建造工业生产流水线并保持其高效运转的游戏。在游戏中，你可以抠矿、搞科研、盖工厂、建设自动生产流水线，同时还要与异星虫子们互相伤害。 你将从一无所有艰辛起步。]]></description>
            <content:encoded><![CDATA[<p><strong>Factorio</strong> 是一款建造工业生产流水线并保持其高效运转的游戏。在游戏中，你可以抠矿、搞科研、盖工厂、建设自动生产流水线，同时还要与异星虫子们互相伤害。</p>
<!-- -->
<p>你将从一无所有艰辛起步。挥斧砍树，抡镐抠矿，手搓机械臂和传送带，然而像这样一直搞下去并没有什么卵用。</p>
<p>因此，你需要高效的大规模发电厂，庞大的石油化工体系，壮观的全自动化产业链，以及替你东奔西走的机器人大队，让你成为物资储备丰盈工业帝国的真正操控者！然而，总有一群刁民想害你。这个星球上的土著虫群对你在自家后院里瞎折腾的行为很不爽，总有一天这群刁民会联合起来找你麻烦。因此，你要制造武器、建立防御、准备镇压，让它们知道谁才是真正的主宰者。</p>
<p>你可以在<strong>多人游戏</strong>中加入不同的阵营，在大触们的带领下与朋友们分工协作，一起建设恢弘无比的工业园区。Factorio 的<strong>模组支持</strong>吸引了全世界的设计师参与到对游戏的完善和革新中来，从优化调整到游戏辅助，甚至对游戏的彻底翻新，日新月异的模组将为你不断提供新的乐趣。</p>
<p>最开始玩：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-20190101145512_1-8279c66667d49aac7576aeac78719403.webp" width="1600" height="900" class="img_ev3q"></p>
<p>玩了几个小时之后：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-20190101161133_1-0ce1893596a944b10f607a51512f0227.webp" width="1600" height="900" class="img_ev3q"></p>
<p>新开了图玩之后：</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-20190607132540_1-92484dbcb8dd7a163a5c0d98beab9f79.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-20190607132643_1-baa5b7381b4312367d5adae2c3cce1a6.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-20190607132629_1-e94bae9e0831cf11276af1e3bec58940.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-20190607132610_1-bdf12ef8ad6cf98766ce0cdee6b4a84e.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-20190404232212_1-90a444e605839209545df0db0802724b.webp" width="1600" height="900" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-20190404232753_1-d42628f86e01577d527736e1193b517b.webp" width="1600" height="900" class="img_ev3q"></p>
<p>最后得出结论：真tm肝</p>]]></content:encoded>
            <category>游戏</category>
        </item>
        <item>
            <title><![CDATA[CentOS docker 修改默认镜像存储路径]]></title>
            <link>https://tanzhuo.xyz/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing</link>
            <guid>https://tanzhuo.xyz/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing</guid>
            <pubDate>Wed, 05 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[记录一次修改 docker 默认镜像存储路径的踩坑过程:从 --graph 参数到存储驱动与 d_type 支持,最终用挂载数据盘的方式解决重启后镜像丢失的问题。]]></description>
            <content:encoded><![CDATA[<p>环境:CentOS-7,docker 1.13.1。记录一次修改 docker 默认镜像存储路径的完整踩坑过程。</p>
<!-- -->
<p>(注意自己的系统与 docker 的版本,请务必看完文章之后再操作,不然后果自负)</p>
<p>docker 默认的镜像存储路径为 /var/lib/docker。我们一般不会采用默认的存储位置,因为镜像文件通常较大,搭建的服务越多,宿主机系统盘的压力就越大,而且每个镜像还有自己的挂载卷等等,这些不在本文的讨论范围。所以不应把数据存放在系统盘上,应切换到对应的数据盘路径。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="修改存储路径">修改存储路径<a href="https://tanzhuo.xyz/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing#%E4%BF%AE%E6%94%B9%E5%AD%98%E5%82%A8%E8%B7%AF%E5%BE%84" class="hash-link" aria-label="修改存储路径的直接链接" title="修改存储路径的直接链接" translate="no">​</a></h2>
<p>进入正题:修改 docker 的配置文件 /usr/lib/systemd/system/docker.service</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-image-2-c8d543b950a8f529a3452dca4033e4b6.png" width="561" height="662" class="img_ev3q"></p>
<p>添加 graph 配置 <code>--graph="your path" \</code>,修改保存之后重新加载配置文件并重启 docker 服务:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 重新加载 systemd 配置</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">systemctl daemon-reload</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># 重启 docker 服务</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">systemctl restart docker.service</span><br></div></code></pre></div></div>
<p>执行 <code>docker info</code> 查看 docker 服务的当前信息:</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQsAAAAhCAYAAADOFY62AAAACXBIWXMAAAsSAAALEgHS3X78AAAGmElEQVR42u1d25XEIAhNT7ZjMenFWiwldWRjnuoAgjGPmeWDc3Znxgdwc4MGTNd13ZiKHd3gRht/Zt04+H40+WfOHv+bfvTDMDqb94dIaD/9PhY/9Wew325jZXOxbm0fvq+dJzAXsk9uu+RzP/repu1Bm5mx93mfqz/2uYTf+LE3mX5zf8tvTR/GM838t9g5wwWGl7m/6fNpjodv1/EhmzmGXUS2HlC8gLpTn5N2CbqvfmBdKxzfnsVuhjMMEyXfwu3qyCJ0ABq8E5AFdhEKyCL5vnaeEEiIPuvaLUCBnJXK8jsQnHGf8Tj7HDYSyfqQXDCEr3oHzR8hi4w4dt0lfm/lozNkQYiF7MEgi6JvpdglcYZgouhbsF0dWYCGuoMssPkhoGHNkwIidTFIAcwCIxdQ612tT/sM+jqbzfkUWcTzAe6kHLLgXtjfRBbYmNEdOb7oFwzKyIKN3YJ+ICYYvgXaScnCTMo7JGwRLkNQB/dTPwcz5kabw6ZoyWJ6CDSCeZJksTp76teaKMTurTyyyJ1PLEM4gJrvPHkYP//Gz0s6PFzmzmUdIx8z0QvAy9SXS5aURhZZSH1E9knoXsAZbpfQJ4b17M4ebLGPz/WtELslnIGYYPj2sx2xVvbwOjOsP63pBGs7aWQRHAesd+PvwwW8z9Md6/eaeRbIYiMkcDwKwKU9mZNksYEWWtdCoHc+Xdeaqotimx+Bl2AvG9kr1h3cezhJ6KU+Ud1LOKvYP9nsFo0XExLp21rsMnGWYoLyLdquG1VUVFQYokZQUVFRslBRUVGyUFFRUbJQUVH5Z2QRdlNrnqn/GyF2x++SN/uoNifjTfq10OF1ZMF5dPN2RyGpry3Gx1JluXNpYs8rCOLpi4ka/x+ShRhnj5FF9px84GTmvY0s4vHiNOMnyKK1PRtJknz08MVEZikqWXwHWUCg2hJNoCSpfk9uiZJQEkdBtRFZOyA33SXJUAWjl1JfKR2w7zhJRMy5xPbcsy/BRLYK3dmSZVyuPor9kKRzQ3aZ24Ss1jVBbW3L9y2d/em3Ni4n/y/AYEGHvfAx76uAM7TdW8jiKGQ5svaw9OskK277e3fUYvScMZN2Zsl6Oy6cNUNQYhiyqIbSgdavRWQBFwZhexYF3aXZspQee18GmCNil73+AU/jpn2L2fRzvCG70F+PQVIHIJUeSMEu4ex0EecVZLHflfPiGKQsGg4B3ewAD5b+YgU3lZt/UOprHG5zdQBSi1uQxWe/NFmguleTBVAIBuke72dAdonu1kuxEbAPQvq24VzehkFKB+YNpap47DWRRb7uy51YuAP6OczDwk0svz46s6HFPoFEh1onNo0shLrXVm7W2IVFFgN9bkWrubwNgwUsJTVGwJICw1mp3Xv2LE6x+gGqj4InUuHzZFGtw0WRhQXPDLiLLJA+SxdhbWRR49vLIosbMcjQYe+Pe1NitHuQLPIS2bPrRbjKLe8nRAK9bUsW3R5dnN+zGFzt/glSclxLFjXLEOr8BfROSOxZUGRR8i0K9iv3LG7CIKXDRiSmQ/czQJwx2r0rz+L0TvQ2Rn62gEdKaxuRRbwJV/M0JF5je+HTENSea2l5Js5eRxb7RS0Mm/GnITRZUL5F53L505AbMFh8GhI9deohvMI4K7fTdG+VX8vS1KxeTfdWUVFRslBRUenoJSSeYKdkoaKiopGFioqKyhVk8a0bVq2fVQvsENeFUIcW31m+Hnbbrxjr0LX8VIFnF5XvLVF/qAz9ADZwwvUDZFH1HpWbk2vIxLJL/Mh8p8hvnwHxo2RRU1L91JkV0RvKQgXks2TxHRcFnYV6QdYolVmpZPE7ZPF5t0RKebMS5yRZhpHwgybgkEAKhUELCEP4nLyVq5B4hY/HKamWghtJFCq0K5avM/yQhP3MMvvTb5nLq2azg4i2+RTLrVG7AO9uaeE/lZYl6kQpb1LibOiDVQqpxHyy6OeXDM2vVgttPt4lyUvplpZUS0uG0RRk1h0U3rPg+aGu/Bktfa6tcmVEFuCYkF3y+ohwsJFr4z+VK0vUY+CVCnwkRUoCsjDbASzbWQrSUnpqLsLCMar4x1aH2wBZSP0grZzF3vV5IVmguAPJYopOrGGN8/ITp344sqBKeVuVFUvJIr6YgGpI8VxKJdXSuyNnL6eKLCr8cDayaLxnUSy3xpYh4d2k3u9LySPKOOc/ldYl6uR5AQ9EFmcP6eGeh1C7Z3FVZEH54SRZtNuzIGzAKbdmYMH0Df2nwpI/uvvDt5dva/EAAAAASUVORK5CYII=" width="267" height="33" class="img_ev3q"></p>
<p>Docker Root Dir 显示为 "your path",说明配置成功。建议下载个镜像之后,去对应文件夹里查看是否生效;之后再 reboot 重启服务器,看 docker 里面的镜像是否还在。</p>
<p><strong>docker images 执行之后,如果发现下载的镜像消失了。</strong></p>
<p>恭喜你,和我的情况一样,你可能会怀疑人生。再执行 docker info,发现 Docker Root Dir 的路径并没有问题。这里我的做法是删除掉原来的 /var/lib/docker 文件夹。当然在删除之前,如果你还想要里面的数据,建议先 copy 一份再删除,或者直接移动文件夹并换个名称,不换名称也无所谓,重要的是让 /var/lib/docker 不复存在。之后你再试一试,我想就应该没问题了,至少我是如此。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="追记重启后镜像又消失了2019-06-16">追记:重启后镜像又消失了(2019-06-16)<a href="https://tanzhuo.xyz/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing#%E8%BF%BD%E8%AE%B0%E9%87%8D%E5%90%AF%E5%90%8E%E9%95%9C%E5%83%8F%E5%8F%88%E6%B6%88%E5%A4%B1%E4%BA%862019-06-16" class="hash-link" aria-label="追记:重启后镜像又消失了(2019-06-16)的直接链接" title="追记:重启后镜像又消失了(2019-06-16)的直接链接" translate="no">​</a></h2>
<p>本以为是配置好了的,结果事情果然没有那么简单,毕竟我对 docker 的了解还不够多。</p>
<p>这次服务器供应商突然修复服务器的硬件设备,导致大部分服务器强制关机(所以还是不要贪便宜买杂牌云服务器)。我的服务器也关机了,修复好之后服务器重启,我发现我的博客仍然不能访问。我设置的是 docker 开机自启、容器跟随启动,然而好像并没有生效,自然需要去排查问题所在。(我上次真的以为已经配置好了,结果好像被打脸了)</p>
<p>登录服务器之后执行 docker images,发现镜像又像上次一样没了。说明上次那样操作其实并没有真正解决问题——准确地说,配置是生效了的,但随着宿主机重启,配置就出现了问题。</p>
<p>虽然 docker info 下看到的 Docker Root Dir 路径没问题,但其实已经出了故障:执行 docker images 和 docker ps -a 均没有任何信息。而且此时如果执行 <code>service docker stop</code> 关闭 docker 服务,你就再也启动不了 docker 了。执行 <code>service docker start</code> 启动时会报错:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Job </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">for</span><span class="token plain"> docker.service failed because the control process exited with error code. See </span><span class="token string" style="color:hsl(95, 38%, 62%)">"systemctl status docker.service"</span><span class="token plain"> and </span><span class="token string" style="color:hsl(95, 38%, 62%)">"journalctl -xe"</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">for</span><span class="token plain"> details.</span><br></div></code></pre></div></div>
<p>就算按提示执行 systemctl status docker.service、journalctl -xe 查看错误详情,也根本看不出个所以然。想要看到具体的错误信息,可以直接前台启动 docker 守护进程,就能看到启动日志,从里面排查错误:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 前台启动 docker 守护进程,直接输出启动日志</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">dockerd</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="存储驱动的坑">存储驱动的坑<a href="https://tanzhuo.xyz/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing#%E5%AD%98%E5%82%A8%E9%A9%B1%E5%8A%A8%E7%9A%84%E5%9D%91" class="hash-link" aria-label="存储驱动的坑的直接链接" title="存储驱动的坑的直接链接" translate="no">​</a></h2>
<p>我在排查这个错误的途中学习到了很多知识,比如 docker 的 5 种存储驱动模式:</p>
<p>简书:<a href="https://www.jianshu.com/p/00ffd8df6010" target="_blank" rel="noopener noreferrer" class="">https://www.jianshu.com/p/00ffd8df6010</a></p>
<p>别人的博客:<a href="https://blog.csdn.net/qq_34018840/article/details/89853119" target="_blank" rel="noopener noreferrer" class="">https://blog.csdn.net/qq_34018840/article/details/89853119</a></p>
<p><strong>AUFS、Btrfs、Device mapper、OverlayFS、ZFS</strong></p>
<p>最后我选择了 OverlayFS 的升级版本 Overlay2。而我原来的驱动模式是 Device mapper——执行 docker info 时最下面有个提示信息,告诉我 Device mapper 这个模式只是测试环境下自动选择的,生产环境可以自行切换。这更加坚定了我切换 Overlay2 的想法,不过我没有着急切换,先继续排查这个神奇的现象:宿主机重启之后,为啥 docker 读取不到 /home/docker_data 下的镜像文件呢?</p>
<p>我在 google、百度、github、知乎、帖子、简书、官方文档里都找不到这个问题,最后在 docker 的官方社区里发现有个外国佬出现了和我差不多的情况。他们讨论说这是版本问题,我就寻思着,好吧,看来要装个新点的版本。</p>
<p>于是彻底删除 docker 之后重新安装(注意:docker 从 1.13 版本之后采用时间线的方式作为版本号,分为社区版 CE 和企业版 EE):</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 彻底卸载旧版本 docker</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">yum remove </span><span class="token function" style="color:hsl(207, 82%, 66%)">docker</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">yum remove docker-selinux</span><br></div></code></pre></div></div>
<p>重新装了 docker-ce-17.12.0.ce 版本(听说是比较稳定的版本),并配置了 /etc/docker/daemon.json</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="505" height="78" class="img_ev3q"></p>
<p>启动 docker,发现我的 linux 内核不能支持 overlay2 存储驱动方式,于是升级了内核,需要 4.0 以上的内核才可以支持。</p>
<p>之后执行 docker info,又有提示告诉我磁盘不支持 d_type。我又去格式化了挂载盘,格式化为 ftype=1 的 xfs:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(220, 13%, 18%);--prism-color:hsl(220, 14%, 71%)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="background-color:hsl(220, 13%, 18%);color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token comment" style="color:hsl(220, 10%, 40%)"># 查看文件系统信息,ftype=1 表示支持 d_type</span><span class="token plain"></span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">xfs_info /home</span><br></div></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/assets/images/2019-06-image-5-58cf24accf2b008eebe5ea5ec87f3add.png" width="818" height="180" class="img_ev3q"></p>
<p>并配置 docker 默认的镜像存储位置到这块支持 d_type 的数据挂载盘,重启 docker。</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="415" height="138" class="img_ev3q"></p>
<p>再执行 docker info 就没有任何警告信息了,d_type 已开启,存储方式也修改为了 overlay2。</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="438" height="268" class="img_ev3q"></p>
<p>我感觉这次已经很完美了,毕竟执行 docker info 都没有任何警告。我立刻下载了个 tomcat 镜像,发现镜像已经放在指定的文件夹,太好了!我立刻执行 reboot 重启宿主机。</p>
<p><strong>重启之后 docker images 镜像又没了!!!</strong></p>
<p><strong>我是谁,我在干什么,我做了这么多都是在干嘛?</strong></p>
<p>我真的是服了,不知道我哪一步做错了什么。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="最终解决">最终解决<a href="https://tanzhuo.xyz/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing#%E6%9C%80%E7%BB%88%E8%A7%A3%E5%86%B3" class="hash-link" aria-label="最终解决的直接链接" title="最终解决的直接链接" translate="no">​</a></h2>
<p>最后我是这样解决的:把数据盘直接挂载到 /var/lib/docker 这个文件夹下,并设置开机自动挂载,同时删除了 /etc/docker/daemon.json 中 data-root 的配置。</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="505" height="78" class="img_ev3q"></p>
<p>之后宿主机再重启,执行 docker images 就看见了我的镜像,容器也自动启动了起来。这次就算断电、关机都不怕了,宿主机重启后 docker 会自动启动容器。</p>
<p>但之后我换用阿里云服务器,什么事情都没有,一切正常,所以可能还是硬件问题。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://tanzhuo.xyz/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>修改镜像存储路径看似只是加一个 --graph 参数的事,实际却牵扯出存储驱动、内核版本、文件系统 d_type 支持等一连串问题。折腾一圈后,我的最终方案是反其道而行:不改 docker 的路径配置,直接把数据盘挂载到 /var/lib/docker 并设置开机自动挂载,重启后镜像和容器都能正常恢复。如果你也遇到重启后镜像丢失的情况,可以优先排查存储驱动与磁盘挂载的时序问题,也不排除是廉价服务器本身的硬件毛病。选云服务商这件事,还是别太贪便宜。</p>]]></content:encoded>
            <category>技术</category>
        </item>
        <item>
            <title><![CDATA[hello，Ghost]]></title>
            <link>https://tanzhuo.xyz/2019/06/04/first_post</link>
            <guid>https://tanzhuo.xyz/2019/06/04/first_post</guid>
            <pubDate>Tue, 04 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[通过docker 部署了n多个容器服务之后，我也顺便部署了一个开源的ghost博客系统，可以记录一下技术知识点和平时生活中的点点滴滴~]]></description>
            <content:encoded><![CDATA[<p>通过 docker 部署了 n 多个容器服务之后，我也顺便部署了一个开源的 ghost 博客系统，可以记录一下技术知识点和平时生活中的点点滴滴~</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="data:image/jpeg;base64,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" width="156" height="158" class="img_ev3q"></p>]]></content:encoded>
            <category>生活</category>
        </item>
    </channel>
</rss>