<?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/en/</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>en</language>
        <item>
            <title><![CDATA[AgentMux: A Control Plane for AI Coding Agents on Remote Servers]]></title>
            <link>https://tanzhuo.xyz/en/2026/08/16/agentmux</link>
            <guid>https://tanzhuo.xyz/en/2026/08/16/agentmux</guid>
            <pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Open-sourced AgentMux, a desktop app for managing AI coding agents on remote servers: every server, project and agent in one tree, sessions running inside tmux on the server, so closing the app only detaches. Go + Wails 3 + React, MIT.]]></description>
            <content:encoded><![CDATA[<p>I open-sourced <a href="https://github.com/tan-zhuo/AgentMux" target="_blank" rel="noopener noreferrer" class="">AgentMux</a>: a desktop app for managing AI coding agents that run on remote servers. Go + Wails 3 + React, MIT licensed.</p>
<!-- -->
<p>Most of my coding agents ended up on remote servers — an agent runs for tens of minutes at a time, and a laptop that goes into meetings, sleeps and changes networks is the wrong place for that. The annoying part is managing them: open a terminal, SSH in, remember which directory this project lives in, start the agent. Once you have a few machines and a few projects you stop remembering who is running what, and anything started in the foreground dies with the connection.</p>
<p>That is what AgentMux is for. Every server, project and agent sits in one tree, colour-coded by run state, so a glance tells you what is running and how far along it is.</p>
<p>The important bit is that everything it starts runs inside a tmux session on the server; the desktop app never holds a process. Closing a tab is just a detach. Quitting the app, losing the network or shutting the lid does not stop the agent, and when you come back the terminal is where you left it, scrollback included.</p>
<p>Roughly what else it does:</p>
<ul>
<li class="">A real terminal — xterm.js attached to the agent's own pane, so you can take over typing without interrupting it first</li>
<li class="">One instruction to many agents, with a separate delivery receipt from each</li>
<li class="">An SFTP file browser; double-click a text file and it opens in Monaco, with atomic saves that keep the permission bits and warn you if something else changed the file</li>
<li class="">Host metrics: CPU broken down and per core, memory, disk, network, temperature, plus utilisation and memory for NVIDIA GPUs</li>
<li class="">An install panel that detects which agent CLIs a box is missing (Claude Code, Codex, Gemini CLI, OpenCode, Aider, Cursor CLI) and installs them inside tmux</li>
</ul>
<p>Passwords and key passphrases are encrypted with AES-256-GCM before they touch disk, the master key lives in the OS keychain, and the frontend only ever learns whether a credential is set. It does not proxy the agent's model traffic and never touches API keys.</p>
<p>If you want to try it, the <a href="https://github.com/tan-zhuo/AgentMux/releases" target="_blank" rel="noopener noreferrer" class="">releases page</a> has macOS, Windows and Linux builds (unsigned, so the first launch takes one extra click). Building from source needs Go 1.25+ and Node 20+; on the remote side, <code>tmux</code> and an SSH account is all it takes:</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>Plenty is still missing: the orchestrator AI from the original design only has its plumbing in place, there are no split panes inside a tab, file transfers are one file at a time, and metrics history lives only in the open panel. The editor is Monaco, not VS Code, so extensions do not run — if you need them, running <code>code-server</code> on the box and letting AgentMux keep it alive in tmux is the better answer.</p>
<p>The code is at <a href="https://github.com/tan-zhuo/AgentMux" target="_blank" rel="noopener noreferrer" class="">github.com/tan-zhuo/AgentMux</a> — try it and file issues.</p>]]></content:encoded>
            <category>AI</category>
            <category>Go</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Earth: A 3D Globe in the Browser, All the Way Out to the Observable Universe]]></title>
            <link>https://tanzhuo.xyz/en/2026/08/13/earth</link>
            <guid>https://tanzhuo.xyz/en/2026/08/13/earth</guid>
            <pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Open-sourced a new project, Earth: a pure-frontend 3D globe in the spirit of Google Earth. Click a country for its flag, population, GDP, and government; GDP bars, trade routes, a time-travel mode, and a cosmic scale ladder from Earth to the observable universe.]]></description>
            <content:encoded><![CDATA[<p>I open-sourced a new project, <a href="https://github.com/tan-zhuo/earth" target="_blank" rel="noopener noreferrer" class="">Earth</a>: a pure-frontend 3D globe in the spirit of Google Earth. Drag to rotate, scroll to zoom, click any country to fly over and see its flag, population, GDP, and system of government; the top bar also climbs a cosmic scale ladder — the Moon, the Solar System, the Milky Way, all the way to the observable universe. Try it live at <a href="https://earth.kim/" target="_blank" rel="noopener noreferrer" class="">earth.kim</a>.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-build-this">Why build this<a href="https://tanzhuo.xyz/en/2026/08/13/earth#why-build-this" class="hash-link" aria-label="Direct link to Why build this" title="Direct link to Why build this" translate="no">​</a></h2>
<p>What I wanted was simple: a globe that spins the moment you open the browser, where clicking a country shows you how it's doing today — no client to install, no token to register, no paid API. The professional options (Cesium, say) are far heavier than this calls for, so I assembled a lightweight one myself.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-inside">What's inside<a href="https://tanzhuo.xyz/en/2026/08/13/earth#whats-inside" class="hash-link" aria-label="Direct link to What's inside" title="Direct link to What's inside" translate="no">​</a></h2>
<p><strong>Country info</strong>: click a country and the camera glides over, with altitude adapted to the country's size. The detail panel shows the flag, bilingual and official names, capital, region, area, population, languages, currency, GDP (nominal and per capita), development status, and system of government, plus an embedded Wikipedia history summary that follows the language setting.</p>
<p><strong>Data layers</strong>: a GDP bar layer stands every country's latest GDP up as 3D columns (sqrt-normalized, colored by World Bank income group); a flag layer pins each flag at its country, sized by area; a shipping layer draws the world's major maritime trade corridors — animated arcs segmented by hub ports, with width and color encoding traffic level. Each layer toggles independently.</p>
<p><strong>Global rankings</strong>: GDP, exports, and imports (live World Bank data), plus static snapshots of oil and gas production. Click a row to fly straight to that country.</p>
<p><strong>Time-travel mode</strong>: the paleogeographic 3D evolution of 16 geological eras, from the supercontinent Rodinia 750 million years ago to the present, with a draggable timeline and autoplay (paleogeographic maps © C.R. Scotese, PALEOMAP Project). You can watch the continents drift, collide, and split apart.</p>
<p><strong>The cosmic scale ladder</strong>: the top bar switches between five scales, each with a bilingual fact card — Earth (10⁷ m), the Moon (10⁸ m, lunar textures plus 10 historic landing sites from Apollo, Chang'e, and Luna), the Solar System (10¹³ m, eight planets orbiting), the Milky Way (10²¹ m, a procedural barred spiral of 50,000 particles, with the Solar System's position and Sagittarius A* marked), and the observable universe (10²⁷ m, a cosmic-web point cloud of galaxy clusters and filaments).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-the-data-comes-from">Where the data comes from<a href="https://tanzhuo.xyz/en/2026/08/13/earth#where-the-data-comes-from" class="hash-link" aria-label="Direct link to Where the data comes from" title="Direct link to Where the data comes from" translate="no">​</a></h2>
<p>All free, no API keys. Country basics come from <a href="https://github.com/mledoze/countries" target="_blank" rel="noopener noreferrer" class="">mledoze/countries</a> (the upstream dataset behind REST Countries), statically bundled <strong>at build time</strong> — zero requests at runtime. Population, GDP, and trade figures come from the World Bank Open Data API, fetched on demand when you click a country and cached in localStorage for 30 days. Flags come from flagcdn.com; country borders are Natural Earth 110m, self-hosted with the app; the Earth textures are the NASA ones bundled with three-globe; planet and Moon textures are from Solar System Scope (CC BY 4.0).</p>
<p>One gotcha: the REST Countries v3.1 API was deprecated in 2026 (the new version requires registration), so the project never depends on it at runtime — the upstream dataset is statically baked in at build time, and updating the data is a single script re-run.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech stack<a href="https://tanzhuo.xyz/en/2026/08/13/earth#tech-stack" class="hash-link" aria-label="Direct link to Tech stack" title="Direct link to Tech stack" translate="no">​</a></h2>
<p>React 18 + TypeScript + Vite, with Three.js (via globe.gl) for the 3D — orders of magnitude lighter than Cesium, no Ion token, and country polygons, fly-to animation, and hover picking all work out of the box. Styling is Tailwind CSS v4, state is Zustand, i18n is react-i18next (browser-language detection plus localStorage memory). Pure frontend, statically deployable, with a right-hand panel on desktop and a bottom drawer on mobile.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-next">What's next<a href="https://tanzhuo.xyz/en/2026/08/13/earth#whats-next" class="hash-link" aria-label="Direct link to What's next" title="Direct link to What's next" translate="no">​</a></h2>
<p>Still planned: country search, major exports and resources, multi-country comparison, and chart visualizations. The code is at <a href="https://github.com/tan-zhuo/earth" target="_blank" rel="noopener noreferrer" class="">github.com/tan-zhuo/earth</a> — give it a spin and file issues.</p>]]></content:encoded>
            <category>Frontend</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[AI Room: Step Inside a Neural Network That Is Actually Computing]]></title>
            <link>https://tanzhuo.xyz/en/2026/08/06/ai-room</link>
            <guid>https://tanzhuo.xyz/en/2026/08/06/ai-room</guid>
            <pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Open-sourced a new project, AI Room: a 3D space built in the browser where an MLP, a CNN, and a tiny Transformer are genuinely trained and run real forward passes — every node can be opened and checked by hand.]]></description>
            <content:encoded><![CDATA[<p>I open-sourced a new project, <a href="https://github.com/tan-zhuo/ai-room" target="_blank" rel="noopener noreferrer" class="">AI Room</a>: it puts a neural network inside a 3D space you can walk into and inspect node by node. Try it live at <a href="https://airoom.run/" target="_blank" rel="noopener noreferrer" class="">airoom.run</a>.</p>
<p>There is one essential difference from the usual "neural network explainer animations": <strong>this is not an animation — it is real computation</strong>. At load time, every network is genuinely trained in your browser with deterministic seeds; every value you see — activations, attention weights, softmax probabilities — is the true result of the forward pass. Click any node and check the arithmetic yourself.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-i-built-it">Why I built it<a href="https://tanzhuo.xyz/en/2026/08/06/ai-room#why-i-built-it" class="hash-link" aria-label="Direct link to Why I built it" title="Direct link to Why I built it" translate="no">​</a></h2>
<p>I've seen plenty of diagrams and videos explaining neural networks, and most stop at the "illustration" level: arrows, boxes, gradient colors, made-up numbers, staged flows. I always wanted a version you could audit — every neuron's inputs, weights, bias, and activation laid out in front of you, where the Σ you add up is exactly the number glowing on screen. If you suspect it's lying, grab a calculator.</p>
<p>That became AI Room. Pure frontend, no backend, no runtime network calls — all training and inference happens in your browser.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-inside">What's inside<a href="https://tanzhuo.xyz/en/2026/08/06/ai-room#whats-inside" class="hash-link" aria-label="Direct link to What's inside" title="Direct link to What's inside" translate="no">​</a></h2>
<p><strong>MLP (multi-layer perceptron)</strong>: classifies three Gaussian clusters. Data-flow particles run through the weighted connections layer by layer, and the animation order is the computation order.</p>
<p><strong>CNN (convolutional network)</strong>: classifies patterns (vertical / horizontal / diagonal / ring). The receptive-field window slides across the input exactly in computation order. Kernels come in two modes — hand-crafted Sobel-style edge detectors (interpretable; only the dense head trains), or kernels that start as random noise and are trained end-to-end through a hand-written backward pass. There's also a draw mode: paint your own pattern on the input grid and watch the whole network classify it live, stroke by stroke. Three sizes (S/M/L), and switching retrains the network on the spot.</p>
<p><strong>Tiny Transformer (character-level)</strong>: a structurally faithful Transformer block — tokenizer → embedding → sinusoidal positional encoding → multi-head causal attention (2 heads, with output projection) → residual + LayerNorm → feed-forward → residual + LayerNorm → output softmax. Both the forward and backward passes (including LayerNorm and residual gradients) are hand-written, trained in-browser on a small corpus in about 2.5 seconds. Both heads' 8×8 attention matrices light up row by row; open an Add &amp; Norm cell and you'll see the full arithmetic down to μ, σ, γ, β. Hit "Generate" for true autoregressive decoding: the sampled character is appended to the context, the window slides, the whole pipeline re-runs, and the text streams out one character at a time — exactly how real LLMs write. A temperature slider (0.2–1.4) controls the sampling distribution live.</p>
<p><strong>Language ID (an AI application)</strong>: type anything; the text becomes 8 interpretable statistics (Latin %, CJK %, kana %, and so on) and a trained MLP decides whether it's 中文, English, or 日本語. It's also direct proof that the computation is real: your input, its numbers, its prediction — one chain you can follow end to end.</p>
<p>Every layer title can be clicked for an explanation: what it does, why the network needs it, and a plain-words analogy — in Chinese, English, and Japanese — while the layer glows in 3D and everything else dims.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="it-doesnt-pretend-to-be-gpt">It doesn't pretend to be GPT<a href="https://tanzhuo.xyz/en/2026/08/06/ai-room#it-doesnt-pretend-to-be-gpt" class="hash-link" aria-label="Direct link to It doesn't pretend to be GPT" title="Direct link to It doesn't pretend to be GPT" translate="no">​</a></h2>
<p>The README has a dedicated section on the differences from production models: a single Transformer block, 2 heads, d=12, character-level tokens; training is plain sample-by-sample SGD; the CNN has one conv+pool stage; language ID deliberately uses hand-crafted statistical features instead of modern embeddings. The math shown is real; the scale is not. I think honestly listing the simplifications is worth more than pretending "this is how ChatGPT works."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stack">Stack<a href="https://tanzhuo.xyz/en/2026/08/06/ai-room#stack" class="hash-link" aria-label="Direct link to Stack" title="Direct link to Stack" translate="no">​</a></h2>
<p>Vite + React + TypeScript + React Three Fiber + Drei + Zustand. <code>npm run sanity</code> trains every network at every scale and verifies accuracy.</p>
<p>The code is at <a href="https://github.com/tan-zhuo/ai-room" target="_blank" rel="noopener noreferrer" class="">github.com/tan-zhuo/ai-room</a> — give it a spin, and issues are welcome.</p>]]></content:encoded>
            <category>AI</category>
            <category>Frontend</category>
        </item>
        <item>
            <title><![CDATA[From Container Cloud to Intelligent Cloud]]></title>
            <link>https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji</link>
            <guid>https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji</guid>
            <pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A single agent is no longer enough. This post proposes building an AI-native control plane on top of Kubernetes — the six-layer architecture and evolution path of a Kubernetes AI OS.]]></description>
            <content:encoded><![CDATA[<p>AI agent systems have reached a point where a single agent is no longer enough. What we need is <strong>multi-agent collaboration</strong>, <strong>observable execution traces</strong>, <strong>safe side-effect governance</strong>, and reliable scheduling in distributed environments.</p>
<!-- -->
<p>OpenClaw excels at gateway, tools, sessions, and local multi-agent routing, but falls short when it comes to large-scale collaboration, cross-node scheduling, and operator visibility. Meanwhile, golutra-style orchestration and trace capabilities offer a valuable complement.</p>
<p>Building on OpenClaw's gateway runtime, I propose an evolutionary architecture: <strong>treat Kubernetes as a solid foundation and build an AI-native control plane on top of it</strong> — call it the <strong>Kubernetes AI OS</strong>. It does not replace Kubernetes; it treats Kubernetes as the infrastructure layer and adds an agent runtime, execution scheduling, capability discovery, side-effect governance, and cross-node coordination — a genuinely operator-facing execution system.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2026-04-Gemini_Generated_Image_xonbpixonbpixonb-c7b4a8c8648cceb82c3292ec7ce19fe8.webp" width="1600" height="873" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-architecture">Why This Architecture?<a href="https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#why-this-architecture" class="hash-link" aria-label="Direct link to Why This Architecture?" title="Direct link to Why This Architecture?" translate="no">​</a></h2>
<p>OpenClaw is currently strong in channels, tools, sessions, and local multi-agent routing, but has clear gaps in large-scale collaboration, execution-trace auditing, and cross-node placement.</p>
<p>What we want to fuse together:</p>
<ul>
<li class="">OpenClaw's southbound integrations and agent runtime</li>
<li class="">golutra-style execution traces and visualization</li>
<li class="">Kubernetes's lifecycle management, scheduling, and failure recovery</li>
</ul>
<p>The core idea fits in one sentence: <strong>execution must be visible, inspectable, and attributable</strong>. Without a clear execution ledger, a multi-agent system quickly becomes a black box.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="design-goals">Design Goals<a href="https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#design-goals" class="hash-link" aria-label="Direct link to Design Goals" title="Direct link to Design Goals" translate="no">​</a></h2>
<ul>
<li class="">Clear multi-agent collaboration within a single node</li>
<li class="">Workflow-level scheduling across nodes</li>
<li class="">Nodes that can automatically join, be quarantined, and recover</li>
<li class="">Small models assist with role recommendation, but high-risk decisions must go through the policy plane</li>
<li class="">Every meaningful action lands in a unified execution ledger</li>
<li class="">Clean separation of control plane, execution plane, observability plane, and policy plane</li>
</ul>
<p><strong>Non-goals</strong>: no replacing Kubernetes-native scheduling, no unrestricted free-form chat between agents, and no letting the model directly decide on high-risk operations like privilege escalation or topology changes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-six-layer-architecture-at-a-glance">The Six-Layer Architecture at a Glance<a href="https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#the-six-layer-architecture-at-a-glance" class="hash-link" aria-label="Direct link to The Six-Layer Architecture at a Glance" title="Direct link to The Six-Layer Architecture at a Glance" translate="no">​</a></h2>
<p>The system is divided into six layers with clear responsibility boundaries:</p>
<ul>
<li class=""><strong>Southbound Gateway Layer</strong>: reuses OpenClaw's channel, tool, and session integration capabilities</li>
<li class=""><strong>Execution Kernel</strong>: the core execution layer, managing the full Run → Flow → Step → SideEffect lifecycle</li>
<li class=""><strong>Cluster Mesh</strong>: handles node registration, capability publishing, step scheduling, and security isolation (free-form chat between nodes is discouraged)</li>
<li class=""><strong>Observability Plane</strong>: the unified execution ledger, with visual views of runs, flows, steps, and side effects</li>
<li class=""><strong>Cognition Plane</strong>: uses small models for workload identification, role recommendation, and state summarization</li>
<li class=""><strong>Policy Plane</strong>: owns security approvals, secret scoping, side-effect governance, and trust checks</li>
</ul>
<p>The control plane decides "what should run, where, and under which policies"; the execution plane does the actual work and produces the trace.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-single-node-multi-agent-runtime">A Single-Node Multi-Agent Runtime<a href="https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#a-single-node-multi-agent-runtime" class="hash-link" aria-label="Direct link to A Single-Node Multi-Agent Runtime" title="Direct link to A Single-Node Multi-Agent Runtime" translate="no">​</a></h2>
<p>Even with just one node, multiple roles should run in coordination. The recommended base roles:</p>
<ul>
<li class="">Planner</li>
<li class="">Executor</li>
<li class="">Reviewer</li>
<li class="">Watcher</li>
<li class="">Specialist (domain expert)</li>
</ul>
<p>Every action is first reported to the local ledger and then aggregated by the control plane, keeping the execution trace clearly visible.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cross-node-scheduling-steps-not-chat">Cross-Node Scheduling: Steps, Not Chat<a href="https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#cross-node-scheduling-steps-not-chat" class="hash-link" aria-label="Direct link to Cross-Node Scheduling: Steps, Not Chat" title="Direct link to Cross-Node Scheduling: Steps, Not Chat" translate="no">​</a></h2>
<p>The key to cross-node collaboration is <strong>scheduling at the granularity of Steps</strong>, rather than letting nodes forward messages freely. Only then are ownership, cancellation, and auditing guaranteed.</p>
<p>The scheduler weighs required tools, model availability, hardware resources, locality, trust level, and tenant affinity.</p>
<p>Node states include joining, ready, degraded, quarantined, and draining, with automatic quarantine of high-risk nodes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-core-primitive-the-execution-ledger">The Core Primitive: the Execution Ledger<a href="https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#the-core-primitive-the-execution-ledger" class="hash-link" aria-label="Direct link to The Core Primitive: the Execution Ledger" title="Direct link to The Core Primitive: the Execution Ledger" translate="no">​</a></h2>
<p>This is the most important piece of the entire system. Every event — run, flow, step, side effect, policy, and so on — is bound to a runId, forming a unified, queryable ledger.</p>
<p>An operator should be able to easily answer:</p>
<ul>
<li class="">Which agent is executing?</li>
<li class="">What side effects have occurred?</li>
<li class="">Which step is it currently stuck on?</li>
<li class="">Is this failure safe to retry?</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-relationship-with-kubernetes">The Relationship with Kubernetes<a href="https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#the-relationship-with-kubernetes" class="hash-link" aria-label="Direct link to The Relationship with Kubernetes" title="Direct link to The Relationship with Kubernetes" translate="no">​</a></h2>
<p>Kubernetes continues to own the infrastructure capabilities: Pod scheduling, liveness, service discovery, storage, and RBAC.</p>
<p>The AI OS layer expresses objects like HiveRun, HiveFlow, HiveStepLease, and HiveNode through CustomResources (CRDs), with Operators implementing the reconciliation logic.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-suggested-evolution-path">A Suggested Evolution Path<a href="https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#a-suggested-evolution-path" class="hash-link" aria-label="Direct link to A Suggested Evolution Path" title="Direct link to A Suggested Evolution Path" translate="no">​</a></h2>
<p>Start small:</p>
<ol>
<li class=""><strong>Phase 1</strong>: on a single node, build out the run ledger, flow graph, and side-effect visualization</li>
<li class=""><strong>Phase 2</strong>: implement the single-cluster step scheduler and node management</li>
<li class=""><strong>Phase 3</strong>: add lightweight role recommendation</li>
<li class=""><strong>Phase 4</strong>: implement adaptive cluster behavior</li>
</ol>
<p>The first goal is not full autonomy, but a <strong>single-node AI runtime with first-class observability</strong>. Make execution visible first, then make side effects governable, and only then tackle cross-node scheduling.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2026/04/03/dang-ai-xue-hui-gan-zhi-zi-ji#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The goal of the Kubernetes AI OS is to make Kubernetes the true operating-system foundation of the AI era: unlocking multi-agent collaboration while staying safe and controllable. It doesn't reinvent the wheel — it stitches together OpenClaw's southbound integrations, golutra-style execution traces, and Kubernetes's scheduling and recovery. The entire design revolves around a single thread: execution must be visible, inspectable, and attributable. And you don't have to get there in one leap — start with a single-node execution ledger, then move step by step toward cross-node scheduling and adaptive clusters.</p>]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[The Architecture of OpenClaw]]></title>
            <link>https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji</link>
            <guid>https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji</guid>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[OpenClaw fully decouples the reasoning power of large models from the local execution environment. This post breaks down the open-source AI agent tool across three dimensions: overall architecture, runtime logic, and security mechanisms.]]></description>
            <content:encoded><![CDATA[<p>Most AI applications are still stuck at the surface level of "chat interaction" — the user types a question, the model returns text, but it never actually reaches the local device or executes real tasks. OpenClaw (formerly Clawdbot/Moltbot/Molty) breaks through this limitation: it fully decouples the model's "reasoning" from the local execution environment, upgrading AI from a "conversational assistant" to an "agent that can act autonomously." This post breaks down this TypeScript/Node.js open-source tool by Peter Steinberger across three dimensions: overall architecture, runtime logic, and design highlights.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overall-architecture-a-hub-and-spoke-layered-os-grade-framework">Overall Architecture: A Hub-and-Spoke, Layered "OS-Grade" Framework<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#overall-architecture-a-hub-and-spoke-layered-os-grade-framework" class="hash-link" aria-label="Direct link to Overall Architecture: A Hub-and-Spoke, Layered &quot;OS-Grade&quot; Framework" title="Direct link to Overall Architecture: A Hub-and-Spoke, Layered &quot;OS-Grade&quot; Framework" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2026-04-Gemini_Generated_Image_iy2svaiy2svaiy2s-1-f3d52510266b418bdf06cabe1bb30c7a.webp" width="1408" height="768" class="img_ev3q"></p>
<p>OpenClaw's core design philosophy is to "treat AI as an infrastructure problem, not a prompt-engineering problem" — the model only reasons, while the system owns state management, execution control, security, and multi-channel coordination. The architecture combines a <strong>hub-and-spoke</strong> topology with <strong>layered decoupling</strong>, keeping the core logic centralized and controllable while leaving individual components flexible.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hub-and-spoke-the-gateway-as-the-single-control-plane">Hub-and-Spoke: the Gateway as the Single Control Plane<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#hub-and-spoke-the-gateway-as-the-single-control-plane" class="hash-link" aria-label="Direct link to Hub-and-Spoke: the Gateway as the Single Control Plane" title="Direct link to Hub-and-Spoke: the Gateway as the Single Control Plane" translate="no">​</a></h3>
<p>OpenClaw is centered on the <strong>Gateway</strong>, with every component — clients, channel adapters, agent executors — radiating out from it. The Gateway is the system's "traffic hub." It binds to the local loopback address (127.0.0.1:18789) by default, allowing access only from the local device and protecting data privacy at the source. Its core responsibilities include:</p>
<ul>
<li class=""><strong>Unified authentication and session isolation</strong>: sessions from different users or devices are managed independently, preventing data from leaking across sessions;</li>
<li class=""><strong>Lane Queue</strong>: each session executes tasks serially by default, eliminating state conflicts and interleaved logs from concurrent tasks (parallel mode can be enabled manually);</li>
<li class=""><strong>Message routing and dispatch</strong>: forwarding normalized messages precisely to the corresponding agent;</li>
<li class=""><strong>Streaming interaction</strong>: pushing model output and "typing" status in real time to simulate a natural conversation.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="layered-architecture-capabilities-unpacked-from-the-outside-in">Layered Architecture: Capabilities Unpacked from the Outside In<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#layered-architecture-capabilities-unpacked-from-the-outside-in" class="hash-link" aria-label="Direct link to Layered Architecture: Capabilities Unpacked from the Outside In" title="Direct link to Layered Architecture: Capabilities Unpacked from the Outside In" translate="no">​</a></h3>
<p>OpenClaw's layers progress from user interaction down to infrastructure, each with clear responsibilities and independently extensible.</p>
<ol>
<li class=""><strong>Client layer: multiple entry points</strong></li>
</ol>
<p>Users don't need a dedicated app — they can interact with OpenClaw through:</p>
<ul>
<li class="">The command-line tool (CLI): great for technical users and quick debugging;</li>
<li class="">The Web UI: a visual control panel;</li>
<li class="">The macOS menu bar app and iOS/Android Nodes: lightweight mobile entry points;</li>
<li class="">Third-party chat platforms: 20+ popular apps including WhatsApp, Telegram, and Discord, so users can issue commands from tools they already know.</li>
</ul>
<ol start="2">
<li class=""><strong>Access coordination layer: the Gateway's "nerve center"</strong></li>
</ol>
<p>As described above, the Gateway is the core of access coordination — it converts heterogeneous input into a format the system understands and manages session lifecycles.</p>
<ol start="3">
<li class=""><strong>Channel abstraction layer: breaking through platform protocol barriers</strong></li>
</ol>
<p>Message protocols differ wildly across chat platforms (WhatsApp's Baileys protocol, Telegram's MTProto protocol). <strong>Channel Adapters</strong> play the role of "translator": they normalize each platform's messages (text, images, voice, etc.) into OpenClaw's internal standard format, and handle attachment download, caching, and access control (such as "only allow designated contacts to send commands").</p>
<ol start="4">
<li class=""><strong>Core logic layer: the agent's "thinking and execution engine"</strong></li>
</ol>
<p>The core logic layer consists of the <strong>Agent Runner</strong> and the <strong>Memory System</strong> — the heart of an AI agent that gets things done:</p>
<ul>
<li class=""><strong>Agent Runner</strong>: each channel or group can map to an independent agent instance (multi-agent collaboration supported), invoking the Pi Agent Runtime via RPC to complete the full loop of "receive instruction → call model → execute tools → produce result";</li>
<li class=""><strong>Memory System</strong>: owns context management, including session history, long-term user memory, and tool capability descriptions, providing the model with what it needs to decide.</li>
</ul>
<ol start="5">
<li class=""><strong>Infrastructure layer: the foundation for local execution</strong></li>
</ol>
<p>The infrastructure layer provides the base capabilities the system runs on:</p>
<ul>
<li class="">Local persistence: all data stored as Markdown and .jsonl files (no cloud database dependency);</li>
<li class="">WebSocket streaming: keeping the real-time connection between clients and the Gateway;</li>
<li class="">Sandboxed execution: restricting tools' system permissions to prevent malicious operations;</li>
<li class="">Cron and Webhooks: proactively triggered tasks (such as a daily morning report).</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-the-core-components-work-together">How the Core Components Work Together<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#how-the-core-components-work-together" class="hash-link" aria-label="Direct link to How the Core Components Work Together" title="Direct link to How the Core Components Work Together" translate="no">​</a></h3>
<p>To make the architecture easier to grasp, the relationships between core components can be simplified as:</p>
<ol>
<li class="">The <strong>Gateway</strong> acts as the dispatch center, receiving messages from clients or channel adapters and assigning them to the right agent;</li>
<li class="">The <strong>Agent Runner</strong> follows the Gateway's instructions, calls the model to generate decisions, and executes tools (reading/writing files, driving the browser);</li>
<li class=""><strong>Channel Adapters</strong> "translate" messages from third-party platforms so users can interact without switching tools;</li>
<li class=""><strong>Skills</strong>: plugins stored under <code>~/.openclaw/workspace/skills</code>, which the agent can discover, install, and invoke on its own (skills can be shared via the ClawHub registry);</li>
<li class=""><strong>Nodes</strong>: device-side executors running on macOS, iOS, etc., exposing local hardware capabilities (voice input, camera, screen recording) over WebSocket;</li>
<li class=""><strong>Canvas</strong>: a visual workspace served on a dedicated port (18793), where the agent can generate HTML/A2UI interfaces (task progress bars, data dashboards).</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="runtime-logic-the-agent-loop-and-proactive-scheduling">Runtime Logic: the Agent Loop and Proactive Scheduling<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#runtime-logic-the-agent-loop-and-proactive-scheduling" class="hash-link" aria-label="Direct link to Runtime Logic: the Agent Loop and Proactive Scheduling" title="Direct link to Runtime Logic: the Agent Loop and Proactive Scheduling" translate="no">​</a></h2>
<p>OpenClaw's core value is "letting AI actually do things," powered by a <strong>repeatable Agent Loop</strong> and a <strong>proactive scheduling mechanism</strong> — the former handles user-triggered tasks, the latter enables unattended autonomous operation.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-typical-message-flow-from-input-to-output">The Typical Message Flow: from Input to Output<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#the-typical-message-flow-from-input-to-output" class="hash-link" aria-label="Direct link to The Typical Message Flow: from Input to Output" title="Direct link to The Typical Message Flow: from Input to Output" translate="no">​</a></h3>
<p>Here is the full journey of a user instruction such as "organize the files on my desktop from the last 3 days and generate a list."</p>
<ol>
<li class=""><strong>Message intake and normalization</strong></li>
</ol>
<p>The user sends the instruction via Telegram → the Channel Adapter converts the Telegram message into the system's standard format (text content, sender info, context) → the normalized message is placed into the corresponding Session Lane Queue in the Gateway (serial execution, no conflicts).</p>
<ol start="2">
<li class=""><strong>Routing and agent activation</strong></li>
</ol>
<p>Based on configuration (e.g. "the main session binds to the default agent," "non-main sessions require an @mention"), the Gateway routes the message to the target agent. For example, when the user @mentions OpenClaw in a family group, the Gateway activates the "family assistant" agent.</p>
<ol start="3">
<li class=""><strong>Context assembly: making sure the agent "remembers" what matters</strong></li>
</ol>
<p>The Memory System automatically assembles the following as the model's input context:</p>
<ul>
<li class="">The current session's message history (so the agent doesn't "forget");</li>
<li class="">Long-term user memory (e.g. "the user likes organizing files in Markdown," "the desktop path is ~/Desktop");</li>
<li class="">System prompts: AGENTS.md (the agent's behavioral baseline, e.g. "prefer local tools"), SOUL.md (personality, e.g. "concise and efficient"), TOOLS.md (tool capability descriptions, e.g. "file.read can read local files");</li>
<li class="">Installed Skills (e.g. a "file classification" skill).</li>
</ul>
<p>If the context grows too long, the system automatically compresses and summarizes it to keep the model's input within the context window.</p>
<ol start="4">
<li class=""><strong>Model invocation and decision generation</strong></li>
</ol>
<p>The Agent Runner calls the configured LLM (Claude, GPT, or a local model like Llama 3), and the model decides based on the context: does this need a tool?</p>
<ul>
<li class="">If not: it generates a natural-language reply directly (e.g. "Done — the list has been saved to your desktop");</li>
<li class="">If so: it generates a tool call (e.g. <code>{"name": "file.list", "parameters": {"path": "~/Desktop", "days": 3}}</code>).</li>
</ul>
<ol start="5">
<li class=""><strong>Tool execution: looping until the task is done</strong></li>
</ol>
<p>Tool execution runs as a <strong>loop</strong>:</p>
<ol>
<li class="">The Agent Runner executes the tool in a sandbox (the main session has host permissions by default; non-main sessions can be forced into Docker sandbox isolation);</li>
<li class="">The tool returns its result (e.g. "found 5 files: report.pdf, photo.jpg...");</li>
<li class="">The result is written back into the context, and the Agent Runner calls the model again to decide whether further action is needed (e.g. "should the list be converted to Markdown?");</li>
<li class="">The steps repeat until the model judges the task complete.</li>
</ol>
<ol start="6">
<li class=""><strong>Output and persistence</strong></li>
</ol>
<p>The final result is streamed back to the originating channel (e.g. Telegram) via the Gateway, while all interaction records, tool execution logs, and memory updates are written to local .jsonl and Markdown files — fully traceable, no cloud required.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="proactive-operation-where-the-ais-initiative-comes-from">Proactive Operation: Where the AI's "Initiative" Comes From<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#proactive-operation-where-the-ais-initiative-comes-from" class="hash-link" aria-label="Direct link to Proactive Operation: Where the AI's &quot;Initiative&quot; Comes From" title="Direct link to Proactive Operation: Where the AI's &quot;Initiative&quot; Comes From" translate="no">​</a></h3>
<p>Traditional chatbots wait for the user. OpenClaw supports <strong>proactive operation</strong>, letting the AI complete tasks without being asked. There are three mechanisms.</p>
<ol>
<li class=""><strong>Scheduled tasks (Heartbeat Scheduler/Cron)</strong></li>
</ol>
<p>Users can configure recurring tasks, such as "every morning at 8, organize yesterday's email and produce a summary" or "every Sunday, back up the desktop to the external drive." The Gateway wakes the agent on schedule, and results are automatically delivered to the designated channel (e.g. the user's WhatsApp).</p>
<ol start="2">
<li class=""><strong>External event triggers (Webhook/Pub/Sub)</strong></li>
</ol>
<p>OpenClaw can receive external events via Webhooks — a new Gmail message, a GitHub issue being created, a smart-home device firing. For example, when an important work email arrives, OpenClaw can automatically extract the key information and alert the user.</p>
<ol start="3">
<li class=""><strong>Multi-agent collaboration</strong></li>
</ol>
<p>Agents can collaborate through built-in tools (<code>sessions_list</code> to see all sessions, <code>sessions_send</code> to message another agent). For instance, a "calendar agent" that notices a meeting tomorrow can ask the "file organizer agent" to prepare the meeting materials in advance.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-it-running-deployment-in-a-few-steps">Getting It Running: Deployment in a Few Steps<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#getting-it-running-deployment-in-a-few-steps" class="hash-link" aria-label="Direct link to Getting It Running: Deployment in a Few Steps" title="Direct link to Getting It Running: Deployment in a Few Steps" translate="no">​</a></h3>
<p>OpenClaw's deployment is lightweight enough for everyday users to get started quickly:</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%)"># Install the latest version globally</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Onboarding: guided setup of chat channels, LLM model, and workspace 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">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%)"># Run persistently: register the Gateway as a system daemon for 24/7 operation</span><span class="token plain"></span><br></div><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>Once done, connect to the Gateway via the CLI, Web UI, or a chat app and start using it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-design-highlights-and-security-local-first-privacy">Key Design Highlights and Security: Local-First Privacy<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#key-design-highlights-and-security-local-first-privacy" class="hash-link" aria-label="Direct link to Key Design Highlights and Security: Local-First Privacy" title="Direct link to Key Design Highlights and Security: Local-First Privacy" translate="no">​</a></h2>
<p>OpenClaw's design revolves around user control and privacy. Here are its core highlights.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="local-first-your-data-stays-with-you">Local-First: Your Data Stays with You<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#local-first-your-data-stays-with-you" class="hash-link" aria-label="Direct link to Local-First: Your Data Stays with You" title="Direct link to Local-First: Your Data Stays with You" translate="no">​</a></h3>
<p>All data — session history, memory, tool execution logs — is stored on the local device. The Gateway listens only on 127.0.0.1 (the local loopback address) by default and never sends data to the cloud. Even offline, OpenClaw can still execute local tasks (file operations, local model calls).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="security-isolation-layered-protection">Security Isolation: Layered Protection<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#security-isolation-layered-protection" class="hash-link" aria-label="Direct link to Security Isolation: Layered Protection" title="Direct link to Security Isolation: Layered Protection" translate="no">​</a></h3>
<p>OpenClaw protects the system through multiple mechanisms:</p>
<ul>
<li class=""><strong>DM pairing approval</strong>: direct-message commands from third-party chat platforms require manual user approval (e.g. after a Telegram user sends <code>/pair</code>, OpenClaw sends a verification code, and interaction is only allowed after verification);</li>
<li class=""><strong>Sandboxed execution</strong>: tool execution in non-main sessions defaults to a Docker sandbox, restricting access to the host system;</li>
<li class=""><strong>Tool blocklists</strong>: users can forbid the agent from calling certain dangerous tools (such as <code>system.exec</code> for running system commands);</li>
<li class=""><strong>Explicit control</strong>: commands like <code>/think high</code> (deepen model reasoning) and <code>/verbose on</code> (show tool execution details) let users fine-tune agent behavior.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="extensibility-a-plugin-architecture-for-customization">Extensibility: a Plugin Architecture for Customization<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#extensibility-a-plugin-architecture-for-customization" class="hash-link" aria-label="Direct link to Extensibility: a Plugin Architecture for Customization" title="Direct link to Extensibility: a Plugin Architecture for Customization" translate="no">​</a></h3>
<p>Every core component of OpenClaw is hot-swappable:</p>
<ul>
<li class=""><strong>Channels</strong>: build your own Channel Adapter (WeChat, Signal, etc.);</li>
<li class=""><strong>Models</strong>: plug in any LLM (just implement the standard interface);</li>
<li class=""><strong>Skills</strong>: write custom Skills ("auto-sync Obsidian notes," "generate a slide outline") — the agent reads the Skill's contract file and invokes it on its own;</li>
<li class=""><strong>Memory</strong>: swap the Memory System for a vector database (such as Pinecone) to improve long-term memory retrieval.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-memory-system-four-layers-that-make-the-agent-smarter-over-time">The Memory System: Four Layers That Make the Agent Smarter Over Time<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#the-memory-system-four-layers-that-make-the-agent-smarter-over-time" class="hash-link" aria-label="Direct link to The Memory System: Four Layers That Make the Agent Smarter Over Time" title="Direct link to The Memory System: Four Layers That Make the Agent Smarter Over Time" translate="no">​</a></h3>
<p>OpenClaw's memory system has four layers, ensuring the agent uses historical information efficiently:</p>
<ol>
<li class=""><strong>SOUL layer</strong>: the agent's personality and values (e.g. "protect user privacy first," "answer concisely");</li>
<li class=""><strong>TOOLS layer</strong>: descriptions of what tools can do and how to use them;</li>
<li class=""><strong>USER vector memory</strong>: the user's long-term preferences (e.g. "prefers coffee over tea," "commonly used file paths"), stored as vectors for fast retrieval;</li>
<li class=""><strong>Session short-term memory</strong>: the current session's message history, automatically compressed to avoid redundancy.</li>
</ol>
<p>The system periodically runs memory "compaction," keeping the essentials and dropping duplicates so the agent's memory stays efficient.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2026/04/02/openclaw-jia-gou-she-ji#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>OpenClaw answers the question of "how AI actually gets things done" with an OS-grade architecture: the Gateway serves as the single control plane for unified dispatch, and the layered design makes channels, models, and skills independently replaceable. The Agent Loop's multi-round tool cycle, combined with proactive mechanisms like cron and Webhooks, lets the AI both respond to commands and run unattended. Meanwhile, local-first storage, sandbox isolation, and pairing approval draw a clear line between capability and safety. For anyone looking to wire a large model into real workflows, there is plenty in this design worth borrowing.</p>]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[The Rise of Cursor]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan</link>
            <guid>https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Tracing Cursor's trajectory: from traditional IDEs and code-completion tools to the birth of this AI-native editor, its core technical design, its growth, and the challenges it faces.]]></description>
            <content:encoded><![CDATA[<p>Cursor is probably one of the fastest-growing developer tools of the past two years. This post traces its trajectory: starting from traditional IDEs, looking at what this AI-native editor actually did—and what problems it faces.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background-ides-as-programming-tools">Background: IDEs as Programming Tools<a href="https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan#background-ides-as-programming-tools" class="hash-link" aria-label="Direct link to Background: IDEs as Programming Tools" title="Direct link to Background: IDEs as Programming Tools" translate="no">​</a></h2>
<p>For a long time, software development relied mainly on traditional IDEs like Visual Studio Code, IntelliJ IDEA, and Eclipse. Their core capabilities were code editing, syntax highlighting, autocompletion, and debugging tools—but the actual code logic still had to be <strong>written by the programmer</strong>.</p>
<p>It wasn't until <strong>large language models (LLMs) arrived</strong> that software development entered the <strong>era of AI programming</strong>. The earliest wave of AI coding tools included GitHub Copilot and Tabnine, which mainly tackled <strong>code completion</strong>. Cursor's goal, however, was never simple completion. It was:</p>
<blockquote>
<p><strong>Build an AI-native development environment.</strong></p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-birth-of-cursor">The Birth of Cursor<a href="https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan#the-birth-of-cursor" class="hash-link" aria-label="Direct link to The Birth of Cursor" title="Direct link to The Birth of Cursor" translate="no">​</a></h2>
<p>Cursor is an <strong>AI-native code editor (AI-Native IDE)</strong> developed by <strong>Anysphere</strong>. The company was founded in <strong>2022</strong> by four MIT graduates:</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 was <strong>officially released in 2023</strong>. Technically, it is:</p>
<blockquote>
<p><strong>An AI IDE built as a fork of VS Code</strong></p>
</blockquote>
<p>On top of the VS Code foundation it added a large set of AI features: AI code generation, AI debugging, AI refactoring, AI codebase queries. Cursor's goal was crystal clear:</p>
<blockquote>
<p><strong>Let developers write code directly through natural language.</strong></p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cursors-core-technical-design">Cursor's Core Technical Design<a href="https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan#cursors-core-technical-design" class="hash-link" aria-label="Direct link to Cursor's Core Technical Design" title="Direct link to Cursor's Core Technical Design" translate="no">​</a></h2>
<p>Cursor's design philosophy centers on three key points.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-ai-code-generation">1) AI Code Generation<a href="https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan#1-ai-code-generation" class="hash-link" aria-label="Direct link to 1) AI Code Generation" title="Direct link to 1) AI Code Generation" translate="no">​</a></h3>
<p>Cursor generates code through large language models. A developer can simply type "implement a Redis distributed lock," and Cursor will generate the code, comments, and tests. This is <strong>completely different from traditional IDE code completion</strong>:</p>
<ul>
<li class="">Traditional IDE: the developer writes code, the IDE assists with completion</li>
<li class="">Cursor: the developer describes the requirement, the AI generates the code</li>
</ul>
<p>This mode of working has come to be known as <strong>Vibe Coding</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-codebase-understanding">2) Codebase Understanding<a href="https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan#2-codebase-understanding" class="hash-link" aria-label="Direct link to 2) Codebase Understanding" title="Direct link to 2) Codebase Understanding" translate="no">​</a></h3>
<p>One of Cursor's core capabilities is <strong>understanding the entire code repository</strong>. It builds vector indexes of the code, along with file relationships and dependencies, so a developer can ask "where is the user login logic in this project?" and Cursor will locate the relevant code. This is similar to a RAG + code embedding approach.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-ai-code-modification">3) AI Code Modification<a href="https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan#3-ai-code-modification" class="hash-link" aria-label="Direct link to 3) AI Code Modification" title="Direct link to 3) AI Code Modification" translate="no">​</a></h3>
<p>Cursor supports a capability called <strong>Smart Rewrite</strong>. For example, given "make this module asynchronous," Cursor can automatically modify multiple files, adjust functions, and update call sites. This is nearly impossible in a traditional IDE.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cursors-rapid-growth">Cursor's Rapid Growth<a href="https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan#cursors-rapid-growth" class="hash-link" aria-label="Direct link to Cursor's Rapid Growth" title="Direct link to Cursor's Rapid Growth" translate="no">​</a></h2>
<p>Cursor's growth has been astonishing. Less than two years after release:</p>
<ul>
<li class="">ARR exceeded <strong>$300 million</strong></li>
<li class="">Funding raised exceeded <strong>$1 billion</strong></li>
<li class="">Company valuation approached <strong>$30 billion</strong></li>
</ul>
<p>Cursor became <strong>one of the fastest-growing AI SaaS products</strong> ever. Many companies started using it—Nvidia, Stripe, Spotify—with Nvidia having <strong>over 30,000 engineers writing code with Cursor</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cursors-technical-challenges">Cursor's Technical Challenges<a href="https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan#cursors-technical-challenges" class="hash-link" aria-label="Direct link to Cursor's Technical Challenges" title="Direct link to Cursor's Technical Challenges" translate="no">​</a></h2>
<p>Despite Cursor's popularity, academic research has pointed out some issues: AI coding assistants speed up development, but may <strong>increase code complexity and the number of warnings</strong>.</p>
<p>One research paper noted:</p>
<blockquote>
<p>After adopting AI coding assistants, project velocity improves in the short term, but code complexity rises as well.</p>
</blockquote>
<p>Another study found:</p>
<blockquote>
<p>AI code assistants are easily influenced by faulty code in their context, leading them to generate defective code.</p>
</blockquote>
<p>Which is to say, AI programming still requires <strong>developers to review and stay in control</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cursors-future-direction">Cursor's Future Direction<a href="https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan#cursors-future-direction" class="hash-link" aria-label="Direct link to Cursor's Future Direction" title="Direct link to Cursor's Future Direction" translate="no">​</a></h2>
<p>Cursor's vision is aggressive: software development may eventually become "natural language → software system." The development workflow could turn into:</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">Requirements</span><br></div><div 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 generates 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">↓</span><br></div><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 generates tests</span><br></div><div 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 deploys automatically</span><br></div></code></pre></div></div>
<p>In other words, <strong>software development may shift from "writing code" to "describing requirements."</strong> Cursor has even articulated a vision:</p>
<blockquote>
<p>One day, developers will only need to write 50 lines of pseudocode to generate 2,000 lines of complete code.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2026/03/15/cursor-de-fa-zhan#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>By forking VS Code, Cursor quickly acquired a mature editor foundation, then built LLM capabilities into code generation, repository understanding, and cross-file modification—hitting the AI programming boom at exactly the right moment. The staggering growth numbers show the approach works, but the research is a reminder that AI-generated code brings new problems like rising complexity. No matter how powerful the tool, the developer remains ultimately responsible for code quality. That's unlikely to change for the foreseeable future.</p>]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[PolarDB's Storage-Compute Separation]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou</link>
            <guid>https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[An architectural look at PolarDB's storage-compute separation: how decoupling compute from storage addresses scalability, storage cost, and high availability, compared with AWS Aurora and traditional MySQL architecture.]]></description>
            <content:encoded><![CDATA[<p>As businesses scale, traditional relational database architectures gradually expose bottlenecks: limited scalability, high storage costs, and concentrated read/write pressure. Cloud vendors designed a new architectural pattern to address this: <strong>storage-compute decoupling</strong>. This post analyzes PolarDB's storage-compute separation from an architectural perspective and compares it with AWS Aurora and the traditional MySQL architecture.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>PolarDB is a cloud-native database from Alibaba Cloud, and one of its core design principles is <strong>decoupling the compute layer from the storage layer</strong>. This architecture gives the database stronger elastic scalability and higher resource utilization.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2026-04-Gemini_Generated_Image_lr15jglr15jglr15-aaa1df73e131dae3dab907117c43cd3a.webp" width="1408" height="768" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="problems-with-the-traditional-mysql-architecture">Problems with the Traditional MySQL Architecture<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#problems-with-the-traditional-mysql-architecture" class="hash-link" aria-label="Direct link to Problems with the Traditional MySQL Architecture" title="Direct link to Problems with the Traditional MySQL Architecture" translate="no">​</a></h2>
<p>In a traditional MySQL setup, the database typically runs on a single server, with compute and storage on the same machine:</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      # data and compute bound to the same machine</span><br></div></code></pre></div></div>
<p>This architecture was good enough in the early internet era, but as businesses grow, several obvious problems emerge.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-storage-is-hard-to-scale">1) Storage Is Hard to Scale<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#1-storage-is-hard-to-scale" class="hash-link" aria-label="Direct link to 1) Storage Is Hard to Scale" title="Direct link to 1) Storage Is Hard to Scale" translate="no">​</a></h3>
<p>Database data usually lives on local disks. When data volume grows, the only options are upgrading the disk or moving to a bigger machine—scalability is limited.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-read-scaling-is-expensive">2) Read Scaling Is Expensive<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#2-read-scaling-is-expensive" class="hash-link" aria-label="Direct link to 2) Read Scaling Is Expensive" title="Direct link to 2) Read Scaling Is Expensive" translate="no">​</a></h3>
<p>The common way to scale MySQL is <strong>primary-replica replication</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      # each replica holds a full copy of the data</span><br></div><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>Every read-only node needs a complete copy of the data. As read nodes multiply, storage costs grow rapidly, and replication pressure rises with them.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-high-availability-is-complicated">3) High Availability Is Complicated<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#3-high-availability-is-complicated" class="hash-link" aria-label="Direct link to 3) High Availability Is Complicated" title="Direct link to 3) High Availability Is Complicated" translate="no">​</a></h3>
<p>If the primary node fails, a failover—manual or automatic—is required. Replication lag may exist during the switch, and data consistency needs extra handling.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="polardbs-storage-compute-separated-architecture">PolarDB's Storage-Compute Separated Architecture<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#polardbs-storage-compute-separated-architecture" class="hash-link" aria-label="Direct link to PolarDB's Storage-Compute Separated Architecture" title="Direct link to PolarDB's Storage-Compute Separated Architecture" translate="no">​</a></h2>
<p>PolarDB's core design: <strong>compute nodes and storage nodes are fully decoupled</strong>. The architecture looks roughly like this:</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) |   # compute layer: SQL parsing, execution, transactions</span><br></div><div 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|   # storage layer: data, logs, replicas</span><br></div><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>Compute nodes handle SQL parsing, query execution, and transaction processing; the storage layer handles data storage, log management, and replica replication. All compute nodes share a single distributed storage system—an approach known as <strong>Shared Storage Architecture</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-storage-compute-separation-solves">What Storage-Compute Separation Solves<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#what-storage-compute-separation-solves" class="hash-link" aria-label="Direct link to What Storage-Compute Separation Solves" title="Direct link to What Storage-Compute Separation Solves" translate="no">​</a></h2>
<p>This design addresses several core problems of traditional databases.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-elastic-scalability">1) Elastic Scalability<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#1-elastic-scalability" class="hash-link" aria-label="Direct link to 1) Elastic Scalability" title="Direct link to 1) Elastic Scalability" translate="no">​</a></h3>
<p>With a traditional database, adding compute capacity means upgrading the server. In PolarDB, you simply add compute nodes:</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      # new compute nodes need no data copy</span><br></div><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>All nodes share the same data, enabling read scaling, distributed computing, and scale-out in seconds.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-lower-storage-costs">2) Lower Storage Costs<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#2-lower-storage-costs" class="hash-link" aria-label="Direct link to 2) Lower Storage Costs" title="Direct link to 2) Lower Storage Costs" translate="no">​</a></h3>
<p>Traditional MySQL read nodes each need a full data copy. Under storage-compute separation, multiple compute nodes share one storage system—no full copies needed—cutting storage costs significantly.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-stronger-high-availability">3) Stronger High Availability<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#3-stronger-high-availability" class="hash-link" aria-label="Direct link to 3) Stronger High Availability" title="Direct link to 3) Stronger High Availability" translate="no">​</a></h3>
<p>PolarDB's storage layer typically uses a distributed replica mechanism:</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      # multiple replicas guarantee durability</span><br></div><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>When a node fails, the system can recover extremely quickly. This design enables automatic failure recovery, a highly available architecture, and durability guarantees.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-extreme-performance">4) Extreme Performance<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#4-extreme-performance" class="hash-link" aria-label="Direct link to 4) Extreme Performance" title="Direct link to 4) Extreme Performance" translate="no">​</a></h3>
<p>PolarDB's storage layer typically uses distributed log structures, parallel I/O, and SSD cloud storage, which dramatically improves database performance. The main bottleneck shifts from the storage layer to internal network I/O.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="polardbs-drawbacks">PolarDB's Drawbacks<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#polardbs-drawbacks" class="hash-link" aria-label="Direct link to PolarDB's Drawbacks" title="Direct link to PolarDB's Drawbacks" translate="no">​</a></h2>
<p>While storage-compute separation has many advantages, it also comes with challenges.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-network-latency">1) Network Latency<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#1-network-latency" class="hash-link" aria-label="Direct link to 1) Network Latency" title="Direct link to 1) Network Latency" translate="no">​</a></h3>
<p>Separating compute from storage means data access goes over the network, adding some latency compared with local disks.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-high-architectural-complexity">2) High Architectural Complexity<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#2-high-architectural-complexity" class="hash-link" aria-label="Direct link to 2) High Architectural Complexity" title="Direct link to 2) High Architectural Complexity" translate="no">​</a></h3>
<p>A storage-compute separated architecture requires designing a distributed storage system, network protocols, and data consistency mechanisms—far harder to build than a traditional database.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-heavy-dependence-on-cloud-infrastructure">3) Heavy Dependence on Cloud Infrastructure<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#3-heavy-dependence-on-cloud-infrastructure" class="hash-link" aria-label="Direct link to 3) Heavy Dependence on Cloud Infrastructure" title="Direct link to 3) Heavy Dependence on Cloud Infrastructure" translate="no">​</a></h3>
<p>This architecture depends heavily on high-performance networking, distributed storage, and cloud platform scheduling, so it generally only fits cloud environments.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="polardb-vs-aws-aurora">PolarDB vs AWS Aurora<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#polardb-vs-aws-aurora" class="hash-link" aria-label="Direct link to PolarDB vs AWS Aurora" title="Direct link to PolarDB vs AWS Aurora" translate="no">​</a></h2>
<p>PolarDB and Aurora are very close in overall design philosophy. Both are archetypal <strong>cloud-native database architectures</strong>, and both adopt <strong>compute + storage decoupling</strong>.</p>
<p>Aurora separates compute nodes from a distributed storage cluster, with multiple compute nodes sharing the same storage system. A sketch of the architecture:</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         # readers share the underlying 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><div 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's distributed storage is typically deployed across multiple Availability Zones for higher availability. In their concrete implementations, however, the two still differ in important ways.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-storage-architecture-differences">1) Storage Architecture Differences<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#1-storage-architecture-differences" class="hash-link" aria-label="Direct link to 1) Storage Architecture Differences" title="Direct link to 1) Storage Architecture Differences" translate="no">​</a></h3>
<p>Aurora's core design is <strong>log-structured storage</strong>. In Aurora, compute nodes do not write database pages directly; instead they send the <strong>redo log</strong> to the storage nodes, and the storage layer reconstructs data pages from those logs. Simplified, the flow is:</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                    # only logs are shipped, not full data pages</span><br></div><div 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 nodes reconstruct data pages</span><br></div></code></pre></div></div>
<p>Aurora's storage layer typically consists of <strong>6 replicas</strong> spread across <strong>3 Availability Zones (AZs)</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>Writes commit with just a <strong>4/6 quorum</strong>. This design brings several advantages:</p>
<ul>
<li class="">Writes only ship logs, so network traffic is smaller</li>
<li class="">No need to fully replicate database pages</li>
<li class="">Faster failure recovery</li>
</ul>
<p>PolarDB's storage implementation is somewhat different. PolarDB uses a <strong>shared distributed storage</strong> architecture: compute nodes access the underlying <strong>PolarStore / PolarFS distributed file system</strong> directly, and database pages are still stored as pages. Simplified structure:</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        # distributed file system, page-based 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><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>This design stays closer to how traditional databases store data, so the data page format remains compatible and the changes to the database kernel are relatively small. On the write path, however, the amount of data going over the network can be larger than with Aurora's log-shipping approach.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-read-node-architecture">2) Read Node Architecture<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#2-read-node-architecture" class="hash-link" aria-label="Direct link to 2) Read Node Architecture" title="Direct link to 2) Read Node Architecture" translate="no">​</a></h3>
<p>Aurora's read nodes are <strong>Reader Instances</strong> that generally handle reads only, with writes concentrated on the <strong>Writer Node</strong>. PolarDB supports a <strong>Reader Node + primary node with shared storage</strong> model, where multiple nodes access the shared storage directly, enabling more flexible read scaling.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-ecosystem-and-compatibility-strategy">3) Ecosystem and Compatibility Strategy<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#3-ecosystem-and-compatibility-strategy" class="hash-link" aria-label="Direct link to 3) Ecosystem and Compatibility Strategy" title="Direct link to 3) Ecosystem and Compatibility Strategy" translate="no">​</a></h3>
<p>Aurora is a database engine deeply reworked by AWS; although compatible with MySQL / PostgreSQL, its internals have changed substantially. PolarDB places more emphasis on <strong>native database compatibility</strong>:</p>
<ul>
<li class="">PolarDB MySQL</li>
<li class="">PolarDB PostgreSQL</li>
<li class="">PolarDB Oracle</li>
</ul>
<p>As a result, migrating applications to PolarDB often costs less in changes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2026/03/15/polardb-du-xie-fen-chi-jia-gou#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The essence of storage-compute separation is splitting a database's compute and storage into two layers that scale independently. Through shared distributed storage, PolarDB gains scale-out in seconds, low-cost read scaling, and stronger high availability—at the price of network latency and higher architectural complexity. Compared with Aurora, the two share a philosophy, but PolarDB takes the shared file system route, staying closer to traditional database storage with better compatibility, while Aurora uses log-structured storage to minimize write traffic. Understanding the trade-offs between these two routes is genuinely useful when evaluating cloud database options.</p>]]></content:encoded>
            <category>Database</category>
            <category>Tech</category>
            <category>Linux</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Lessons from Managing an Engineering Team]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/15/tuan-dui-guan-li-jing-yan</link>
            <guid>https://tanzhuo.xyz/en/2026/03/15/tuan-dui-guan-li-jing-yan</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Some plain-spoken lessons from a few years of leading engineering teams: think things through first, make ownership clear, let the team grow by letting go, use process to reduce problems—and why team atmosphere and a long-term view matter just as much.]]></description>
            <content:encoded><![CDATA[<p>A lot of people assume managing an engineering team is some deep and complicated discipline, but after leading a team myself I slowly realized that much of it is simpler than it looks. At its core, engineering management isn't about "managing people"—it's about <strong>enabling the team to deliver reliably</strong>.</p>
<!-- -->
<p>When I first started leading a team, I took some wrong turns. Early on, I believed that being technically strong meant I could solve most problems by doing them myself. But over time I found that once a team grows, one person can't carry it. What a tech lead really needs to do is raise the capability of the whole team—not become the busiest person on it. Over these years of engineering management, I've gradually distilled a few lessons that are simple but important.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="think-it-through-first-then-let-the-team-build">Think It Through First, Then Let the Team Build<a href="https://tanzhuo.xyz/en/2026/03/15/tuan-dui-guan-li-jing-yan#think-it-through-first-then-let-the-team-build" class="hash-link" aria-label="Direct link to Think It Through First, Then Let the Team Build" title="Direct link to Think It Through First, Then Let the Team Build" translate="no">​</a></h2>
<p>When engineering teams are inefficient, it's usually not because the engineers aren't capable—it's because the problem itself wasn't thought through. I've been there: a requirement lands, everyone starts coding immediately, and halfway through development we discover the design doesn't hold up and the work has to be redone.</p>
<p>Over time I built a habit: before a project starts, make sure the <strong>design approach and overall plan are thought through</strong>. What features the system needs, roughly how it should be broken apart, how the core modules should be designed, where the bottlenecks might be. If those things aren't clear, the harder the team works, the further off course it may drift.</p>
<p>So now the first thing I do is this: <strong>understand the problem clearly, then assign the work</strong>. It's not just more efficient—it also eliminates a lot of unnecessary rework.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ownership-must-be-clear">Ownership Must Be Clear<a href="https://tanzhuo.xyz/en/2026/03/15/tuan-dui-guan-li-jing-yan#ownership-must-be-clear" class="hash-link" aria-label="Direct link to Ownership Must Be Clear" title="Direct link to Ownership Must Be Clear" translate="no">​</a></h2>
<p>One of the most common problems in engineering teams is unclear responsibility. Many systems look like everyone is working on them, but when something actually breaks, nobody knows who to go to.</p>
<p>I gradually realized that for a team to run smoothly, responsibilities must be clearly divided. Every system needs an owner—someone who doesn't have to write all the code, but who must understand the system's architecture, core logic, and how it's running.</p>
<p>I usually assign an Owner to every core system—a service, a module, or a foundational component. When something goes wrong, the team can find the responsible person quickly, and nobody gets to pass the buck. Once ownership is clear, team efficiency improves noticeably.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-tech-lead-shouldnt-do-everything-themselves">A Tech Lead Shouldn't Do Everything Themselves<a href="https://tanzhuo.xyz/en/2026/03/15/tuan-dui-guan-li-jing-yan#a-tech-lead-shouldnt-do-everything-themselves" class="hash-link" aria-label="Direct link to A Tech Lead Shouldn't Do Everything Themselves" title="Direct link to A Tech Lead Shouldn't Do Everything Themselves" translate="no">​</a></h2>
<p>When I first led a team, I couldn't resist writing a lot of the code myself. Doing it myself was faster, and I trusted the result. But over time it becomes clear that this approach holds the team back. If the tech lead does all the critical work personally, team members can't grow, and in the long run the team becomes ever more dependent on one person.</p>
<p>So I gradually changed my approach. Now I lean toward <strong>involving team members in design and decision-making</strong>, not just execution. In system design discussions, for example, I have everyone participate rather than deciding alone. It may be slower at first, but over the long run, the team keeps getting stronger.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="process-isnt-there-to-constrainits-there-to-make-you-faster">Process Isn't There to Constrain—It's There to Make You Faster<a href="https://tanzhuo.xyz/en/2026/03/15/tuan-dui-guan-li-jing-yan#process-isnt-there-to-constrainits-there-to-make-you-faster" class="hash-link" aria-label="Direct link to Process Isn't There to Constrain—It's There to Make You Faster" title="Direct link to Process Isn't There to Constrain—It's There to Make You Faster" translate="no">​</a></h2>
<p>Many engineers hear "process" and think "more overhead." In reality, a mature team needs basic processes. In our team, for instance, there are a few fixed stages:</p>
<ol>
<li class="">Requirements review</li>
<li class="">Technical design</li>
<li class="">Code review</li>
<li class="">Testing and verification</li>
<li class="">Release</li>
</ol>
<p>The purpose of these processes isn't added complexity—it's fewer problems. Code review catches potential bugs early, technical design prevents architectural mistakes, and the testing process reduces production incidents. Once the process stabilizes, collaboration becomes much smoother.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-tech-lead-has-to-watch-the-long-term">A Tech Lead Has to Watch the Long Term<a href="https://tanzhuo.xyz/en/2026/03/15/tuan-dui-guan-li-jing-yan#a-tech-lead-has-to-watch-the-long-term" class="hash-link" aria-label="Direct link to A Tech Lead Has to Watch the Long Term" title="Direct link to A Tech Lead Has to Watch the Long Term" translate="no">​</a></h2>
<p>Teams handling requirements every day can easily end up focused only on what's in front of them. But if technical debt goes unattended for long, the system gets harder and harder to maintain. So I usually set aside time to think about the architecture:</p>
<ul>
<li class="">Does the system have performance bottlenecks?</li>
<li class="">Does the architecture need adjusting?</li>
<li class="">Should we introduce new technical components?</li>
</ul>
<p>These things may show no results in the short term, but over the long run they have a large impact on system stability. A tech lead has to solve today's problems while also considering <strong>where the system is headed</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="team-atmosphere-really-matters">Team Atmosphere Really Matters<a href="https://tanzhuo.xyz/en/2026/03/15/tuan-dui-guan-li-jing-yan#team-atmosphere-really-matters" class="hash-link" aria-label="Direct link to Team Atmosphere Really Matters" title="Direct link to Team Atmosphere Really Matters" translate="no">​</a></h2>
<p>An engineering team's effectiveness depends not only on technical ability but also on its atmosphere. Where trust is missing between team members, everything gets harder.</p>
<p>One thing I care about is making sure people can discuss technical problems freely. Different people bring different perspectives, and those discussions often produce better solutions. I'm also not a fan of overly strict management—engineering teams work better in a relatively open environment where people can focus on doing the work well.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="engineering-management-is-its-own-kind-of-growth">Engineering Management Is Its Own Kind of Growth<a href="https://tanzhuo.xyz/en/2026/03/15/tuan-dui-guan-li-jing-yan#engineering-management-is-its-own-kind-of-growth" class="hash-link" aria-label="Direct link to Engineering Management Is Its Own Kind of Growth" title="Direct link to Engineering Management Is Its Own Kind of Growth" translate="no">​</a></h2>
<p>Going from engineer to tech lead is a big transition. Before, you only had to care about code; now you care about the team, the projects, and the system as a whole. It feels uncomfortable at first, but gradually you find that engineering management is a new kind of challenge in its own right. It takes not just technical skill, but thinking and communication skills too.</p>
<p>For me, the greatest reward of leading a team is watching team members keep growing while the system keeps improving.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2026/03/15/tuan-dui-guan-li-jing-yan#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>After these years of engineering management, my biggest takeaway is actually quite simple: managing an engineering team isn't complicated management theory—it's enabling a group of engineers to <strong>get things done well together</strong>. With clear goals, the team's effort isn't wasted; with clear ownership, someone is accountable when things break; with stable processes, collaboration doesn't depend on luck. Add mutual trust on top of that, and most things simply start to flow.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Microservice Architecture Design]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji</link>
            <guid>https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Starting from the bottlenecks of monolithic architecture, this post walks through the core design principles of microservices, the overall architecture, and key design questions — plus their scalability, adoption challenges, and where they actually fit.]]></description>
            <content:encoded><![CDATA[<p>Once a monolith grows past a certain point, splitting it up becomes almost unavoidable. This post traces where microservice architecture came from, its core design principles and overall composition — and the new costs it brings along.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-microservices-emerged">Why Microservices Emerged<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#why-microservices-emerged" class="hash-link" aria-label="Direct link to Why Microservices Emerged" title="Direct link to Why Microservices Emerged" translate="no">​</a></h2>
<p>In the early days of software, most applications used a <strong>monolithic architecture</strong>. Every functional module ran inside a single application process — the user system, order system, payment system, inventory system, and so on were all packaged into one project and deployed together. When the system is small, this is simple and direct, and development moves fast.</p>
<p>But as the business keeps growing, the problems of a monolith start to surface. The codebase balloons — a single project may contain hundreds of thousands or even millions of lines of code, and every change requires rebuilding and redeploying the entire system. Modules become tightly coupled, so a failure in one module can easily take down the whole application. And when traffic grows, you can't scale just the hot module — you have to scale the entire system, which wastes resources.</p>
<p>Against this backdrop, microservice architecture gradually became the mainstream design for large systems. The core idea is to <strong>break a massive system into multiple independent services, each owning a well-defined business capability that can be developed, deployed, and scaled independently</strong>. This reduces system complexity while improving maintainability and scalability.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-design-principles-of-microservices">Core Design Principles of Microservices<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#core-design-principles-of-microservices" class="hash-link" aria-label="Direct link to Core Design Principles of Microservices" title="Direct link to Core Design Principles of Microservices" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2026-04-Gemini_Generated_Image_qjw17xqjw17xqjw1-40a2fd0d4db35c47974e2ed1357238bd.webp" width="1408" height="768" class="img_ev3q"></p>
<p>Microservice architecture is not just about chopping a system into many small services. What matters more is designing the system around <strong>business capabilities</strong>. Each service should have a clear boundary of responsibility and be able to run on its own.</p>
<p>Microservice design typically follows a few core principles:</p>
<ul>
<li class=""><strong>Single responsibility</strong>: each microservice owns one core business domain — a user service, an order service, an inventory service, and so on. This keeps responsibilities from blurring together and makes it easier to divide work across teams.</li>
<li class=""><strong>Service autonomy</strong>: each microservice has its own database and data model; multiple services should never share a database. This reduces hard dependencies between services and keeps the system loosely coupled.</li>
<li class=""><strong>Independent deployment</strong>: each service can be released and upgraded on its own without affecting others. This matters enormously for large teams and dramatically improves engineering velocity.</li>
<li class=""><strong>Interface-driven communication</strong>: services talk to each other through APIs or messaging systems, never by reaching into each other's databases or sharing code libraries.</li>
</ul>
<p>Together, these principles form the foundation of microservice architecture.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overall-architecture-of-a-microservice-system">Overall Architecture of a Microservice System<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#overall-architecture-of-a-microservice-system" class="hash-link" aria-label="Direct link to Overall Architecture of a Microservice System" title="Direct link to Overall Architecture of a Microservice System" translate="no">​</a></h2>
<p>In practice, a complete microservice architecture is built from multiple foundational components — it's much more than just splitting up services.</p>
<p>At the outermost layer sits the <strong>API Gateway</strong>. As the unified entry point, the gateway handles client requests and provides authentication, rate limiting, logging, and routing. It hides the complexity of the backend services so that clients only ever face a single unified interface.</p>
<p>Behind the gateway is the <strong>business service layer</strong>, made up of the individual microservices — user service, order service, product service, payment service, and so on. Each service owns an independent business capability and interacts with others via API or RPC.</p>
<p>To let services find and talk to each other, systems typically introduce a <strong>service registry and discovery</strong> component such as etcd, Nacos, or Consul. Services register themselves on startup, and other services look up target addresses through the registry.</p>
<p>High-concurrency systems usually also introduce a <strong>message queue (MQ)</strong> for asynchronous communication — Kafka, RabbitMQ, or RocketMQ. Message queues help smooth out traffic spikes, decouple services, and build event-driven architectures.</p>
<p>On the data side, each microservice generally has its own database — MySQL, PostgreSQL, or a NoSQL store. For performance, Redis is commonly added as a caching layer to take pressure off the database.</p>
<p>Finally, the whole system typically runs on a <strong>container platform such as Kubernetes</strong>, using container orchestration for automated deployment, scaling, and failure recovery.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-design-questions-in-microservice-architecture">Key Design Questions in Microservice Architecture<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#key-design-questions-in-microservice-architecture" class="hash-link" aria-label="Direct link to Key Design Questions in Microservice Architecture" title="Direct link to Key Design Questions in Microservice Architecture" translate="no">​</a></h2>
<p>For all its benefits, microservice architecture introduces new complexity of its own. Several key questions deserve close attention during system design.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-service-granularity">1) Service Granularity<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#1-service-granularity" class="hash-link" aria-label="Direct link to 1) Service Granularity" title="Direct link to 1) Service Granularity" translate="no">​</a></h3>
<p>Split too coarsely, and you still end up with a large monolith; split too finely, and you drown in services and complexity. The usual advice is to draw boundaries along business domains, not technical modules.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-service-communication">2) Service Communication<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#2-service-communication" class="hash-link" aria-label="Direct link to 2) Service Communication" title="Direct link to 2) Service Communication" translate="no">​</a></h3>
<p>Synchronous calls usually go over HTTP or gRPC, while asynchronous communication goes through message queues. In high-concurrency systems, heavy synchronous calling can create tangled call chains between services, so communication patterns need to be designed deliberately.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-distributed-transactions">3) Distributed Transactions<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#3-distributed-transactions" class="hash-link" aria-label="Direct link to 3) Distributed Transactions" title="Direct link to 3) Distributed Transactions" translate="no">​</a></h3>
<p>In a monolith, database transactions guarantee data consistency. In a microservice architecture, each service has its own database, so traditional transactions no longer apply directly. The common solutions are <strong>eventual consistency</strong>, the Saga pattern, or a distributed transaction framework.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-system-stability">4) System Stability<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#4-system-stability" class="hash-link" aria-label="Direct link to 4) System Stability" title="Direct link to 4) System Stability" translate="no">​</a></h3>
<p>More services means more points of failure, so mechanisms like rate limiting, circuit breaking, and graceful degradation become necessary — implemented via a service governance framework or a Service Mesh for traffic control.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scalability-of-microservice-architecture">Scalability of Microservice Architecture<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#scalability-of-microservice-architecture" class="hash-link" aria-label="Direct link to Scalability of Microservice Architecture" title="Direct link to Scalability of Microservice Architecture" translate="no">​</a></h2>
<p>One of the biggest advantages of microservices is <strong>elastic scalability</strong>.</p>
<p>In a traditional monolith, if one module becomes the bottleneck — say the order service is under heavy load — you have to scale the entire system. With microservices, you can scale just the order service, for example by adding instances behind a load balancer.</p>
<p>In cloud-native environments, Kubernetes can automatically scale service instances based on load: adding nodes during traffic peaks and scaling back down when traffic subsides, which greatly improves resource utilization. This on-demand elasticity makes microservices a natural fit for high-concurrency internet workloads.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-challenges-of-microservices">The Challenges of Microservices<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#the-challenges-of-microservices" class="hash-link" aria-label="Direct link to The Challenges of Microservices" title="Direct link to The Challenges of Microservices" translate="no">​</a></h2>
<p>Microservices solve many problems, but they are no silver bullet. Compared to a monolith, a microservice system is significantly more complex:</p>
<ul>
<li class=""><strong>Higher operational overhead</strong>: a system may contain dozens or even hundreds of services, each with its own deployment pipeline and monitoring, which demands a mature DevOps platform.</li>
<li class=""><strong>Service governance</strong>: as the number of services grows, the call graph between them becomes very complex and requires dedicated governance tooling to manage.</li>
<li class=""><strong>Inherent distributed-systems problems</strong>: network latency, data consistency, service dependencies, and so on. These barely exist in a monolith but become first-class concerns in a microservice system.</li>
</ul>
<p>So when a system is still small, a monolith is often simpler and more efficient — microservice architecture is better suited to <strong>medium and large systems</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-jia-gou-she-ji#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>At its core, microservice architecture reduces system complexity through <strong>service decomposition and autonomy</strong>, improving scalability and maintainability along the way. It lets the system grow flexibly with the business and enables teams to develop in parallel — which is exactly what large internet systems need.</p>
<p>But microservices are not a simple technology upgrade. They require supporting infrastructure: a service registry, an API gateway, a messaging system, monitoring, a container platform, and more. Only with this complete technical foundation in place can microservice architecture deliver its real value.</p>
<p>In real-world system design, architects need to weigh business scale, team capability, and system complexity to choose the architecture that fits — rather than chasing microservices for their own sake. Truly good architecture is not the most technically sophisticated option, but <strong>the one that strikes the right balance between complexity and payoff</strong>.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Designing L1 and L2 Caches]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun</link>
            <guid>https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A look at the two-tier caching architecture combining a local (L1) cache with a Redis (L2) cache: the access flow, what each tier solves, consistency and capacity control, and common cache update strategies.]]></description>
            <content:encoded><![CDATA[<p>When the database can't keep up, the first instinct is to add Redis. When Redis itself starts straining, it's time for multi-level caching. This post covers the most common pattern: the L1 + L2 cache design.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-multi-level-caching">Why Multi-Level Caching<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#why-multi-level-caching" class="hash-link" aria-label="Direct link to Why Multi-Level Caching" title="Direct link to Why Multi-Level Caching" translate="no">​</a></h2>
<p>In internet-scale systems, the database tends to become the performance bottleneck as the business grows. If a flood of requests hits the database directly, you get high latency, an overloaded database, and eventually exhausted connections and slow queries. That's why virtually every high-concurrency system introduces a <strong>cache</strong> to boost performance.</p>
<p>The most common setup uses Redis as a unified caching layer. Client requests hit Redis first; on a cache hit, the data is returned directly, and on a miss, the system queries the database and writes the result back into the cache. This alone can dramatically reduce database load.</p>
<p>But as the system keeps scaling, relying on Redis alone runs into new problems. Under high QPS, a huge number of requests hitting Redis simultaneously generates network overhead and puts pressure on Redis CPU. Meanwhile, certain hot keys get read constantly, and paying a network round trip to Redis every single time adds unnecessary latency.</p>
<p>To solve these problems, many large systems adopt a <strong>multi-level cache architecture</strong>, and the most common form is the <strong>L1 + L2 cache design</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-basic-l1l2-cache-architecture">The Basic L1/L2 Cache Architecture<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#the-basic-l1l2-cache-architecture" class="hash-link" aria-label="Direct link to The Basic L1/L2 Cache Architecture" title="Direct link to The Basic L1/L2 Cache Architecture" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2026-04-Gemini_Generated_Image_a0c1wqa0c1wqa0c1-f8f48454a1f04bb95342ba3fc5447ebd.webp" width="1408" height="768" class="img_ev3q"></p>
<p>L1/L2 caching is essentially a <strong>tiered caching architecture</strong>, typically with two layers.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="l1-cache">L1 Cache<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#l1-cache" class="hash-link" aria-label="Direct link to L1 Cache" title="Direct link to L1 Cache" translate="no">​</a></h3>
<p>The L1 cache lives <strong>inside the application process</strong> and is also known as a <strong>local cache</strong>. For example:</p>
<ul>
<li class="">Go: bigcache / ristretto</li>
<li class="">Java: Caffeine / Guava Cache</li>
<li class="">Node: LRU Cache</li>
</ul>
<p>Its defining characteristics:</p>
<ul>
<li class="">Extremely fast access (in-memory)</li>
<li class="">No network communication</li>
<li class="">Each service instance has its own copy</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="l2-cache">L2 Cache<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#l2-cache" class="hash-link" aria-label="Direct link to L2 Cache" title="Direct link to L2 Cache" translate="no">​</a></h3>
<p>The L2 cache is typically a <strong>distributed caching system</strong> — most commonly Redis or Memcached — shared by all service instances.</p>
<p>Its defining characteristics:</p>
<ul>
<li class="">Shared across nodes</li>
<li class="">Better data consistency</li>
<li class="">Larger capacity</li>
<li class="">Supports distributed deployment</li>
</ul>
<p>The overall access flow: client request → service process → check L1 cache → on miss, check L2 cache → on miss again, query the database.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-l1l2-access-flow">The L1/L2 Access Flow<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#the-l1l2-access-flow" class="hash-link" aria-label="Direct link to The L1/L2 Access Flow" title="Direct link to The L1/L2 Access Flow" translate="no">​</a></h2>
<p>In a real system, a typical request flows like this.</p>
<p>First, the request enters the application service, which checks the <strong>L1 cache</strong>. If the data is there, it returns immediately — the lowest-latency path, usually on the order of microseconds.</p>
<p>On an L1 miss, the system queries the <strong>L2 cache (Redis)</strong>. If Redis has the data, it returns the result and also writes it into the L1 cache, so subsequent requests can hit locally.</p>
<p>If Redis doesn't have it either, the cache has fully missed, and the system goes to the database. Once the database returns, the result is written into both Redis and the L1 cache, establishing fresh cache entries.</p>
<p>The whole flow boils down to:</p>
<p>L1 Cache → L2 Cache → Database</p>
<p>This way, the vast majority of requests are intercepted at the cache layers and never touch the database.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-two-tiers">Why Two Tiers<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#why-two-tiers" class="hash-link" aria-label="Direct link to Why Two Tiers" title="Direct link to Why Two Tiers" translate="no">​</a></h2>
<p>The two cache tiers each solve different problems, and combining them delivers a major performance boost.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="reducing-redis-load">Reducing Redis Load<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#reducing-redis-load" class="hash-link" aria-label="Direct link to Reducing Redis Load" title="Direct link to Reducing Redis Load" translate="no">​</a></h3>
<p>In a high-concurrency system, if every request goes to Redis, Redis itself can become the new bottleneck. With an L1 cache, hot data is served straight from application memory, cutting the number of Redis requests.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cutting-network-overhead">Cutting Network Overhead<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#cutting-network-overhead" class="hash-link" aria-label="Direct link to Cutting Network Overhead" title="Direct link to Cutting Network Overhead" translate="no">​</a></h3>
<p>Talking to Redis means a network round trip. Even at 1ms of latency, that adds up under high QPS. The L1 cache is an in-process lookup — far faster than any network call.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="increasing-throughput">Increasing Throughput<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#increasing-throughput" class="hash-link" aria-label="Direct link to Increasing Throughput" title="Direct link to Increasing Throughput" translate="no">​</a></h3>
<p>With an L1 cache, the system can handle more requests without adding load to Redis or the database.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="faster-access-to-hot-data">Faster Access to Hot Data<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#faster-access-to-hot-data" class="hash-link" aria-label="Direct link to Faster Access to Hot Data" title="Direct link to Faster Access to Hot Data" translate="no">​</a></h3>
<p>In many business scenarios, a small set of hot data gets read constantly — popular products, user profiles, configuration data, recommendation results. The L1 cache serves these locally.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-of-l1-cache-design">Pitfalls of L1 Cache Design<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#pitfalls-of-l1-cache-design" class="hash-link" aria-label="Direct link to Pitfalls of L1 Cache Design" title="Direct link to Pitfalls of L1 Cache Design" translate="no">​</a></h2>
<p>The L1 cache performs beautifully, but it introduces problems of its own.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-data-consistency">1) Data Consistency<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#1-data-consistency" class="hash-link" aria-label="Direct link to 1) Data Consistency" title="Direct link to 1) Data Consistency" translate="no">​</a></h3>
<p>Since each service instance holds its own L1 cache, the copies across instances can drift apart. When data is updated, without a proper invalidation mechanism you may serve stale reads. The usual fix is a <strong>cache invalidation strategy</strong> — on update, notify all service instances to evict their local copies via a message queue or a pub/sub mechanism.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-capacity-control">2) Capacity Control<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#2-capacity-control" class="hash-link" aria-label="Direct link to 2) Capacity Control" title="Direct link to 2) Capacity Control" translate="no">​</a></h3>
<p>The L1 cache lives in application memory; if it grows unbounded, it can eat significant memory and hurt system stability. Eviction policies like LRU or LFU are typically used to cap its size.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-cache-penetration-and-breakdown">3) Cache Penetration and Breakdown<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#3-cache-penetration-and-breakdown" class="hash-link" aria-label="Direct link to 3) Cache Penetration and Breakdown" title="Direct link to 3) Cache Penetration and Breakdown" translate="no">​</a></h3>
<p>When a flood of requests targets nonexistent data, it can slam the database directly. Bloom filters or mutex-based locking are the usual defenses.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cache-update-strategies">Cache Update Strategies<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#cache-update-strategies" class="hash-link" aria-label="Direct link to Cache Update Strategies" title="Direct link to Cache Update Strategies" translate="no">​</a></h2>
<p>In a multi-level cache system, the update strategy is critical. Common approaches:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-cache-aside">1) Cache Aside<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#1-cache-aside" class="hash-link" aria-label="Direct link to 1) Cache Aside" title="Direct link to 1) Cache Aside" translate="no">​</a></h3>
<p>The application checks the cache first; on a miss it queries the database and then populates the cache. This is the most common pattern.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-write-through">2) Write Through<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#2-write-through" class="hash-link" aria-label="Direct link to 2) Write Through" title="Direct link to 2) Write Through" translate="no">​</a></h3>
<p>The application updates the cache at the same time it writes to the database, keeping the two in sync.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-write-back">3) Write Back<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#3-write-back" class="hash-link" aria-label="Direct link to 3) Write Back" title="Direct link to 3) Write Back" translate="no">​</a></h3>
<p>The application writes only to the cache, and the cache system asynchronously flushes to the database. This offers higher performance but is more complex to implement.</p>
<p>Most internet-facing workloads go with <strong>Cache Aside</strong>, because it's simple to implement and reliable.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-l1l2-caching-fits">When L1/L2 Caching Fits<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#when-l1l2-caching-fits" class="hash-link" aria-label="Direct link to When L1/L2 Caching Fits" title="Direct link to When L1/L2 Caching Fits" translate="no">​</a></h2>
<p>Multi-level caching is a good fit for scenarios like:</p>
<ul>
<li class=""><strong>Read-heavy, high-concurrency systems</strong>: product detail pages, user profile lookups, config reads.</li>
<li class=""><strong>Frequently accessed hot data</strong>: leaderboards, recommendation lists.</li>
<li class=""><strong>Heavy database load</strong>: the cache layers can drastically cut database queries.</li>
</ul>
<p>For systems with strict real-time consistency requirements — financial trading systems, for example — multi-level caching is usually avoided in favor of going straight to the database.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2026/03/15/wei-fu-wu-yi-er-ji-huan-cun#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The core idea of L1/L2 caching is layered interception: the local cache absorbs the hottest traffic, Redis holds the data shared across instances, and the database only handles true misses. This architecture significantly reduces latency and backend load — at the cost of multi-copy consistency, which must be designed alongside invalidation notifications, capacity control, and penetration defenses. Whether multi-level caching is worth it depends on your read/write ratio and consistency requirements — for consistency-sensitive scenarios, the simpler design is often the safer one.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[Exploring etcd]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun</link>
            <guid>https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Starting from real distributed coordination needs, this post covers what etcd is for and how Raft delivers strong consistency, walks through a cluster setup verified with etcdctl, and compares etcd with Redis for coordination workloads.]]></description>
            <content:encoded><![CDATA[<p>I started writing this article because, while organizing my Kubernetes notes, I kept running into the name etcd: cluster state lives in it, service discovery depends on it, and quite a few configuration centers are built on top of it too. Rather than treating it as a black box, I decided to set it up from scratch and work through it properly. In distributed systems, you frequently run into problems like these:</p>
<!-- -->
<ul>
<li class="">Service nodes need to share configuration</li>
<li class="">The system needs service discovery</li>
<li class="">Distributed locks need a coordination point</li>
<li class="">The cluster needs a consistent state store</li>
</ul>
<p>At their core, all of these problems call for a <strong>reliable distributed coordination system</strong>.</p>
<p>In the single-machine era, these needs could be met with a database table or even a plain file. But once you have multiple nodes, the question becomes: how do multiple replicas agree on the "current state"? Who has the final say? Can you still trust the data after a node dies? These are exactly the questions a distributed coordination system has to answer.</p>
<p>In the modern cloud-native world, the most widely used component for this is <strong>etcd</strong>.</p>
<p>For example:</p>
<ul>
<li class="">Kubernetes</li>
<li class="">CoreDNS</li>
<li class="">service mesh</li>
<li class="">distributed configuration centers</li>
</ul>
<p>All of these systems rely on etcd underneath.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-etcd">What is etcd<a href="https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun#what-is-etcd" class="hash-link" aria-label="Direct link to What is etcd" title="Direct link to What is etcd" translate="no">​</a></h2>
<p>In short:</p>
<p><strong>etcd is a highly reliable distributed key-value store.</strong></p>
<p>But what sets it apart from an ordinary database is this:</p>
<p><strong>It was designed specifically for distributed coordination.</strong></p>
<p>That positioning dictates its trade-offs: it doesn't chase massive data volumes or extreme throughput; instead, it aims to make every piece of data you store trustworthy. So it's a great fit for small but critical data — metadata, configuration, state — and a poor fit as a general-purpose business database.</p>
<p>Its main features are:</p>
<ul>
<li class="">Strong consistency</li>
<li class="">Distributed cluster support</li>
<li class="">A Watch mechanism</li>
<li class="">Transactions</li>
<li class="">A Lease mechanism</li>
</ul>
<p>The combination of these features is quite interesting:</p>
<ol>
<li class="">
<p>Watch lets clients subscribe to changes on a key or prefix. When a config value changes, every node learns about it in real time — no polling required.</p>
</li>
<li class="">
<p>A Lease is essentially a heartbeat token with a TTL. Keys can be attached to a lease, and once the client stops renewing it, those keys are deleted automatically — this is exactly how service registration and failure eviction are implemented.</p>
</li>
<li class="">
<p>Transactions (Txn) provide compare-and-swap semantics: "write this key only if it doesn't exist" can be done atomically, which is the foundation of distributed locking.</p>
</li>
</ol>
<p>Many distributed systems use etcd for:</p>
<ul>
<li class="">Service registries</li>
<li class="">Configuration centers</li>
<li class="">Distributed locks</li>
<li class="">Leader election</li>
</ul>
<p>Kubernetes, for example, stores the entire <strong>cluster state</strong> in etcd. kube-apiserver is the only component that reads and writes etcd directly; all other controllers observe state changes indirectly through the apiserver's Watch mechanism.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="strong-consistency-the-raft-consensus-algorithm">Strong consistency (the Raft consensus algorithm)<a href="https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun#strong-consistency-the-raft-consensus-algorithm" class="hash-link" aria-label="Direct link to Strong consistency (the Raft consensus algorithm)" title="Direct link to Strong consistency (the Raft consensus algorithm)" translate="no">​</a></h2>
<p>Internally, etcd uses the <strong>Raft consensus algorithm</strong>.</p>
<p>The core idea of Raft is:</p>
<p>The cluster elects a single <strong>Leader node</strong>.</p>
<p>All write requests must go through the Leader.</p>
<p>The flow looks roughly like this:</p>
<p>Client → Leader → replicate to a majority of nodes → commit succeeds</p>
<p>Only when:</p>
<p><strong>more than half of the nodes have acknowledged the write</strong></p>
<p>is the data considered truly committed.</p>
<p>Let's break this down a bit further. In Raft, every node is in one of three roles: Leader, Follower, or Candidate. During normal operation there is exactly one Leader, which periodically sends heartbeats to all Followers. If a Follower doesn't receive a heartbeat within its timeout, it turns itself into a Candidate and starts an election; whichever node wins a majority of votes becomes the new Leader.</p>
<p>Writes go through log replication: the Leader first appends the write operation to its own log, then sends it to all Followers in parallel. Only when a <strong>majority</strong> (more than half) of nodes have persisted the log entry does the Leader mark it as committed and respond to the client.</p>
<p>The "majority" design is the key to strong consistency: any two majority sets must overlap, so even if the Leader crashes, the newly elected Leader is guaranteed to contain all committed data — no committed writes are lost.</p>
<p>This also explains why an odd number of etcd nodes (3, 5, 7) is recommended:</p>
<ul>
<li class="">3 nodes tolerate 1 failure; 5 nodes tolerate 2</li>
<li class="">4 nodes also only tolerate 1 failure (a majority requires 3), so fault tolerance is the same as 3 nodes, with one extra replica's worth of sync overhead</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="setting-up-an-etcd-cluster">Setting up an etcd cluster<a href="https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun#setting-up-an-etcd-cluster" class="hash-link" aria-label="Direct link to Setting up an etcd cluster" title="Direct link to Setting up an etcd cluster" translate="no">​</a></h2>
<p>Deployment environment:</p>
<p>Operating system:</p>
<p>Debian 11.5.0</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-download-etcd">1 Download etcd<a href="https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun#1-download-etcd" class="hash-link" aria-label="Direct link to 1 Download etcd" title="Direct link to 1 Download etcd" translate="no">​</a></h3>
<p>Download the etcd binaries from GitHub:</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>The release page provides archives per platform; just pick the one for your architecture (e.g. linux-amd64). etcd ships as a single static binary with no extra runtime dependencies — one of the reasons it's so easy to deploy.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-install-etcd">2 Install etcd<a href="https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun#2-install-etcd" class="hash-link" aria-label="Direct link to 2 Install etcd" title="Direct link to 2 Install etcd" translate="no">​</a></h3>
<p>After extracting, place the following files into:</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>Mainly these two:</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>The division of labor is clear: <code>etcd</code> is the server process, and <code>etcdctl</code> is the command-line client — all subsequent queries, writes, and maintenance go through it. Putting them in <code>/usr/local/bin</code> gets them onto the PATH so you can invoke them from any directory.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-set-execute-permissions">3 Set execute permissions<a href="https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun#3-set-execute-permissions" class="hash-link" aria-label="Direct link to 3 Set execute permissions" title="Direct link to 3 Set execute permissions" 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%)"># The extracted files may lack the executable bit; add it manually</span><span class="token plain"></span><br></div><div class="token-line" 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>At this point, the binaries for a single node are ready. In cluster mode, repeat the installation steps above on every node, then declare each node's name, listen addresses, and the initial cluster member list via startup flags. Nodes talk to each other over port 2380, and clients connect on port 2379. My environment has TLS enabled, with certificates stored under <code>/opt/etcd/ssl</code>, so the verification commands below all need certificate arguments.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-verify-the-cluster">4 Verify the cluster<a href="https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun#4-verify-the-cluster" class="hash-link" aria-label="Direct link to 4 Verify the cluster" title="Direct link to 4 Verify the cluster" translate="no">​</a></h3>
<p>You can use etcdctl to inspect the data:</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%)"># List all keys in the cluster (keys only, no 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"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --cacert/--cert/--key: CA and client certificates for mutual TLS</span><span class="token plain"></span><br></div><div class="token-line" 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: list all three nodes; the client load-balances and fails over automatically</span><span class="token plain"></span><br></div><div class="token-line" 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>If the command returns results, the client-to-cluster path, the certificates, and quorum are all working. <code>--prefix</code> enables prefix matching; combined with <code>/</code> it effectively walks every key.</p>
<p>Delete keys:</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%)"># Delete by prefix; the empty prefix "" matches ALL keys — use with extreme caution in production</span><span class="token plain"></span><br></div><div class="token-line" 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>warning</div><div class="admonitionContent_BuS1"><p><code>del --prefix ""</code> deletes everything. If this etcd cluster is serving as the backend for Kubernetes, this command wipes the entire cluster state — double-check which environment you're targeting before running it.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-etcd-beats-redis-for-distributed-coordination">Why etcd beats Redis for distributed coordination<a href="https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun#why-etcd-beats-redis-for-distributed-coordination" class="hash-link" aria-label="Direct link to Why etcd beats Redis for distributed coordination" title="Direct link to Why etcd beats Redis for distributed coordination" translate="no">​</a></h2>
<p>A common question:</p>
<p>Can't Redis do distributed locks too?</p>
<p>It can.</p>
<p>But Redis is an <strong>AP system</strong>, while etcd is a <strong>CP system</strong>.</p>
<p>Put simply:</p>
<p>Redis prioritizes <strong>availability and performance</strong>.</p>
<p>etcd prioritizes <strong>consistency and reliability</strong>.</p>
<p>For locking specifically, the difference shows up in failure behavior: Redis master-replica replication is asynchronous. If the master writes a lock and crashes before replicating it, the promoted replica has no record of the lock — it's simply "lost", and two clients can hold the lock at the same time. The Redlock algorithm tries to mitigate this, but its safety has long been debated in the community. etcd writes, on the other hand, must be confirmed by a majority before they succeed; a Leader failover never loses committed data, so the lock's semantics hold even under failure.</p>
<p>The cost, of course, is performance: every write requires a round of majority replication plus an fsync to disk, so latency and throughput are far behind Redis. So the conclusion isn't that one is better than the other — it's about matching the tool to the scenario. Use Redis for caching, counters, and lightweight locks that can tolerate occasional failure; use etcd for configuration, leader election, and locks that require strict mutual exclusion.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-notes">Pitfalls and notes<a href="https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and notes" title="Direct link to Pitfalls and notes" translate="no">​</a></h2>
<p>A few things worth recording from the setup and usage process:</p>
<ol>
<li class="">
<p>Don't forget the <code>ETCDCTL_API=3</code> environment variable. Older versions of etcdctl default to the v2 API, and v2 and v3 data are completely isolated — using the wrong API version produces the illusion that "I wrote the data but can't read it back". Newer versions default to v3, but declaring it explicitly in scripts never hurts.</p>
</li>
<li class="">
<p>List all nodes in <code>--endpoints</code>. If you list only one node and it goes down, the client loses connectivity entirely; with all nodes listed, the client fails over to healthy nodes automatically.</p>
</li>
<li class="">
<p>Certificate paths and SANs must match. With TLS enabled, the IPs/domains in the certificate must cover the addresses actually used in the endpoints, or the handshake will be rejected.</p>
</li>
<li class="">
<p>Keep the node count odd. The Raft section above explains why: an even node count adds no fault tolerance, only synchronization cost.</p>
</li>
<li class="">
<p>etcd is sensitive to disk latency. Every Raft log commit requires an fsync; a slow disk directly inflates write latency and can even trigger Leader election churn. Use SSDs if you can.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2026/03/14/da-jian-etcdji-qun#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>etcd is a textbook case of trading performance for consistency: Raft's majority commit guarantees that any committed data you read is trustworthy at any moment, and Watch, Lease, and transactions turn that trustworthy state store into a general-purpose foundation for service discovery, leader election, and distributed locking. The setup itself is simple — two binaries and a set of startup flags. What actually deserves your attention is TLS certificates, node planning, and a clear-eyed judgment of where etcd fits. Figuring out the division of labor between etcd and Redis, and using each where it excels, is far more useful than arguing about which one is better.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Understanding Flink]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian</link>
            <guid>https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Starting from the shift from batch to stream processing, this post walks through the problems Flink solves — real-time computation, state management, fault tolerance, and event time — and its core design ideas: Stream First, State Driven, and Exactly Once.]]></description>
            <content:encoded><![CDATA[<p>Throughout the evolution of big data systems, one core problem has never gone away: <strong>data keeps growing, but processing keeps getting slower.</strong> This article starts from batch processing and works through why Flink emerged and what problems it actually solves.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="from-batch-processing-to-stream-processing">From batch processing to stream processing<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#from-batch-processing-to-stream-processing" class="hash-link" aria-label="Direct link to From batch processing to stream processing" title="Direct link to From batch processing to stream processing" translate="no">​</a></h2>
<p>Traditional data processing is mostly <strong>offline</strong>: run a job every night to aggregate user behavior, compute ad metrics, generate reports, and analyze business KPIs. This approach is called <strong>batch processing</strong>, with typical tools such as:</p>
<ul>
<li class="">Hadoop MapReduce</li>
<li class="">Hive</li>
<li class="">Spark</li>
</ul>
<p>These systems are great at crunching <strong>massive volumes of historical data</strong>. But as the internet grew, more and more businesses started needing <strong>real-time data processing</strong>, for example:</p>
<ul>
<li class="">Real-time risk control</li>
<li class="">Real-time recommendations</li>
<li class="">Real-time monitoring</li>
<li class="">Real-time ad bidding</li>
<li class="">Real-time log analysis</li>
</ul>
<p>These scenarios share one trait: <strong>data must be processed as it is produced</strong> — you can't wait until tomorrow. This gave rise to a new computation model: <strong>stream processing</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2026-04-Gemini_Generated_Image_xvg9q2xvg9q2xvg9-d0ff4be7b9e2ef01d55dbbf821b03ac4.webp" width="1600" height="873" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-flink-was-needed">Why Flink was needed<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#why-flink-was-needed" class="hash-link" aria-label="Direct link to Why Flink was needed" title="Direct link to Why Flink was needed" translate="no">​</a></h2>
<p>Early real-time systems mostly relied on Storm and Spark Streaming, but both had issues.</p>
<p>Storm is a genuine stream processor, but development is complex, fault tolerance is mediocre, and state management is hard. Spark Streaming is stable, but it is fundamentally <strong>micro-batch processing</strong> — in other words, not true streaming. For example:</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">Process one batch of data every second</span><br></div></code></pre></div></div>
<p>For many scenarios, this still isn't real-time enough. So Flink emerged, with a very clear goal: <strong>build a true stream processing system.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-problems-flink-solves">What problems Flink solves<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#what-problems-flink-solves" class="hash-link" aria-label="Direct link to What problems Flink solves" title="Direct link to What problems Flink solves" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-real-time-data-processing">1) Real-time data processing<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#1-real-time-data-processing" class="hash-link" aria-label="Direct link to 1) Real-time data processing" title="Direct link to 1) Real-time data processing" translate="no">​</a></h3>
<p>Flink delivers true <strong>millisecond-level real-time computation</strong>. Take a user clicking on a product:</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">Click → Kafka → Flink → real-time recommendation</span><br></div></code></pre></div></div>
<p>The whole pipeline can complete in <strong>tens of milliseconds</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-state-management">2) State management<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#2-state-management" class="hash-link" aria-label="Direct link to 2) State management" title="Direct link to 2) State management" translate="no">​</a></h3>
<p>One crucial problem in stream processing is <strong>state</strong>. Take counting user clicks:</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">User A clicks: 1 time</span><br></div><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 A clicks: 2 times</span><br></div><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 A clicks: 3 times</span><br></div></code></pre></div></div>
<p>The system has to remember what it saw before. Flink has built-in state management (Keyed State, Operator State) and supports both local state and RocksDB-backed state storage, which allows it to handle <strong>state at enormous scale</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-fault-tolerance">3) Fault tolerance<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#3-fault-tolerance" class="hash-link" aria-label="Direct link to 3) Fault tolerance" title="Direct link to 3) Fault tolerance" translate="no">​</a></h3>
<p>In distributed systems, node failures are the norm. Flink protects data through its <strong>checkpoint mechanism</strong>: the system periodically saves computation state, and if a node goes down, computation resumes from the checkpoint. This mechanism guarantees <strong>exactly-once processing</strong> — each record is processed exactly one time.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-event-time">4) Event time<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#4-event-time" class="hash-link" aria-label="Direct link to 4) Event time" title="Direct link to 4) Event time" translate="no">​</a></h3>
<p>Stream processing has another notoriously hard problem: <strong>event time</strong>. Log data may arrive late, and if you compute purely by system time, your statistics come out wrong.</p>
<p>Flink introduced the <strong>watermark mechanism</strong>. Watermarks help the system decide when data has "mostly all arrived", which makes it possible to correctly handle late data, window computations, and time-based aggregations.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flinks-core-design-ideas">Flink's core design ideas<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#flinks-core-design-ideas" class="hash-link" aria-label="Direct link to Flink's core design ideas" title="Direct link to Flink's core design ideas" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-stream-first">1) Stream First<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#1-stream-first" class="hash-link" aria-label="Direct link to 1) Stream First" title="Direct link to 1) Stream First" translate="no">​</a></h3>
<p>Flink's core philosophy is that <strong>everything is a stream</strong> — batch processing is just a <strong>bounded stream</strong>. As a result, Flink runs batch and streaming on the same engine.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-state-driven">2) State Driven<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#2-state-driven" class="hash-link" aria-label="Direct link to 2) State Driven" title="Direct link to 2) State Driven" translate="no">​</a></h3>
<p>Flink is a <strong>state-driven computation system</strong>: all computation revolves around state. User behavior analytics, real-time aggregation, real-time monitoring — all of them depend on state management.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-exactly-once">3) Exactly Once<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#3-exactly-once" class="hash-link" aria-label="Direct link to 3) Exactly Once" title="Direct link to 3) Exactly Once" translate="no">​</a></h3>
<p>Flink puts heavy emphasis on <strong>correctness of data processing</strong>. Through checkpoints, distributed snapshots, and two-phase commit, Flink achieves <strong>exactly-once semantics</strong> — a capability that many real-time systems consider essential.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink-in-real-world-systems">Flink in real-world systems<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#flink-in-real-world-systems" class="hash-link" aria-label="Direct link to Flink in real-world systems" title="Direct link to Flink in real-world systems" translate="no">​</a></h2>
<p>Flink is used extensively across internet companies, in scenarios such as real-time recommendation systems, user behavior analytics, ad click counting, real-time risk control, and real-time log analysis. A common data pipeline looks like:</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">User behavior → Kafka → Flink → ClickHouse</span><br></div></code></pre></div></div>
<p>Flink sits in the middle, handling data cleaning, real-time computation, and real-time aggregation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2026/03/14/flink-ji-qun-da-jian#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>Flink exists, at its core, to do "true stream processing". It unifies batch and streaming on a single engine, supports large-scale stateful computation with built-in state management, buys exactly-once correctness with its checkpoint mechanism, and handles out-of-order and late data with watermarks. Once you understand these points, you understand the fundamental difference between Flink and earlier real-time systems — and the specific APIs and cluster deployment will make much more sense afterwards.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[A Deep Dive into Netty]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi</link>
            <guid>https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Starting from the limitations of BIO, this post works through Java NIO's three core components — Channel, Buffer, and Selector — and the Reactor threading model, to understand the design ideas behind Netty's high-performance networking.]]></description>
            <content:encoded><![CDATA[<p>In distributed systems and microservice architectures, network communication is the most fundamental — and most important — building block.<br>
Many high-performance frameworks (Dubbo, gRPC, RocketMQ, Elasticsearch, and others) rely on <strong>Netty</strong> underneath for their network communication.</p>
<!-- -->
<p>When I first picked up Netty, I went straight to its API and example code — and got more confused the more I read. Why does EventLoop exist? Why are connections and reads/writes handled by two separate thread groups? It took me a while to realize that these designs didn't appear out of thin air; they exist to solve inherent problems in traditional network programming. Learning Netty while skipping this history is like memorizing answers without reading the questions.</p>
<p>So this post holds off on code and first sorts out the <strong>design ideas behind the NIO networking model</strong> that Netty is built on. Once you grasp this main thread, every Netty component falls into place.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-with-traditional-network-programming">The problem with traditional network programming<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#the-problem-with-traditional-network-programming" class="hash-link" aria-label="Direct link to The problem with traditional network programming" title="Direct link to The problem with traditional network programming" translate="no">​</a></h2>
<p>In early Java network programming, most programs used the <strong>BIO (Blocking IO)</strong> model.</p>
<p>BIO "blocks" in two places: <code>accept()</code> blocks while waiting for new connections, and <code>read()</code> blocks while waiting for data to arrive. Once a thread is blocked on a connection's read or write, it can't do anything else.</p>
<p>For example:</p>
<p>The server creates a new thread for every incoming client connection.</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">one connection = one thread</span><br></div></code></pre></div></div>
<p>The upside of this model is that it's simple and intuitive to program — each thread only cares about its one connection, reading it from start to finish. With a small number of connections, it works perfectly fine.</p>
<p>But with many connections, say:</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">10,000 connections = 10,000 threads</span><br></div></code></pre></div></div>
<p>several serious problems emerge:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="massive-thread-resource-consumption">Massive thread resource consumption<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#massive-thread-resource-consumption" class="hash-link" aria-label="Direct link to Massive thread resource consumption" title="Direct link to Massive thread resource consumption" translate="no">​</a></h3>
<p>Threads themselves cost memory and scheduling overhead. Every Java thread occupies its own stack memory; with tens of thousands of connections, the thread stacks alone can eat a huge amount of memory — before even counting the OS's overhead for maintaining thread structures.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="heavy-context-switching-overhead">Heavy context-switching overhead<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#heavy-context-switching-overhead" class="hash-link" aria-label="Direct link to Heavy context-switching overhead" title="Direct link to Heavy context-switching overhead" translate="no">​</a></h3>
<p>The CPU has to switch between threads constantly. Every switch means saving and restoring registers and flushing caches. When the thread count far exceeds the number of CPU cores, a significant share of CPU time goes into the switching itself rather than actual work.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="poor-scalability">Poor scalability<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#poor-scalability" class="hash-link" aria-label="Direct link to Poor scalability" title="Direct link to Poor scalability" translate="no">​</a></h3>
<p>As connections pile up, the system becomes prone to collapse. What's more awkward: most of those connections are actually idle (in long-lived connection scenarios, most clients aren't sending data most of the time), yet each one hogs a thread that just sits there waiting — resources wasted on waiting.</p>
<p>So traditional BIO is simply not suited for <strong>high-concurrency network services</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-core-idea-behind-nio">The core idea behind NIO<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#the-core-idea-behind-nio" class="hash-link" aria-label="Direct link to The core idea behind NIO" title="Direct link to The core idea behind NIO" translate="no">​</a></h2>
<p>To solve BIO's problems, Java introduced <strong>NIO (Non-Blocking IO)</strong>.</p>
<p>The core idea of NIO is remarkably simple:</p>
<blockquote>
<p><strong>Manage a large number of connections with a small number of threads</strong></p>
</blockquote>
<p>The shift in thinking is this: since most connections are idle most of the time, don't have threads babysit connections. Instead, hand the job of watching "which connection has data" over to the operating system, and have threads step in only when an event actually occurs. This is the idea of <strong>IO multiplexing</strong>, which on Linux corresponds to system calls like select/poll/epoll.</p>
<p>It relies on three core components:</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>Together they form the core architecture of NIO. Let's look at each in turn.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="channel">Channel<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#channel" class="hash-link" aria-label="Direct link to Channel" title="Direct link to Channel" translate="no">​</a></h2>
<p>A Channel can be understood as:</p>
<blockquote>
<p><strong>a pipe for data transfer</strong></p>
</blockquote>
<p>Unlike traditional IO:</p>
<p>Traditional 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">input stream / output stream</span><br></div></code></pre></div></div>
<p>A Channel, however:</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">is bidirectional</span><br></div></code></pre></div></div>
<p>It can both read and write. A Channel is essentially an <strong>abstraction of a network connection</strong>.</p>
<p>In Java NIO, the common implementations are <code>ServerSocketChannel</code> (listens on a port and accepts connections) and <code>SocketChannel</code> (represents a specific TCP connection). A Channel can be set to non-blocking mode — the prerequisite for being managed by a Selector: when there's no data to read, it returns immediately instead of stalling the thread.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="buffer">Buffer<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#buffer" class="hash-link" aria-label="Direct link to Buffer" title="Direct link to Buffer" translate="no">​</a></h2>
<p>In NIO, all data must first pass through a <strong>Buffer</strong>.</p>
<p>Think of it as:</p>
<blockquote>
<p><strong>a temporary staging area for data</strong></p>
</blockquote>
<p>The data flow:</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">network -&gt; Buffer -&gt; program</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">program -&gt; Buffer -&gt; network</span><br></div></code></pre></div></div>
<p>A Buffer is essentially a stateful region of memory, tracked internally by a few pointers — position, limit, capacity — that record "how far we've written, how far we can read". Switching between read and write modes requires methods like <code>flip()</code>, which is one of the reasons the raw NIO API is famously easy to get wrong. Netty later implemented its own ByteBuf largely to escape this awkward machinery.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="selector">Selector<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#selector" class="hash-link" aria-label="Direct link to Selector" title="Direct link to Selector" translate="no">​</a></h2>
<p>The Selector is <strong>the most central component of NIO</strong>.</p>
<p>Its job is:</p>
<blockquote>
<p><strong>managing multiple Channels with a single thread</strong></p>
</blockquote>
<p>That is:</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">one thread</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">monitoring many connections</span><br></div></code></pre></div></div>
<p>It works roughly like:</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">event polling</span><br></div></code></pre></div></div>
<p>Concretely, when a Channel registers with a Selector, it declares which event types it cares about: a new connection arriving (ACCEPT), data readable (READ), ready to write (WRITE), and so on. The thread calls <code>select()</code> and blocks; as soon as any Channel becomes ready, the call returns the ready set and the thread processes them one by one.</p>
<p>The flow:</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">monitors multiple Channels</span><br></div><div 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">which Channel has an event</span><br></div><div 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">process that one</span><br></div></code></pre></div></div>
<p>This way, the thread spends all of its time "handling ready events" rather than "waiting on a particular connection" — which is precisely the fundamental difference between NIO and BIO.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-reactor-threading-model">The Reactor threading model<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#the-reactor-threading-model" class="hash-link" aria-label="Direct link to The Reactor threading model" title="Direct link to The Reactor threading model" translate="no">​</a></h2>
<p>The Reactor threading model is a <strong>design pattern widely used in high-concurrency network servers</strong>. Its core idea: <strong>handle a large number of network connections with a small number of threads, driven by events</strong>. Instead of creating a dedicated thread per connection, threads listen for network events (connect, read, write, etc.) and process a connection only when data actually arrives on it. This avoids the resource consumption and context-switching problems of massive thread counts, dramatically improving a server's concurrent capacity.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2026-03-6f2341a1-2edd-42d2-b66a-a174f49001c4-1d85f6faeca7214b64186dc69cd8562e.webp" width="1536" height="1024" class="img_ev3q"></p>
<p>The Reactor model typically involves several key roles:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-event-monitoring-reactor">1. Event monitoring (Reactor)<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#1-event-monitoring-reactor" class="hash-link" aria-label="Direct link to 1. Event monitoring (Reactor)" title="Direct link to 1. Event monitoring (Reactor)" translate="no">​</a></h3>
<p>Listens for network events, such as new connections arriving, data becoming readable, or the socket becoming writable.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-connection-acceptance-acceptor">2. Connection acceptance (Acceptor)<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#2-connection-acceptance-acceptor" class="hash-link" aria-label="Direct link to 2. Connection acceptance (Acceptor)" title="Direct link to 2. Connection acceptance (Acceptor)" translate="no">​</a></h3>
<p>When a new client connects, accepts the connection and registers it with the downstream processing threads.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-event-dispatching-dispatcher">3. Event dispatching (Dispatcher)<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#3-event-dispatching-dispatcher" class="hash-link" aria-label="Direct link to 3. Event dispatching (Dispatcher)" title="Direct link to 3. Event dispatching (Dispatcher)" translate="no">​</a></h3>
<p>Routes different network events to their corresponding handling logic.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-business-processing-handler">4. Business processing (Handler)<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#4-business-processing-handler" class="hash-link" aria-label="Direct link to 4. Business processing (Handler)" title="Direct link to 4. Business processing (Handler)" translate="no">​</a></h3>
<p>Executes the actual business logic: parsing the protocol, handling the request, returning the result.</p>
<p>The Reactor model itself has evolved through several forms: single Reactor, single thread (one thread does both monitoring and processing); single Reactor, multiple threads (monitoring on one thread, processing handed to a thread pool); and master-worker Reactor (connection acceptance and read/write processing handled by separate Reactor thread groups). The higher the connection volume, the more important it is to separate acceptance from read/write so they don't drag each other down.</p>
<p>In Netty, the Reactor model typically shows up as the <strong>BossGroup + WorkerGroup thread structure</strong>, which maps exactly to the master-worker Reactor form:</p>
<ul>
<li class=""><strong>Boss threads</strong>: accept client connections</li>
<li class=""><strong>Worker threads</strong>: handle network reads/writes and business logic</li>
</ul>
<p>Through this design, Netty can handle <strong>tens of thousands of connections with a small number of threads</strong> — the core reason it achieves high-performance network communication.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-notes">Pitfalls and notes<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and notes" title="Direct link to Pitfalls and notes" translate="no">​</a></h2>
<p>A few things worth knowing up front when learning and using this model:</p>
<ol>
<li class="">
<p><strong>Never do slow work on a Worker thread.</strong> The Reactor model assumes event handling is fast. A Worker thread typically serves many connections; if a Handler runs a slow query or a synchronous remote call, every other connection on that thread gets stuck along with it. Time-consuming business logic belongs in a separate business thread pool.</p>
</li>
<li class="">
<p><strong>Raw NIO APIs are very easy to get wrong.</strong> Buffer read/write mode switching and Selector event-handling details are full of traps — which is why real-world projects almost always choose Netty over hand-rolled NIO: it encapsulates all that complexity away.</p>
</li>
<li class="">
<p><strong>NIO wins by saving threads, not by making individual IO faster.</strong> With very few connections, BIO isn't necessarily worse; the advantage of NIO/Reactor shows up when huge numbers of connections coexist. Choose your technology based on the scenario.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2026/03/14/netty-shen-ru-xue-xi#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>To recap the main thread of this evolution:</p>
<ul>
<li class="">BIO's problem is "one connection, one thread" — threads sit wasted on idle connections;</li>
<li class="">NIO uses Channel + Buffer + Selector to achieve IO multiplexing, letting a few threads handle only ready events;</li>
<li class="">The Reactor model defines, on top of that, the division of responsibilities for event monitoring, dispatching, and handling;</li>
<li class="">Netty's BossGroup + WorkerGroup is the production-grade implementation of the master-worker Reactor.</li>
</ul>
<p>With this thread in mind, Netty's concrete components — EventLoop, Pipeline, ByteBuf — no longer look like designs that came out of nowhere. I'll cover Netty's core components and hands-on usage separately in a follow-up.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Redis Distributed Locks]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo</link>
            <guid>https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[From local locks to distributed locks: implementing locking and safe release with Redis atomic commands, plus lock expiration, WatchDog renewal, consistency risks under master-replica failover, and the trade-offs versus etcd/ZooKeeper.]]></description>
            <content:encoded><![CDATA[<p>Almost everyone new to distributed systems runs into this question:<br>
<strong>When multiple service instances handle the same job at the same time, how do you prevent data from being processed twice?</strong></p>
<!-- -->
<p>For example:</p>
<ul>
<li class="">Users grabbing coupons</li>
<li class="">Scheduled task execution</li>
<li class="">Inventory deduction</li>
<li class="">Order status updates</li>
</ul>
<p>If the system runs as a single process, it's easy — a <strong>local lock (mutex)</strong> solves it. But with a microservice architecture or cluster deployment, the problem changes: the system may have 10 service instances, 100 workers, even multiple data centers. Local locks become completely useless, because different processes have no idea about each other's lock state.</p>
<p>This is where the concept of a <strong>distributed lock</strong> comes in. Its goal is simple:</p>
<blockquote>
<p>In a distributed environment, guarantee that only one node can execute a given piece of logic at any moment.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-can-redis-serve-as-a-distributed-lock">Why can Redis serve as a distributed lock?<a href="https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo#why-can-redis-serve-as-a-distributed-lock" class="hash-link" aria-label="Direct link to Why can Redis serve as a distributed lock?" title="Direct link to Why can Redis serve as a distributed lock?" translate="no">​</a></h2>
<p>When implementing a distributed lock, many people's first instinct is the database, for example:</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%)">-- Mutual exclusion via database row locks</span><span class="token plain"></span><br></div><div class="token-line" 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>But database locks have problems: poor performance, coarse granularity, and heavy pressure under high concurrency. So people looked for a system better suited to locking — and Redis fits very well, because it naturally has three advantages.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-single-threaded-model">1) Single-threaded model<a href="https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo#1-single-threaded-model" class="hash-link" aria-label="Direct link to 1) Single-threaded model" title="Direct link to 1) Single-threaded model" translate="no">​</a></h3>
<p>Redis executes its core commands on a single thread — only one command runs at a time. So an operation like:</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: write only if the key doesn't exist — inherently mutually exclusive</span><span class="token plain"></span><br></div><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>is an <strong>absolutely atomic operation</strong>, with no possibility of a race condition.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-in-memory-operations-extremely-fast">2) In-memory operations, extremely fast<a href="https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo#2-in-memory-operations-extremely-fast" class="hash-link" aria-label="Direct link to 2) In-memory operations, extremely fast" title="Direct link to 2) In-memory operations, extremely fast" translate="no">​</a></h3>
<p>All Redis data lives in memory. A lock operation is usually just one SET and one DEL, with latency typically in the <strong>tens of microseconds</strong> — far faster than a database.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-built-in-expiration">3) Built-in expiration<a href="https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo#3-built-in-expiration" class="hash-link" aria-label="Direct link to 3) Built-in expiration" title="Direct link to 3) Built-in expiration" translate="no">​</a></h3>
<p>Redis keys can carry a TTL, for example:</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: create only if absent; EX 10: auto-expire after 10 seconds</span><span class="token plain"></span><br></div><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>This way, even if the service crashes, the lock won't be stuck forever.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="releasing-a-redis-lock">Releasing a Redis lock<a href="https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo#releasing-a-redis-lock" class="hash-link" aria-label="Direct link to Releasing a Redis lock" title="Direct link to Releasing a Redis lock" translate="no">​</a></h2>
<p>You can't just DEL to release a lock, because this can happen:</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">Thread A acquires the lock</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Thread A runs past the timeout</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">The lock expires</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Thread B acquires the lock</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Thread A finishes and deletes the lock</span><br></div></code></pre></div></div>
<p>Now thread A has mistakenly deleted thread B's lock. The correct approach is to <strong>only delete the lock you actually hold</strong>, usually via a Lua script:</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">-- Compare the lock's value (unique identifier) first; delete only on match,</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">-- ensuring you only release your own lock</span><br></div><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 scripts execute atomically as a whole in Redis — no other command can slip in between the GET and the DEL — which is what makes the release safe.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="classic-problems-with-redis-distributed-locks">Classic problems with Redis distributed locks<a href="https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo#classic-problems-with-redis-distributed-locks" class="hash-link" aria-label="Direct link to Classic problems with Redis distributed locks" title="Direct link to Classic problems with Redis distributed locks" translate="no">​</a></h2>
<p>Although a Redis lock is simple, real systems hit plenty of pitfalls. The two most common are below.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-lock-expiration">1) Lock expiration<a href="https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo#1-lock-expiration" class="hash-link" aria-label="Direct link to 1) Lock expiration" title="Direct link to 1) Lock expiration" translate="no">​</a></h3>
<p>If the business logic runs longer than the TTL, the lock is released early, and other nodes may enter concurrently. The fix is <strong>automatic renewal (WatchDog)</strong>. For example:</p>
<ul>
<li class="">Set the lock TTL to 10 seconds</li>
<li class="">When the remaining validity enters its last 30%, renew once automatically, with random jitter added so that renewal requests don't all hammer Redis at the same instant</li>
</ul>
<p>As long as the task is still running, the lock never expires.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-redis-as-a-single-point-of-failure">2) Redis as a single point of failure<a href="https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo#2-redis-as-a-single-point-of-failure" class="hash-link" aria-label="Direct link to 2) Redis as a single point of failure" title="Direct link to 2) Redis as a single point of failure" translate="no">​</a></h3>
<p>If Redis goes down, every lock is lost. Common solutions:</p>
<ul>
<li class="">Redis Sentinel</li>
<li class="">Redis Cluster</li>
<li class="">The RedLock algorithm</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="rediss-ap-design-versus-the-cp-architecture-of-etcd-and-zookeeper">Redis's AP design versus the CP architecture of etcd and ZooKeeper<a href="https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo#rediss-ap-design-versus-the-cp-architecture-of-etcd-and-zookeeper" class="hash-link" aria-label="Direct link to Redis's AP design versus the CP architecture of etcd and ZooKeeper" title="Direct link to Redis's AP design versus the CP architecture of etcd and ZooKeeper" translate="no">​</a></h2>
<p>Redis is fundamentally an <strong>AP (availability-first)</strong> system: during network partitions or node failures, Redis prioritizes keeping the service available over keeping data strictly consistent. This introduces a risk — in extreme cases, <strong>two clients can both believe they hold the lock</strong>.</p>
<p>For example:</p>
<p>Client A acquires the lock on the master node<br>
The master hasn't yet replicated it to the replica<br>
The master suddenly crashes<br>
The replica is promoted to the new master</p>
<p>The new master has no idea A ever took the lock, so client B can acquire it again — and now two clients hold the lock simultaneously. This is the theoretical consistency risk of Redis distributed locks.</p>
<p>By contrast, systems like <strong>etcd and ZooKeeper</strong> are built on the <strong>CP model (strong consistency)</strong>, using the <strong>Raft or ZAB protocol</strong> internally to commit data only after a majority of nodes confirm it. Once a lock is acquired, the entire cluster agrees on it, and multiple clients can never hold the lock at once.</p>
<p>Hence a common rule of thumb in practice: for <strong>business-level locks</strong> (inventory, task control, and the like), a Redis distributed lock is usually good enough; but for <strong>scenarios demanding strong consistency</strong> — financial transactions, global schedulers, distributed coordination services — many systems opt for strongly consistent lock implementations on etcd / ZooKeeper.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2026/03/14/redisfen-bu-shi-tan-suo#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>The core idea of a Redis distributed lock is dead simple: <strong>use Redis's atomic operations to achieve mutual exclusion</strong>. It comes down to three key points: atomic acquisition, safe release, and automatic expiration or renewal. In practice, a mature Redis lock also adds retry logic, WatchDog auto-renewal, Lua-based safe release, and a unique lock identifier. And you have to stay clear about its AP nature — good enough for business-level mutual exclusion, but for strong-consistency scenarios, look to etcd / ZooKeeper. Only with all of these pieces combined does it become a genuinely stable, reliable distributed locking system.</p>]]></content:encoded>
            <category>Redis</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Algorithms: The Basics]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/14/suan-fa-ji-chu-zhi-shi</link>
            <guid>https://tanzhuo.xyz/en/2026/03/14/suan-fa-ji-chu-zhi-shi</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Starting from 'what is an algorithm': how algorithms relate to data structures, why time complexity matters, and why learning algorithms is about modeling problems rather than grinding through problem sets.]]></description>
            <content:encoded><![CDATA[<p>When I first started writing code, I assumed algorithms were something only competitive programmers needed—irrelevant to everyday business logic. Turns out that's wrong: slow endpoints, laggy lists, timeouts once the data grows—behind these everyday problems there's often an algorithm problem. This post walks through the most fundamental concepts.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-an-algorithm">What Is an Algorithm?<a href="https://tanzhuo.xyz/en/2026/03/14/suan-fa-ji-chu-zhi-shi#what-is-an-algorithm" class="hash-link" aria-label="Direct link to What Is an Algorithm?" title="Direct link to What Is an Algorithm?" translate="no">​</a></h2>
<p>An algorithm is, at its core, <strong>a set of steps for solving a problem</strong>.</p>
<p>Anything that:</p>
<p>takes some input data<br>
processes it according to defined rules<br>
produces a result</p>
<p>—that whole process is an algorithm.</p>
<p>It doesn't have to be sophisticated. Looking up a word alphabetically in a dictionary is an algorithm; so is a recipe—both are explicit, repeatable sequences of steps. The only difference when writing programs is that the steps will be executed by a computer, so every step has to be precise, with no room for ambiguity.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="understanding-algorithms">Understanding Algorithms<a href="https://tanzhuo.xyz/en/2026/03/14/suan-fa-ji-chu-zhi-shi#understanding-algorithms" class="hash-link" aria-label="Direct link to Understanding Algorithms" title="Direct link to Understanding Algorithms" translate="no">​</a></h2>
<p>Why should programmers care about algorithms? Because for the same problem, different algorithms can differ enormously in efficiency. One approach might finish in seconds; another might run for minutes or longer. As the data grows, the gap widens—which is why the performance of many systems comes down largely to algorithm design.</p>
<p>This gap is usually described with "time complexity," the familiar big-O notation. It doesn't care how many milliseconds a particular run took; it cares how the amount of work grows as the data grows: a linear scan is O(n)—double the data, double the work; binary search is O(log n)—double the data, just one extra lookup; two nested loops give you O(n²)—double the data, four times the work. With small inputs everything is fast, but once you hit hundreds of thousands or millions of records, the difference in growth curves is the difference between "usable" and "unusable."</p>
<p>Algorithms rarely exist in isolation; they almost always work together with data structures. Data structures organize the data—arrays, linked lists, trees, hash tables—while algorithms operate on that data. Simply put, one stores the data, the other processes it, and only together do they make a program efficient.</p>
<p>The two choices influence each other. For "check whether an element exists," an array requires scanning from start to end, while a hash table locates it in essentially one step; for "frequent insertions in the middle," an array has to shift elements around, while a linked list only needs pointer updates. Often, switching to a more suitable data structure makes the algorithm fast on its own—how you model the problem determines which algorithms are even available afterward.</p>
<p>In real systems, algorithms are everywhere. Search engines rank web pages with algorithms, short-video platforms use them to recommend content, navigation apps use them to compute the shortest route, and e-commerce platforms use them for product recommendations and ranking. Much of the software we use every day has all kinds of algorithms working behind the scenes.</p>
<p>Learning algorithms isn't about how many problems you've solved—it's about developing a way of thinking about problems. When you face a problem, being able to quickly come up with several approaches and pick the more efficient one—that's the real value of algorithms. Many experienced engineers are, in essence, continuously refining how they solve problems.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-should-you-focus-on-when-learning-algorithms">What Should You Focus On When Learning Algorithms?<a href="https://tanzhuo.xyz/en/2026/03/14/suan-fa-ji-chu-zhi-shi#what-should-you-focus-on-when-learning-algorithms" class="hash-link" aria-label="Direct link to What Should You Focus On When Learning Algorithms?" title="Direct link to What Should You Focus On When Learning Algorithms?" translate="no">​</a></h2>
<p>Many people fall into the trap of grinding problem sets.</p>
<p>What actually matters more is understanding:</p>
<ol>
<li class="">How to model the problem</li>
</ol>
<p>Translate a fuzzy real-world requirement into clear inputs, outputs, and constraints. Get the model right and the problem is often half solved; get it wrong and no amount of fast coding afterward will help.</p>
<ol start="2">
<li class="">How to design the data structures</li>
</ol>
<p>Choose structures based on access patterns: heavy lookups call for a hash table, ordered traversal suggests a tree, first-in-first-out means a queue. Pick the right structure and the code naturally becomes simpler.</p>
<ol start="3">
<li class="">How to optimize time complexity</li>
</ol>
<p>First estimate whether the current approach can handle the target data volume, then decide whether to optimize. Not all code deserves optimization, but you should know where the bottleneck is.</p>
<ol start="4">
<li class="">How solutions evolve</li>
</ol>
<p>Start from the brute-force solution, observe where computation is repeated, and improve step by step toward something better. That derivation process is far more valuable than memorizing the optimal solution.</p>
<p><strong>Real algorithmic skill isn't about how many problems you've memorized. It's this: see a problem, quickly think of a way to solve it.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2026/03/14/suan-fa-ji-chu-zhi-shi#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ul>
<li class="">Don't chase the optimal solution right away. Write a working brute-force solution first to confirm you understand the problem, then talk about optimization. Often the brute-force approach is good enough at real-world data sizes.</li>
<li class="">Complexity analysis has to account for data volume. When n is only a few hundred, the difference between O(n²) and O(n log n) is negligible; don't complicate simple code just to show off.</li>
<li class="">If you only memorize conclusions from solutions without working through the reasoning, you'll forget them in two weeks. Close the solution and re-derive it yourself—that's when you've actually learned it.</li>
<li class="">Don't ignore space complexity. Trading space for time is a common technique, but caches and hash tables eat memory too—know what the cost is.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2026/03/14/suan-fa-ji-chu-zhi-shi#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>An algorithm is a set of steps for solving a problem; data structures organize the data; together they determine a program's efficiency. The core tool for judging an algorithm is complexity analysis, which describes how the amount of work grows as the data grows. The point of learning algorithms isn't the number of problems solved but a few key skills—modeling, choosing structures, deriving solutions. Practice those until they're second nature, and when a new problem shows up, you'll know where to start.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[A Brief History of AI]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi</link>
            <guid>https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[AI didn't appear out of nowhere in the last few years—it's been over 70 years in the making. From the Turing test through deep learning to the era of large models, this post traces AI's three stages of development, where it stands today, and where it's headed.]]></description>
            <content:encoded><![CDATA[<p>A lot of people feel like AI just appeared out of nowhere in the past few years. It didn't. AI has been developing for <strong>over 70 years</strong>, and its history can be roughly split into three stages.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-one-the-birth-of-ai-19501980">Stage One: The Birth of AI (1950–1980)<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#stage-one-the-birth-of-ai-19501980" class="hash-link" aria-label="Direct link to Stage One: The Birth of AI (1950–1980)" title="Direct link to Stage One: The Birth of AI (1950–1980)" translate="no">​</a></h2>
<p>In 1950, the British scientist <strong>Alan Turing</strong> posed a question:</p>
<blockquote>
<p>"Can machines think like humans?"</p>
</blockquote>
<p>He proposed a now-famous test (the <strong>Turing test</strong>). In simple terms: if a human chats with a machine and can't tell which one is the machine, the machine counts as "intelligent."</p>
<p>In 1956, a conference in the United States formally coined the term:</p>
<blockquote>
<p>Artificial Intelligence</p>
</blockquote>
<p>From then on, AI became an official field of research. But computers at the time were weak and data was scarce, so AI mostly stayed in the realm of theory.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-two-ai-gets-somewhat-useful-19802010">Stage Two: AI Gets Somewhat Useful (1980–2010)<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#stage-two-ai-gets-somewhat-useful-19802010" class="hash-link" aria-label="Direct link to Stage Two: AI Gets Somewhat Useful (1980–2010)" title="Direct link to Stage Two: AI Gets Somewhat Useful (1980–2010)" translate="no">​</a></h2>
<p>As computers grew more powerful, some AI techniques started to land in the real world, such as:</p>
<ul>
<li class="">Speech recognition</li>
<li class="">Machine translation</li>
<li class="">Recommendation systems</li>
</ul>
<p>Many internet products were using AI long before anyone called it that, for example:</p>
<ul>
<li class="">Taobao product recommendations</li>
<li class="">Google search ranking</li>
<li class="">Netflix movie recommendations</li>
</ul>
<p>But AI back then was still fairly "dumb"—it could basically only handle <strong>a single task</strong> at a time.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-three-the-era-of-large-models-2010present">Stage Three: The Era of Large Models (2010–Present)<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#stage-three-the-era-of-large-models-2010present" class="hash-link" aria-label="Direct link to Stage Three: The Era of Large Models (2010–Present)" title="Direct link to Stage Three: The Era of Large Models (2010–Present)" translate="no">​</a></h2>
<p>Three factors truly set AI off:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-compute-got-stronger">1) Compute got stronger<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#1-compute-got-stronger" class="hash-link" aria-label="Direct link to 1) Compute got stronger" title="Direct link to 1) Compute got stronger" translate="no">​</a></h3>
<p>GPU compute exploded.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-data-got-bigger">2) Data got bigger<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#2-data-got-bigger" class="hash-link" aria-label="Direct link to 2) Data got bigger" title="Direct link to 2) Data got bigger" translate="no">​</a></h3>
<p>The internet generated massive amounts of data.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-algorithms-broke-through">3) Algorithms broke through<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#3-algorithms-broke-through" class="hash-link" aria-label="Direct link to 3) Algorithms broke through" title="Direct link to 3) Algorithms broke through" translate="no">​</a></h3>
<p>Especially <strong>deep learning + the Transformer architecture</strong>.</p>
<p>In 2017, Google published a paper:</p>
<blockquote>
<p>Attention Is All You Need</p>
</blockquote>
<p>That paper directly gave rise to today's large models, and with them a wave of AI products:</p>
<ul>
<li class="">ChatGPT</li>
<li class="">Claude</li>
<li class="">Gemini</li>
<li class="">DeepSeek</li>
</ul>
<p>Almost overnight, AI went from a "tool" to an intelligent assistant that can converse, write, code, and draw.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-does-ai-stand-today">Where Does AI Stand Today?<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#where-does-ai-stand-today" class="hash-link" aria-label="Direct link to Where Does AI Stand Today?" title="Direct link to Where Does AI Stand Today?" translate="no">​</a></h2>
<p>Ten years ago, most people would have seen AI as a lab technology or a collection of amusing toys. That's no longer the case at all. Today's AI has gone from novelty to genuine <strong>productivity tool</strong>, and it's pulling its weight in more and more industries.</p>
<p>The most visible change is in <strong>software development</strong>. Many programmers now use AI to help write code day to day, with tools like Cursor, Copilot, ChatGPT, and Claude. AI can write functions and generate scripts, help hunt down bugs, explain code logic, and even scaffold a complete program from a requirement. Across many teams, adopting AI has lifted development productivity by <strong>30–50%</strong>.</p>
<p>AI is also widely used in <strong>content creation</strong>. It can now write news articles, fiction, ad copy, and summary reports, and plenty of independent creators use AI to draft articles, organize material, or generate ideas. The final content usually still needs human editing, but AI handles a large share of the groundwork, dramatically speeding up the creative process.</p>
<p>In <strong>enterprise services</strong>, AI is starting to take over a lot of repetitive work—customer support being the obvious example. Many e-commerce platforms, banks, and telecom operators now run AI support systems for common questions. Compared with human agents, AI responds <strong>24/7</strong>, handles huge request volumes simultaneously, and slashes labor costs.</p>
<p><strong>Visual content production</strong> is advancing fast too. Tools like Midjourney and DALL·E can generate high-quality images from text, and video generation from Sora, Runway, and others is maturing. Short videos, ad design, and even film production are starting to use AI to help generate scenes, characters, and animation.</p>
<p>AI is playing a growing role in <strong>data analysis</strong> as well. Many companies use AI to analyze reports automatically, generate BI dashboards, and even offer trend assessments and decision recommendations. Some have started deploying "AI data analyst" assistants that help management understand data and make decisions faster.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="which-industries-has-ai-already-reached">Which Industries Has AI Already Reached?<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#which-industries-has-ai-already-reached" class="hash-link" aria-label="Direct link to Which Industries Has AI Already Reached?" title="Direct link to Which Industries Has AI Already Reached?" translate="no">​</a></h2>
<p>AI has quietly worked its way into a great many industries. Content recommendation on platforms like Douyin, Taobao, and Bilibili is essentially AI deciding what you see. Finance uses AI for risk control, fraud detection, and investment analysis—many banks have AI models running inside their core systems. In medicine, AI already assists doctors with diagnosis and medical imaging, reading X-rays and CT scans, sometimes faster and more accurately than humans. In manufacturing, AI powers factory automation, quality inspection, and predictive maintenance, making factories steadily smarter. In IT, AI is entering operations: automated monitoring, log analysis, automated troubleshooting and scaling—we may even see "AI ops assistants" helping engineers manage complex systems. AI has gone from a lab technology to foundational infrastructure behind many industries.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-does-ai-mean-for-humanity">What Does AI Mean for Humanity?<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#what-does-ai-mean-for-humanity" class="hash-link" aria-label="Direct link to What Does AI Mean for Humanity?" title="Direct link to What Does AI Mean for Humanity?" translate="no">​</a></h2>
<p>Many people worry about AI replacing humans. The more realistic picture is this: <strong>AI isn't here to replace people—it's here to amplify them.</strong></p>
<p>Look back at the history of technology and you'll see humans have always used tools to amplify their own abilities. The steam engine gave us physical power far beyond our bodies; computers gave us extraordinary computational power; the internet made information travel with essentially no distance limits. What AI amplifies is our capacity for <strong>intellectual work</strong>.</p>
<p>Things that used to take a whole team can now be done by one person plus AI—writing code, analyzing data, producing content, doing design, drafting operations plans. AI rapidly generates ideas, organizes information, and grinds through the groundwork, while humans set the direction, make the decisions, and own the results.</p>
<p>Put another way, the working model of the future may well be:</p>
<p><strong>One person + AI = what used to be a small team.</strong></p>
<p>So over the long run, what AI really changes isn't "whether people lose their jobs" but <strong>how work gets done</strong>. Repetitive, standardized work will increasingly be handled by AI, while humans focus on creating, judging, deciding, and innovating.</p>
<p>AI isn't humanity's opponent—it's more like a new kind of tool. Just as computers and the internet reshaped the world, AI is becoming a new foundational capability, gradually woven into every industry.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-is-ai-headed">Where Is AI Headed?<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#where-is-ai-headed" class="hash-link" aria-label="Direct link to Where Is AI Headed?" title="Direct link to Where Is AI Headed?" translate="no">​</a></h2>
<p>Based on current trends, AI is likely to develop along a few lines. First, it will evolve from a "chat tool" into <strong>agents that can complete tasks on their own (AI Agents)</strong>—writing code, analyzing data, operating systems autonomously; you give it a goal and it works through the steps itself. Second, AI will increasingly enter the physical world, pairing with robots, autonomous driving, and smart factories, so machines don't just "think" but also "act." Third, AI will become a <strong>foundational capability</strong>, embedded in software and systems the way the internet and electricity are—soon nearly every application will ship with AI features. In short, future AI won't be just a tool but something closer to humanity's "intelligent assistant" and "productivity multiplier," helping individuals and companies get more done with fewer people.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="money-and-cost-the-state-of-ai-investment">Money and Cost: The State of AI Investment<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#money-and-cost-the-state-of-ai-investment" class="hash-link" aria-label="Direct link to Money and Cost: The State of AI Investment" title="Direct link to Money and Cost: The State of AI Investment" translate="no">​</a></h2>
<p>Globally, AI has become one of the most capital-intensive areas in tech, with governments, tech companies, and investors all doubling down. According to Stanford's AI Index 2025 report, <strong>global corporate investment in AI reached about $252.3 billion in 2024</strong>, an all-time high.</p>
<p>The United States remains the biggest investor: <strong>US private AI investment hit $109.1 billion in 2024</strong>, roughly 12 times China's (about $9.3 billion) and 24 times the UK's ($4.5 billion).</p>
<p>Within that, <strong>generative AI (large models) is the fastest-growing segment</strong>: global private investment in generative AI reached <strong>$33.9 billion</strong> in 2024, up about 18.7% year over year.</p>
<p>Meanwhile, enterprise adoption is climbing fast. Data shows <strong>about 78% of companies were using AI in their business in 2024</strong>, up sharply from 55% the year before—AI is shifting from "technology experiment" to standard business tooling.</p>
<p>In market terms, the AI industry is in a high-growth phase: the global AI market is around <strong>$391 billion</strong> in 2025 and projected to exceed <strong>$1.8 trillion</strong> by 2030, making it one of the fastest-growing tech industries ahead.</p>
<p>Simply put, today's AI boom is driven by <strong>massive combined investment in capital, compute, and enterprise adoption</strong>. Tech giants, investors, and governments keep pouring in resources, each hoping to lead the coming AI industry race.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-Up<a href="https://tanzhuo.xyz/en/2026/03/13/aide-fa-zhan-shi#wrap-up" class="hash-link" aria-label="Direct link to Wrap-Up" title="Direct link to Wrap-Up" translate="no">​</a></h2>
<p>AI has traveled more than 70 years—from a thought experiment of Turing's, to single-task tools, to today's large models that converse, write code, and generate images. Behind it all is the long accumulation of compute, data, and algorithms, not some overnight explosion. AI has already permeated software development, content creation, finance, medicine, and many other industries, backed by ever-growing investment. For individuals, rather than worrying about being replaced, treat it as a tool that amplifies your own abilities—and learn to work with it sooner rather than later.</p>]]></content:encoded>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[The Design of go-lynx]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/13/go-lynxshe-ji-si-lu</link>
            <guid>https://tanzhuo.xyz/en/2026/03/13/go-lynxshe-ji-si-lu</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[go-lynx is a plugin-based microservice foundation framework built on Kratos: hot-pluggable plugins, config-driven setup, built-in service governance, and automatic TLS certificate rotation—the goal is to make microservice architecture feel like snapping together building blocks.]]></description>
            <content:encoded><![CDATA[<p>github.com/go-lynx is designed as a foundation framework that helps companies build out a microservice architecture fast. I split the whole codebase (29 repos under the go-lynx organization) into two parts: the lynx architectural base, and a family of lynx plugin modules.</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>The headline feature: <strong>Plug-and-Play</strong>—genuinely works out of the box, turning complex microservice architecture into "building blocks."</p>
<p>It doesn't reinvent wheels from scratch; it stands on the shoulders of giants:</p>
<ul>
<li class="">Core runtime borrowed from <strong>Kratos</strong> (Bilibili's open-source framework)</li>
<li class="">Service discovery/governance via <strong>Polaris</strong> (Tencent's cloud-native service mesh) and <strong>Nacos</strong> (open-sourced by Alibaba)</li>
<li class="">Distributed transactions via <strong>Seata, DTM, and others</strong></li>
<li class="">On top of that, I added a <strong>plugin management system + event bus + control plane</strong> of my own, enabling true hot-plugging.</li>
</ul>
<p><strong>Versus Kratos</strong>: Kratos is the "foundation framework"; Lynx is "Kratos hardened for production + a plugin system + zero configuration." If you like Kratos but find its configuration tedious and its plugins inconsistent, this framework was born from exactly that pain.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scaffolding">Scaffolding<a href="https://tanzhuo.xyz/en/2026/03/13/go-lynxshe-ji-si-lu#scaffolding" class="hash-link" aria-label="Direct link to Scaffolding" title="Direct link to Scaffolding" translate="no">​</a></h2>
<p>Zero config + a CLI scaffold—one command generates a complete project:</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%)"># Install the lynx 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">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%)"># Generate a complete microservice project skeleton</span><span class="token plain"></span><br></div><div class="token-line" 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="the-plugin-ecosystem-so-far">The Plugin Ecosystem So Far<a href="https://tanzhuo.xyz/en/2026/03/13/go-lynxshe-ji-si-lu#the-plugin-ecosystem-so-far" class="hash-link" aria-label="Direct link to The Plugin Ecosystem So Far" title="Direct link to The Plugin Ecosystem So Far" translate="no">​</a></h2>
<ul>
<li class="">Existing plugins: gRPC, HTTP, Redis, PostgreSQL, Redis distributed lock, Swagger, Tracer, Seata, and more.</li>
<li class="">Plugins communicate through a built-in <strong>Event Bus</strong>, enabling inter-plugin messaging and control.</li>
<li class="">Service registration and discovery + health checks + multi-version + load balancing (Polaris)</li>
<li class="">Traffic governance: rate limiting, circuit breaking, blue-green/canary, fallback</li>
<li class="">Security: mutual TLS, JWT, OAuth2, RBAC/ABAC</li>
<li class="">Distributed transactions: fully automatic via Seata</li>
<li class="">Observability: Prometheus + OpenTelemetry + Zap (JSON logging)</li>
<li class="">Graceful shutdown, retries, dead-letter queues, recovery manager</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="config-driven">Config-Driven<a href="https://tanzhuo.xyz/en/2026/03/13/go-lynxshe-ji-si-lu#config-driven" class="hash-link" aria-label="Direct link to Config-Driven" title="Direct link to Config-Driven" translate="no">​</a></h2>
<ul>
<li class="">Every plugin has a fixed confPrefix (e.g. <code>lynx.grpc.service</code>, <code>lynx.grpc.client</code>), with configuration loaded from the Lynx Runtime via <code>rt.GetConfig().Value(confPrefix).Scan(...)</code>.</li>
<li class="">Supports hot updates via Configure(c): the server side guards config pointer replacement with <code>confMu</code>, ensuring concurrency safety and allowing partial capabilities to be updated without a restart.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="example-lynx-project-configuration-yaml">Example lynx project configuration (YAML)<a href="https://tanzhuo.xyz/en/2026/03/13/go-lynxshe-ji-si-lu#example-lynx-project-configuration-yaml" class="hash-link" aria-label="Direct link to Example lynx project configuration (YAML)" title="Direct link to Example lynx project configuration (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%)"># Service registration and discovery</span><span class="token plain"></span><br></div><div class="token-line" style="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 listen 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 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 listen 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 comment" style="color:hsl(220, 10%, 40%)"># Ready-made templates exist for metrics/tracing/logging/tls/rate_limit too</span><br></div></code></pre></div></div>
<p>The overall feel is similar to Spring Boot: configure only the plugins you use, and every plugin works out of the box.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="plugin-architecture">Plugin Architecture<a href="https://tanzhuo.xyz/en/2026/03/13/go-lynxshe-ji-si-lu#plugin-architecture" class="hash-link" aria-label="Direct link to Plugin Architecture" title="Direct link to Plugin Architecture" translate="no">​</a></h2>
<ul>
<li class="">Unified plugin interface: plugins implement Lynx's <code>plugins.Plugin</code> interface, and embedding BasePlugin gives them common capabilities—ID, name, version, config prefix, weight, and so on.</li>
<li class="">Factory registration: client plugins register in <code>init()</code> via <code>factory.GlobalTypedFactory().RegisterPlugin(clientPluginName, "lynx.grpc.client", ...)</code>, and the framework loads them by config prefix and manages their lifecycle.</li>
<li class="">Lifecycle: a standard three-phase model—InitializeResources (read and validate config), StartupTasks (start services/connections), CleanupTasks (graceful shutdown, resource release).</li>
<li class="">Dependency injection: server-side plugins receive the application name, Logger, certificates, control plane, etc. via <code>SetDependencies</code>, avoiding global singletons and making testing and multi-instance setups easier.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="security-module-design">Security Module Design<a href="https://tanzhuo.xyz/en/2026/03/13/go-lynxshe-ji-si-lu#security-module-design" class="hash-link" aria-label="Direct link to Security Module Design" title="Direct link to Security Module Design" translate="no">​</a></h2>
<p>To secure communication between internal microservices, Lynx has built-in automatic TLS certificate management and rotation. Certificates update while the service is running with no restart required—newly established connections automatically pick up the latest certificate, delivering true zero-downtime updates. The design centers on a unified CertificateProvider for certificate management: internal gRPC servers fetch the current certificate dynamically during the TLS handshake, while clients obtain the Root CA through the same mechanism with support for credential refresh, so certificate updates take effect automatically. The mechanism can also hook into file watchers, cloud certificate services, or key management systems for automated certificate rotation and enterprise-grade secure communication—improving security while substantially cutting operational overhead.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-Up<a href="https://tanzhuo.xyz/en/2026/03/13/go-lynxshe-ji-si-lu#wrap-up" class="hash-link" aria-label="Direct link to Wrap-Up" title="Direct link to Wrap-Up" translate="no">​</a></h2>
<p>The core idea of go-lynx is not to rebuild the wheel but to fill in the layer that production environments need on top of Kratos: a unified plugin system, a config-driven out-of-the-box experience, and hot-plugging powered by the event bus and control plane. Service governance, distributed transactions, observability, and automatic TLS rotation are all delivered as plugins—use what you need, configure only that. If you're on Kratos and have been burned by inconsistent configuration and plugins, take a look at <a href="https://github.com/go-lynx" target="_blank" rel="noopener noreferrer" class="">go-lynx</a> and see whether this design solves your pain points.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Notes on Using OpenClaw]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/13/openclaw</link>
            <guid>https://tanzhuo.xyz/en/2026/03/13/openclaw</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[OpenClaw is an open-source AI agent automation framework that plans tasks, calls tools, and iterates until goals are met. These notes cover its core capabilities, obvious weaknesses, and where it fits.]]></description>
            <content:encoded><![CDATA[<p>OpenClaw is an <strong>open-source AI agent automation framework</strong>.
Its core goal is to take AI beyond mere "conversation" and let it <strong>plan tasks, call tools, execute operations, and keep iterating until a complex goal is done.</strong> After using it for a while, here is what I've gathered.</p>
<!-- -->
<p>The reason for writing this is simple: after using chat-style AI long enough, you realize its ceiling isn't "how well it answers" but "what happens after the answer." It suggests the code, lists the commands—but actually running, verifying, and fixing things is still on you. Agent frameworks exist to close that last mile: turning "saying" into "doing." OpenClaw is one I've actually run for a stretch recently, so I'm writing down my observations and issues—partly for my own reference, partly for anyone evaluating options.</p>
<p>In short, you can think of OpenClaw as:</p>
<blockquote>
<p><strong>An AI engineering assistant that can write code, run commands, analyze projects, and iterate on tasks automatically.</strong></p>
</blockquote>
<p>Unlike plain ChatGPT or Claude, OpenClaw is designed to:</p>
<ul>
<li class="">Give AI <strong>the ability to execute tasks</strong></li>
<li class=""><strong>Break down complex goals</strong></li>
<li class="">Automatically <strong>call tools and run commands</strong></li>
<li class="">Keep <strong>iterating until the task is done</strong></li>
</ul>
<p>That makes it something closer to an <strong>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="openclaws-core-capabilities">OpenClaw's Core Capabilities<a href="https://tanzhuo.xyz/en/2026/03/13/openclaw#openclaws-core-capabilities" class="hash-link" aria-label="Direct link to OpenClaw's Core Capabilities" title="Direct link to OpenClaw's Core Capabilities" translate="no">​</a></h2>
<p>OpenClaw's capabilities boil down to three things: <strong>task planning + autonomous execution + tool calling</strong>.</p>
<p>These map directly onto the classic agent loop: the model produces a plan from the goal (planning), acts on the real environment through tools (execution), then feeds the results—command output, error messages, test results—back to itself as observations (feedback) and adjusts its next move accordingly. The loop repeats until the model decides the task is complete. Once you understand this plan–execute–observe loop, OpenClaw's behavior becomes fairly predictable.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-task-planning">1) Task Planning<a href="https://tanzhuo.xyz/en/2026/03/13/openclaw#1-task-planning" class="hash-link" aria-label="Direct link to 1) Task Planning" title="Direct link to 1) Task Planning" translate="no">​</a></h3>
<p>When you give OpenClaw a goal, for example:</p>
<blockquote>
<p>"Add a Redis cache to this project"</p>
</blockquote>
<p>OpenClaw automatically breaks the task down, for example:</p>
<ol>
<li class="">Analyze the project structure</li>
<li class="">Locate the database access code</li>
<li class="">Design a caching strategy</li>
<li class="">Modify the code</li>
<li class="">Add tests</li>
<li class="">Run verification</li>
</ol>
<p>The process is essentially <strong>an AI generating a development plan on its own</strong>.</p>
<p>Worth emphasizing: the plan isn't generated once and then executed blindly to the end. After each step, the model re-examines the remaining steps—say it analyzes the project structure and discovers it uses MyBatis rather than JPA, then the subsequent changes adapt accordingly. The plan is alive, and that is the essential difference between an agent and "asking the model to output a script in one shot."</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-autonomous-task-execution">2) Autonomous Task Execution<a href="https://tanzhuo.xyz/en/2026/03/13/openclaw#2-autonomous-task-execution" class="hash-link" aria-label="Direct link to 2) Autonomous Task Execution" title="Direct link to 2) Autonomous Task Execution" translate="no">​</a></h3>
<p>OpenClaw doesn't just give advice—it can:</p>
<ul>
<li class="">Modify code</li>
<li class="">Create files</li>
<li class="">Run shell commands</li>
<li class="">Install dependencies</li>
<li class="">Execute scripts</li>
</ul>
<p>For example:</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%)"># Clone the target project locally</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Install project dependencies</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Run the tests to verify the change didn't break anything</span><span class="token plain"></span><br></div><div class="token-line" 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>The AI executes these operations directly.</p>
<p>The key to execution is result feedback: each command's stdout, stderr, and exit code flow into the next round of context. If tests fail, the model sees exactly which case broke and what the error was, then goes and fixes it itself—that is what "iterating until the task is done" looks like in practice.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-tool-calling">3) Tool Calling<a href="https://tanzhuo.xyz/en/2026/03/13/openclaw#3-tool-calling" class="hash-link" aria-label="Direct link to 3) Tool Calling" title="Direct link to 3) Tool Calling" translate="no">​</a></h3>
<p>OpenClaw can invoke different capabilities through tools, such as:</p>
<ul>
<li class="">Filesystem</li>
<li class="">Shell</li>
<li class="">Git</li>
<li class="">HTTP APIs</li>
<li class="">Build tools</li>
<li class="">Test frameworks</li>
</ul>
<p>A tool is essentially a described interface exposed to the model; the model decides on its own which one to call and with what arguments, based on the task. The mechanism is extensible: wrap an internal system in an interface and register it, and the agent can operate it. The capability boundary isn't set by the framework itself but by how many tools you're willing to plug in—of course, the more you plug in, the more careful you need to be about access control.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="openclaws-weaknesses">OpenClaw's Weaknesses<a href="https://tanzhuo.xyz/en/2026/03/13/openclaw#openclaws-weaknesses" class="hash-link" aria-label="Direct link to OpenClaw's Weaknesses" title="Direct link to OpenClaw's Weaknesses" translate="no">​</a></h2>
<p>As capable as OpenClaw is, it still has some obvious problems.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-heavily-dependent-on-the-model">1) Heavily Dependent on the Model<a href="https://tanzhuo.xyz/en/2026/03/13/openclaw#1-heavily-dependent-on-the-model" class="hash-link" aria-label="Direct link to 1) Heavily Dependent on the Model" title="Direct link to 1) Heavily Dependent on the Model" translate="no">​</a></h3>
<p>OpenClaw's capability is <strong>highly dependent on the underlying model</strong>.</p>
<p>If the model isn't strong enough:</p>
<ul>
<li class="">Task planning degrades</li>
<li class="">Code quality drops</li>
<li class="">Execution goes wrong more often</li>
</ul>
<p>So in many cases:</p>
<p>Agent capability ≈ model capability</p>
<p>With a weak model, OpenClaw becomes pretty dumb too.</p>
<p>This problem gets amplified in agent scenarios: in a normal conversation, if the model gets one answer wrong, a human glances at it and corrects it; in a multi-step execution loop, a mistake in the first planning step is inherited by every subsequent step—errors compound. Prompt engineering at the framework level can absorb some of this, but it can't paper over the model's own reasoning gaps.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-token-cost">2) Token Cost<a href="https://tanzhuo.xyz/en/2026/03/13/openclaw#2-token-cost" class="hash-link" aria-label="Direct link to 2) Token Cost" title="Direct link to 2) Token Cost" translate="no">​</a></h3>
<p>Agent systems typically require:</p>
<ul>
<li class="">Multiple rounds of reasoning</li>
<li class="">Many model calls</li>
<li class="">Persistent context</li>
</ul>
<p>Token consumption is very high—every call carries a large amount of context. Tuning only trims part of the overhead, and swapping in a cheaper model brings back the problem above: OpenClaw becomes completely unreliable and uncontrollable.</p>
<p>The reason isn't hard to see: every round of calls has to carry the task goal, action history, file contents, and command output, so context grows roughly linearly with the number of iterations, and each round is a full model call. A multi-step task can easily cost tens of times more than a single conversation. Cost and capability form an unavoidable tension here—save money with a weaker model, and the first problem comes knocking immediately.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2026/03/13/openclaw#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<p>Given the two weaknesses above, a few practical suggestions:</p>
<ul>
<li class=""><strong>Don't run it directly in production.</strong> The agent really does execute commands—deleted files and changed configs are deleted and changed for real. Run it in a container or an isolated working directory with minimal permissions.</li>
<li class=""><strong>Keep tasks small.</strong> The bigger the goal, the more likely planning drifts off course, and the faster the context balloons. "Add a cache" is far more reliable than "refactor the entire data layer."</li>
<li class=""><strong>Watch the execution.</strong> Given the opacity problem mentioned earlier, without a companion monitoring platform you should at least keep complete operation logs, so you can trace afterward what it actually touched.</li>
<li class=""><strong>Cap the cost.</strong> Token consumption across multiple iterations can spiral easily, especially when the model keeps retrying the same mistake. Set a round limit or a budget cap so you don't wake up to an exploded bill.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overall">Overall<a href="https://tanzhuo.xyz/en/2026/03/13/openclaw#overall" class="hash-link" aria-label="Direct link to Overall" title="Direct link to Overall" translate="no">​</a></h2>
<p>OpenClaw is an <strong>AI agent framework</strong> with a lot of potential.</p>
<p>Strengths:</p>
<ul>
<li class="">Automatic task planning</li>
<li class="">Autonomous execution</li>
<li class="">An extensible agent architecture</li>
</ul>
<p>Weaknesses:</p>
<ul>
<li class="">Heavily model-dependent</li>
<li class="">High token cost (model calls are very frequent; capability on complex tasks depends entirely on the model)</li>
<li class="">Opaque execution (a serious issue—the execution process is completely opaque and needs a control platform for monitoring)</li>
</ul>
<p>For now it's a better fit for:</p>
<ul>
<li class="">AI-driven development automation</li>
<li class="">DevOps automation</li>
<li class="">AI agent experiments</li>
</ul>
<p>But combined with:</p>
<ul>
<li class="">A strong model</li>
<li class="">A visualization system</li>
<li class="">An agent management platform</li>
</ul>
<p>it could eventually become:</p>
<blockquote>
<p><strong>Core infrastructure for AI-automated teams.</strong></p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2026/03/13/openclaw#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The value of an agent framework is connecting model output to the real environment in a closed execution loop, and OpenClaw covers that path completely: planning, execution, and tool calling are all there. But its ceiling is locked by model capability, and its floor is dragged down by cost and opacity. My current usage pattern: small tasks, isolated environments, eyes on the logs. Once the monitoring and management ecosystem around it matures, I'll consider putting it into more important workflows.</p>]]></content:encoded>
            <category>Tech</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[The Evolution of Ops and What Comes After Kubernetes]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/13/ops-evolution</link>
            <guid>https://tanzhuo.xyz/en/2026/03/13/ops-evolution</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The arc from hand-managed servers to IaC, cloud computing, and Kubernetes, plus the trends that follow—GitOps, observability, serverless, and AI ops—and where the ops profession is heading.]]></description>
            <content:encoded><![CDATA[<p>For over three decades, server operations has really been doing one thing: <strong>getting humans to spend less and less time managing machines by hand.</strong> This post traces that evolutionary arc and talks about where ops goes after Kubernetes.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2026/03/13/ops-evolution#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>Early ops was extremely primitive. When servers arrived, you racked them yourself, installed the OS, configured the network, and edited config files. When a machine had a problem, you SSH'd in to troubleshoot, often relying on experience and memory. Ops back then was, in essence, "humans watching machines."</p>
<p>But as server counts grew, this approach quickly broke down. There were two main problems: <strong>repetitive labor</strong> and <strong>configuration chaos</strong>.</p>
<p>Repetitive labor is easy to understand—deploying the same service to dozens of machines meant manually running a pile of commands every time; at scale, ops people spent every day doing these repetitive chores. In the SRE world this kind of work has a dedicated name: <strong>toil</strong> (work that is automatable, repetitive, and low in long-term value).</p>
<p>The other problem is <strong>configuration drift</strong>. You tweak a parameter on one machine today; six months later nobody remembers; configurations across machines gradually diverge, and the system becomes harder and harder to maintain.</p>
<p>To solve these problems, ops moved toward automation. First came <strong>shell scripts</strong> of all kinds, which later evolved into <strong>configuration management tools</strong> (Puppet, Chef, Ansible). Then a more important idea arrived: <strong>Infrastructure as Code (IaC)</strong>.</p>
<p>Simply put, you write server configuration as code and manage it in Git. Every change is recorded, can be rolled back, and can be replayed. Ops is no longer "go change things on the machine" but "change the code."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="virtualization-and-cloud-computing">Virtualization and Cloud Computing<a href="https://tanzhuo.xyz/en/2026/03/13/ops-evolution#virtualization-and-cloud-computing" class="hash-link" aria-label="Direct link to Virtualization and Cloud Computing" title="Direct link to Virtualization and Cloud Computing" translate="no">​</a></h2>
<p>Then came a truly pivotal shift: <strong>virtualization and cloud computing</strong>.</p>
<p>Buying servers used to be a heavyweight affair—procurement, racking, cabling. With virtualization, one physical server could run many virtual machines; servers stopped being hardware and became a "resource." In the cloud era, you don't even need to buy servers—a few clicks in a console provisions the resources. The ops role began shifting from "the person who manages machines" to "the person who schedules resources."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="containerization">Containerization<a href="https://tanzhuo.xyz/en/2026/03/13/ops-evolution#containerization" class="hash-link" aria-label="Direct link to Containerization" title="Direct link to Containerization" translate="no">​</a></h2>
<p>What truly changed how software is delivered was <strong>Docker and Kubernetes</strong>.</p>
<p>Docker solved an old problem: <strong>software behaving differently across environments</strong>. It runs on the dev machine, fails on the test machine, and production is different again. Docker packages the application together with its runtime environment into an image—like a boxed lunch you can take anywhere and it just works.</p>
<p>But Docker alone isn't enough. Once you have dozens, hundreds, or thousands of containers, you need a system to manage them. Enter Kubernetes.</p>
<p>The core idea of Kubernetes is actually simple: <strong>you just tell the system "here is what I want the system to look like," and the system keeps pulling reality back toward that desired state.</strong></p>
<p>For example, you declare "I want 3 instances of this service." If one dies, Kubernetes automatically starts another; if a machine fails, containers are rescheduled onto other machines. This mechanism is called the <strong>control loop</strong>, and it's the key to how Kubernetes automates the management of large clusters.</p>
<p>So from a broader vantage point, ops has gone through three big leaps:</p>
<ul>
<li class=""><strong>Configuration management / IaC</strong>: making server configuration versionable</li>
<li class=""><strong>Virtualization / cloud computing</strong>: turning servers into resources</li>
<li class=""><strong>Docker + Kubernetes</strong>: letting entire clusters run themselves</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="after-kubernetes-where-next">After Kubernetes, Where Next<a href="https://tanzhuo.xyz/en/2026/03/13/ops-evolution#after-kubernetes-where-next" class="hash-link" aria-label="Direct link to After Kubernetes, Where Next" title="Direct link to After Kubernetes, Where Next" translate="no">​</a></h2>
<p>Kubernetes is not the endpoint—it just solved the "cluster management" problem reasonably well. After Kubernetes, ops development is concentrated in three directions: <strong>more automation, more observability, less server-related mental overhead.</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-gitops">1) GitOps<a href="https://tanzhuo.xyz/en/2026/03/13/ops-evolution#1-gitops" class="hash-link" aria-label="Direct link to 1) GitOps" title="Direct link to 1) GitOps" translate="no">​</a></h3>
<p>The GitOps idea is simple: <strong>Git is the single source of truth for the system.</strong></p>
<p>All configuration lives in Git, and the system automatically reconciles the live environment with Git's state. To change a config, you no longer touch a server—you open a PR. Once the code merges, the system deploys automatically.</p>
<p>The biggest wins of this model: changes are auditable, rollbacks are easy, and the process is clearer.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-observability-and-service-governance">2) Observability and Service Governance<a href="https://tanzhuo.xyz/en/2026/03/13/ops-evolution#2-observability-and-service-governance" class="hash-link" aria-label="Direct link to 2) Observability and Service Governance" title="Direct link to 2) Observability and Service Governance" translate="no">​</a></h3>
<p>Once a system becomes microservices, the real complexity usually isn't any single service but the call relationships between services. A request may pass through a dozen services, and if any link slows down, the whole system suffers.</p>
<p>Hence technologies like <strong>Service Mesh, Prometheus, and OpenTelemetry</strong>. Their core goal: <strong>make the system's runtime state more transparent so problems are easier to find.</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-serverless-and-edge-computing">3) Serverless and Edge Computing<a href="https://tanzhuo.xyz/en/2026/03/13/ops-evolution#3-serverless-and-edge-computing" class="hash-link" aria-label="Direct link to 3) Serverless and Edge Computing" title="Direct link to 3) Serverless and Edge Computing" translate="no">​</a></h3>
<p>The serverless idea: developers just write code, and the platform manages the servers. A function, for instance, runs only when a request arrives and consumes nothing otherwise. For many scenarios, this model can dramatically reduce operational cost.</p>
<p>At the same time, some computation is moving toward the "edge"—retail store devices, factory equipment, in-vehicle systems. To fit these scenarios, lightweight Kubernetes distributions (like K3s) and cloud-edge coordination technologies have emerged.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-ai-is-changing-ops">How AI Is Changing Ops<a href="https://tanzhuo.xyz/en/2026/03/13/ops-evolution#how-ai-is-changing-ops" class="hash-link" aria-label="Direct link to How AI Is Changing Ops" title="Direct link to How AI Is Changing Ops" translate="no">​</a></h2>
<p>In recent years, AI has started making its way into operations.</p>
<p>Many traditional monitoring systems are based on <strong>fixed thresholds</strong>—alert when CPU exceeds 80%, say. But in complex systems, many anomalies are hard to describe with thresholds. So more and more platforms use <strong>machine learning for anomaly detection</strong>.</p>
<p>For example:</p>
<ul>
<li class="">AWS DevOps Guru analyzes metric data, automatically surfaces anomalies, and offers recommendations</li>
<li class="">Azure's smart detection automatically identifies performance degradation or rising error rates</li>
<li class="">Elastic's anomaly detection first learns the system's normal behavior, then flags abnormal patterns</li>
</ul>
<p>There are also research efforts and products that apply deep learning to log analysis, finding anomalous patterns in log sequences. The goal of all this is simple: <strong>cut alert noise so ops can find the real problem faster.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-the-ops-profession-goes-from-here">Where the Ops Profession Goes From Here<a href="https://tanzhuo.xyz/en/2026/03/13/ops-evolution#where-the-ops-profession-goes-from-here" class="hash-link" aria-label="Direct link to Where the Ops Profession Goes From Here" title="Direct link to Where the Ops Profession Goes From Here" translate="no">​</a></h2>
<p>Seeing all this automation and AI, many people worry that ops will disappear. The reality is closer to: <strong>the way ops works is changing.</strong></p>
<p>In the past, an ops engineer's value often came down to "can you handle problems by hand"—logging into machines, changing configs, restarting services, troubleshooting incidents. But the capabilities that will matter more going forward are:</p>
<ul>
<li class="">Turning experience into <strong>automation</strong></li>
<li class="">Turning processes into <strong>platforms</strong></li>
<li class="">Understanding system problems through business metrics</li>
<li class="">Designing <strong>observability and reliability systems</strong></li>
</ul>
<p>The way I see it, <strong>the ops engineer of the future is not the person who fixes machines, but the person who builds systems that fix themselves, observe themselves, and carry out construction automatically from human-language instructions.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2026/03/13/ops-evolution#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Across thirty-plus years, the evolution of ops has one central thread: freeing humans from repetitive labor. Configuration management and IaC made changes versionable; virtualization and cloud computing turned servers into resources; Docker and Kubernetes let clusters run themselves. Looking ahead, GitOps, observability, and serverless are the high-certainty directions, while AI is gradually taking over anomaly detection and incident localization. Ops won't disappear, but the value of "handling problems by hand" will keep declining—and the people who can design and build automated systems will keep becoming more valuable.</p>]]></content:encoded>
            <category>Linux</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[The Redis Threading Model]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing</link>
            <guid>https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Redis is often called 'single-threaded,' but that's only half true. From the event loop and IO multiplexing to Redis 6's IO threads—where Redis performance comes from and what the single-threaded model means in practice.]]></description>
            <content:encoded><![CDATA[<p>Redis is famous for its performance, and one core reason is its distinctive <strong>threading model</strong>. Many people have heard that Redis is "single-threaded," but the threading model has actually evolved across versions. Understanding it is essential for understanding both why Redis is fast and how to use it under high concurrency.</p>
<!-- -->
<p>The reason this topic deserves its own post is that "Redis is single-threaded" comes up constantly in interviews and everyday discussion, yet most people's understanding stays at the literal level: either they think the entire Redis process has only one thread, or they think command execution went multi-threaded after Redis 6. Both are wrong. And this model directly shapes how we use Redis—which commands you shouldn't run casually, why big keys are dangerous, why pipelines work—all of it traces back to the threading model.</p>
<p>This post covers <strong>Redis's single-threaded design, the event-driven model, IO multiplexing, and the multi-threading improvements from Redis 6 onward</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-redis-chose-a-single-thread">Why Redis Chose a Single Thread<a href="https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing#why-redis-chose-a-single-thread" class="hash-link" aria-label="Direct link to Why Redis Chose a Single Thread" title="Direct link to Why Redis Chose a Single Thread" translate="no">​</a></h2>
<p>In early versions (before Redis 6), the core execution model was <strong>single-threaded command processing</strong>.</p>
<p>That means:</p>
<ul>
<li class="">All client requests</li>
<li class="">All command execution</li>
<li class="">All data reads and writes</li>
</ul>
<p>are handled by <strong>one main thread</strong>.</p>
<p>This design yields a crucially important property: <strong>only one command executes at any given moment</strong>. Individual Redis commands are therefore naturally atomic—no locks needed, and no possibility of two commands interleaving on the same key. Commands like <code>INCR</code> and <code>SETNX</code> can be used directly as concurrency primitives; that's a byproduct of this model.</p>
<p>But note:</p>
<p>Redis's single-threadedness <strong>refers only to command execution</strong>—it doesn't mean the whole Redis process has a single thread. For example:</p>
<ul>
<li class="">RDB persistence</li>
<li class="">AOF rewrite</li>
<li class="">Asynchronous deletion</li>
<li class="">BIO threads</li>
</ul>
<p>are all background threads.</p>
<p>Take asynchronous deletion: in the main thread, the <code>UNLINK</code> command does just one thing—remove the key from the dictionary. The actual memory reclamation is handed to a background thread to do at its own pace. Even deleting a very large object won't stall the main thread. RDB persistence goes further: it forks a child process to write the snapshot, using the OS's copy-on-write to avoid blocking the main thread.</p>
<p>The core Redis thread is responsible only for:</p>
<ul>
<li class="">Network IO</li>
<li class="">Command parsing</li>
<li class="">Command execution</li>
<li class="">Returning results</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-a-single-thread-is-still-fast">Why a Single Thread Is Still Fast<a href="https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing#why-a-single-thread-is-still-fast" class="hash-link" aria-label="Direct link to Why a Single Thread Is Still Fast" title="Direct link to Why a Single Thread Is Still Fast" translate="no">​</a></h2>
<p>A common puzzle: how can a single thread sustain hundreds of thousands of QPS?</p>
<p>Redis's performance comes from a few main sources.</p>
<p>In-memory database: all Redis data lives in memory, avoiding disk IO. Memory and disk speeds aren't even in the same order of magnitude. For an ordinary <code>GET</code>, the actual execution is just one hash table lookup—microseconds or even nanoseconds. The bottleneck simply isn't CPU computation.</p>
<p>No thread-switching overhead: single-threaded execution means no locks, no contention, no complex concurrency control. In a multi-threaded model, context switches, lock contention, and cache invalidation aren't cheap; when each request needs only a tiny amount of processing, the cost of that concurrency machinery can exceed the benefit. Choosing a single thread is essentially Redis making a judgment call: <strong>command execution isn't the bottleneck, network IO is</strong>—so solve concurrency at the IO layer and keep the execution layer as simple as possible.</p>
<p>On top of that, Redis's internal data structures serve this model: SDS strings, skip lists, ziplists, and so on are all optimized for memory access, keeping individual operation paths very short.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="io-multiplexing">IO Multiplexing<a href="https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing#io-multiplexing" class="hash-link" aria-label="Direct link to IO Multiplexing" title="Direct link to IO Multiplexing" translate="no">​</a></h2>
<p>Redis doesn't handle one connection at a time—it uses an <strong>IO multiplexing model</strong>.</p>
<p>IO multiplexing means one thread monitors many sockets at once, with the operating system telling you "these connections have data ready to read/write," so the thread handles only the ready ones. Compared with the traditional one-thread-per-connection model, it avoids paying each connection's thread memory and scheduling cost, letting a single thread support a massive number of connections.</p>
<p>The IO models Redis uses include:</p>
<ul>
<li class="">epoll (Linux)</li>
<li class="">kqueue (macOS / BSD)</li>
<li class="">select</li>
<li class="">evport</li>
</ul>
<p>At compile time Redis automatically picks the best implementation for the platform—on Linux that's epoll. In the source this abstraction layer is called ae (A simple Event driven programming library), which exposes a unified event interface to the layers above.</p>
<p>Redis listens on all client connections through its event loop:</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>The processing flow:</p>
<ol>
<li class="">Monitor many client sockets</li>
<li class="">Detect which socket has data ready</li>
<li class="">Read the request</li>
<li class="">Execute the command</li>
<li class="">Return the result</li>
</ol>
<p>Each turn of the event loop pulls the ready events from the multiplexing interface and processes them one by one: for read events, parse and execute the command; for write events, send results back to the client. Because each event is handled quickly, the loop spins fast enough that clients never notice they're "waiting in line."</p>
<p>That's how Redis can <strong>handle a huge number of connections on a single thread</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-6s-multi-threading-improvements">Redis 6's Multi-Threading Improvements<a href="https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing#redis-6s-multi-threading-improvements" class="hash-link" aria-label="Direct link to Redis 6's Multi-Threading Improvements" title="Direct link to Redis 6's Multi-Threading Improvements" translate="no">​</a></h2>
<p>As hardware advanced and CPU core counts grew, the single-threaded model hit a ceiling in certain scenarios—and that ceiling is mainly <strong>network IO</strong>.</p>
<p>For example:</p>
<ul>
<li class="">Very large numbers of client connections</li>
<li class="">Massive volumes of network traffic</li>
</ul>
<p>Command execution itself is pure in-memory work and blindingly fast; but reading requests out of kernel buffers and writing responses back—those read/write system calls have real cost. Past a certain traffic level, the main thread spends most of its time moving data, and actual command execution becomes a small fraction—that is the ceiling of the single-threaded model.</p>
<p>So Redis 6 introduced <strong>IO threads</strong>.</p>
<p>The execution flow becomes:</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>The idea is clean: hand the "porter" work—reading/parsing requests and writing back responses—to multiple IO threads in parallel, while command execution still converges on the main thread and runs serially. This exploits multiple cores to boost network throughput while fully preserving the atomicity semantics of single-threaded execution—application code doesn't need to change at all.</p>
<p>Therefore:</p>
<ul>
<li class=""><strong>Command execution remains single-threaded</strong></li>
<li class=""><strong>IO operations can run in parallel</strong></li>
</ul>
<p>Note that IO threads are disabled by default and enabled via configuration:</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%)"># Number of IO threads; keep it below the CPU core count</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># By default only writes use IO threads; enable this to parallelize reads/parsing too</span><span class="token plain"></span><br></div><div class="token-line" style="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>It only makes sense to enable this on instances where network traffic is genuinely the bottleneck; in low-traffic scenarios it just adds a layer of thread coordination overhead.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="practical-advice-for-the-single-threaded-model">Practical Advice for the Single-Threaded Model<a href="https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing#practical-advice-for-the-single-threaded-model" class="hash-link" aria-label="Direct link to Practical Advice for the Single-Threaded Model" title="Direct link to Practical Advice for the Single-Threaded Model" translate="no">​</a></h2>
<p>Because command execution is single-threaded, keep the following in mind. One slow command blocks <strong>every</strong> client's requests—this is the part of the single-threaded model that deserves the most respect.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="avoid-slow-commands">Avoid Slow Commands<a href="https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing#avoid-slow-commands" class="hash-link" aria-label="Direct link to Avoid Slow Commands" title="Direct link to Avoid Slow Commands" translate="no">​</a></h3>
<p>For example:</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%)"># Scans the entire keyspace, O(N), blocking all requests while it runs</span><span class="token plain"></span><br></div><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>Commands like this block Redis.</p>
<p>Instead, use:</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%)"># Cursor-based batched iteration—processes a small chunk each time, no long blocking</span><span class="token plain"></span><br></div><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>Similar offenders include full-read commands on large collections such as <code>SMEMBERS</code>, <code>HGETALL</code>, and <code>LRANGE 0 -1</code>—all of them essentially stuff O(N) work into the single thread.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="keep-big-keys-under-control">Keep Big Keys Under Control<a href="https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing#keep-big-keys-under-control" class="hash-link" aria-label="Direct link to Keep Big Keys Under Control" title="Direct link to Keep Big Keys Under Control" translate="no">​</a></h3>
<p>Big keys cause:</p>
<ul>
<li class="">Network congestion</li>
<li class="">Longer CPU execution time</li>
</ul>
<p>Moreover, deleting and expiring big keys also happens on the main thread (unless you use <code>UNLINK</code> or enable lazy freeing)—when a key of several hundred MB expires, it can produce a visible latency spike.</p>
<p>Recommendations:</p>
<ul>
<li class="">Split the data up</li>
<li class="">Use hashes / sets</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="use-pipelines">Use Pipelines<a href="https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing#use-pipelines" class="hash-link" aria-label="Direct link to Use Pipelines" title="Direct link to Use Pipelines" translate="no">​</a></h3>
<p>Pipelines reduce network round trips:</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>Without a pipeline, each command waits for the previous response before the next one is sent, so throughput is capped by network RTT. A pipeline batches a group of commands, sends them in one go, and collects all responses at once, compressing N round trips into one.</p>
<p>This improves overall throughput.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<p>A few points that are easy to overlook in practice, given the threading model:</p>
<ol>
<li class="">
<p><strong>Lua scripts also block the main thread</strong>. While a script runs, Redis processes no other commands; a script looping over a large amount of data is effectively one giant slow command.</p>
</li>
<li class="">
<p><strong>Don't interpret Redis 6's IO threads as "concurrent command execution."</strong> The atomicity semantics of transactions, Lua, and <code>INCR</code> are completely unchanged—no extra locking needed. Likewise, don't expect IO threads to fix slow commands: if the slowness is in the execution layer, multi-threaded IO can't help.</p>
</li>
<li class="">
<p><strong>When investigating stalls, check the slow log first</strong>. <code>SLOWLOG GET</code> records command execution time (excluding network) and is the first stop for diagnosing "Redis occasionally hiccups"; combined with the <code>latency</code> family of commands, you can further distinguish slow commands from slow forks or slow disks.</p>
</li>
<li class="">
<p><strong>A single instance not saturating multiple cores is normal</strong>. Command execution uses only one core. To utilize a whole multi-core machine, the usual approach is multiple instances per host or Redis Cluster—not waiting for Redis to go multi-threaded on execution.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://tanzhuo.xyz/en/2026/03/13/redis-nei-cun-mo-xing#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>Through <strong>in-memory operations, IO multiplexing, an event-driven architecture (event loop), and single-threaded command execution</strong>, Redis achieves extremely high concurrency while avoiding lock contention and thread-switching overhead. Redis 6's IO threads only parallelize network send/receive—command execution remains serial. That core hasn't changed, so the usage principles built around it (avoid slow commands, control big keys, use pipelines wisely) remain valid. Once you understand this, many Redis best practices stop being memorized checklist items and become inevitable conclusions derived from the model.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[Data Structures: Binary Trees]]></title>
            <link>https://tanzhuo.xyz/en/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao</link>
            <guid>https://tanzhuo.xyz/en/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The basics of binary trees and their common forms: full binary trees, complete binary trees, and BSTs, plus how a BST degenerates into a linked list and the standard traversals—an introductory note on tree structures.]]></description>
            <content:encoded><![CDATA[<p>Linked lists and arrays solve the problem of storing "linear" data, but once hierarchy or fast lookup comes into play, linear structures fall short. Tree structures exist precisely for this, and the binary tree is the most fundamental and most frequently tested one among them—heaps, BSTs, and red-black trees are all built on top of it.</p>
<!-- -->
<p>A <strong>binary tree</strong> is a common data structure made up of nodes, where each node has at most <strong>two children</strong>:</p>
<ul>
<li class="">Left child</li>
<li class="">Right child</li>
</ul>
<p>Hence the name <strong>binary tree</strong>.</p>
<p>The "at most two" constraint looks simple, but it brings an important property: the tree's shape can be defined recursively—every binary tree consists of a root node, a left subtree, and a right subtree, and the left and right subtrees are themselves binary trees. Nearly every binary tree algorithm that follows (traversal, search, insertion) is built around this recursive structure.</p>
<p>Each node typically has three parts:</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   // the data stored in the 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">    left    // reference to the left child, or null if none</span><br></div><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   // reference to the right child, or null if none</span><br></div><div 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>In other words, a binary tree doesn't require contiguous storage in memory; nodes are connected by references (pointers), much like a linked list—except each node goes from "one successor" to "at most two children."</p>
<p>A simple example:</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>Here:</p>
<ul>
<li class="">A is the <strong>root</strong></li>
<li class="">B and C are A's <strong>children</strong></li>
<li class="">D and E are B's <strong>children</strong></li>
</ul>
<p>Nodes with no children (like D, E, F) are called <strong>leaf nodes</strong>. The number of levels from the root down to a leaf is the tree's <strong>height</strong>, which directly determines the cost of most operations on the tree.</p>
<p>Based on shape, binary trees come in a few common special forms—let's go through them one by one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="full-binary-tree">Full Binary Tree<a href="https://tanzhuo.xyz/en/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#full-binary-tree" class="hash-link" aria-label="Direct link to Full Binary Tree" title="Direct link to Full Binary Tree" translate="no">​</a></h2>
<p>If <strong>every node in the tree has either two children or no children</strong>, it's a full binary 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">        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>Characteristics:</p>
<ul>
<li class="">Every node has either <strong>0 children</strong></li>
<li class="">or <strong>2 children</strong></li>
</ul>
<p>In other words, a full binary tree has no node with "only one child." This is the most densely packed shape—for a given number of levels, it holds the most nodes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="complete-binary-tree">Complete Binary Tree<a href="https://tanzhuo.xyz/en/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#complete-binary-tree" class="hash-link" aria-label="Direct link to Complete Binary Tree" title="Direct link to Complete Binary Tree" translate="no">​</a></h2>
<p>A complete binary tree requires:</p>
<ul>
<li class="">Every level except the last is completely filled</li>
<li class="">Nodes in the last level are packed <strong>contiguously from left to right</strong></li>
</ul>
<p>Example:</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>This structure is perfect for <strong>array storage</strong>, which is why the <strong>heap</strong> is a complete binary tree.</p>
<p>Why is a complete binary tree so well suited to arrays? Because numbering nodes "top to bottom, left to right" leaves no gaps in the middle: put the root at index 0, and the node at index i has its left child at <code>2i + 1</code>, right child at <code>2i + 2</code>, and parent at <code>(i - 1) / 2</code> (rounded down). No pointers need to be stored—index arithmetic alone lets you jump between parent and child, which saves memory and is cache-friendly. Heapsort and priority queues exploit exactly this.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="binary-search-tree-bst">Binary Search Tree (BST)<a href="https://tanzhuo.xyz/en/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#binary-search-tree-bst" class="hash-link" aria-label="Direct link to Binary Search Tree (BST)" title="Direct link to Binary Search Tree (BST)" translate="no">​</a></h2>
<p>The two categories above are about "shape"; the binary search tree is about "how node values are arranged." A binary search tree satisfies:</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">left subtree &lt; root &lt; right subtree</span><br></div></code></pre></div></div>
<p>Note that this constraint applies recursively: it's not just the immediate children—every value in the entire left subtree is less than the root, and every value in the entire right subtree is greater.</p>
<p>Example:</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>Characteristics:</p>
<ul>
<li class="">Fast lookups</li>
<li class="">Average time complexity: <strong>O(log n)</strong></li>
</ul>
<p>Searching works just like binary search: start at the root, go left if the target is smaller than the current node, go right if it's larger, and each level down eliminates roughly half the candidates. Insertion works the same way—follow the search path to an empty slot and hang the new node there.</p>
<p>But if the tree degenerates into a linked 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">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>the time complexity degrades to:</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>The classic trigger for degeneration is inserting values in <strong>sorted order</strong>: each new node is larger than all previous ones, so it can only keep hanging off the right side. The tree "grows crooked" into a chain, each level of a search eliminates only one node, and the BST's advantage vanishes entirely. This is exactly why <strong>self-balancing binary search trees</strong> like AVL trees and red-black trees exist—they restructure via rotations on insert and delete, keeping the tree height at O(log n).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="traversals">Traversals<a href="https://tanzhuo.xyz/en/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#traversals" class="hash-link" aria-label="Direct link to Traversals" title="Direct link to Traversals" translate="no">​</a></h2>
<p>Visiting all the nodes of a binary tree is called traversal. There are four common kinds:</p>
<ol>
<li class=""><strong>Preorder</strong>: root → left subtree → right subtree; often used for copying or serializing a tree</li>
<li class=""><strong>Inorder</strong>: left subtree → root → right subtree; for a BST, an inorder traversal yields the values in <strong>ascending order</strong></li>
<li class=""><strong>Postorder</strong>: left subtree → right subtree → root; suited to "handle the children before yourself" scenarios, like freeing an entire tree</li>
<li class=""><strong>Level-order</strong>: visit level by level, left to right, implemented with a queue—breadth-first search on a tree</li>
</ol>
<p>The first three are very natural to write recursively, which confirms the point from earlier: a binary tree is itself a recursively defined structure.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<p>A few things that trip people up in practice and in coding interviews:</p>
<ul>
<li class=""><strong>Don't assume a BST is balanced.</strong> When analyzing complexity, distinguish "average O(log n)" from "worst-case O(n)"—the follow-up question in interviews is usually about the degenerate case.</li>
<li class=""><strong>Validate a BST with an inorder traversal.</strong> To check whether a tree is a valid BST, comparing each node with just its immediate children isn't enough—the entire subtree must satisfy the ordering. Checking that the inorder sequence is strictly increasing is the least error-prone approach.</li>
<li class=""><strong>Recursion depth.</strong> When the tree degenerates into a chain, the call stack of a recursive traversal is also O(n), which can overflow the stack on large inputs; switch to an iterative version with an explicit stack when necessary.</li>
<li class=""><strong>Distinguish "full" from "complete".</strong> The two definitions are easy to mix up: a full binary tree forbids single-child nodes, while a complete binary tree requires the last level to be left-aligned. True/false questions love pitting these two concepts against each other.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://tanzhuo.xyz/en/2026/03/13/shu-ju-jie-gou-shuang-xiang-lian-biao#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>The binary tree is one of the most fundamental and important data structures. Its core traits:</p>
<ul>
<li class="">Each node has at most <strong>two children</strong></li>
<li class="">Supports <strong>efficient search</strong></li>
<li class="">Extends into many advanced data structures</li>
</ul>
<p>Common variants include:</p>
<ul>
<li class="">Binary search tree (BST)</li>
<li class="">AVL tree</li>
<li class="">Red-black tree</li>
<li class="">Heap</li>
</ul>
<p>Understanding binary trees is an essential foundation for learning <strong>algorithms and data structures</strong>. Once you've internalized the recursive definition, the differences between the common shapes, and the traversal patterns, you'll find that balanced trees and heaps—those "advanced" structures—are really just binary trees with extra constraints layered on.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rate Limiting in Istio]]></title>
            <link>https://tanzhuo.xyz/en/2025/11/04/istio</link>
            <guid>https://tanzhuo.xyz/en/2025/11/04/istio</guid>
            <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Global rate limiting on the Istio IngressGateway with Envoy RateLimit + Redis: a full config walkthrough, how requests are evaluated, and the common pitfalls around domain and descriptor matching.]]></description>
            <content:encoded><![CDATA[<p>After one of our production endpoints got hammered, I moved rate limiting from the application layer to the gateway layer. These notes cover the complete setup for global rate limiting on the Istio IngressGateway using Envoy RateLimit + Redis, plus a few matching details that are easy to trip over.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background-and-problem">Background and Problem<a href="https://tanzhuo.xyz/en/2025/11/04/istio#background-and-problem" class="hash-link" aria-label="Direct link to Background and Problem" title="Direct link to Background and Problem" translate="no">​</a></h2>
<p>In a microservice architecture, the system typically exposes a unified entry point through the <strong>Istio IngressGateway</strong>. When traffic spikes (a promotion, crawlers, endpoint abuse) and there's no rate limiting in place, the backend can run into problems like:</p>
<ul>
<li class="">Endpoints hit at high frequency, overloading service CPU or the database</li>
<li class="">Traffic bursts causing system meltdown or cascading failures</li>
<li class="">Core APIs abused by malicious callers, crowding out legitimate users</li>
</ul>
<p>Implementing rate limiting inside each service works, but the rules end up scattered and inconsistent—and by the time a request is rejected, it has already entered the business process and consumed resources. The better approach is <strong>unified rate limiting at the gateway layer</strong>, so traffic is governed before it ever reaches the backend.</p>
<p>Istio's data plane is Envoy Proxy, which has this capability built in: with the <strong>Envoy RateLimit filter + a standalone ratelimit service + Redis</strong>, you get global rate limiting shared across gateway replicas, with per-path access frequency control.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How It Works<a href="https://tanzhuo.xyz/en/2025/11/04/istio#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works" translate="no">​</a></h2>
<p>There are three actors on the path. Once you understand how they relate, the YAML below is easy to read:</p>
<ol>
<li class="">
<p><strong>The Envoy RateLimit filter</strong>. It sits on the IngressGateway's HTTP filter chain. For every request passing through, it builds a set of descriptors from the <code>actions</code> configured on the route, then asks the ratelimit service over gRPC—along with the <code>domain</code>—whether the request should be allowed.</p>
</li>
<li class="">
<p><strong>The ratelimit service</strong> (envoyproxy/ratelimit). A standalone gRPC service that loads quota rules from a ConfigMap. When it receives descriptors, it looks up the rules, decrements the quota, and returns OK or OVER_LIMIT.</p>
</li>
<li class="">
<p><strong>Redis</strong>. The storage for quota counters. Because the counters live in Redis, all gateway replicas share the same counts—that's what makes this "global" rate limiting, as opposed to local rate limiting, where each Envoy instance counts on its own.</p>
</li>
</ol>
<p>The key to matching: the descriptor combination generated by the route's <code>actions</code> must <strong>correspond exactly</strong> to the key/value hierarchy of <code>descriptors</code> in the ConfigMap, and the <code>domain</code> on both sides must match. If any link is off, rate limiting fails silently and every request passes through.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-youd-use-this">When You'd Use This<a href="https://tanzhuo.xyz/en/2025/11/04/istio#when-youd-use-this" class="hash-link" aria-label="Direct link to When You'd Use This" title="Direct link to When You'd Use This" translate="no">​</a></h2>
<p>The typical candidates are low-traffic but sensitive endpoints where abuse is expensive:</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>A hammered login endpoint triggers a flood of password checks and risk-control computation; an abused verification-code endpoint burns SMS fees directly; the payment endpoint touches downstream resources. These endpoints have low QPS by nature, so a very small quota blocks the vast majority of malicious traffic with minimal collateral damage.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="full-configuration">Full Configuration<a href="https://tanzhuo.xyz/en/2025/11/04/istio#full-configuration" class="hash-link" aria-label="Direct link to Full Configuration" title="Direct link to Full Configuration" translate="no">​</a></h2>
<p>Here's a configuration you can apply directly, in four parts: a Secret for the Redis password, a ConfigMap defining quotas, the ratelimit service itself, and two EnvoyFilters—one to mount the filter, one to attach descriptors.</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 password in a Secret (more secure)</span><span class="token plain"></span><br></div><div class="token-line" 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%)">#    Use a Secret for sensitive values (like the Redis password) so they never</span><span class="token plain"></span><br></div><div class="token-line" 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%)">#    appear in plaintext in images or Pod env var history</span><span class="token plain"></span><br></div><div class="token-line" 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 name, referenced by the Deployment via 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%)"># Same namespace as the ratelimit service, for easy reference and management</span><span class="token plain"></span><br></div><div class="token-line" style="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 handles base64 encoding of the plaintext</span><span class="token plain"></span><br></div><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 quota config (runtime config for the 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%)">#    - This ConfigMap is mounted read-only into the container at /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 must exactly match the domain in the Envoy HTTP filter, or nothing matches</span><span class="token plain"></span><br></div><div class="token-line" 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 define the "dimension combinations" for limiting (formed by combined 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          # Must match the domain in the RateLimit 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 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              # Top-level key: matches header_value_match in HTTP_ROUTE (action 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         # Top-level value: from descriptor_value in the route's actions</span><br></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:                   # Second-level descriptors: further refinement (combined into a unique limit 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 scalar string" style="color:hsl(95, 38%, 62%)">          - key: generic_key           # Matches generic_key in HTTP_ROUTE (action 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      # Matches the descriptor_value set on the 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 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             # Time unit: 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     # Quota: requests allowed per unit of time</span><span class="token plain"></span><br></div><div class="token-line" 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 (connects to your existing standalone 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%)">#    - Quota service implementing global sliding-window/token-bucket limits via 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: single replica for testing; in production run at least 2-3 replicas with HA 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: health checks on port 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%)">#    - Key env vars: 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%)"># Resolved by Envoy via cluster name (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%)"># Matches the Deployment's 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 service port (the Envoy RateLimit filter connects here)</span><span class="token plain"></span><br></div><div class="token-line" style="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 for testing; in production use &gt;=3 plus 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%)"># Pin a commit/tag for reproducibility; consider upgrading to a newer official 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%)"># Entry point</span><span class="token plain"></span><br></div><div class="token-line" style="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 (optional: metrics/debugging)</span><span class="token plain"></span><br></div><div class="token-line" 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 main service port</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Health check port (/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%)"># Connect to Redis over TCP</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Your Redis address (in production, use an internal DNS name + sentinel or 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 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%)"># Connection pool size; tune for concurrency/replica count</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Inject the password from the Secret, no plaintext</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Enable if you have a 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%)"># Root of the mount point</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Subdirectory; final config path is /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%)"># Ignore dotfiles</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Must match the runtime path above so config lands in the config 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 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%)"># Initial delay to avoid false negatives during cold start</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Probe interval</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Mounts the ConfigMap above</span><span class="token plain"></span><br></div><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) Inject the global RateLimit filter into the IngressGateway + point it at the 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%)">#    - Uses an EnvoyFilter to insert the global ratelimit HTTP filter before the HTTP 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 comment" style="color:hsl(220, 10%, 40%)">#    - workloadSelector: limits scope to the gateway workload (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: connects to the ratelimit gRPC service defined above (via cluster name/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%)">#    - Also appends a Lua response filter: rewrites backend 429s into a unified JSON body</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Only applies to IngressGateway instances</span><span class="token plain"></span><br></div><div class="token-line" style="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 Insert the ratelimit HTTP filter before the router (applies globally)</span><span class="token plain"></span><br></div><div class="token-line" 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 connection 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 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%)"># Insert before the 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%)"># Must match the domain in the 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">            </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%)"># Whether to reject requests when the ratelimit service is down; false=allow (recommended)</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Timeout for calls to the ratelimit 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">            </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%)"># Cluster name auto-generated by 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), optional</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Use the v3 API (recommended)</span><span class="token plain"></span><br></div><div class="token-line" 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 Insert the Lua response filter on the port 1035 listener (optional: applies only to that 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 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%)"># Only applies to the listener on port 1035 (your gateway exposes this port)</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Notes:</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># - If your gateway doesn't listen on 1035 (or uses multiple ports), consider widening this filter to all ports (drop the portNumber condition)</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># - Alternatively, local rate limiting can generate the 429 locally on rejection, and this Lua rewrites it uniformly</span><span class="token plain"></span><br></div><div class="token-line" 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) Inject per-route rate limit actions into the VirtualHost</span><span class="token plain"></span><br></div><div class="token-line" 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%)">#    - Configure actions on a specific Route (or the whole vhost) to generate descriptors that match the 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"></span><span class="token comment" style="color:hsl(220, 10%, 40%)">#    - Two actions are used here:</span><span class="token plain"></span><br></div><div class="token-line" 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: when :path matches the /api prefix, emit 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: additionally emit 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%)">#    - Combined =&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%)">#      which matches the ConfigMap exactly, activating the quota</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Scope limited to the gateway</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Watch this vhost name: usually "&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%)"># Match the :path pseudo-header, prefix-based</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Only add the descriptor when the match succeeds</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Matches the ConfigMap's top-level 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%)"># Matches the ConfigMap's second-level key/value</span><br></div></code></pre></div></div>
<p>A few points worth unpacking:</p>
<ol>
<li class="">
<p><strong>Both EnvoyFilters are required.</strong> The first one only mounts the ratelimit filter onto the filter chain and tells it where to find the ratelimit service. What actually decides "which requests carry which descriptors to the limiter" is the second one—without <code>rate_limits.actions</code> on the route, the filter does nothing at all for a request.</p>
</li>
<li class="">
<p><strong><code>failure_mode_deny: false</code>.</strong> When the ratelimit service or Redis goes down, allow requests through instead of rejecting them. Rate limiting is a protective measure; it shouldn't become a new single point of failure. Unless an endpoint is so sensitive that you'd rather have it unavailable than abused, keep this false.</p>
</li>
<li class="">
<p><strong>The Lua filter is a UX refinement.</strong> When Envoy's rate limit trips, it returns a bare 429 by default, which frontends can't handle uniformly. This Lua snippet rewrites the 429 body into a consistent JSON structure at the response stage—much friendlier to clients.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2025/11/04/istio#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ul>
<li class=""><strong>A mismatched domain makes rate limiting fail silently.</strong> The <code>domain</code> in the filter and the <code>domain</code> in the ConfigMap must match character for character. When they don't, there's no error—the ratelimit service simply finds no rules and returns OK for everything.</li>
<li class=""><strong>Descriptor hierarchy must correspond exactly.</strong> The two-level structure in the ConfigMap—<code>header_match/path-api</code> nesting <code>generic_key/istio-limit-v1</code>—maps to the order of the two actions on the route. One missing action, a wrong value, or a flipped hierarchy, and nothing matches.</li>
<li class=""><strong>The vhost name isn't arbitrary.</strong> <code>routeConfiguration.vhost.name</code> is usually of the form <code>"&lt;host&gt;:&lt;port&gt;"</code> and must match the vhost name in the route config Envoy actually generates. Confirm it from the gateway's config_dump; if it's wrong, the MERGE simply won't take effect.</li>
<li class=""><strong>Mind the port your EnvoyFilter matches.</strong> The Lua filter above is scoped to <code>portNumber: 1035</code>; if your gateway listens on a different port or multiple ports, adjust or drop that condition, or the 429 rewrite will only apply to part of your traffic.</li>
<li class=""><strong>Availability of the ratelimit service itself.</strong> A single replica is fine for testing; run multiple replicas in production and plan for Redis high availability too. Tune <code>REDIS_POOL_SIZE</code> for your concurrency and replica count.</li>
<li class=""><strong>EnvoyFilter is a fairly low-level extension mechanism.</strong> It patches Envoy config directly and is version-coupled to Envoy's internal APIs. Before a major Istio upgrade, verify in a test cluster that both filters still take effect.</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>tip</div><div class="admonitionContent_BuS1"><p>When debugging rate limits that don't fire, walk the chain in order: does the ratelimit service log show incoming requests → does the domain match → does the descriptor combination line up with the ConfigMap → is the vhost name correct. The vast majority of problems are one of the last three mismatches.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-Up<a href="https://tanzhuo.xyz/en/2025/11/04/istio#wrap-up" class="hash-link" aria-label="Direct link to Wrap-Up" title="Direct link to Wrap-Up" translate="no">​</a></h2>
<p>Global rate limiting at the gateway boils down to three things: the <code>actions</code> on the route tag requests with descriptors, the ratelimit service checks quotas against the rules in the ConfigMap, and Redis lets all gateway replicas share the counts. The configuration itself isn't complicated—the difficulty is concentrated in domain and descriptor matching, where mistakes produce no errors, just silent no-ops. Once the matching chain is clear in your head, the rest is just tuning quotas to your business needs.</p>]]></content:encoded>
            <category>K8s</category>
        </item>
        <item>
            <title><![CDATA[A Real-Time Data Warehouse on Alibaba Cloud]]></title>
            <link>https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang</link>
            <guid>https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang</guid>
            <pubDate>Wed, 10 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Building a fully managed real-time data warehouse with Alibaba Cloud Flink, DataHub, and Hologres: component selection, processing pipeline, and the ODS/DWD/DWS/ADS layered warehouse design.]]></description>
            <content:encoded><![CDATA[<p>Self-hosted big data clusters tend to be complex, hard to maintain, and expensive to deploy. Alibaba Cloud's product ecosystem lets you sidestep all of that. This post documents a real-time data warehouse architecture built entirely on Alibaba Cloud managed products—just enable the services you need to match your business requirements.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pain-points">Pain Points<a href="https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang#pain-points" class="hash-link" aria-label="Direct link to Pain Points" title="Direct link to Pain Points" translate="no">​</a></h2>
<ol>
<li class="">
<p>The underlying database can't handle massive data volumes. As a business grows, 10 TB, 100 TB, or even PB- and EB-scale data no longer fits, and fast query response, data analysis, and data mining become impossible to support.</p>
</li>
<li class="">
<p>Real-time compute performance falls short. You need a reliable compute engine for millisecond-level real-time processing, with data quality that is dependable, controllable, and observable.</p>
</li>
<li class="">
<p>Data model changes are too slow. Without the flexibility to restructure data models quickly, it's hard to keep up with reporting demands from the business.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="use-cases">Use Cases<a href="https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang#use-cases" class="hash-link" aria-label="Direct link to Use Cases" title="Direct link to Use Cases" 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>Real-time risk control with Flink and a rules engine</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>Building a real-time anomaly detection system with Realtime Compute (Flink) and Gaussian models</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="">Building a simple real-time recommendation system with Realtime Compute (Flink)</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-real-time-data-warehouse">The Real-Time Data Warehouse<a href="https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang#the-real-time-data-warehouse" class="hash-link" aria-label="Direct link to The Real-Time Data Warehouse" title="Direct link to The Real-Time Data Warehouse" translate="no">​</a></h2>
<p>Overall data development workflow:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2024-03-p301715-260628d991918b4359af348e3a5f2781.webp" width="1162" height="547" class="img_ev3q"></p>
<p>The pipeline boils down to: data ingestion -&gt; data buffering -&gt; real-time compute -&gt; sink to storage.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2024-03-p399558-45f9e637bf9658f8bc0a4901192767fb.png" width="798" height="448" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="component-selection">Component Selection<a href="https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang#component-selection" class="hash-link" aria-label="Direct link to Component Selection" title="Direct link to Component Selection" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink">Flink<a href="https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang#flink" class="hash-link" aria-label="Direct link to Flink" title="Direct link to 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="">Alibaba Cloud Realtime Compute for Apache Flink</a></p>
<p>A fully managed Flink real-time compute engine from Alibaba Cloud, sparing you from building Yarn and Flink nodes yourself and from all the tedious configuration work. Its job is to process business data flowing through the DataHub message bus and route computed results downstream according to each data stream's business scenario. Being part of the Alibaba Cloud ecosystem also makes metadata management much more convenient.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="datahub">DataHub<a href="https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang#datahub" class="hash-link" aria-label="Direct link to DataHub" title="Direct link to 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 message bus</a></p>
<p>DataHub serves as the data buffering layer, providing high-performance storage and streaming transport for real-time data. Intermediate DWD and DWS layer data produced during Flink computation, as well as downstream data, all pass through it for task forwarding and storage—it acts as the message bus. A single topic can handle terabytes of reads and writes per day, with efficient and stable data flow. This component is also fully managed, so you never think about node counts.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hologres">Hologres<a href="https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang#hologres" class="hash-link" aria-label="Direct link to Hologres" title="Direct link to 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 real-time data warehouse: unified interactive analytics and serving for big data</a></p>
<p>After Flink finishes computing, output for each layer sinks into the Hologres warehouse. Hologres supports row-oriented storage, column-oriented storage, and field-based partitioned storage, solving the massive-data storage problem and delivering sub-second analytics on PB-scale data. Under the hood, Hologres stores data on Pangu, Alibaba's in-house distributed file system (similar to HDFS). Architecturally it follows big data warehouse design principles with full Storage Disaggregation—compute nodes scale out horizontally to increase parallelism and keep queries over massive data fast.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2024-03-p431174-3bb38ec6264307fcee5ef97365184050.png" width="1011" height="595" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="processing-pipeline">Processing Pipeline<a href="https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang#processing-pipeline" class="hash-link" aria-label="Direct link to Processing Pipeline" title="Direct link to Processing Pipeline" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2024-01-3-ad3171d5d2d0142e9db892126744680c.webp" width="1530" height="1140" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="warehouse-layers">Warehouse Layers<a href="https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang#warehouse-layers" class="hash-link" aria-label="Direct link to Warehouse Layers" title="Direct link to Warehouse Layers" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2024-03-v2-27465cd3ad33b8afaa35e914c7f308cb_r-1130bc39ed4a403069f137fd10db67df.png" width="695" height="466" class="img_ev3q"></p>
<p>In the big data world, ODS, DWD, DWS, and ADS are common data layering concepts used to organize and manage data, each playing a distinct role in how data is processed and consumed.</p>
<ol>
<li class="">ODS (Operational Data Store): a storage layer for operational business data, typically holding raw data from source systems while preserving its completeness and detail. The ODS layer primarily supports real-time or near-real-time operational needs, such as online transaction processing and real-time queries. Data at this layer usually undergoes little heavy processing or computation—it serves operational workloads directly.</li>
<li class="">DWD (Data Warehouse Detail): the detail layer of the data warehouse, storing data that has been cleansed, integrated, and transformed—typically subject-oriented, queryable data. DWD data has been through ETL (extract, transform, load) and supports analytics, reporting, and decision support. It offers high quality and consistency, usually organized as fact tables and dimension tables.</li>
<li class="">DWS (Data Warehouse Summary): stores aggregated, summarized, and precomputed data, providing higher-level data summaries and better analytical performance. DWS data is typically organized as summary tables or precomputed metrics, supporting complex analytics, data mining, and business intelligence needs.</li>
<li class="">ADS (Application Data Service): an application-facing data storage layer holding data that has been further processed, computed, and optimized. ADS data is usually application-specific views or datasets, adapted and tuned to deliver faster data access and processing.</li>
</ol>
<p>To sum up: the ODS layer serves operational workloads and preserves raw data intact; the DWD layer provides cleansed, integrated, transformed data ready to query; the DWS layer trades summarization and precomputation for better query performance; and the ADS layer serves specific applications with processed, optimized data.</p>
<p>This layered design brings several benefits:</p>
<ol>
<li class="">Separation of data processing and management: splitting data into layers also splits the responsibilities. The DWD layer handles extraction, cleansing, and integration of raw data; the DWS layer handles aggregation and summarization; the ADS layer provides application-facing data services. Data processing becomes modular and more flexible.</li>
<li class="">Data quality and consistency: the DWD layer preserves the completeness and detail of raw data, useful for data quality analysis and lineage tracing; the DWS layer offers aggregated data with better query performance; the ADS layer guarantees consistency and accuracy across different application scenarios.</li>
<li class="">Query performance and development efficiency: through summarization and precomputation, the DWS layer speeds up analytics and decision-making; the ADS layer exposes application-oriented data services and interfaces, simplifying application development and boosting productivity.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-Up<a href="https://tanzhuo.xyz/en/2024/01/10/a-li-yun-shi-shi-shu-cang#wrap-up" class="hash-link" aria-label="Direct link to Wrap-Up" title="Direct link to Wrap-Up" translate="no">​</a></h2>
<p>The core idea of this architecture is to hand cluster operations over to the cloud vendor: DataHub buffers and routes data, Flink handles real-time compute, and Hologres takes care of storage and analytics—the whole pipeline is managed, with no nodes of your own to maintain. Combined with the ODS/DWD/DWS/ADS layered design, each layer has a clear responsibility, and both query performance and reporting productivity are well covered. For teams that don't want to sink headcount into a self-hosted big data cluster, this is a pragmatic path.</p>]]></content:encoded>
            <category>Linux</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Self-Signed TLS Certificates for gRPC]]></title>
            <link>https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s</link>
            <guid>https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s</guid>
            <pubDate>Wed, 08 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[The complete workflow for generating self-signed certificates for internal gRPC traffic with OpenSSL: root certificate, service certificates, the SAN extension, and verifying certificate contents.]]></description>
            <content:encoded><![CDATA[<p>Microservice communication needs encryption to keep internal traffic secure, so here is a record of the process for generating self-signed certificates.</p>
<!-- -->
<p>Some background first. gRPC defaults to plaintext HTTP/2, which means request bodies between services can be captured and read directly on the internal network. In production, even an internal network cannot be assumed absolutely trustworthy — once a single machine is compromised, sniffing lateral traffic costs the attacker very little. Adding TLS to gRPC is the most direct hardening measure. And for internal services there's no need (and often no way) to request certificates from a public CA; the more common approach is to build your own root certificate and issue a certificate for each service. The whole process can be done with OpenSSL, but there are plenty of details — especially Go's validation rules for certificate fields — worth writing down in full.</p>
<p>Below are the steps to generate self-signed certificates with the OpenSSL command-line tool on Linux or macOS. The process creates a new root certificate (CA), then uses that root certificate to sign a new server certificate.</p>
<p>The certificate hierarchy here has two tiers: the root certificate signs itself and serves as the trust anchor; service certificates are issued by the root. As long as a client trusts the root certificate up front, it can verify any service certificate the root has signed. That's also why only <code>ca.crt</code> needs to be distributed to clients, rather than every individual service certificate.</p>
<p>A quick outline of the overall flow:</p>
<p>Root certificate:</p>
<ol>
<li class="">
<p>Generate the root private key</p>
</li>
<li class="">
<p>Generate the root certificate from the root private key</p>
</li>
</ol>
<p>Service certificate:</p>
<ol>
<li class="">
<p>Generate the service private key</p>
</li>
<li class="">
<p>Generate a service <strong>CSR</strong> from the service private key</p>
</li>
<li class="">
<p>Sign the service .csr file with the root certificate + root certificate key to produce the service certificate .crt file</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="generating-the-root-certificates-private-key"><strong>Generating the Root Certificate's Private Key</strong><a href="https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s#generating-the-root-certificates-private-key" class="hash-link" aria-label="Direct link to generating-the-root-certificates-private-key" title="Direct link to generating-the-root-certificates-private-key" 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%)"># Generate a 2048-bit RSA private key to serve as the root CA's 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">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>This command generates a new 2048-bit RSA private key, saved to a file named ca<code>.key</code>.</p>
<p>This key is the root of the entire trust chain — whoever holds it can issue certificates trusted by every client. Guard it carefully and never commit it to a code repository.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="generating-the-root-certificate"><strong>Generating the Root Certificate</strong><a href="https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s#generating-the-root-certificate" class="hash-link" aria-label="Direct link to generating-the-root-certificate" title="Direct link to generating-the-root-certificate" 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%)"># Self-sign an X.509 root certificate with the root key, valid for 365 days</span><span class="token plain"></span><br></div><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>This command generates a new X.509 root certificate, signed with SHA-256 and valid for 365 days (adjust as you like). The certificate is saved to a file named ca<code>.crt</code>.</p>
<p>The <code>-x509</code> flag means output a self-signed certificate directly rather than a signing request — this is exactly what "the root certificate signs itself" means.</p>
<p>While running this command, OpenSSL will prompt you for some information to be embedded in the certificate. In most cases you can just press Enter to accept the defaults.</p>
<p>However, the <strong>Subject Name or Common Name</strong> should be filled in with the name of your microservice.</p>
<p>With the root certificate ready, the next step is generating certificates for individual services. Just repeat the three steps below for each service.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="generating-the-server-certificates-private-key"><strong>Generating the Server Certificate's Private Key</strong><a href="https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s#generating-the-server-certificates-private-key" class="hash-link" aria-label="Direct link to generating-the-server-certificates-private-key" title="Direct link to generating-the-server-certificates-private-key" 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%)"># Generate a 2048-bit RSA private key for the 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">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>Similar to the first command; the generated private key is saved to a file named [service-name]<code>.key</code>, where [service-name] is your own service's name.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="generating-the-server-certificate-signing-request-csr"><strong>Generating the Server Certificate Signing Request (CSR)</strong><a href="https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s#generating-the-server-certificate-signing-request-csr" class="hash-link" aria-label="Direct link to generating-the-server-certificate-signing-request-csr" title="Direct link to generating-the-server-certificate-signing-request-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%)"># Generate a certificate signing request (CSR) from the service private 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">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>This command generates a new certificate signing request (CSR) containing the server certificate's public key and some additional information, saved to a file named <code>server.csr</code>.</p>
<p>A CSR is not itself a certificate — it's an application form stating "who I am and what my public key is," which only becomes a usable certificate after the CA signs it.</p>
<p>Again, OpenSSL will prompt you for information to be included in the CSR.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="signing-the-server-certificate-with-the-root-certificate"><strong>Signing the Server Certificate with the Root Certificate</strong><a href="https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s#signing-the-server-certificate-with-the-root-certificate" class="hash-link" aria-label="Direct link to signing-the-server-certificate-with-the-root-certificate" title="Direct link to signing-the-server-certificate-with-the-root-certificate" 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%)"># Sign the CSR with the root certificate + root key to produce the service certificate</span><span class="token plain"></span><br></div><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>This command signs the server certificate with the root certificate; the resulting certificate is saved to a file named <code>server.crt</code>.</p>
<p>The <code>-CAcreateserial</code> flag creates a <code>.srl</code> serial-number file that records the serial numbers of certificates this CA has issued; on subsequent signings it increments automatically with no manual handling needed.</p>
<p>The <code>rootCA.crt</code>, <code>server.crt</code>, and <code>server.key</code> produced by the steps above are now ready for encrypted gRPC communication — configure them on the corresponding server and client sides. The server loads its own certificate and private key; the client loads the root certificate to verify the server's identity.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="custom-fields">Custom Fields<a href="https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s#custom-fields" class="hash-link" aria-label="Direct link to Custom Fields" title="Direct link to Custom Fields" translate="no">​</a></h2>
<p>In newer versions of Go, the validation and retrieval of certain certificate fields has changed. (Since Go 1.15, the alt_names field of the certificate is used for service name verification.) So we need to specify some custom fields when generating certificates.</p>
<p>In other words, putting the service name in the Common Name alone is no longer enough: Go's TLS client takes the connection's target name during the handshake and matches it against the certificate's SAN (Subject Alternative Name) field — no match, and the handshake fails outright. The certificate must therefore explicitly carry the SAN extension. Since the interactive command line is awkward for filling in these extension fields, we switch to a configuration file.</p>
<p>Create a san.cnf file with the following contents:</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%)"># Certificate subject info, replacing interactive input</span><span class="token plain"></span><br></div><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%)"># Extensions used at the CSR stage: declare the 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%)"># The SAN list — these DNS names are what the Go client validates against</span><span class="token plain"></span><br></div><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%)"># Extensions used at the certificate-issuing stage</span><span class="token plain"></span><br></div><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>In this config, <code>prompt = no</code> tells OpenSSL to skip the interactive questions and read the <code>[dn]</code> section directly; <code>extendedKeyUsage</code> declares both <code>serverAuth</code> and <code>clientAuth</code>, meaning this certificate can be used on the server side and also as a client certificate in mutual-TLS scenarios.</p>
<p>Then bring the config into the certificate-generation steps above.</p>
<p>When generating the service CSR, add the -extensions req_ext -config san.cnf options:</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%)"># Reference the config file when generating the CSR, carrying the SAN declared in the req_ext section</span><span class="token plain"></span><br></div><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>When issuing the service certificate, likewise add the -extensions v3_ext -extfile san.cnf options:</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%)"># The v3_ext extensions must also be applied at signing time, or the SAN won't make it into the final certificate</span><span class="token plain"></span><br></div><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>Certificates generated this way contain the fields we specified, along with some certificate description information.</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>warning</div><div class="admonitionContent_BuS1"><p>The <code>-extensions v3_ext -extfile san.cnf</code> at the signing step is easy to miss. By default, <code>openssl x509 -req</code> does not carry the CSR's extension fields into the final certificate — if the SAN is declared only at the CSR stage, the signed certificate will still have no SAN.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="verifying-certificate-contents">Verifying Certificate Contents<a href="https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s#verifying-certificate-contents" class="hash-link" aria-label="Direct link to Verifying Certificate Contents" title="Direct link to Verifying Certificate Contents" translate="no">​</a></h2>
<p>You can check whether the generated certificate really contains the expected fields with:</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%)"># Print the certificate in text form to inspect the SAN and other extensions</span><span class="token plain"></span><br></div><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>In the output, focus on whether the <code>X509v3 Subject Alternative Name</code> section contains the expected DNS names, and whether the validity period and issuer are correct. It's worth making this check a fixed step of the process — far easier than debugging a handshake failure after the service is already live.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>The target name the client connects with must match a DNS name in the certificate's SAN. If you connect directly by IP, the SAN needs an IP entry declared as <code>IP.1 = ...</code> — a DNS entry will not match.</p>
</li>
<li class="">
<p>Once a certificate expires, the gRPC handshake fails outright, and this class of failure tends to surface only on expiry day. Nobody reminds you to renew self-signed internal certificates, so record the expiry dates and rotate ahead of time.</p>
</li>
<li class="">
<p><code>ca.key</code> and each service's <code>.key</code> file are sensitive material. Watch the file permissions and never ship them in images or repositories; clients only need <code>ca.crt</code>.</p>
</li>
<li class="">
<p>After changing san.cnf, both the CSR and the signing step must be redone. Re-signing without regenerating the CSR may carry stale subject information.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/11/08/grpcs-t-l-s#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The whole process boils down to one chain of trust: the root key produces the root certificate, the service key produces a CSR, and the root certificate signs the CSR into a service certificate. In Go's gRPC scenario, the key points are writing the SAN field into the certificate via san.cnf, explicitly specifying the extensions at both the CSR and signing stages, and finally verifying with <code>openssl x509 -text</code>. Once these steps are captured in a script, issuing a certificate for a new service is a one-minute job.</p>]]></content:encoded>
            <category>Go</category>
        </item>
        <item>
            <title><![CDATA[Dinky]]></title>
            <link>https://tanzhuo.xyz/en/2023/09/08/dinky</link>
            <guid>https://tanzhuo.xyz/en/2023/09/08/dinky</guid>
            <pubDate>Fri, 08 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Dinky is an open-source platform for developing and managing Flink jobs, with online Flink SQL editing and debugging, whole-database synchronization, and data lineage analysis. Here are my notes on what it does and where it fits.]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="introduction">Introduction<a href="https://tanzhuo.xyz/en/2023/09/08/dinky#introduction" class="hash-link" aria-label="Direct link to Introduction" title="Direct link to Introduction" translate="no">​</a></h2>
<p>Anyone doing real-time computing has probably felt this: Flink itself is powerful enough, but the development and operations experience around it is not friendly. Finished SQL has to be packaged and submitted, debugging means digging through logs, multiple clusters are each managed on their own — and once the number of jobs grows, things get messy. While researching tooling for a real-time data warehouse recently, I came across Dinky. These notes cover which of those problems it solves.</p>
<!-- -->
<p>Dinky is an open-source <strong>platform for developing and managing Flink jobs</strong>. Its design is lightweight and easy to use, and it can manage multiple Flink clusters from one place.
Developers can write and debug Flink SQL online through the web console, then submit it to a chosen Flink cluster with one click, dramatically lowering the development and operations cost of real-time computing tasks.</p>
<p>In other words, it pulls the entire chain of "write SQL, debug, submit, monitor" into a single browser page. Compare that with the native Flink workflow — write code locally, build a jar, submit from the command line, then check status in the Flink Web UI — and this one-stop experience is a huge time-saver for SQL-first real-time job development.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="project-home">Project Home<a href="https://tanzhuo.xyz/en/2023/09/08/dinky#project-home" class="hash-link" aria-label="Direct link to Project Home" title="Direct link to Project Home" 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/en/assets/images/2023-09-image-45b4a814277ea0241a0bcacb441651e3.webp" width="1600" height="832" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="features">Features<a href="https://tanzhuo.xyz/en/2023/09/08/dinky#features" class="hash-link" aria-label="Direct link to Features" title="Direct link to Features" translate="no">​</a></h2>
<p>Dinky ships with built-in <strong>whole-database synchronization</strong>, which can sync database tables from microservice systems into a real-time data warehouse — an effective answer to the data-silo problem in microservice architectures.</p>
<p>This point deserves elaboration. Under a microservice architecture, every service has its own database, and analytics often needs to bring together tens or hundreds of tables. With native Flink CDC, you typically write one source definition per table — tedious at scale, and it eats up a lot of database connections. The idea behind whole-database sync is: one job reads the change log for the entire database (e.g., MySQL's binlog), then splits the stream by table inside the job and writes to downstream targets. Both the configuration burden and the resource footprint are much smaller.</p>
<p>Dinky also supports <strong>developing and managing Flink SQL across multiple Flink versions</strong>, and provides <strong>data lineage analysis</strong> to help developers see data flows and dependencies clearly, which makes troubleshooting and system maintenance easier.</p>
<p>The value of lineage analysis only becomes obvious once job counts grow. When a result table shows anomalous data, you can trace up the lineage graph to the intermediate and source tables it depends on, instead of relying on memory or reading through code. Conversely, before decommissioning or changing a table, you can first see exactly which downstream jobs would be affected.</p>
<p>Overall, Dinky is quite complete across real-time data development, job management, and data governance. It is a genuinely practical Flink development platform and well worth adopting in real-time data warehouse projects.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-a-real-time-data-warehouse"><strong>Building a Real-Time Data Warehouse</strong><a href="https://tanzhuo.xyz/en/2023/09/08/dinky#building-a-real-time-data-warehouse" class="hash-link" aria-label="Direct link to building-a-real-time-data-warehouse" title="Direct link to building-a-real-time-data-warehouse" translate="no">​</a></h2>
<p>With Flink SQL plus Dinky, you can quickly build real-time warehouse jobs, for example:</p>
<ul>
<li class="">Real-time user behavior analysis</li>
<li class="">Real-time transaction statistics</li>
<li class="">Real-time risk control systems</li>
</ul>
<p>What these scenarios have in common: data keeps flowing in, and the business wants results at minute or even second granularity. A traditional offline warehouse running daily or hourly batches can't meet that freshness bar. Writing the logic as streaming jobs in Flink SQL and letting Dinky manage the lifecycle brings the development rhythm very close to writing offline SQL.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-synchronization-and-cdc"><strong>Data Synchronization and CDC</strong><a href="https://tanzhuo.xyz/en/2023/09/08/dinky#data-synchronization-and-cdc" class="hash-link" aria-label="Direct link to data-synchronization-and-cdc" title="Direct link to data-synchronization-and-cdc" translate="no">​</a></h2>
<p>Using Dinky's whole-database sync capability, you can implement Change Data Capture (CDC) and stream data from business databases into a data warehouse or message queue in real time.</p>
<p>In a nutshell, CDC works by subscribing to the database's transaction log: every INSERT / UPDATE / DELETE leaves a record in the log, and the CDC tool parses those records into a stream of change events. Compared to periodic full-table scans, it is less intrusive on the source database, has lower latency, and can even capture deletes. A typical Flink SQL CDC source table definition looks roughly like this:</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%)">-- Declare a CDC source table that captures changes to the MySQL orders table in real time</span><span class="token plain"></span><br></div><div class="token-line" 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%)">-- Use the MySQL CDC connector to read the 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>In Dinky, this SQL can be debugged online and its results previewed; once confirmed, submit it to the cluster — no more repeated local package-and-verify cycles.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-real-time-data-development-platform"><strong>A Real-Time Data Development Platform</strong><a href="https://tanzhuo.xyz/en/2023/09/08/dinky#a-real-time-data-development-platform" class="hash-link" aria-label="Direct link to a-real-time-data-development-platform" title="Direct link to a-real-time-data-development-platform" translate="no">​</a></h2>
<p>Dinky can also serve as a company's unified real-time computing development platform, giving data engineers a standardized development environment.</p>
<p>The point of a unified platform is not just convenience: SQL scripts are hosted centrally with version history, and jobs are submitted and operated the same way for everyone, so new team members don't have to first untangle everyone's personal submission scripts and environment quirks. For team collaboration, that often matters more than any single feature.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2023/09/08/dinky#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ul>
<li class="">Dinky itself does not run compute workloads — it is the entry point for submission and management. The Flink cluster still needs to be deployed and maintained separately, and version compatibility between the two should be confirmed in advance.</li>
<li class="">Before using CDC whole-database sync, verify that the source database has its change log enabled (e.g., MySQL's binlog) and that the sync account has been granted the necessary privileges, or the job won't start.</li>
<li class="">Online debugging is convenient, but the debug environment differs from the production cluster in resources and data volume. For jobs with complex logic, it's still wise to validate in a production-spec environment before going live.</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>tip</div><div class="admonitionContent_BuS1"><p>When evaluating, just spin up a test environment and run the most complex pipeline in your own business end to end (say, whole-database sync of a large table plus a multi-table join in SQL). That tells you far more than reading a feature list.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/09/08/dinky#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>What Dinky fills in is the "developer experience" gap in the Flink ecosystem: online SQL editing, one-click submission, whole-database sync, and lineage analysis cover most of the daily work of real-time warehouse development. If your team's real-time jobs are mostly Flink SQL and you've been missing a unified place to develop and manage them, it deserves a spot on your shortlist.</p>]]></content:encoded>
            <category>Big Data</category>
        </item>
        <item>
            <title><![CDATA[SonarQube]]></title>
            <link>https://tanzhuo.xyz/en/2023/09/08/sonarqube</link>
            <guid>https://tanzhuo.xyz/en/2023/09/08/sonarqube</guid>
            <pubDate>Fri, 08 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[An introduction to the SonarQube code quality platform: how static analysis works, the key quality metrics, wiring up a Maven project, and using it as a Quality Gate in CI/CD pipelines.]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="introduction">Introduction<a href="https://tanzhuo.xyz/en/2023/09/08/sonarqube#introduction" class="hash-link" aria-label="Direct link to Introduction" title="Direct link to Introduction" translate="no">​</a></h2>
<p>Code that runs is not necessarily good code. These are my notes on SonarQube: what problems it can catch, how it works under the hood, and how to use it in CI/CD.</p>
<!-- -->
<p>Why does this topic deserve its own post? Because code quality problems have a peculiar trait: invisible when you write them, expensive when they blow up. Manual code review catches some of it, but it's limited by reviewers' energy and experience, and a lot of the repetitive work — checking for null pointers, unclosed resources, hard-coded passwords — can be handed to a tool entirely. Once a team grows, without a unified, automated quality standard, the codebase quickly fragments into everyone's personal style.</p>
<p>SonarQube is an open-source platform for <strong>code quality management</strong> that helps developers continuously detect potential problems during development.
Through static code analysis, coverage statistics, and multi-dimensional quality metrics, SonarQube can surface defects, security vulnerabilities, and performance risks before code reaches production, improving overall system stability and reducing long-term maintenance cost.</p>
<p>In team and continuous-integration settings, SonarQube typically serves as the <strong>code quality gatekeeper (Quality Gate)</strong>, making quality checks automated and standardized.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How It Works<a href="https://tanzhuo.xyz/en/2023/09/08/sonarqube#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works" translate="no">​</a></h2>
<p>A quick look at how it operates — once the mechanism is clear, the configuration that follows is straightforward.</p>
<p>SonarQube's analysis is <strong>static analysis</strong>: it never runs the code. Instead, it parses the source into a syntax tree and runs a rules engine over it to match known problem patterns. The system splits into two roles:</p>
<ol>
<li class=""><strong>Scanner</strong>: runs on the build machine; it parses source code, executes the rules, collects coverage reports, and uploads the results to the server. There are Scanners for Maven, Gradle, and the command line.</li>
<li class=""><strong>Server</strong>: stores historical data, renders the web reports, and evaluates the Quality Gate. Every scan's results are compared against previous versions, which is how you get quality trends.</li>
</ol>
<p>A Quality Gate is essentially a set of threshold assertions — for instance "zero bugs in new code," or "new code coverage no lower than some percentage." After a scan, the server evaluates each condition; if any one fails, the overall status is a failure — and the CI pipeline can use that status to decide whether to halt a release. This is the key difference from tools that merely "run and hand you a report to browse": the result can actually block the process.</p>
<p>Another concept worth mentioning is "New Code": for legacy projects, fixing every historical issue at once is unrealistic, so SonarQube's default strategy is to apply strict standards only to added and modified code, while the backlog gets paid down gradually. This lets old projects onboard smoothly.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="improving-code-quality"><strong>Improving Code Quality</strong><a href="https://tanzhuo.xyz/en/2023/09/08/sonarqube#improving-code-quality" class="hash-link" aria-label="Direct link to improving-code-quality" title="Direct link to improving-code-quality" translate="no">​</a></h2>
<p>SonarQube automatically detects potential problems in code, such as:</p>
<ul>
<li class="">Bugs</li>
<li class="">Security Vulnerabilities</li>
<li class="">Code Smells</li>
<li class="">Potential performance issues</li>
</ul>
<p>These categories descend in severity. Bugs are logic that will very likely go wrong — conditions that are always true, possible null-pointer dereferences. Security vulnerabilities cover risks like injection and hard-coded credentials. Code smells don't affect correctness but make the code progressively harder to change — overly long methods, deeply nested logic. SonarQube tags each issue with a severity level and an estimated fix time, which helps with prioritization.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="improving-maintainability"><strong>Improving Maintainability</strong><a href="https://tanzhuo.xyz/en/2023/09/08/sonarqube#improving-maintainability" class="hash-link" aria-label="Direct link to improving-maintainability" title="Direct link to improving-maintainability" translate="no">​</a></h2>
<p>SonarQube analyzes code across multiple dimensions, for example:</p>
<ul>
<li class="">Complexity</li>
<li class="">Duplicated code</li>
<li class="">Test coverage</li>
<li class="">Technical Debt</li>
</ul>
<p>Through these metrics, developers get a much clearer picture of the system's health and can refactor and optimize with focus.</p>
<p>Technical debt here is a very intuitive measure: sum the estimated effort of all outstanding issues, and you get the time needed to "pay off the debt." It may not be precise, but the trend is meaningful — steadily rising technical debt means the team is borrowing against its future development velocity.</p>
<p>Developers can find and fix these problems before release, reducing production incidents.</p>
<p>Beyond detection itself, SonarQube brings several engineering-practice advantages:</p>
<ol>
<li class="">A rich plugin ecosystem: SonarQube supports many languages and frameworks, and its extensive plugin ecosystem lets you pick the right plugins to extend functionality for your project.</li>
<li class="">Continuous integration and deployment: SonarQube integrates with CI and CD tools to automate code checking and quality measurement, ensuring every build maintains good code quality.</li>
<li class="">Better team collaboration: SonarQube provides a centralized platform where team members can view code quality and metrics in one unified place, improving collaboration efficiency.</li>
<li class="">Better code maintenance: by analyzing quality metrics, developers gain a clearer understanding of the code's health and can maintain and optimize it with more precision.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started">Getting Started<a href="https://tanzhuo.xyz/en/2023/09/08/sonarqube#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started" translate="no">​</a></h2>
<p>Example setup:
Suppose we have a Java project. First add SonarQube's Maven plugin to the project, then run the command below to trigger static analysis:</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 plugin configuration --&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;!-- The official Maven Scanner plugin from SonarQube --&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;!-- Bind to the verify phase: scan after unit tests, before install/deploy --&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>Besides the plugin itself, the Scanner needs to know where to send results: the server address (<code>sonar.host.url</code>) and the authentication token (<code>sonar.login</code> or a token) usually go in Maven's <code>settings.xml</code> or in CI environment variables. Don't hard-code them in <code>pom.xml</code> and commit them to the repository.</p>
<p>After running <code>mvn clean verify</code>, SonarQube automatically analyzes the project's source code and generates a report on the SonarQube server. Developers can view the report through the server's web interface and optimize the code based on its findings.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="using-it-in-a-devops-pipeline">Using It in a DevOps Pipeline<a href="https://tanzhuo.xyz/en/2023/09/08/sonarqube#using-it-in-a-devops-pipeline" class="hash-link" aria-label="Direct link to Using It in a DevOps Pipeline" title="Direct link to Using It in a DevOps Pipeline" translate="no">​</a></h2>
<p>Running a scan on a single machine is only the starting point — SonarQube's real value shows up inside a pipeline. In an actual DevOps workflow, SonarQube is typically paired with the <strong>CI/CD toolchain</strong>, for example:</p>
<ol>
<li class="">Developers push code to the Git repository</li>
<li class="">The CI system (e.g., Jenkins) triggers a build</li>
<li class="">The build runs the SonarQube scan</li>
<li class="">The Quality Gate decides whether the release may proceed</li>
<li class="">Scan results are pushed to the team via DingTalk / WeCom / email</li>
</ol>
<p>Step 4 is the heart of the whole chain: a failed Quality Gate halts the pipeline, blocking quality problems before merge or release instead of digging through reports afterwards. Combined with the platform's Pull Request analysis, issues can even be annotated directly in the review interface, letting code review focus on design while the mechanical checks go to the tool.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2023/09/08/sonarqube#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<p>A few things trip people up in real deployments:</p>
<ol>
<li class=""><strong>The server's resource requirements are not small.</strong> SonarQube embeds Elasticsearch for indexing, which has real memory demands; on Linux you usually also need to raise kernel parameters like <code>vm.max_map_count</code>, or the service won't start. Small teams can start with a single-node Docker deployment, but don't put it on an under-provisioned machine.</li>
<li class=""><strong>SonarQube does not compute coverage.</strong> It only reads coverage reports (in the Java ecosystem, typically generated by JaCoCo). If the build doesn't generate a report first, the coverage on the dashboard stays at 0 — the single most common source of confusion for newcomers.</li>
<li class=""><strong>Tailor the rules to your team.</strong> The default rule set is comprehensive; enabling everything wholesale produces a flood of warnings, and the team quickly becomes "immune" to the results. Start from the default Quality Profile, disable rules that clearly don't apply, and make every warning worth acting on.</li>
<li class=""><strong>For legacy projects, start with "New Code."</strong> Don't try to zero out the backlog in one go. Put the Quality Gate's constraints on new code, and pay down the existing debt on a long-term plan.</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>tip</div><div class="admonitionContent_BuS1"><p>Where the scan sits in the pipeline matters too: too early (a full scan on every push) slows down feedback; too late (only scanning before release) lets problems pile up until they're hard to fix. A common approach is to scan once on merge requests and once on mainline builds.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/09/08/sonarqube#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The core problem SonarQube solves is turning code quality from "people keeping watch" into "process guarantees." Static analysis finds bugs, vulnerabilities, and code smells; multi-dimensional metrics quantify maintainability; and the Quality Gate wires those standards into CI/CD, blocking anything below the bar. Onboarding is cheap — one Maven plugin and one command gets it running. The hard part is tuning the rules and thresholds to fit your team afterwards, so that it becomes a gatekeeper people genuinely trust rather than a red warning everyone ignores.</p>]]></content:encoded>
            <category>CI/CD</category>
            <category>Tech</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[The Canal Component]]></title>
            <link>https://tanzhuo.xyz/en/2023/09/06/canal</link>
            <guid>https://tanzhuo.xyz/en/2023/09/06/canal</guid>
            <pubDate>Wed, 06 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Canal is Alibaba's open-source component for incremental subscription and consumption based on MySQL Binlog. This post walks through how it works, how it compares to other common CDC tools, and what to watch out for in a highly available cluster deployment.]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="introduction">Introduction<a href="https://tanzhuo.xyz/en/2023/09/06/canal#introduction" class="hash-link" aria-label="Direct link to Introduction" title="Direct link to Introduction" translate="no">​</a></h2>
<p>When building business systems, you frequently run into this requirement: a row changes in the database, and downstream caches, search engines, and data warehouses all need to change with it. Dual writes in application code are easy to miss; periodic full-table comparisons are far too heavy. There is a more elegant answer to this class of problems — subscribing directly to the database's change log — and that's why I put together these notes on Canal.</p>
<!-- -->
<p>Canal is an open-source Alibaba component for <strong>real-time data subscription and consumption based on MySQL Binlog</strong>, commonly used to implement Change Data Capture (CDC).</p>
<p>In real-world business scenarios, database changes often need to be propagated beyond ordinary reads and writes, for example:</p>
<ul>
<li class="">Real-time data synchronization (database → data warehouse)</li>
<li class="">Pushing data changes to an MQ (Kafka / RocketMQ)</li>
<li class="">Building real-time analytics or monitoring systems</li>
<li class="">Syncing data to a search engine (such as Elasticsearch)</li>
</ul>
<p>What these scenarios share is that downstream systems don't care about "what the database looks like right now" — they care about "what changed." If application code has to manually notify downstream systems after every write, it's not only invasive but also very hard to keep consistent with the database transaction: if the write succeeds but the notification fails, the data quietly drifts out of sync. The Binlog, on the other hand, is the log MySQL itself guarantees to write, so using it as the data source sidesteps the dual-write problem entirely.</p>
<p>To make this work, Canal <strong>subscribes to the Binlog by impersonating a MySQL slave</strong>, parses out the insert/update/delete operations, and pushes these change events to downstream consumers in real time, achieving near-real-time data synchronization.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How It Works<a href="https://tanzhuo.xyz/en/2023/09/06/canal#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works" translate="no">​</a></h2>
<p>Canal borrows the mechanics of MySQL master-slave replication. In normal replication, the slave sends a dump request to the master, the master continuously streams Binlog events to the slave, and the slave replays those events to stay in sync.</p>
<p>What Canal does is "disguise" itself as a slave:</p>
<ol>
<li class="">It sends a dump protocol request to MySQL, using exactly the same interaction as a real replica;</li>
<li class="">MySQL streams Binlog events to Canal, which parses the binary log and reconstructs the changes for every row;</li>
<li class="">The parsed, structured events are handed to downstream consumers — either pulled by clients over TCP, or delivered directly to a message queue such as Kafka or RocketMQ.</li>
</ol>
<p>From MySQL's point of view, Canal is just an ordinary replica. No plugin needs to be installed on the database side, which is why this approach is so low-touch.</p>
<p>For Canal to subscribe successfully, MySQL needs to meet a few prerequisites:</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%)"># Key settings in 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        # Enable 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        # Must be ROW mode to capture the full before/after image of every 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              # Unique ID for each node in the replication topology; must not clash with Canal's slaveId</span><br></div></code></pre></div></div>
<p>The Binlog format must be ROW. STATEMENT mode records the SQL statements themselves, from which the concrete per-row changes cannot be reconstructed; only ROW mode records row-level before-and-after images, which is what makes CDC meaningful. In addition, the account Canal uses to connect to MySQL needs the <code>REPLICATION SLAVE</code> and <code>REPLICATION CLIENT</code> privileges — the same requirements as a real replica.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="comparing-common-cdc-components">Comparing Common CDC Components<a href="https://tanzhuo.xyz/en/2023/09/06/canal#comparing-common-cdc-components" class="hash-link" aria-label="Direct link to Comparing Common CDC Components" title="Direct link to Comparing Common CDC Components" translate="no">​</a></h2>
<p>The CDC components you'll commonly see today are:</p>
<ul>
<li class="">Canal</li>
<li class="">Debezium</li>
<li class="">Flink CDC</li>
</ul>
<p>Canal currently supports only MySQL, versions 5.x and 8.x.</p>
<p><a href="https://github.com/alibaba/canal" target="_blank" rel="noopener noreferrer" class="">GitHub - alibaba/canal: Alibaba's MySQL binlog incremental subscription &amp; consumption component</a></p>
<p>The core mechanism of all these tools is essentially the same: <strong>parse the database's Binlog to obtain the changed data along with the operation type (INSERT / UPDATE / DELETE)</strong>, and turn those changes into structured events for downstream systems to consume.</p>
<p>Where they differ is mainly in ecosystem positioning. Debezium is built on Kafka Connect, supports MySQL, PostgreSQL, and several other databases, and suits teams that already run Kafka. Flink CDC embeds change capture directly into Flink stream processing, so capture and transformation happen in the same job. Canal is more lightweight and focuses exclusively on MySQL.</p>
<p>Note that <strong>Canal currently supports mainly MySQL (versions 5.x and 8.x)</strong>, which is why it is so widely used to build real-time synchronization and data distribution systems within the MySQL ecosystem. If your stack is pure MySQL and you don't want to bring in a heavy dependency like Kafka Connect or Flink, Canal is a pragmatic choice.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="high-availability">High Availability<a href="https://tanzhuo.xyz/en/2023/09/06/canal#high-availability" class="hash-link" aria-label="Direct link to High Availability" title="Direct link to High Availability" translate="no">​</a></h2>
<p>For high availability, Canal offers a <strong>cluster deployment mode</strong>. In a cluster, each Server node manages multiple synchronization task instances (Instances), and a task-distribution mechanism spreads the load.</p>
<p>Cluster mode relies on ZooKeeper for coordination: multiple Server nodes compete for the right to run a given Instance, only one node actually runs it at any moment, and the rest stand by. When the running node fails, a standby node detects this via ZooKeeper and takes over the task. The consumption position (i.e., the Binlog consumption progress) is also stored in ZooKeeper, so the node taking over can resume from the last recorded position, avoiding data loss or large-scale duplication.</p>
<p>In practice, though, network hiccups, database connection failures, or resource limits can occasionally cause synchronization instances to terminate. In production, Canal is therefore usually paired with <strong>an automatic restart mechanism or an ops monitoring system</strong> so that tasks recover automatically after failures.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2023/09/06/canal#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<p>Building on the mechanics above, a few points are worth thinking through before rollout:</p>
<ol>
<li class="">
<p><strong>Binlog format and retention.</strong> The upstream database must run in ROW mode, and Binlog retention needs to be long enough. Otherwise, if Canal is down for a while and the logs at its saved position have already been purged, the task cannot resume and must be re-initialized from scratch.</p>
</li>
<li class="">
<p><strong>The delivery semantics are at-least-once.</strong> After a failover or restart, a small window of change events may be delivered again. Downstream consumers must deduplicate by primary key and never assume each change arrives exactly once.</p>
</li>
<li class="">
<p><strong>Ordering depends on the partitioning strategy.</strong> When delivering to an MQ, if changes to multiple tables or primary keys get scattered across different partitions, the consumption order may diverge from the Binlog order. For order-sensitive scenarios, route partitions by table name or primary key.</p>
</li>
<li class="">
<p><strong>Zombie Instances.</strong> Task instances sometimes don't exit cleanly — they stall at a position and stop advancing. Monitoring can't just check process liveness; it must also verify that the position keeps moving forward and track the lag against the database's current Binlog position.</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>tip</div><div class="admonitionContent_BuS1"><p>Before going live, run a "link-break drill": manually kill the running Server node and watch whether the standby takes over as expected and whether the position resumes correctly. It's far more comfortable than validating this for the first time during a real incident.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/09/06/canal#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Canal's idea is not complicated: impersonate a MySQL replica, subscribe to the Binlog over the standard replication protocol, and reconstruct row-level changes into structured events for downstream consumption. It's zero-intrusion on the database and lightweight to deploy, making it a great fit for real-time sync and data distribution in a pure-MySQL stack. What deserves engineering attention are the ROW format and Binlog retention policy, idempotent consumption downstream, and monitoring based on position advancement — get those solid, and the pipeline's stability follows.</p>]]></content:encoded>
            <category>Java</category>
            <category>Linux</category>
            <category>Database</category>
            <category>Big Data</category>
        </item>
        <item>
            <title><![CDATA[Polaris Mesh]]></title>
            <link>https://tanzhuo.xyz/en/2023/09/01/polaris</link>
            <guid>https://tanzhuo.xyz/en/2023/09/01/polaris</guid>
            <pubDate>Fri, 01 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[PolarisMesh is Tencent's open-source one-stop cloud-native service governance platform, covering service registration and discovery, traffic management, fault tolerance, and configuration management. This post walks through its core features and compares its positioning against Nacos.]]></description>
            <content:encoded><![CDATA[<p>PolarisMesh delivers a complete set of microservice governance capabilities — it is Tencent's open-source, one-stop cloud-native service governance platform. Built around <strong>service registration and discovery, traffic management, fault tolerance, and configuration management</strong>, it provides a stable and reliable foundation for microservice architectures. With PolarisMesh, service instances register and discover each other automatically, and combined with intelligent load-balancing strategies, requests are distributed sensibly across instances, improving the system's overall stability and throughput.</p>
<!-- -->
<p>Official site: <a href="http://polarismesh.cn/" target="_blank" rel="noopener noreferrer" class="">PolarisMesh</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="microservice-governance">Microservice Governance<a href="https://tanzhuo.xyz/en/2023/09/01/polaris#microservice-governance" class="hash-link" aria-label="Direct link to Microservice Governance" title="Direct link to Microservice Governance" translate="no">​</a></h2>
<p>For traffic management, PolarisMesh supports <strong>rate limiting, circuit breaking with degradation, retries, and canary releases</strong>. When a service misbehaves or its response time climbs, the system can automatically trip the circuit breaker to keep the failure from spreading. Meanwhile, flexible traffic-control policies let you route traffic by percentage, by label, or by version, making version upgrades and feature rollouts safer and more controlled.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="high-performance-proxy">High-Performance Proxy<a href="https://tanzhuo.xyz/en/2023/09/01/polaris#high-performance-proxy" class="hash-link" aria-label="Direct link to High-Performance Proxy" title="Direct link to High-Performance Proxy" translate="no">​</a></h2>
<p>PolarisMesh ships with a high-performance proxy component, Polaris Sidecar, which can serve as the data plane in a service mesh. Built on a high-performance network model, the proxy handles request forwarding and response processing efficiently, keeping network overhead as low as possible while providing full governance capabilities. With the proxy model, applications gain complete service governance without any intrusion into business code, dramatically lowering the cost of adoption.</p>
<p>PolarisMesh also supports a <strong>Proxyless mode</strong>, meaning services can adopt governance capabilities without a sidecar proxy. Developers can integrate directly via SDK or Java Agent, choosing whichever integration style fits their architecture best.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="multi-protocol-support">Multi-Protocol Support<a href="https://tanzhuo.xyz/en/2023/09/01/polaris#multi-protocol-support" class="hash-link" aria-label="Direct link to Multi-Protocol Support" title="Direct link to Multi-Protocol Support" translate="no">​</a></h2>
<p>PolarisMesh supports the major communication protocols — <strong>HTTP, gRPC, TCP, and more</strong> — covering the communication needs of different kinds of microservices. It also offers SDKs for multiple languages, including <strong>Java, Go, Python, and C++</strong>, and supports common microservice frameworks such as Spring Cloud and Dubbo, so developers can bring existing systems onto PolarisMesh quickly without large-scale changes to business code.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="service-configuration-management">Service Configuration Management<a href="https://tanzhuo.xyz/en/2023/09/01/polaris#service-configuration-management" class="hash-link" aria-label="Direct link to Service Configuration Management" title="Direct link to Service Configuration Management" translate="no">​</a></h2>
<p>Beyond governance, PolarisMesh includes a configuration center for centrally managing the configuration a microservice needs at runtime. Developers can manage service parameters in one place, with support for dynamic configuration updates: applications load new configuration without restarting, improving both operational efficiency and flexibility.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="observability">Observability<a href="https://tanzhuo.xyz/en/2023/09/01/polaris#observability" class="hash-link" aria-label="Direct link to Observability" title="Direct link to Observability" translate="no">​</a></h2>
<p>In a microservice architecture, observability is essential to keeping the system running smoothly. PolarisMesh provides comprehensive monitoring and logging, collecting key data in real time: call traces, request success rates, latency metrics, and more. With these metrics, operators can quickly pinpoint bottlenecks and misbehaving services, speeding up troubleshooting.</p>
<p>PolarisMesh also offers an <strong>event center and operation auditing</strong>, recording significant system events and user operations to support both operations and security audits.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="extensibility-and-flexibility">Extensibility and Flexibility<a href="https://tanzhuo.xyz/en/2023/09/01/polaris#extensibility-and-flexibility" class="hash-link" aria-label="Direct link to Extensibility and Flexibility" title="Direct link to Extensibility and Flexibility" translate="no">​</a></h2>
<p>PolarisMesh is built on a cloud-native architecture, with native support for <strong>Kubernetes, container environments, and hybrid-cloud deployments</strong>. Its components communicate through loose coupling, so users can extend individual modules as business needs dictate. It can also be used together with a Service Mesh architecture, giving microservice systems stronger governance capabilities without giving up performance.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="polarismesh-vs-nacos">PolarisMesh vs. Nacos<a href="https://tanzhuo.xyz/en/2023/09/01/polaris#polarismesh-vs-nacos" class="hash-link" aria-label="Direct link to PolarisMesh vs. Nacos" title="Direct link to PolarisMesh vs. Nacos" translate="no">​</a></h2>
<p>PolarisMesh and Nacos are both common service governance components in microservice architectures, but they differ in positioning and emphasis.</p>
<p>In terms of positioning, <strong>Nacos leans toward being a service registry and configuration center</strong>, focused primarily on service discovery and configuration management, whereas <strong>PolarisMesh positions itself as a complete microservice governance platform</strong> — it provides registration and configuration management, plus much richer traffic and service governance capabilities.</p>
<p>In terms of architectural capability, PolarisMesh offers a more complete, integrated feature set across traffic management, service governance, and observability — routing policies, rate limiting, circuit breaking, canary releases, and so on — while Nacos concentrates on service registration and configuration management themselves.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/09/01/polaris#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>PolarisMesh consolidates registration and discovery, traffic management, fault tolerance, configuration management, and observability into a single platform, and its two integration modes — Sidecar and Proxyless — give teams with different architectures room to choose. If all you need is a registry plus a configuration center, Nacos is pleasantly lightweight; but once governance needs like rate limiting, circuit breaking, and canary releases surface, the all-in-one PolarisMesh saves you the cost of stitching multiple components together. Before choosing, it's worth mapping out your actual governance requirements and then deciding which layer of capability to bring in.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Go</category>
            <category>K8s</category>
        </item>
        <item>
            <title><![CDATA[Alibaba Cloud ASK Clusters]]></title>
            <link>https://tanzhuo.xyz/en/2023/07/29/a-li-ask-ji-qun</link>
            <guid>https://tanzhuo.xyz/en/2023/07/29/a-li-ask-ji-qun</guid>
            <pubDate>Sat, 29 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Notes from evaluating Alibaba Cloud ASK (Serverless Kubernetes): what it is and how it works, its pros and cons versus self-hosted K8S, and the cost and vendor lock-in concerns to weigh when choosing it.]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2023/07/29/a-li-ask-ji-qun#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>While evaluating our team's containerization options recently, one question was unavoidable: should the K8S cluster be self-hosted, managed, or should we skip nodes altogether? ASK, a fully serverless flavor of cluster, sits squarely in that last category, and it's worth writing up separately.</p>
<!-- -->
<p>For small teams, the hidden cost of self-hosting K8S is routinely underestimated: the control plane needs high availability, etcd needs backups, versions need upgrading, nodes need patching. None of this work produces business value, yet none of it can be skipped. Cloud vendors' managed offerings target exactly this pain, and ASK goes one step further by removing the node layer from the user's view entirely.</p>
<p>Alibaba Cloud ASK (Alibaba Cloud ACK) is Alibaba Cloud's managed Kubernetes service. Built on the open-source Kubernetes project, it gives users an easy-to-use, elastically scalable, highly available, and secure platform for deploying and managing containers — essentially serverless Kubernetes. You can deploy containerized applications directly without purchasing nodes, with no node maintenance or capacity planning for the cluster, and you pay on demand based on the CPU and memory resources your applications are configured with. ASK clusters offer full Kubernetes compatibility while lowering the barrier to using Kubernetes, letting you focus on your applications rather than managing the underlying infrastructure.</p>
<p>The "SK" in ASK stands for Serverless Kubernetes — the official docs now also call it ACK Serverless. The core difference from standard managed ACK: the managed version runs the control plane for you, but the worker nodes are still your own ECS instances; ASK does away with the concept of worker nodes entirely, and the smallest resource unit you deal with is the Pod itself.</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="">What is ACK Serverless — Container Service for Kubernetes (ACK) — Alibaba Cloud Help Center</a></p>
<p>Pods in an ASK cluster run on Alibaba Cloud Elastic Container Instance (ECI) in a securely isolated container runtime environment. Each Pod instance is fully and strongly isolated underneath by a lightweight virtualized security sandbox, so container instances cannot affect one another.</p>
<p>Let me unpack that mechanism a bit. In traditional K8S, multiple Pods share the host machine's kernel, and isolation relies on namespaces and cgroups — soft isolation. If a container-escape vulnerability appears, other Pods on the same node can be affected. ECI's approach wraps every Pod in a lightweight VM sandbox: Pods don't share a kernel, so the isolation level approaches that of a virtual machine, while startup time and resource overhead stay far below a traditional VM. To the user, the sandbox is transparent — you still submit standard K8S resources like Deployments and Services, and the scheduling layer lands your Pods on ECI.</p>
<p>And precisely because billing is based on the CPU and memory declared by each Pod, resource requests/limits in ASK are no longer just scheduling parameters — they directly determine the numbers on your bill, which is a very different mindset from running your own cluster.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="advantages-of-ask-clusters">Advantages of ASK Clusters<a href="https://tanzhuo.xyz/en/2023/07/29/a-li-ask-ji-qun#advantages-of-ask-clusters" class="hash-link" aria-label="Direct link to Advantages of ASK Clusters" title="Direct link to Advantages of ASK Clusters" translate="no">​</a></h2>
<ol>
<li class="">Easy to use: ASK provides an intuitive graphical interface and command-line tools, making it easy to create, deploy, and manage Kubernetes clusters.</li>
<li class="">Elastic scaling: ASK clusters automatically scale out and in based on actual demand, keeping applications performant and adapting flexibly to traffic changes.</li>
<li class="">High availability: ASK clusters use a multi-availability-zone architecture with automatic fault tolerance and recovery, keeping applications available during failures and minimizing business interruption.</li>
<li class="">Secure and reliable: ASK clusters offer multiple layers of security — network isolation, access control, data encryption, and more — to protect applications and data.</li>
<li class="">Ecosystem integration: ASK integrates seamlessly with other Alibaba Cloud products and services, such as Container Registry, Log Service, and CloudMonitor, providing a comprehensive solution.</li>
</ol>
<p>Of these, I find elastic scaling the most practically meaningful. In a self-hosted cluster, scaling out Pods first requires schedulable nodes to exist; node scale-out alone takes minutes at minimum, plus cluster-autoscaler tuning. Once ASK removes the node layer, scaling out reduces to a single action — launching Pods — so the elasticity path is much shorter. For workloads with pronounced traffic peaks and troughs, this difference shows up directly in both responsiveness and cost.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="some-downsides">Some Downsides<a href="https://tanzhuo.xyz/en/2023/07/29/a-li-ask-ji-qun#some-downsides" class="hash-link" aria-label="Direct link to Some Downsides" title="Direct link to Some Downsides" translate="no">​</a></h2>
<ol>
<li class="">Relatively high price: ASK carries service fees, which may matter for budget-constrained users. Compared with an ACK cluster on annual/monthly subscription, pay-as-you-go can incur extra costs if resource allocation is uneven.</li>
<li class="">Steep learning curve: users who have never worked with Kubernetes will need time to learn and adjust to ASK's concepts and workflows.</li>
<li class="">Cloud provider lock-in: using ASK means deploying your applications on Alibaba Cloud, so you may face constraints and dependencies tied to that specific provider.</li>
</ol>
<p>One more note on price: pay-as-you-go unit prices are usually higher than subscription ECS, so ASK only pays off when your workload is genuinely elastic — always-on services running at full load 24/7 are actually cheaper on reserved resources. Before choosing, it's worth estimating how much of your workload is "steady-state" versus "elastic."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2023/07/29/a-li-ask-ji-qun#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Configure resource specs carefully. ASK bills by the resources each Pod declares: casually oversized requests inflate the bill, while undersized ones risk OOM kills. Before going live, base your configuration on load-test results that reflect real usage.</p>
</li>
<li class="">
<p>Watch for behavioral differences from standard K8S. Having no real nodes means node-dependent features — DaemonSet, hostPath, hostNetwork — behave differently from a normal cluster. Verify each item before migrating existing applications, and treat the official docs as the source of truth for what's supported.</p>
</li>
<li class="">
<p>Cold starts aren't free. Spinning up a Pod on ECI takes extra instance-creation time compared with scheduling onto an existing node. For latency-sensitive bursty traffic, consider pre-warming or keeping some always-on replicas.</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>tip</div><div class="admonitionContent_BuS1"><p>If you just want to try the serverless K8S experience, run a test application on the smallest Pod spec, watch the bill for a while, and then decide whether to migrate core workloads.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://tanzhuo.xyz/en/2023/07/29/a-li-ask-ji-qun#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>Compared with building and maintaining your own K8S cluster — heavy time investment, high maintenance cost, long-term upkeep, tedious upgrades, security patching you have to guarantee yourself, fiddly configuration, and real demands on your team's ops capability — using ASK spares you all of that and lets you pour more energy into product development. And when traffic grows later, you simply scale out Pod replicas, without managing the physical limits of underlying node resources.</p>
<p>My take: for a small team without dedicated ops staff, a serverless cluster like ASK is a pragmatic starting point. Once the business scales up and cost sensitivity rises, it's not too late to reevaluate moving back to managed or self-hosted clusters.</p>]]></content:encoded>
            <category>K8s</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Principles of Architecture Design]]></title>
            <link>https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze</link>
            <guid>https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze</guid>
            <pubDate>Sun, 18 Jun 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[System architecture design means trading off scalability, reliability, performance, security, maintainability, and more. This post walks through these common design principles and the mindset an architect should bring to them.]]></description>
            <content:encoded><![CDATA[<p>Designing a system architecture usually requires weighing the system's capabilities across multiple dimensions. A good architecture doesn't just meet today's business needs — it also has to accommodate future business growth and technical evolution. The design principles that most commonly deserve attention are the following.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="extensibility">Extensibility<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#extensibility" class="hash-link" aria-label="Direct link to Extensibility" title="Direct link to Extensibility" translate="no">​</a></h2>
<p>A system should be designed with good extensibility, so it can support future business growth and changing requirements. This usually means adopting modular, layered architectures, so that new features, larger data volumes, or more users can be added without jeopardizing overall stability.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reliability">Reliability<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#reliability" class="hash-link" aria-label="Direct link to Reliability" title="Direct link to Reliability" translate="no">​</a></h2>
<p>A system needs a high degree of reliability — it must stay stable and available over long-term operation. Architecture design typically has to account for failure recovery mechanisms, fault tolerance, and data backup strategies, so that the business can recover quickly when something goes wrong.</p>
<p>A highly reliable system generally needs to be able to:</p>
<ul>
<li class="">Detect failures automatically</li>
<li class="">Repair problems automatically</li>
<li class="">Switch over automatically (failover)</li>
</ul>
<p>These mechanisms minimize the business impact of system failures.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="extreme-performance">Extreme Performance<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#extreme-performance" class="hash-link" aria-label="Direct link to Extreme Performance" title="Direct link to Extreme Performance" translate="no">​</a></h2>
<p>Architecture also has to address performance, ensuring the system responds to user requests quickly and handles heavy concurrent load. Common performance optimization techniques include:</p>
<ul>
<li class="">Well-chosen data structures and algorithms</li>
<li class="">Caching</li>
<li class="">Load balancing</li>
<li class="">Concurrency control</li>
</ul>
<p>In practice, performance is usually measured in <strong>TPS (Transactions Per Second)</strong>. System performance and concurrent user count are not simply positively correlated. In general:</p>
<ul>
<li class="">A system's maximum TPS is fixed within a certain range</li>
<li class="">Concurrent user count can be regulated through queuing, rate limiting, and similar techniques</li>
</ul>
<p>Performance testing usually means simulating the worst case and stress-testing the servers. For example:</p>
<ul>
<li class="">Large systems: 10,000 – 50,000 concurrent users</li>
<li class="">Small to mid-sized systems: around 5,000 concurrent users</li>
</ul>
<p>System throughput is calculated as:</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">Throughput (TPS) = concurrency / average response time</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="security">Security<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#security" class="hash-link" aria-label="Direct link to Security" title="Direct link to Security" translate="no">​</a></h2>
<p>A system must be designed with solid security to protect user data and system resources. Common security measures include:</p>
<ul>
<li class="">Authentication</li>
<li class="">Access control (authorization)</li>
<li class="">Data encryption</li>
<li class="">Security auditing</li>
</ul>
<p>Layered security mechanisms effectively guard against data leaks, unauthorized access, and potential attacks.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="maintainability">Maintainability<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#maintainability" class="hash-link" aria-label="Direct link to Maintainability" title="Direct link to Maintainability" translate="no">​</a></h2>
<p>A well-designed architecture should be highly maintainable, so the system can be modified, extended, and serviced easily down the road. This usually requires:</p>
<ul>
<li class="">A clear code structure</li>
<li class="">Complete documentation</li>
<li class="">Consistent code comments</li>
<li class="">Ease of debugging and testing</li>
</ul>
<p>All of this helps developers understand the system quickly and iterate on it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scalability">Scalability<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#scalability" class="hash-link" aria-label="Direct link to Scalability" title="Direct link to Scalability" translate="no">​</a></h2>
<p>A system also needs to scale well, expanding or shrinking resources dynamically with the size of the business. Common approaches include:</p>
<ul>
<li class=""><strong>Vertical scaling (scale up)</strong>: adding resources to a single machine, e.g. more CPU or memory</li>
<li class=""><strong>Horizontal scaling (scale out)</strong>: adding server nodes and spreading the load across a distributed architecture</li>
</ul>
<p>Modern architectures generally favor horizontal scaling to support large-scale business growth.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="high-availability">High Availability<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#high-availability" class="hash-link" aria-label="Direct link to High Availability" title="Direct link to High Availability" translate="no">​</a></h2>
<p>High availability is one of the key goals for large systems: the system must keep serving even when failures occur. Common techniques include:</p>
<ul>
<li class="">Multi-node deployment</li>
<li class="">Load balancing</li>
<li class="">Automatic service failover</li>
<li class="">Multiple data replicas</li>
</ul>
<p>Together these mechanisms minimize the impact of downtime on users.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="testability">Testability<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#testability" class="hash-link" aria-label="Direct link to Testability" title="Direct link to Testability" translate="no">​</a></h2>
<p>A system should be designed for testability, so it can support automated testing and continuous integration. This usually comes from modular, decoupled design that makes it easy to run:</p>
<ul>
<li class="">Unit tests</li>
<li class="">Integration tests</li>
<li class="">System tests</li>
</ul>
<p>Good testability meaningfully improves both software quality and development velocity.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="portability">Portability<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#portability" class="hash-link" aria-label="Direct link to Portability" title="Direct link to Portability" translate="no">​</a></h2>
<p>Architecture also needs good portability, so the system can be deployed and run easily in different environments. For example:</p>
<ul>
<li class="">Supporting different operating systems or cloud platforms</li>
<li class="">Using standardized interfaces</li>
<li class="">Reducing dependencies on any specific platform</li>
</ul>
<p>This lowers the cost of future migrations and upgrades.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cost">Cost<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#cost" class="hash-link" aria-label="Direct link to Cost" title="Direct link to Cost" translate="no">​</a></h2>
<p>In small projects, cost usually isn't a primary concern. But as a system grows, cost becomes an important metric in architecture design.</p>
<p>One thing to keep in mind:</p>
<p><strong>Low cost, high performance, and high availability tend to conflict with each other.</strong></p>
<p>So in architecture design, low cost usually isn't the top priority — it's a factor to be balanced after performance and availability requirements are met.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-an-architect-should-think">How an Architect Should Think<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#how-an-architect-should-think" class="hash-link" aria-label="Direct link to How an Architect Should Think" title="Direct link to How an Architect Should Think" translate="no">​</a></h2>
<p>In real engineering work, an architect does more than design system structure — what matters more is the ability to think holistically. A mature architect weighs system design across multiple dimensions instead of fixating on any single technical point.</p>
<ol>
<li class="">
<p><strong>A global perspective.</strong> An architect needs to think about the system as a whole — business trajectory, technology choices, team capability, and future room to grow — not just the technical problem at hand.</p>
</li>
<li class="">
<p><strong>The ability to make trade-offs.</strong> In real system design, a "perfect solution" rarely exists. Performance, cost, complexity, and maintainability constantly pull against each other. One of an architect's core skills is finding the most reasonable balance among them.</p>
</li>
<li class="">
<p><strong>Long-term thinking.</strong> Architecture shouldn't only solve today's problems; it should account for how the system evolves over three years, five years, or longer. Good architecture supports continuous business evolution instead of needing a rewrite every so often.</p>
</li>
<li class="">
<p><strong>Decomposing complex problems.</strong> Large systems are often extremely complex. An architect needs to break them down into clearly defined modules so the team can divide the work and iterate continuously.</p>
</li>
<li class="">
<p><strong>Technical judgment.</strong> In a constantly shifting technology landscape, an architect must judge which technologies genuinely fit the business and which are just passing fads, and make stable, dependable technology decisions accordingly.</p>
</li>
</ol>
<p>Put simply, an architect isn't just a technical designer — they're the planner of the system's long-term evolution.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/06/18/xi-tong-she-zhi-yuan-ze#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>There is no silver bullet in architecture design, and these principles are inherently in tension: extreme performance conflicts with low cost, high availability conflicts with architectural simplicity. In practice, first identify what the business truly needs at its current stage, then make trade-offs around that — rather than trying to max out every dimension at once. For an architect, the ability to keep balancing these dimensions matters more than mastery of any specific technology.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Ent-ORM]]></title>
            <link>https://tanzhuo.xyz/en/2023/06/13/go</link>
            <guid>https://tanzhuo.xyz/en/2023/06/13/go</guid>
            <pubDate>Tue, 13 Jun 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Ent is an open-source Go ORM framework from Meta, built around a schema-first design and code generation. This post walks through its design philosophy, strengths and weaknesses, its history, and how it compares to GORM, XORM, and SQLBoiler.]]></description>
            <content:encoded><![CDATA[<p>Ent is a <strong>Go ORM framework</strong> developed by Facebook (now Meta), built around one core idea: Schema First + Code Generation. This post covers where it came from, what it does well and poorly, and how it stacks up against other Go ORM frameworks.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2023/06/13/go#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>Ent started as an internal Facebook project, designed to tackle the problems of complex data models, dense entity relationships, and hard-to-maintain data access layers in large systems. As Facebook's internal systems kept growing, traditional ORMs increasingly showed their limits in <strong>type safety, query maintainability, and modeling complex relationships</strong> — so Facebook built a brand-new ORM: Ent.</p>
<p>Ent's core philosophy is <strong>Schema First + Code Generation</strong>. You first define your data model schema in Go code, then run a code generator that produces the complete data access layer: entity structs, query builders, relationship operations, and CRUD methods. Because most of the logic is <strong>generated and validated at compile time</strong>, Ent delivers stronger type safety and better runtime performance without sacrificing development speed.</p>
<p>After going open source, Ent quickly drew broad attention in the Go community and has become <strong>one of the most distinctive ORM frameworks in the Go ecosystem</strong>, particularly well suited to large systems with complex data relationships — social platforms, e-commerce systems, and core data services in microservice architectures.</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="strengths">Strengths<a href="https://tanzhuo.xyz/en/2023/06/13/go#strengths" class="hash-link" aria-label="Direct link to Strengths" title="Direct link to Strengths" translate="no">​</a></h2>
<ul>
<li class=""><strong>Performance</strong>: Ent builds its data access layer through <strong>code generation</strong>. Where traditional ORMs use reflection at runtime to inspect structs and produce SQL, Ent generates the complete data operation code at compile time, so runtime overhead is minimal and performance approaches hand-written SQL. This design lets Ent hold up well even under high concurrency.</li>
<li class=""><strong>Ease of use</strong>: Ent's query builder is a <strong>strongly typed DSL (Domain Specific Language)</strong>. All query fields, relationships, and operations are produced by the code generator, so mistakes surface at compile time.</li>
<li class=""><strong>Extensibility</strong>: Ent offers rich extension mechanisms that developers can customize to their needs, making it straightforward to integrate Ent deeply with existing systems.</li>
<li class=""><strong>Type safety</strong>: Ent leans on Go's type system to guarantee code correctness, reducing the risk of runtime errors.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="weaknesses">Weaknesses<a href="https://tanzhuo.xyz/en/2023/06/13/go#weaknesses" class="hash-link" aria-label="Direct link to Weaknesses" title="Direct link to Weaknesses" translate="no">​</a></h2>
<p>Compared with other ORM frameworks, Ent has a smaller community and relatively sparse documentation and learning resources. In certain specific scenarios its performance can hit limits and require targeted optimization.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-brief-history-of-ent">A Brief History of Ent<a href="https://tanzhuo.xyz/en/2023/06/13/go#a-brief-history-of-ent" class="hash-link" aria-label="Direct link to A Brief History of Ent" title="Direct link to A Brief History of Ent" translate="no">​</a></h2>
<ul>
<li class=""><strong>Early 2018</strong>: Ent is started as an internal project at Facebook.</li>
<li class=""><strong>Early 2019</strong>: Facebook open-sources Ent. It attracts wide attention and adoption, and the community steadily grows.</li>
<li class=""><strong>Mid 2019</strong>: A series of releases bring performance optimizations, bug fixes, and new features, making the framework more stable and efficient.</li>
<li class=""><strong>Early 2020</strong>: Ent ships version 1.0, adding multi-database support, custom types, and other new capabilities. The 1.0 release marks Ent as mature, stable, and ready for production.</li>
<li class=""><strong>Mid 2020 to now</strong>: As the framework continues to evolve, more and more developers adopt Ent and contribute code and documentation.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ent-vs-other-go-orm-frameworks">Ent vs. Other Go ORM Frameworks<a href="https://tanzhuo.xyz/en/2023/06/13/go#ent-vs-other-go-orm-frameworks" class="hash-link" aria-label="Direct link to Ent vs. Other Go ORM Frameworks" title="Direct link to Ent vs. Other Go ORM Frameworks" translate="no">​</a></h2>
<p>The common ORM frameworks in the Go ecosystem are <strong>GORM, XORM, SQLBoiler, and Ent</strong>, and their design philosophies differ noticeably.</p>






























<table><thead><tr><th>Framework</th><th>Type</th><th>Key traits</th></tr></thead><tbody><tr><td>GORM</td><td>Runtime ORM</td><td>The most popular Go ORM; easy to use, mature ecosystem</td></tr><tr><td>XORM</td><td>Runtime ORM</td><td>Lightweight ORM, good for simple business logic</td></tr><tr><td>SQLBoiler</td><td>Code-generation ORM</td><td>Generates code from the database schema; strong performance</td></tr><tr><td>Ent</td><td>Schema-first + Code Generation</td><td>Strongly typed query building, suited to systems with complex relationships</td></tr></tbody></table>
<p>From a design-pattern perspective:</p>
<ul>
<li class=""><strong>GORM / XORM</strong>: Runtime ORMs that use reflection to inspect structs and generate SQL; easy to pick up.</li>
<li class=""><strong>SQLBoiler</strong>: Generates code from the database schema, with good type safety and performance.</li>
<li class=""><strong>Ent</strong>: You define models via Go schemas, then generate the data access code automatically, with an emphasis on <strong>type safety and complex relationship modeling</strong>.</li>
</ul>
<p>In short:</p>
<ul>
<li class="">Small projects or rapid development: <strong>GORM is the better fit</strong></li>
<li class="">Complex data relationships or large systems: <strong>Ent has the edge</strong></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/06/13/go#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Ent's core competitive advantage is the strong typing that comes from Schema First plus code generation: query errors surface at compile time instead of blowing up in production. The trade-off is a community and body of documentation that don't match GORM's, and a workflow that requires you to buy into code generation first. If your project has complex data relationships and demands strong type safety, Ent deserves serious consideration; for a small project or quick prototype, a runtime ORM like GORM is simply less hassle.</p>]]></content:encoded>
            <category>Go</category>
        </item>
        <item>
            <title><![CDATA[Kratos]]></title>
            <link>https://tanzhuo.xyz/en/2023/05/17/k-2</link>
            <guid>https://tanzhuo.xyz/en/2023/05/17/k-2</guid>
            <pubDate>Wed, 17 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Kratos is a Go microservice framework open-sourced by Bilibili. This post covers its design strengths and core features, and compares it with Spring Cloud, Dubbo, Go-Micro, and other common frameworks.]]></description>
            <content:encoded><![CDATA[<p>Kratos is an open-source microservice framework written in Go, developed and maintained by Bilibili. It provides the standard building blocks a microservice needs — service discovery and registration, HTTP, gRPC, rate limiting, configuration center, and more. The framework is well designed, lightweight, and easy to pick up.</p>
<!-- -->
<p>Official docs: <a href="https://go-kratos.dev/" target="_blank" rel="noopener noreferrer" class="">Kratos</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="framework-strengths">Framework Strengths<a href="https://tanzhuo.xyz/en/2023/05/17/k-2#framework-strengths" class="hash-link" aria-label="Direct link to Framework Strengths" title="Direct link to Framework Strengths" translate="no">​</a></h2>
<ol>
<li class=""><strong>High performance</strong>: Kratos leverages Go's goroutines and efficient concurrency model to handle high-concurrency workloads with low per-request overhead.</li>
<li class=""><strong>Simple and approachable</strong>: the framework ships with thorough documentation and examples, and its clean design means developers don't need to master a pile of concepts and technologies — the barrier to entry is low.</li>
<li class=""><strong>Modular design</strong>: built-in modules for configuration, logging, database access, and more make it quick to stand up a microservice, and plugin-based development lets you extend the framework as needed.</li>
<li class=""><strong>Highly customizable</strong>: rich configuration options let you tailor it to your requirements, and support for custom middleware, interceptors, and the like means developers can extend it themselves.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-features">Core Features<a href="https://tanzhuo.xyz/en/2023/05/17/k-2#core-features" class="hash-link" aria-label="Direct link to Core Features" title="Direct link to Core Features" translate="no">​</a></h2>
<ol>
<li class="">Service registration and discovery: supports etcd, Consul, and other registries.</li>
<li class="">Configuration management: supports multiple configuration sources, such as local files and etcd.</li>
<li class="">Logging: supports multiple log outputs, such as files, console, and Kafka.</li>
<li class="">Database access: supports MySQL, PostgreSQL, Redis, and other stores.</li>
<li class="">HTTP services: supports HTTP and HTTPS.</li>
<li class="">gRPC services: gRPC is supported natively.</li>
<li class="">Security: provides security middleware, with support for JWT authentication, TLS, and more.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="comparison-with-other-microservice-frameworks">Comparison with Other Microservice Frameworks<a href="https://tanzhuo.xyz/en/2023/05/17/k-2#comparison-with-other-microservice-frameworks" class="hash-link" aria-label="Direct link to Comparison with Other Microservice Frameworks" title="Direct link to Comparison with Other Microservice Frameworks" translate="no">​</a></h2>
<ol>
<li class=""><strong>vs. Spring Cloud</strong>: Spring Cloud is a microservice framework in the Java ecosystem. Compared with Kratos it has a steeper learning curve, requires mastering more concepts and technologies, and is generally more heavyweight, with higher deployment and operating costs.</li>
<li class=""><strong>vs. Dubbo</strong>: Dubbo is a Java microservice framework open-sourced by Alibaba, with no Go support. Kratos's documentation and examples are much closer to how Go developers work, making it cheaper to learn and use.</li>
<li class=""><strong>vs. gRPC</strong>: gRPC is Google's open-source cross-language RPC framework with excellent performance, but it only solves the communication layer — it lacks the service-governance middleware, interceptors, and supporting pieces, and has a fairly steep learning curve of its own.</li>
<li class=""><strong>vs. Go-Micro</strong>: Go-Micro is also a Go microservice framework, with a more flexible design, support for multiple protocols and registries, and multi-language support — but a steeper learning curve, requiring more concepts and technologies.</li>
<li class=""><strong>vs. Gin</strong>: Gin is a Go web framework — even lighter, and great for quickly building simple web services, but it lacks microservice-framework capabilities like service registration and discovery and RPC calls.</li>
<li class=""><strong>vs. Micro</strong>: Micro is another Go microservice framework, flexible in design with multiple protocols and registries, but it involves more concepts and is harder to get started with than Kratos.</li>
<li class=""><strong>vs. Beego</strong>: Beego is a Go web framework positioned similarly to Gin — good for quickly building web services, and likewise missing microservice capabilities such as service registration and discovery and RPC calls.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/05/17/k-2#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Among Go microservice frameworks, Kratos strikes a good balance between performance, ease of use, and feature completeness. It bakes in the microservice fundamentals — service discovery, configuration management, logging, dual gRPC/HTTP protocols — while staying lightweight and customizable. Compared with pure web frameworks like Gin and Beego, it fills in the service-governance capabilities; compared with Go-Micro and Micro, it has fewer concepts and a faster ramp-up. Add Bilibili's ongoing maintenance and the wealth of documentation and examples, and Kratos is an option worth putting at the top of the list for any team bringing microservices to Go.</p>]]></content:encoded>
            <category>Go</category>
        </item>
        <item>
            <title><![CDATA[Neo4j: A Graph Database]]></title>
            <link>https://tanzhuo.xyz/en/2023/05/16/neo4j-graph-database</link>
            <guid>https://tanzhuo.xyz/en/2023/05/16/neo4j-graph-database</guid>
            <pubDate>Tue, 16 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Neo4j is a high-performance graph database built on the graph data model, excelling at relationship-heavy workloads. This post covers its licensing, data structure, and main features, plus caveats to weigh when evaluating it.]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="preface">Preface<a href="https://tanzhuo.xyz/en/2023/05/16/neo4j-graph-database#preface" class="hash-link" aria-label="Direct link to Preface" title="Direct link to Preface" translate="no">​</a></h2>
<p>Once your business logic starts requiring multi-level relationship queries, joins in a relational database get unwieldy. These notes cover Neo4j, a graph database: what it is, how it's licensed, how its data is organized, and when it's worth adopting.</p>
<!-- -->
<p>Neo4j is a high-performance graph database that stores and processes data using the <strong>graph data model</strong>. Compared with traditional relational databases, Neo4j is far better at handling complex, interconnected data, making it a natural fit for <strong>relationship-heavy workloads</strong>.</p>
<p>In many business systems, data is riddled with relationships, for example:</p>
<ul>
<li class="">Friend relationships in social networks</li>
<li class="">User-product relationships in recommendation systems</li>
<li class="">Entity relationships in knowledge graphs</li>
<li class="">Attack path analysis in network security</li>
</ul>
<p>In these scenarios, querying with a traditional relational database usually requires heavy <strong>join operations</strong>, and both query complexity and performance degrade quickly. A graph database can traverse relationships directly, making it much more efficient at handling complex connections.</p>
<p>Why do joins become the bottleneck? In a relational database, "relationships" are not first-class citizens — they're expressed through foreign keys and junction tables. Every level of join forces the database to do an index lookup to match rows across two tables, and the deeper the joins and the larger the data, the faster that matching cost grows. The classic example is a "friends of friends of friends" query: in MySQL that means self-joining the same relationship table three times, while in a graph database it's just two more hops along pointers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="licensing">Licensing<a href="https://tanzhuo.xyz/en/2023/05/16/neo4j-graph-database#licensing" class="hash-link" aria-label="Direct link to Licensing" title="Direct link to Licensing" translate="no">​</a></h2>
<p>Official site: <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 uses a dual open-source/commercial licensing model, so get clear on the boundary between the two editions before committing.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="community-edition">Community Edition<a href="https://tanzhuo.xyz/en/2023/05/16/neo4j-graph-database#community-edition" class="hash-link" aria-label="Direct link to Community Edition" title="Direct link to Community Edition" translate="no">​</a></h3>
<ul>
<li class="">Open source and free</li>
<li class="">Suitable for personal projects and small-to-medium applications</li>
<li class="">Single-instance deployment only — no clustering or online hot backup</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="enterprise-edition">Enterprise Edition<a href="https://tanzhuo.xyz/en/2023/05/16/neo4j-graph-database#enterprise-edition" class="hash-link" aria-label="Direct link to Enterprise Edition" title="Direct link to Enterprise Edition" translate="no">​</a></h3>
<ul>
<li class="">Commercially licensed</li>
<li class="">Provides clustering, high availability, security and authentication, and other advanced features</li>
<li class="">Aimed at production environments with hard requirements on availability and access control</li>
</ul>
<p>In short: for feature validation, internal tools, and workloads with manageable data volumes, the Community Edition is enough. Once you need high availability or fine-grained access control, there's no way around the Enterprise Edition — factor that licensing cost into your plan early.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="graph-database-data-structure">Graph Database Data Structure<a href="https://tanzhuo.xyz/en/2023/05/16/neo4j-graph-database#graph-database-data-structure" class="hash-link" aria-label="Direct link to Graph Database Data Structure" title="Direct link to Graph Database Data Structure" translate="no">​</a></h2>
<p>A graph database is built on three constructs:</p>
<p>Node
Relationship
Property</p>
<p>The division of labor is clean: nodes represent entities (people, products, devices); relationships represent connections between entities and <strong>must have a direction and a type</strong>; properties are key-value pairs attached to nodes or relationships. Mapping to relational terms: a node roughly corresponds to a table row, and a relationship roughly corresponds to a foreign key — except a relationship can carry its own properties (a "friend" relationship can store when it was established, for instance), which a foreign key cannot.</p>
<p>For example:</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>Relationships are stored as <strong>direct pointers between nodes</strong>. The database simply <strong>follows the relationship pointers</strong> during traversal — no table joins required — which is why relationship queries dramatically outperform relational databases. This design has a name: <strong>index-free adjacency</strong>. Finding a node's neighbors doesn't go through a global index; it starts directly from the node's own pointers, so the cost of one traversal step depends only on how many relationships that node has, not on the size of the entire database.</p>
<p>Queries use the Cypher language, whose syntax mirrors the graph notation above almost exactly:</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">// Find A's friends of friends (a two-hop traversal)</span><br></div><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>You essentially draw the pattern and it becomes the query — one reason graph databases are so intuitive to model with.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="main-features">Main Features<a href="https://tanzhuo.xyz/en/2023/05/16/neo4j-graph-database#main-features" class="hash-link" aria-label="Direct link to Main Features" title="Direct link to Main Features" translate="no">​</a></h2>
<p>Graph data storage and querying: Neo4j uses the graph data model to store data, making complex relationship data easy to work with.</p>
<p>Efficient query performance: thanks to the graph data model, Neo4j handles deep queries and complex relationship queries with ease and delivers higher query performance. Query time depends mainly on the size of the subgraph being traversed, not the size of the whole database.</p>
<p>Scalability: Neo4j scales comfortably to tens of billions of nodes and relationships.</p>
<p>ACID transaction support: Neo4j supports ACID transactions, ensuring data consistency and reliability. This sets it apart from the many graph computation frameworks that only do offline analytics — it is a database capable of taking online writes, not just an analysis tool.</p>
<p>Through the graph data model, Neo4j makes data relationships far more intuitive to express and holds a clear performance advantage in complex relationship queries. In traditional business systems, relational databases remain the core data store. But in <strong>relationship-heavy scenarios</strong> (social graphs, recommendation systems, knowledge graphs, and the like), a graph database can serve as a relationship analysis engine that complements the traditional database.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2023/05/16/neo4j-graph-database#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Don't use a graph database as a replacement for a relational one. For aggregations, batch reporting, and other column-scan workloads, the graph model offers no advantage; the sensible architecture is usually an RDB for the master data and Neo4j for the relationship subset.</p>
</li>
<li class="">
<p>Change your modeling mindset. Graph modeling starts with "what are the nodes, what are the relationships called, which way do they point" — not with designing table schemas. Get a relationship's direction or type wrong and every subsequent query will feel awkward.</p>
</li>
<li class="">
<p>Bound your traversal depth. Cypher supports variable-length path matching; a query without a depth limit or node labels can easily degenerate into a full-graph scan and drag down the entire instance.</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>tip</div><div class="admonitionContent_BuS1"><p>Validate with real relationship queries before adopting. If most of your queries only involve one or two levels of joins, a relational database with proper indexes is usually enough — no need to maintain another storage system.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/05/16/neo4j-graph-database#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>A graph database solves exactly one core problem: making "relationships" first-class citizens and replacing joins with pointer traversal. The Neo4j Community Edition is plenty for validating ideas; production high availability means budgeting for the Enterprise license. It isn't a replacement for relational databases but a complementary engine for relationship-heavy workloads — confirm that your query patterns really are deep traversals before bringing it in.</p>]]></content:encoded>
            <category>Database</category>
        </item>
        <item>
            <title><![CDATA[A Taxonomy of Databases]]></title>
            <link>https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku</link>
            <guid>https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku</guid>
            <pubDate>Tue, 16 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Classifying databases along the dimensions of data model, processing style, deployment, storage medium, and access pattern, with typical use cases for each category.]]></description>
            <content:encoded><![CDATA[<p>Databases can be classified along several dimensions, each reflecting differences in <strong>data model, processing capability, deployment style, storage medium, and application scenario</strong>. Understanding these categories helps you pick the right database when designing a system architecture.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="by-data-model">By Data Model<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#by-data-model" class="hash-link" aria-label="Direct link to By Data Model" title="Direct link to By Data Model" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="relational-databases-rdbms">Relational Databases (RDBMS)<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#relational-databases-rdbms" class="hash-link" aria-label="Direct link to Relational Databases (RDBMS)" title="Direct link to Relational Databases (RDBMS)" translate="no">​</a></h3>
<p>Relational databases use the <strong>table</strong> as their core data structure, organizing data into <strong>rows and columns</strong> and using <strong>SQL (Structured Query Language)</strong> for querying and management. Tables are linked to one another through <strong>primary keys and foreign keys</strong>, enabling complex cross-table queries.</p>
<p>Their characteristics:</p>
<ul>
<li class="">Clear, strongly constrained data structures</li>
<li class="">Support for complex queries (joins, aggregations, etc.)</li>
<li class="">Transactions and ACID guarantees</li>
<li class="">Well suited to structured data</li>
</ul>
<p>Typical databases: MySQL, PostgreSQL, Oracle, SQL Server, and others.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="non-relational-databases-nosql">Non-Relational Databases (NoSQL)<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#non-relational-databases-nosql" class="hash-link" aria-label="Direct link to Non-Relational Databases (NoSQL)" title="Direct link to Non-Relational Databases (NoSQL)" translate="no">​</a></h3>
<p>NoSQL (Not Only SQL) databases drop the traditional relational table structure in favor of more flexible data models, built to handle large-scale data and high-concurrency access.</p>
<p>Their characteristics:</p>
<ul>
<li class="">Flexible data structures</li>
<li class="">Strong scalability</li>
<li class="">Good performance under high concurrency</li>
<li class="">Usually weak or no transaction support</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="by-processing-style">By Processing Style<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#by-processing-style" class="hash-link" aria-label="Direct link to By Processing Style" title="Direct link to By Processing Style" translate="no">​</a></h2>
<ul>
<li class="">Transactional databases: support transactions and the ACID properties (atomicity, consistency, isolation, durability).</li>
<li class="">Non-transactional databases: no transactions or ACID guarantees, but higher performance and scalability in return.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="by-deployment">By Deployment<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#by-deployment" class="hash-link" aria-label="Direct link to By Deployment" title="Direct link to By Deployment" translate="no">​</a></h2>
<ul>
<li class="">Single-node databases: deployed on one server, with all data and computation on a single node.</li>
<li class="">Distributed databases: spread data across multiple servers that coordinate over the network, achieving greater scalability and availability.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="by-storage-medium">By Storage Medium<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#by-storage-medium" class="hash-link" aria-label="Direct link to By Storage Medium" title="Direct link to By Storage Medium" translate="no">​</a></h2>
<ul>
<li class="">In-memory databases: keep data in <strong>RAM</strong>, so reads and writes are extremely fast — e.g. Redis, Memcached.</li>
<li class="">Disk-based databases: persist data to <strong>disk or SSD</strong> — e.g. MySQL, PostgreSQL, Oracle.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="by-access-pattern">By Access Pattern<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#by-access-pattern" class="hash-link" aria-label="Direct link to By Access Pattern" title="Direct link to By Access Pattern" translate="no">​</a></h2>
<ul>
<li class="">OLTP (Online Transaction Processing) databases serve <strong>online transactional systems</strong>.</li>
<li class="">OLAP (Online Analytical Processing) databases serve <strong>analytics and decision support</strong> — e.g. ClickHouse, Doris, StarRocks, Snowflake.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="typical-use-cases">Typical Use Cases<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#typical-use-cases" class="hash-link" aria-label="Direct link to Typical Use Cases" title="Direct link to Typical Use Cases" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="relational-databases-rdbms-1">Relational Databases (RDBMS)<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#relational-databases-rdbms-1" class="hash-link" aria-label="Direct link to Relational Databases (RDBMS)" title="Direct link to Relational Databases (RDBMS)" translate="no">​</a></h3>
<ul>
<li class="">Applications handling large volumes of structured data, such as finance, e-commerce, and logistics.</li>
<li class="">Applications requiring transactions and ACID guarantees, such as banking, telecom, and aviation.</li>
<li class="">Applications requiring complex queries and analysis, such as data warehouses and business intelligence.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="non-relational-databases-nosql-1">Non-Relational Databases (NoSQL)<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#non-relational-databases-nosql-1" class="hash-link" aria-label="Direct link to Non-Relational Databases (NoSQL)" title="Direct link to Non-Relational Databases (NoSQL)" translate="no">​</a></h3>
<ul>
<li class="">Document databases: for semi-structured data, such as blogs, news, and comments.</li>
<li class="">Key-value databases: for simple key-value data, such as caching and session management.</li>
<li class="">Columnar databases: for large volumes of structured data, such as logs, events, and time-series data.</li>
<li class="">Graph databases: for complex, interconnected data, such as social networks, recommendation systems, bioinformatics, and network security.</li>
<li class="">Object databases: for applications that need object-oriented programming support, such as game development and multimedia applications.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="in-memory-databases">In-Memory Databases<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#in-memory-databases" class="hash-link" aria-label="Direct link to In-Memory Databases" title="Direct link to In-Memory Databases" translate="no">​</a></h3>
<ul>
<li class="">Applications needing fast response times, such as high-concurrency transaction processing and real-time analytics.</li>
<li class="">Applications needing fast reads and writes, such as caching and session management.</li>
<li class="">Applications needing real-time computation and analysis, such as real-time risk control and real-time recommendations.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="distributed-databases">Distributed Databases<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#distributed-databases" class="hash-link" aria-label="Direct link to Distributed Databases" title="Direct link to Distributed Databases" translate="no">​</a></h3>
<ul>
<li class="">Applications needing high availability and fault tolerance, such as distributed computing and distributed storage.</li>
<li class="">Applications needing large-scale data processing and analysis, such as big data and machine learning.</li>
<li class="">Applications needing multi-region deployment and data sharing, such as cloud computing and IoT.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/05/16/ren-shi-shu-ju-ku#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Databases can be sliced along many dimensions, and a single product often belongs to several categories at once — MySQL, for example, is a relational database, a disk-based database, and a transactional database all at the same time. When choosing one, don't obsess over the taxonomy itself; instead start from your data structure, consistency requirements, concurrency scale, and query patterns, and find the category that matches the business. Combining multiple databases in one system is also common in practice — Redis for caching, MySQL for business data, ClickHouse for analytical queries. Understanding what each category is for is what lets you make sound trade-offs in architecture design.</p>]]></content:encoded>
            <category>Database</category>
        </item>
        <item>
            <title><![CDATA[Preventing Duplicate Form Submissions]]></title>
            <link>https://tanzhuo.xyz/en/2023/05/09/fang-zhong-fu-ti-jiao</link>
            <guid>https://tanzhuo.xyz/en/2023/05/09/fang-zhong-fu-ti-jiao</guid>
            <pubDate>Tue, 09 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[The server generates a one-time token, stores it in Redis, sends it down with the form, and deletes it immediately after validation. Notes on how this mechanism defends against duplicate submissions and CSRF, how to implement it, and the security caveats.]]></description>
            <content:encoded><![CDATA[<p>A double-clicked submit button, an automatic retry after a network timeout, a browser back-then-resubmit — any of these can make the server process the same form twice. A token mechanism is an effective defense against both CSRF attacks and duplicate submissions: when a form is served, the server generates a token, stores it in Redis, and passes it to the client as a hidden form field or URL parameter; on submission the token is validated and then deleted immediately.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-problem-is-worth-solving">Why This Problem Is Worth Solving<a href="https://tanzhuo.xyz/en/2023/05/09/fang-zhong-fu-ti-jiao#why-this-problem-is-worth-solving" class="hash-link" aria-label="Direct link to Why This Problem Is Worth Solving" title="Direct link to Why This Problem Is Worth Solving" translate="no">​</a></h2>
<p>The business consequences of duplicate submissions are usually worse than they look: an order endpoint processed twice means two orders, and a payment request retried twice means a double charge. Disabling the submit button on the frontend only stops accidental clicks — it can't stop automatic retries at the network layer, let alone maliciously crafted requests. That's why this protection ultimately has to live on the server.</p>
<p>A CSRF attack works by tricking the user's browser into sending a request to the target site, and it succeeds because the browser automatically attaches cookies. The token, however, is not stored in a cookie, so a third-party site has no way to obtain it — which is why a single token mechanism can defend against both classes of problems at once.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How It Works<a href="https://tanzhuo.xyz/en/2023/05/09/fang-zhong-fu-ti-jiao#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works" translate="no">​</a></h2>
<p>The token mechanism is a common way for web applications to prevent duplicate submissions and CSRF attacks. The basic idea: each time a form is served, the server generates a token and stores it in Redis, then passes it to the client as a hidden form field or URL parameter. When the client submits the form, it sends the token back along with it. The server checks that the token is valid while processing the form, and deletes the token once processing is done.</p>
<p>The two protection goals rely on different aspects of the mechanism. Duplicate-submission protection depends on "delete immediately after validation" — by the time the same token arrives a second time, it no longer exists and the request is rejected. CSRF protection depends on the token only being issued to legitimate pages — a request forged by an attacker simply doesn't contain it. Redis plays two roles here: centralized storage, so any instance in a multi-instance deployment can perform the validation; and expiry control, using TTLs to automatically clean up tokens that were never consumed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementation-steps">Implementation Steps<a href="https://tanzhuo.xyz/en/2023/05/09/fang-zhong-fu-ti-jiao#implementation-steps" class="hash-link" aria-label="Direct link to Implementation Steps" title="Direct link to Implementation Steps" translate="no">​</a></h2>
<ol>
<li class="">Generate a token on the server and store it in Redis. The token can be a random number, a UUID, or anything else — as long as it is sufficiently random and unique. Set an expiry while writing it:</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%)"># NX ensures an existing token is never overwritten; EX sets the expiry in seconds</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># so stale tokens don't pile up</span><span class="token plain"></span><br></div><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>
<ol start="2">
<li class="">
<p>Pass the token to the client. For a form submission, include it as a hidden field; for an AJAX request, send it as an HTTP header.</p>
</li>
<li class="">
<p>When the client submits the form, it sends the token back to the server. For a form submission, a bit of JavaScript in the submit handler can add the token as a hidden field; for an AJAX request, attach the token as an HTTP header before sending.</p>
</li>
<li class="">
<p>While processing the form, the server checks whether the token is valid. If it is, this is a legitimate request and processing can continue; if not, this is a duplicate submission or a CSRF attack and the request should be rejected.</p>
</li>
<li class="">
<p>After processing the form, the server deletes the token from Redis so it can't be reused.</p>
</li>
</ol>
<p>The "validate" and "delete" in steps 4 and 5 must happen as a single atomic operation. If you do GET first and DEL second as two separate steps, two concurrent requests can both pass validation and duplicate-submission protection falls apart. A short Lua script lets Redis perform validate-and-delete atomically:</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">-- Validate and delete: if the token exists, delete it and return 1; otherwise 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">-- The whole script runs atomically inside Redis, so only one concurrent request can succeed</span><br></div><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="security-considerations">Security Considerations<a href="https://tanzhuo.xyz/en/2023/05/09/fang-zhong-fu-ti-jiao#security-considerations" class="hash-link" aria-label="Direct link to Security Considerations" title="Direct link to Security Considerations" translate="no">​</a></h2>
<p>The token mechanism is an effective defense against duplicate submissions and CSRF, but the token itself must be protected, or an attacker can turn it against you. Specifically:</p>
<p>The token must be sufficiently random and unique, or an attacker may guess it or reuse it.</p>
<p>The token must be protected in transit, or an attacker may intercept or tamper with it. Always use HTTPS, and never put the token in a URL that will end up in logs.</p>
<p>The token must have an expiry, or an attacker may reuse it.</p>
<p>The token must be protected while stored in Redis, or an attacker may steal or tamper with it. The Redis instance should not be exposed to the public internet, and authentication should be enabled.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2023/05/09/fang-zhong-fu-ti-jiao#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" 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>warning</div><div class="admonitionContent_BuS1"><p>Writing validation and deletion as two separate steps is the most common broken implementation. Under load testing, or when a user double-clicks quickly, both requests pass the GET check and each processes the business logic once. Use a Lua script or an equivalent mechanism to guarantee atomicity.</p></div></div>
<p>The expiry should be set based on how long users actually take to fill out the form. Too short, and the token expires while the user is still working through a long form, guaranteeing a failed submission; too long, and stale tokens pile up in Redis.</p>
<p>When validation fails, the frontend must give clear feedback and guide the user to refresh the page to obtain a new token before resubmitting — not silently drop the request. Otherwise users just see "nothing happened when I clicked" and keep hammering the button.</p>
<p>Also think through when to delete: whether to return the token when business processing fails, allowing the user to retry directly, depends on the idempotency requirements of the specific business — there is no one-size-fits-all answer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/05/09/fang-zhong-fu-ti-jiao#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The heart of the token mechanism is the "one-time credential": issued by the server, stored in Redis, validated on submission, deleted after use — one flow that solves both duplicate submissions and CSRF. When rolling it out, keep an eye on four things: token randomness, security in transit and at rest, a sensible expiry, and atomic validate-and-delete. Get those right and you have a low-cost, high-value protection you can apply broadly.</p>]]></content:encoded>
            <category>Java</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[How Docker Works]]></title>
            <link>https://tanzhuo.xyz/en/2023/05/06/docker</link>
            <guid>https://tanzhuo.xyz/en/2023/05/06/docker</guid>
            <pubDate>Sat, 06 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Docker study notes: how the Daemon and Client interact, how containers are built on Namespaces/Cgroups/union file systems, and the underlying storage mechanics of image layers and data volumes.]]></description>
            <content:encoded><![CDATA[<p>Notes on Docker's internal architecture, how containers are implemented, and the underlying storage model.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="internal-architecture-components">Internal Architecture Components<a href="https://tanzhuo.xyz/en/2023/05/06/docker#internal-architecture-components" class="hash-link" aria-label="Direct link to Internal Architecture Components" title="Direct link to Internal Architecture Components" translate="no">​</a></h2>





























<table><thead><tr><th>Component</th><th>Description</th></tr></thead><tbody><tr><td>Docker Daemon</td><td>Docker's main component, a daemon process responsible for creating, starting, stopping, and deleting containers.</td></tr><tr><td>Docker Client</td><td>The command-line tool that talks to the Docker Daemon; users send commands through it to operate containers.</td></tr><tr><td>Docker Registry</td><td>The central repository for Docker images; the Docker Client can pull the images it needs from it.</td></tr><tr><td>Docker Image</td><td>The basis of a container: a read-only file system containing the application, dependency libraries, configuration files, and so on. Built from a Dockerfile.</td></tr><tr><td>Docker Container</td><td>An instance of a Docker image, containing a complete file system, runtime environment, and system tools.</td></tr></tbody></table>
<p>The Docker Daemon and Docker Client communicate over a RESTful API: the Client sends commands to the Daemon, the Daemon receives and handles them, and finally returns the result to the Client.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2023-05-image-3-8e8f77d570d13db756ced57e591ce0d5.webp" width="816" height="742" class="img_ev3q"></p>
<p>When the Docker Daemon receives a command to create a container, it first checks whether the required image already exists locally. If not, the Daemon pulls it from the Docker Registry; if it does, the Daemon creates a container from that image and runs it inside its own isolated namespace.</p>
<p>While a container is running, the Daemon routes its output and error messages to stdout and stderr, and users can view the container's output through the Client. When the container is no longer needed, the user sends a stop command via the Client. The Daemon sends SIGTERM to the processes inside the container so they can exit gracefully; if they haven't exited within a certain window, the Daemon sends SIGKILL to force-kill them.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-containers-are-implemented">How Containers Are Implemented<a href="https://tanzhuo.xyz/en/2023/05/06/docker#how-containers-are-implemented" class="hash-link" aria-label="Direct link to How Containers Are Implemented" title="Direct link to How Containers Are Implemented" translate="no">​</a></h2>
<p>Docker containers rest on a few mechanisms:</p>
<ol>
<li class="">Namespaces: Docker uses namespaces to isolate processes, networking, file systems, users, and other resources between containers. Each container has its own namespaces, keeping containers independent of and invisible to one another.</li>
<li class="">Control Groups (Cgroups): Docker uses Cgroups to limit the CPU, memory, network bandwidth, and other resources a container can use. Each container gets a resource quota, so containers share the host's resources fairly.</li>
<li class="">Union File System: Docker uses a union file system to isolate container file systems. Each container has its own file system, yet can share parts of the host's. This keeps container file systems extremely lightweight, so they can be created and destroyed quickly.</li>
<li class="">Container images: containers are created from images — read-only file systems containing the application, dependency libraries, and configuration. Docker uses the union file system to store images in layers, so images can be shared and reused, dramatically cutting storage space and download time.</li>
<li class="">Container runtime: Docker uses a container runtime to start and manage containers. The runtime creates the container's namespaces, Cgroups, and union file system, then launches the processes inside. It also monitors container state and resource usage, adjusting resource quotas as needed.</li>
</ol>
<p>Docker's operating model, then, is the interaction between the Docker Daemon and the Docker Client to create, start, stop, and delete containers. Images are the foundation of containers and can be built from Dockerfiles; containers run in isolated namespaces, providing isolation and security.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="underlying-storage">Underlying Storage<a href="https://tanzhuo.xyz/en/2023/05/06/docker#underlying-storage" class="hash-link" aria-label="Direct link to Underlying Storage" title="Direct link to Underlying Storage" translate="no">​</a></h2>
<p>Docker's storage model comes down to two things: container images and data volumes.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="image-storage">Image storage<a href="https://tanzhuo.xyz/en/2023/05/06/docker#image-storage" class="hash-link" aria-label="Direct link to Image storage" title="Direct link to Image storage" translate="no">​</a></h3>
<p>Docker uses a union file system to store images in layers. An image is made up of multiple read-only layers, each of which can be shared and reused. When a container starts, Docker unions those read-only layers together and stacks a writable layer on top, forming the container's file system. This keeps images very lightweight and containers fast to create and destroy.</p>
<p>By default Docker uses AUFS (Advanced Multi-Layered Unification File System) as the union file system, which supports multi-layer unioning, fast creation and teardown, and read-only layer sharing. Docker also supports other union file systems, such as OverlayFS, DeviceMapper, and Btrfs.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-volume-storage">Data volume storage<a href="https://tanzhuo.xyz/en/2023/05/06/docker#data-volume-storage" class="hash-link" aria-label="Direct link to Data volume storage" title="Direct link to Data volume storage" translate="no">​</a></h3>
<p>A data volume is a directory or file a container can read and write, used for sharing data between containers, persistent storage, and so on. Docker supports several volume types, including host bind mounts, named volumes, and anonymous volumes.</p>
<p>A host bind mount maps a directory or file on the host into the container, which can then read and write it. A named volume is managed by Docker and can be given a name, making it easy to manage and share. An anonymous volume is generated automatically by Docker for a container's temporary data.</p>
<p>Volume storage is built on Linux's file system mount mechanism. When a container starts, Docker mounts the volume at the specified path inside the container; data written to that path lands on the volume. When the container stops, Docker unmounts the volume, and the data stays on it — that's how persistence works.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/05/06/docker#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Docker's architecture isn't complicated: the Client sends instructions to the Daemon over a RESTful API, and the Daemon handles image pulls and the full container lifecycle. Containers themselves rely on Linux Namespaces for isolation and Cgroups for resource limits, while the union file system underpins layered image storage and the container's writable layer. Persistence is delegated to data volumes, whose mount mechanism keeps data independent of the container's lifecycle. Once you understand these mechanisms, the "lightweight, portable, reusable" qualities of containers pretty much explain themselves.</p>]]></content:encoded>
            <category>Docker</category>
        </item>
        <item>
            <title><![CDATA[Flink CDC Components]]></title>
            <link>https://tanzhuo.xyz/en/2023/05/05/flink</link>
            <guid>https://tanzhuo.xyz/en/2023/05/05/flink</guid>
            <pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[An introduction to Flink CDC and its built-in components: starting from how change-log capture works, then covering the Source, Debezium Connector, Sink, and other core modules, plus common caveats in practice.]]></description>
            <content:encoded><![CDATA[<p>While recently mapping out our real-time data pipelines, I revisited everything I know about Flink CDC. The need to "stream database changes out in real time" comes up on virtually every data team, so it deserves its own write-up.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2023/05/05/flink#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>The most basic approach to data synchronization is scheduled batch jobs: pull a full copy of the business database every night and write it into the warehouse. It's simple to build, but latency is measured in hours or even days, and once data volumes grow, the load on the source database is significant. The moment the business asks for "real-time dashboards," batch jobs can't keep up. CDC is the technical path that emerged to solve this, and Flink CDC is one of the most thorough integrations of CDC capability with a stream processing engine.</p>
<p>Flink CDC (Change Data Capture) is an important component in the Apache Flink ecosystem, used to capture database changes in real time and turn them into data streams for processing. In modern data architectures, CDC is widely used to build real-time data synchronization, real-time data warehouses, and event-driven systems.</p>
<p>Compared with traditional synchronization approaches (such as scheduled full syncs), CDC captures data changes by reading the database's <strong>change logs—Binlog, WAL, and the like</strong>—achieving low-latency, low-intrusion synchronization.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2023-05-flink-header-logo-5b83b4e45940e5c5870da4d58bde8b88.svg" width="427" height="220" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-cdc">What Is CDC<a href="https://tanzhuo.xyz/en/2023/05/05/flink#what-is-cdc" class="hash-link" aria-label="Direct link to What Is CDC" title="Direct link to What Is CDC" translate="no">​</a></h2>
<p>CDC stands for <strong>Change Data Capture</strong>. The core idea:
whenever data in the database undergoes an <strong>INSERT / UPDATE / DELETE</strong>, record those changes and deliver them to downstream systems in real time.</p>
<p>It's worth unpacking the implementation mechanics a bit. CDC comes in two broad flavors. One is query-based: it polls timestamp or auto-increment key columns to discover new data—simple to implement, but it can't catch DELETEs and easily misses records modified multiple times between two polls. The other is log-based: it consumes the change log the database already maintains for primary-replica replication—MySQL's Binlog and PostgreSQL's WAL both fall into this category. The log naturally records the complete before and after state of every row change, so log-based CDC captures the full set of insert/update/delete events with virtually no extra query load on the source database. Flink CDC takes the log-based path.</p>
<p>In real data architectures, CDC is commonly used for:</p>
<ul>
<li class="">Database → data warehouse (real-time warehousing)</li>
<li class="">Database → Kafka (event streams)</li>
<li class="">Database → search engines (e.g. Elasticsearch)</li>
<li class="">Database → caching systems (e.g. Redis)</li>
</ul>
<p>CDC is therefore one of the key foundational capabilities of a modern <strong>real-time data platform</strong>.</p>
<p>Flink CDC works by capturing the source system's change logs, converting them into data streams, and processing them in real time. This happens without affecting the source system, because the CDC component only reads the source system's logs and never writes to it. From the database's point of view, a CDC client is essentially indistinguishable from an ordinary replica—it masquerades as a replication client, subscribes to the change log from the primary, and parses log events into structured change records for Flink to process.</p>
<p>A strength of Flink CDC is its support for many data sources, including relational databases (MySQL, Oracle, etc.) and NoSQL databases (MongoDB, Cassandra, etc.). It also supports multiple data formats, such as JSON and CSV.</p>
<p>Another easily underrated strength is how deeply it integrates with Flink itself. Once the change stream enters Flink, it's just an ordinary data stream: Flink's windowing, multi-stream joins, state management, and checkpoint-based fault tolerance all apply directly. In other words, "capturing changes" and "processing changes" happen in the same engine—no separate Kafka Connect cluster to maintain as a relay, a shorter pipeline, and fewer moving parts to operate.</p>
<p>With Flink CDC you get real-time data synchronization and stream processing in one. For example, you can use the CDC component to sync data from a MySQL database into Kafka, then process it in real time with Flink—achieving real-time synchronization and processing that improve both the timeliness and the accuracy of your data.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink-cdc-components">Flink CDC Components<a href="https://tanzhuo.xyz/en/2023/05/05/flink#flink-cdc-components" class="hash-link" aria-label="Direct link to Flink CDC Components" title="Direct link to Flink CDC Components" translate="no">​</a></h2>
<p>At a high level, the division of labor in a Flink CDC pipeline is: the Source handles "in," the Sink handles "out," and Flink's operators and state mechanisms do the computation in between. Flink CDC ships with the following built-in components:</p>
<ol>
<li class="">Source: reads change logs from the data source and converts them into a Flink data stream. A CDC Source typically takes a snapshot of the existing data first, then seamlessly hands off to incremental log consumption, so downstream consumers receive the complete "full plus incremental" dataset.</li>
<li class="">Debezium Connector: an open source CDC tool that connects to many data sources (MySQL, PostgreSQL, MongoDB, etc.) and captures their change logs. Flink CDC reuses Debezium's log-parsing capability under the hood, running it embedded inside the Flink job and eliminating the cost of deploying a standalone Debezium service.</li>
<li class="">Sink: writes the Flink data stream into a target system, such as Kafka, HDFS, or Elasticsearch. Working with Flink's checkpoint mechanism, the Sink avoids data loss when a job fails and restarts.</li>
<li class="">State: handles stateful stream processing—for example, merging two data streams requires the State component to store intermediate state. State is especially important in CDC scenarios: dimension-table joins, deduplication by primary key, and reconstructing the latest image from UPDATE events all depend on state storage.</li>
<li class="">Table API: provides a SQL-like API for convenient stream processing and querying. For most synchronization needs, declaring a CDC source table and a target table in Flink SQL and writing a single INSERT INTO gets the whole pipeline running—no Java code required.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2023/05/05/flink#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<p>A few things worth checking up front in practice:</p>
<ol>
<li class="">
<p>Confirm the source database's log configuration first. Taking MySQL as an example, Binlog must be enabled with format ROW, or you won't get row-level before/after images; the connection account also needs the replication privileges required to read the Binlog.</p>
</li>
<li class="">
<p>Don't set the log retention too short. If the job is stopped for a while and the Binlog for that period has already been purged by the database, the job can't find its resume position and has to redo a full snapshot.</p>
</li>
<li class="">
<p>The downstream must be able to handle UPDATE and DELETE. A CDC stream is a changelog stream with retraction semantics; if the Sink side (say, an append-only store) can't digest deletes and updates, the pipeline design needs extra handling.</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>warning</div><div class="admonitionContent_BuS1"><p>A CDC job is fundamentally a long-running streaming job—configure checkpointing properly. A CDC job without checkpoints, once it fails, loses its resume position and possibly data too.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://tanzhuo.xyz/en/2023/05/05/flink#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>CDC answers the question of how changes inside a database can flow out with low latency and low intrusion, and the log-based approach is today's mainstream path. Flink CDC's value is pulling log capture (via Debezium) and stream computation (the Flink engine itself) into a single framework: the Source reads changes, the Sink writes to targets, State powers stateful computation, and the Table API lowers the barrier to entry. For teams building a real-time warehouse or syncing heterogeneous data, it's a short-pipeline, few-component option well worth considering first.</p>]]></content:encoded>
            <category>Big Data</category>
        </item>
        <item>
            <title><![CDATA[MySQL Rounds Your Timestamps]]></title>
            <link>https://tanzhuo.xyz/en/2023/05/05/mysql-shi-jian-si-she-wu-ru</link>
            <guid>https://tanzhuo.xyz/en/2023/05/05/mysql-shi-jian-si-she-wu-ru</guid>
            <pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[MySQL's DATETIME defaults to second precision, so milliseconds coming from Java get rounded — sometimes adding a whole second out of thin air. Here's why it happens, plus two fixes: DATETIME(fsp) and truncation on the Java side.]]></description>
            <content:encoded><![CDATA[<p>MySQL's DATETIME defaults to second precision, while Java's LocalDateTime is precise down to nanoseconds. When you write a timestamp with milliseconds into MySQL, anything beyond the column's precision gets rounded — and the stored time can end up a full second later than what you sent.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-symptom">The Symptom<a href="https://tanzhuo.xyz/en/2023/05/05/mysql-shi-jian-si-she-wu-ru#the-symptom" class="hash-link" aria-label="Direct link to The Symptom" title="Direct link to The Symptom" translate="no">​</a></h2>
<p>Say the Java side has <code>2022-01-01 12:34:56.789</code>. After writing it into a plain DATETIME column, it reads back as <code>2022-01-01 12:34:57</code> — the milliseconds got rounded up into the seconds.</p>
<p>The discrepancy is easy to miss day-to-day, but it surfaces in a few scenarios:</p>
<ol>
<li class="">
<p>A unit test saves an entity, reads it back, and compares the time field with <code>assertEquals</code> — and fails intermittently. The carry only happens when the milliseconds are 500 or above, so the problem comes and goes.</p>
</li>
<li class="">
<p>Cursor pagination or range queries on a creation-time column count a boundary record twice, or skip it entirely.</p>
</li>
<li class="">
<p>The timestamp in your logs and the one in the database differ by a second, and nothing lines up when you're tracing a request.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-it-happens">Why It Happens<a href="https://tanzhuo.xyz/en/2023/05/05/mysql-shi-jian-si-she-wu-ru#why-it-happens" class="hash-link" aria-label="Direct link to Why It Happens" title="Direct link to Why It Happens" translate="no">​</a></h2>
<p>Since MySQL 5.6.4, DATETIME, TIMESTAMP, and TIME all support fractional seconds. The precision is set by the fsp (fractional seconds precision) in the type definition, ranging from 0 to 6, with a default of 0 — seconds only.</p>
<p>The key behavior: when an inserted value has higher precision than the column, MySQL rounds per the SQL standard rather than truncating, and by default emits no warning at all. So <code>.789</code> carries into the seconds, while <code>.400</code> is silently dropped — either way, the stored value no longer matches what Java has in memory.</p>
<p>On the Java side, <code>LocalDateTime</code> stores time internally in nanoseconds, and the JDBC driver passes the fractional seconds along to the server, so the rounding happens on MySQL's end. <code>java.sql.Timestamp</code> also has nanosecond precision — switching the Java type to it doesn't avoid the database-side rounding. The root of the problem is the column's precision definition.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fixes">Fixes<a href="https://tanzhuo.xyz/en/2023/05/05/mysql-shi-jian-si-she-wu-ru#fixes" class="hash-link" aria-label="Direct link to Fixes" title="Direct link to Fixes" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-raise-the-column-precision">1) Raise the column precision<a href="https://tanzhuo.xyz/en/2023/05/05/mysql-shi-jian-si-she-wu-ru#1-raise-the-column-precision" class="hash-link" aria-label="Direct link to 1) Raise the column precision" title="Direct link to 1) Raise the column precision" translate="no">​</a></h3>
<p>If your use case needs millisecond precision (enough for the vast majority of created/updated timestamp scenarios), just define the column as <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>Use <code>DATETIME(6)</code> if you need microseconds. Note that the matching default-value functions need a precision argument too: <code>NOW()</code> only goes to seconds, so write <code>NOW(3)</code> or <code>CURRENT_TIMESTAMP(3)</code>, otherwise the default value itself has no fractional seconds.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-truncate-ahead-of-time-on-the-java-side">2) Truncate ahead of time on the Java side<a href="https://tanzhuo.xyz/en/2023/05/05/mysql-shi-jian-si-she-wu-ru#2-truncate-ahead-of-time-on-the-java-side" class="hash-link" aria-label="Direct link to 2) Truncate ahead of time on the Java side" title="Direct link to 2) Truncate ahead of time on the Java side" translate="no">​</a></h3>
<p>If you'd rather not touch the schema, align the precision to seconds before writing:</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%)">// Drop everything below seconds, matching DATETIME(0) precision</span><span class="token plain"></span><br></div><div class="token-line" 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>Now the in-memory value and the stored value are identical, and comparisons and assertions never drift. Handling this once in a shared entity base class or audit-field populator is enough.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gotchas">Gotchas<a href="https://tanzhuo.xyz/en/2023/05/05/mysql-shi-jian-si-she-wu-ru#gotchas" class="hash-link" aria-label="Direct link to Gotchas" title="Direct link to Gotchas" translate="no">​</a></h2>
<ul>
<li class="">The rounding carries up rather than zeroing out. Many people instinctively assume extra precision gets chopped off; it's actually rounded, which is exactly where the "time gained a second" weirdness comes from. MySQL 8.0 offers the <code>TIME_TRUNCATE_FRACTIONAL</code> sql_mode to switch the behavior to truncation, but it affects behavior globally — evaluate existing workloads before flipping it.</li>
<li class="">After changing to <code>DATETIME(3)</code>, all existing rows have <code>.000</code> fractional seconds. Keep that in mind when sorting a mix of old and new data.</li>
<li class="">Higher fsp costs more storage. Pick what you need — there's no reason to default everything to <code>DATETIME(6)</code>.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/05/05/mysql-shi-jian-si-she-wu-ru#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The "extra second" is fundamentally a precision mismatch: Java sends nanoseconds, the MySQL column only stores seconds, and the excess gets rounded. Two ways out — either change the column to <code>DATETIME(3)</code>/<code>DATETIME(6)</code> so the database can hold it, or truncate on the Java side with <code>truncatedTo(ChronoUnit.SECONDS)</code> up front. There's only one principle: make the precision in memory match the precision in the column definition, so what you write is what you read.</p>]]></content:encoded>
            <category>Database</category>
        </item>
        <item>
            <title><![CDATA[Data Lakes and Iceberg]]></title>
            <link>https://tanzhuo.xyz/en/2023/05/05/shu-ju-hu</link>
            <guid>https://tanzhuo.xyz/en/2023/05/05/shu-ju-hu</guid>
            <pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Sorting out the differences between data lakes and data warehouses, and introducing Apache Iceberg, an open-source table format: versioning, metadata management, partitioning, and snapshots, plus some notes for technology selection.]]></description>
            <content:encoded><![CDATA[<p>Notes on Iceberg, currently one of the most popular open-source data lake projects.</p>
<!-- -->
<p>While reviewing big-data storage options recently, there was no getting around the term "data lake". Business data long ago stopped being just tables in a relational database: event logs, JSON payloads, images, audio, and video all need somewhere to land. Traditional data warehouses struggle more and more with these workloads, and a wave of open-source projects built around the data lake concept fills exactly that gap. This post sorts out the difference between data lakes and warehouses, and takes a look at Iceberg, one of the most representative projects in the space — my own study notes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-lakes-vs-data-warehouses">Data Lakes vs. Data Warehouses<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-hu#data-lakes-vs-data-warehouses" class="hash-link" aria-label="Direct link to Data Lakes vs. Data Warehouses" title="Direct link to Data Lakes vs. Data Warehouses" translate="no">​</a></h2>
<p>A data lake is a centralized repository for storing both structured and unstructured data. It can hold data of every type: traditional relational data, semi-structured data, unstructured data, and more. Its defining traits are flexibility and scalability — data can be stored and processed with minimal friction.</p>
<p>A core idea behind data lakes is "store first, manage later" (schema-on-read): data is written in its raw format to cheap distributed storage (HDFS or object storage), and its structure is only parsed when it's actually analyzed. This is the exact opposite of the warehouse approach of "model first, then load" (schema-on-write), and it's why lakes can accommodate data in any shape.</p>
<p>Traditional data warehouses, by contrast, are built mainly for structured data and typically use relational databases for storage and management. They are highly normalized and structured, which guarantees data accuracy and consistency. The downside is that warehouses are inflexible: they can't store or process unstructured or semi-structured data, and they're hard to scale and upgrade.</p>
<p>The main advantages of a data lake over a data warehouse:</p>
<ol>
<li class="">Unrestricted storage: a data lake can hold every kind of data — structured, semi-structured, unstructured. That makes it flexible enough for storage needs of any type and scale.</li>
<li class="">Faster processing: data lakes typically use distributed storage and compute, enabling high-speed processing and analysis. Traditional warehouses often need multiple rounds of data transformation and computation, which slows things down.</li>
<li class="">Lower cost: data lakes are usually built on open-source technology like Hadoop and Spark, keeping costs relatively low, whereas traditional warehouses require commercial database software.</li>
<li class="">More flexibility: a data lake makes it easy to store and process data however you need, while a traditional warehouse's repeated transformation and computation steps make the process complex and cumbersome.</li>
</ol>
<p>Of course, flexibility has a price. A bare data lake is just a pile of file directories — no transactions, no schema enforcement. If writers and readers aren't careful, they trample each other, and over time the lake easily degrades into a "data swamp". The table format layer of abstraction exists to solve exactly this: it defines "table" semantics on top of the files. Iceberg is one of those projects.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="apache-iceberg">Apache Iceberg<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-hu#apache-iceberg" class="hash-link" aria-label="Direct link to Apache Iceberg" title="Direct link to 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 is an open-source table format and processing library designed to solve the problems of managing and processing tables in a data lake. It was developed at Netflix and has since become a top-level project of the Apache Software Foundation.</p>
<p>Iceberg addresses several pain points of traditional data lakes, such as table versioning, table metadata management, partition management, and table snapshot management. It works with a range of storage and processing systems, including Hadoop, Spark, Presto, and Flink.</p>
<p>Its working mechanism deserves a closer look. Iceberg itself doesn't store data — the data still lives in columnar files like Parquet and ORC; what Iceberg manages is the metadata layer on top. The metadata has roughly three levels:</p>
<ol>
<li class="">Snapshots: the complete state of the table at a point in time. Every committed write produces a new snapshot, and old snapshots remain readable.</li>
<li class="">Manifest lists and manifest files: they record which data files a snapshot contains, along with each file's partition ranges, row counts, column-level statistics, and so on.</li>
<li class="">Data files: the actual columnar files. Metadata only references them, never modifies them.</li>
</ol>
<p>On write, the new metadata is persisted first, and the commit completes with a single atomic swap of the table pointer — giving you database-like ACID semantics on top of a file system. On read, the engine consults the metadata first and uses its statistics to skip irrelevant files, instead of listing an entire directory the way Hive does — on object storage, that makes a huge difference to performance.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="icebergs-key-features">Iceberg's Key Features<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-hu#icebergs-key-features" class="hash-link" aria-label="Direct link to Iceberg's Key Features" title="Direct link to Iceberg's Key Features" translate="no">​</a></h2>
<p>Iceberg's main features:</p>
<ol>
<li class="">Table versioning: Iceberg supports version control for tables. You can easily roll back to a historical version, or merge different versions of a table.</li>
<li class="">Table metadata management: Iceberg provides a metadata mechanism that makes tables easy to manage and query. Through the metadata you can inspect a table's structure, partitions, data statistics, and more.</li>
<li class="">Partition management: Iceberg supports multiple partitioning schemes, including hash partitioning, range partitioning, and hybrid hash-range partitioning. Pick whichever fits your needs.</li>
<li class="">Table snapshot management: Iceberg supports snapshots of tables, making backup and recovery straightforward.</li>
</ol>
<p>One more detail on partitioning: Iceberg's partitions are hidden partitions. Partition values are derived automatically from column values through transform functions (say, truncating a timestamp to the day), so queries just filter on the original columns and the engine applies partition pruning automatically. Whoever writes the query doesn't need to know how the table is partitioned — much friendlier than Hive's manually maintained partition columns. The partition spec itself can also evolve, and changing it doesn't require rewriting historical data.</p>
<p>Schema evolution is another strong suit: adding, dropping, and renaming columns only touch metadata. Columns are tracked by unique IDs rather than by name or position, which avoids the classic old-table-format problem where renaming a column could make you read the wrong data.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gotchas">Gotchas<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-hu#gotchas" class="hash-link" aria-label="Direct link to Gotchas" title="Direct link to Gotchas" translate="no">​</a></h2>
<ol>
<li class="">The small-files problem still exists. High-frequency streaming writes produce lots of small data files and metadata files; you need to run compaction regularly, or query performance keeps degrading.</li>
<li class="">Snapshots aren't cleaned up automatically. Every commit leaves a snapshot behind, and files referenced by historical snapshots are never deleted. Skip snapshot expiration for long enough and storage just keeps piling up.</li>
<li class="">Think through your catalog choice first. Iceberg's table pointer needs a catalog to manage it (Hive Metastore, JDBC, and others all work). For different engines to access the same table they must share the same catalog, and migrating later is painful.</li>
<li class="">Delta Lake and Apache Hudi are peer projects — similar positioning, different ecosystem emphasis. Before choosing, compare them against the compute engine you actually use most; don't just follow the crowd.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-hu#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Data lakes solve the "store anything" problem, but storage without management turns a lake into a swamp fast. Table formats like Iceberg fill in transactions, versioning, partitioning, and schema management on top of the files, giving the lake table semantics close to a warehouse's while keeping the advantages of open formats and low cost. For me, the key to understanding it is that three-level metadata structure — once you see how snapshots and manifest files are organized, rollback, time travel, and partition pruning all fall into place naturally. When I get the chance, I'll write up the hands-on experience of reading and writing Iceberg tables from Flink / Spark separately.</p>]]></content:encoded>
            <category>Big Data</category>
        </item>
        <item>
            <title><![CDATA[Data Structures: The Stack]]></title>
            <link>https://tanzhuo.xyz/en/2023/05/05/shu-ju-jie-gou</link>
            <guid>https://tanzhuo.xyz/en/2023/05/05/shu-ju-jie-gou</guid>
            <pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[A stack is a last-in-first-out (LIFO) data structure that only allows insertion and deletion at the top, and can be implemented with an array or a linked list. This post walks through its basic operations, complexity, and its uses in function calls, expression evaluation, compilers, and operating systems.]]></description>
            <content:encoded><![CDATA[<p>A stack is a last-in-first-out (LIFO) data structure that only allows insertion and deletion at the top. It can be implemented with an array or a linked list.</p>
<!-- -->
<p>The reason the stack deserves its own post is that it's just about the simplest data structure there is, yet it's everywhere: function calls, exception stack traces, the browser's back button, an editor's undo — all the same model underneath. Plenty of problems that look complicated (bracket matching, expression evaluation) become instantly clear once you think of using a stack. Understanding the stack is also a prerequisite for understanding recursion and call-stack overflow issues.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="basic-concepts">Basic Concepts<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-jie-gou#basic-concepts" class="hash-link" aria-label="Direct link to Basic Concepts" title="Direct link to Basic Concepts" translate="no">​</a></h2>
<p>In a stack, insertion and deletion are usually called push and pop. When an element is inserted, it goes on top of the stack; when an element is removed, it comes off the top. The top of the stack holds the most recently added element; the bottom holds the earliest.</p>
<p>Think of a stack as a pile of plates: a new plate can only go on top, and you can only take plates from the top. That "single opening" constraint is exactly where the stack's value lies — it naturally remembers "who came in last", making it perfect for anything that needs to "retrace its steps".</p>
<p>Stacks are used everywhere. For instance, function calls and recursive calls in a computer are implemented with a stack. When a function is called, its arguments, return address, and local variables are pushed onto the stack; when the function returns, that information is popped back off.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="basic-operations">Basic Operations<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-jie-gou#basic-operations" class="hash-link" aria-label="Direct link to Basic Operations" title="Direct link to Basic Operations" translate="no">​</a></h2>
<p>The basic operations of a stack:</p>
<p>push(element): push an element onto the top of the stack.</p>
<p>pop(): pop an element off the top of the stack.</p>
<p>top(): return the top element without modifying the stack.</p>
<p>isEmpty(): check whether the stack is empty.</p>
<p>size(): return the number of elements in the stack.</p>
<p>Note the difference between pop and top: pop removes the top element and returns it, while top (called peek in some implementations) just "takes a look" and leaves the stack unchanged. Mixing these two up is a common source of bugs.</p>
<p>Implementing a stack with an array is very direct — just maintain an index pointing at the top:</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%)">// A simple array-based stack, core logic only</span><span class="token plain"></span><br></div><div class="token-line" 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%)">// index of the top element; -1 means empty</span><span class="token plain"></span><br></div><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%)">// In real code, grow the array or throw a stack-full exception here</span><span class="token plain"></span><br></div><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%)">// advance the top pointer first, then write</span><span class="token plain"></span><br></div><div class="token-line" 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%)">// Should throw on an empty stack; check omitted here</span><span class="token plain"></span><br></div><div class="token-line" 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%)">// return the top element and move the pointer back</span><span class="token plain"></span><br></div><div class="token-line" 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%)">// read only, no modification</span><span class="token plain"></span><br></div><div class="token-line" 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>A linked-list implementation treats the list head as the top of the stack: push is a head insertion, pop deletes the head node. The advantage is that you don't need to pre-allocate capacity.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="complexity-analysis">Complexity Analysis<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-jie-gou#complexity-analysis" class="hash-link" aria-label="Direct link to Complexity Analysis" title="Direct link to Complexity Analysis" translate="no">​</a></h2>
<p>A stack's time complexity is O(1), because every operation happens at the top. Its space complexity, however, is O(n), since all elements have to be stored.</p>
<p>Put another way: the stack's efficiency comes precisely from its restrictions. Because insertion and deletion in the middle aren't allowed, every operation reduces to a single read or write at the top — no shifting of other elements, no traversal. The one exception in the array implementation is the copy during resizing, but amortized, push is still constant time.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="classic-applications">Classic Applications<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-jie-gou#classic-applications" class="hash-link" aria-label="Direct link to Classic Applications" title="Direct link to Classic Applications" translate="no">​</a></h2>
<p>In computing, stacks are widely used in function calls, expression evaluation, compilers, operating systems, and more.</p>
<ol>
<li class="">Function calls: when a function is called, its arguments, return address, and local variables are pushed onto the stack, and popped off when the function returns. This is the function call stack, the foundation of how function calls work.</li>
<li class="">Expression evaluation: when a computer evaluates an expression, a stack is typically involved. For example, converting an infix expression to postfix requires a stack to hold operators so the expression's value is computed correctly.</li>
<li class="">Compilers: while translating source code into target code, compilers use stacks for parsing and code generation. In a compiler, stacks hold information about variables, functions, statements, and so on.</li>
<li class="">Operating systems: process scheduling, interrupt handling, and similar OS features also rely on stacks. When a process is interrupted, the OS pushes the current process's context (register values, program counter, etc.) onto a stack, then runs the interrupt handler. When the handler finishes, the OS pops the context back off and resumes the process.</li>
</ol>
<p>These four scenarios share one thing: a nested "enter — process — exit in reverse order" structure. Nested function calls, nested brackets, nested interrupts — they're fundamentally the same class of problem, which is why they all map onto the stack model. Bracket matching, monotonic stacks, and the iterative form of depth-first traversal you meet in coding exercises are all extensions of the same idea.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gotchas">Gotchas<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-jie-gou#gotchas" class="hash-link" aria-label="Direct link to Gotchas" title="Direct link to Gotchas" translate="no">​</a></h2>
<ol>
<li class="">
<p>Empty-stack operations: calling pop or top on an empty stack is either undefined behavior or throws outright. Either check with isEmpty first or explicitly rely on exception handling — don't just assume.</p>
</li>
<li class="">
<p>Stack overflow: recursion is fundamentally consuming the call stack, and going too deep overflows it (StackOverflowError in Java). For recursion of uncontrolled depth, rewrite it as an iterative version with an explicit stack and a loop, moving the data onto the heap.</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>tip</div><div class="admonitionContent_BuS1"><p>In Java, avoid java.util.Stack — it extends Vector, its methods carry synchronization overhead, and the design is dated. The official docs recommend ArrayDeque as a stack.</p></div></div>
<ol start="3">
<li class="">Confusing pop with top: calling pop when you only meant to read the top silently drops an element — a class of bug that's especially hard to track down inside a loop.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2023/05/05/shu-ju-jie-gou#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The stack is the textbook case of trading constraints for efficiency: expose only one opening at the top, and in exchange every operation costs O(1), while naturally fitting anything nested or backtracking-shaped. Once you've internalized push/pop/top and the LIFO mental model, the function call stack, expression evaluation, and the rest are just the same model applied at different levels.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Comparing Common Ingress Controllers]]></title>
            <link>https://tanzhuo.xyz/en/2023/05/03/ingress</link>
            <guid>https://tanzhuo.xyz/en/2023/05/03/ingress</guid>
            <pubDate>Wed, 03 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[A comparison of the Ingress controllers commonly used in k8s clusters, how Ingress works under the hood, and how to expose TCP/UDP layer-4 services.]]></description>
            <content:encoded><![CDATA[<p>This time let's compare some of the ingress components frequently used in k8s clusters. Ingress serves as the entry point for HTTP and HTTPS traffic into the cluster. It can route traffic to different Services inside a Kubernetes cluster, providing load balancing and traffic control.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-ingress-matters">Why Ingress Matters<a href="https://tanzhuo.xyz/en/2023/05/03/ingress#why-ingress-matters" class="hash-link" aria-label="Direct link to Why Ingress Matters" title="Direct link to Why Ingress Matters" translate="no">​</a></h2>
<p>By default, services in a cluster can only reach each other from inside the cluster. The most direct ways to expose a service externally are NodePort or LoadBalancer Services, but both have limitations: NodePort has a restricted port range and makes domain and path management awkward, while LoadBalancer consumes one load balancer per exposed service—which on a cloud provider means real money.</p>
<p>Ingress solves exactly this: a single unified entry point receives all external HTTP/HTTPS traffic and dispatches it to the cluster's Services by hostname and path. Which Ingress controller you choose directly shapes the day-to-day experience of routing configuration, TLS management, and layer-4 exposure, so it's worth lining up the mainstream options side by side at selection time.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How It Works<a href="https://tanzhuo.xyz/en/2023/05/03/ingress#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works" translate="no">​</a></h2>
<p>Ingress works by mapping HTTP and HTTPS requests onto Services. In Kubernetes, a Service is the resource object that routes traffic to Pods. When Ingress receives an external HTTP or HTTPS request, it matches the hostname and path in the request against the Ingress rules and routes the request to the corresponding Service.</p>
<p>Here's a point that trips people up: the Ingress resource itself is just a declarative routing rule—writing one does not make anything happen on its own. The real work is done by the Ingress controller. It runs in the cluster as Pods, continuously watches the API Server for changes to Ingress, Service, Endpoint, and other resources, translates those rules into configuration for its underlying proxy (Nginx, Envoy, or Traefik's built-in proxy, for example), and hot-reloads it into effect. In other words:</p>
<ol>
<li class="">Ingress resource: declares "which path of which hostname forwards to which Service";</li>
<li class="">Ingress controller: watches for rule changes, generates and loads the proxy configuration;</li>
<li class="">Underlying proxy: actually receives external requests and forwards them.</li>
</ol>
<p>When matching a request, the controller first matches the virtual host by Host, then matches routing rules by Path, and finally forwards the request to the Pods behind the corresponding Service. TLS certificates are typically attached to Ingress rules as Secrets, and the controller handles TLS termination at the edge.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-ingress-controllers">Common Ingress Controllers<a href="https://tanzhuo.xyz/en/2023/05/03/ingress#common-ingress-controllers" class="hash-link" aria-label="Direct link to Common Ingress Controllers" title="Direct link to Common Ingress Controllers" translate="no">​</a></h2>
<p>To use Ingress, you must install an Ingress controller first. Kubernetes does not ship a default one, but there are many third-party controllers to choose from:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2023-05-image-bc5d955ed94dca937cb7cdc2516b29d2.webp" width="1518" height="718" class="img_ev3q"></p>
<p>A quick take on the trade-offs of the common options:</p>
<ol>
<li class="">Nginx Ingress Controller: the community's most common choice, built on Nginx as a reverse proxy, with rich documentation and case studies. Much of its configuration is done via annotations. A solid default option;</li>
<li class="">Traefik: ships with a dashboard, applies configuration dynamically without an Nginx-style reload, and integrates smoothly with Let's Encrypt for automatic certificate issuance;</li>
<li class="">Istio Ingress Gateway: if the cluster is already running the Istio service mesh, using its Gateway as the entry point ties in with in-mesh traffic governance (canary rollouts, circuit breaking, telemetry)—but pulling in Istio just for an entry point is far too heavy;</li>
<li class="">Others like HAProxy and Kong each lean into performance and API gateway capabilities respectively.</li>
</ol>
<p>The selection logic can be plain and simple: with no special requirements, go with Nginx Ingress; if you want dynamic configuration and a more modern operations experience, look at Traefik; if you're already on a service mesh, just use the mesh's own Gateway.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="exposing-layer-4-services">Exposing Layer-4 Services<a href="https://tanzhuo.xyz/en/2023/05/03/ingress#exposing-layer-4-services" class="hash-link" aria-label="Direct link to Exposing Layer-4 Services" title="Direct link to Exposing Layer-4 Services" translate="no">​</a></h2>
<p>We often want to expose services outside the cluster beyond layer-7 HTTP—some run on layer-4 protocols, such as MySQL, Redis, and MongoDB.</p>
<p>However, the Kubernetes Ingress specification itself only supports HTTP and HTTPS, so default Ingress controllers only support these two protocols. Some third-party Ingress controllers, though, can be extended to expose TCP and UDP.</p>
<ol>
<li class="">Traefik: Traefik supports exposing TCP by specifying <code>traefik.tcp.routers</code> and <code>traefik.tcp.services</code> in the routing rules to configure TCP services and routes.</li>
<li class="">Istio: Istio is a service mesh framework whose Ingress Gateway component supports exposing TCP. You configure TCP services and routes by defining a VirtualService against the Ingress Gateway.</li>
</ol>
<p>One addition: the Nginx Ingress Controller can also expose TCP/UDP services. The approach is to maintain two ConfigMaps, <code>tcp-services</code> and <code>udp-services</code>, containing the "external port → namespace/Service:port" mappings, then open the corresponding ports on the controller's Service. This bypasses the Ingress resource entirely—it's the controller's own extension mechanism.</p>
<p>Layer-4 exposure differs fundamentally from layer-7: TCP has no notion of Host or Path, so you cannot distinguish backends by hostname the way HTTP does—only by port number. Every exposed TCP service therefore consumes one port at the entry point, and port management gets fiddly as services multiply.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2023/05/03/ingress#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">If you create an Ingress resource and nothing happens, most likely no controller is installed in the cluster, or the <code>ingressClassName</code> on the Ingress doesn't match the controller—an especially easy trap when multiple controllers run in one cluster;</li>
<li class="">Be careful exposing database services (MySQL, Redis, etc.) to the public internet through Ingress: layer-4 forwarding adds no authentication hardening. Safer options are the internal network, a VPN, or a bastion host;</li>
<li class="">Annotations are not portable across controllers. When migrating from Nginx to Traefik, every rewrite, rate-limit, and timeout previously configured via annotations must be translated one by one;</li>
<li class="">For long-lived layer-4 connections, watch the proxy's idle timeout: the default is often shorter than what database client connection pools expect, and connections can get cut off mid-flight.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://tanzhuo.xyz/en/2023/05/03/ingress#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>Ingress is the unified entry point for north-south traffic in the cluster: rules are declared by Ingress resources, and the actual forwarding is done by the controller. For layer-7 scenarios, any of the mainstream controllers will do the job—the differences lie mainly in configuration style and ecosystem. Layer-4 exposure goes beyond the Ingress spec itself and relies on each vendor's own extension mechanism, whether Traefik, Istio Gateway, or Nginx Ingress's ConfigMaps. Choosing based on your team's existing stack and operational habits is more practical than chasing feature lists.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A First Look at Service Mesh]]></title>
            <link>https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh</link>
            <guid>https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh</guid>
            <pubDate>Wed, 14 Dec 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Starting from the limitations of microservices, this post walks through the Sidecar approach behind Service Mesh, mainstream implementations like Istio, and how Knative and Serverless grew out of the mesh.]]></description>
            <content:encoded><![CDATA[<p><strong>This tech talk is about the big-picture ideas and mental models only; specific configuration details and deployment steps are out of scope.</strong></p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="introduction">Introduction<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#introduction" class="hash-link" aria-label="Direct link to Introduction" title="Direct link to Introduction" translate="no">​</a></h2>
<p>The discussion revolves around three points:</p>
<ol>
<li class="">The limitations of microservices</li>
<li class="">What Service Mesh does and how it is implemented</li>
<li class="">Serverless platforms built on top of Service Mesh today</li>
</ol>
<p>The stacks we know best—Java, Spring Cloud, Spring Cloud Alibaba, and the like—belong to the traditional microservice architecture. This design suffers from tight SDK coupling: every update to the underlying SDK version can easily destabilize services at the business layer. When the SDK is tightly coupled to the code environment, many constraints follow—you cannot fully leverage the different languages and their ecosystems in the domains where they excel. For example: the nimble Go and Python languages with their goroutines, web scraping, and neural network libraries; or the rigorous Rust language with no garbage collection overhead, better suited to enterprise middleware scenarios that demand high efficiency and a low memory footprint. Every language has its own strengths and ecosystem, and looking further ahead, it is a clear trend that large industry leaders will spread their business deployments across different languages as they iterate.</p>
<p>The traditional microservice stack:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-12-9fdf013b22e8458233744cad658d63a2-18ebc7a7b51196340270f5c789e43fc9.webp" width="479" height="422" class="img_ev3q"></p>
<p>Moreover, the cross-OS portability the JVM once prided itself on has been superseded by container technologies and ecosystems such as K8S, Docker, Containerd, CRI-O, and Kata. Advanced features like garbage collection, dynamic proxies, agents, and ZGC have also been borrowed by other languages. What props up Java's position today is the ecosystem accumulated over the years (open source communities from Apache, Spring, Alibaba, Huawei, and others keep injecting new middleware—the well-known Kafka, Hadoop, HBase, RocketMQ, Flink, Seata, and so on—plus whatever comes next to keep the ecosystem alive). But over time, in the long run (20–30 years or more—a subjective prediction, take it for what it's worth), these advantages may be claimed by rising newcomers, and the next generation of microservice architecture is bound to iterate on the strengths of many languages.</p>
<p>A personal take: Java is now owned by Oracle, and Oracle is not a technologically aggressive company, so it may well constrain Java's development going forward. There is an open source edition, but open source efforts are less concentrated than corporate ones. Meanwhile Google's Go language has risen rapidly and already taken a slice of Java's market—likely one of the reasons companies like ByteDance, Bilibili, Tencent, Baidu, JD.com, and Xiaomi have adopted it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="service-mesh">Service Mesh<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#service-mesh" class="hash-link" aria-label="Direct link to Service Mesh" title="Direct link to Service Mesh" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h3>
<p>Back to our topic: Service Mesh. Its dominant implementation approach today is the Sidecar pattern. In K8S the Pod is the basic deployment unit; the Sidecar approach deploys a Proxy container inside the same Pod as the business Server container (Pod container injection), and the Proxy container intercepts all of the Server container's traffic.</p>
<p>The big-picture architecture of Istio's Service Mesh implementation:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-12-image-50-29cab4357ba6d65501863969da0b1a2a.webp" width="1057" height="715" class="img_ev3q"></p>
<p><strong>citadel:</strong> the core security component responsible for identity authentication and certificate management.</p>
<p><strong>galley:</strong> the configuration management component—it validates the format and content of configuration files and feeds that configuration to pilot and mixer.</p>
<p><strong>pilot:</strong> the control hub, covering service discovery plus rule translation and distribution.</p>
<p><strong>proxy:</strong> implemented by the C++-based Envoy together with Pilot-agent, providing dynamic service discovery, load balancing, TLS, circuit breaking, health checks, traffic splitting, canary rollouts, and more. It also produces telemetry data, giving microservices observability.</p>
<p><strong>Ingressgateway:</strong> the gateway at the entrance—traffic from outside the mesh reaches services inside the mesh through this gateway.</p>
<p>With the Proxy container, business code is fully decoupled from its SDK, and you gain a long list of capabilities: <strong>traffic mirroring, canary releases, service registration and discovery, remote invocation, circuit breaking, degradation, distributed tracing, control plane management, and encrypted in-cluster transport (distributed transactions have no dedicated solution here, since transactions strictly speaking do not belong to the service governance layer)</strong>. All of this is completely transparent to programmers: developers do not need to know the deployment details and can code almost exactly as they would for a single machine, with no language restrictions and no dependency on any microservice SDK. Companies are then free to develop different functional modules in different languages (which may bring higher code-management and communication costs, and a broader spread of languages the team must master).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-official-demo">The Official Demo<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#the-official-demo" class="hash-link" aria-label="Direct link to The Official Demo" title="Direct link to The Official Demo" translate="no">​</a></h3>
<p>The official demo, the Book-Info microservice app, can be deployed to a K8S environment with one Helm command so you can experience these features firsthand (Book-Info architecture diagram):</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-12-noistio-6757dcc885fcdd61bb4011d88efc5dd7.svg" width="662" height="453" class="img_ev3q"></p>
<p>The Bookinfo microservices are deployed with Istio sidecars and are written in 4 different languages, and the Java-based Reviews service ships 3 different versions of the application—an excellent showcase of Istio's capabilities in multi-version control, traffic management, traffic migration, and integration. Deploy it yourself and you will get a very direct feel for how powerful Service Mesh is and how flexible the configuration can be (all changes take effect within seconds, with imperceptible traffic migration).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-most-common-implementations">The Most Common Implementations<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#the-most-common-implementations" class="hash-link" aria-label="Direct link to The Most Common Implementations" title="Direct link to The Most Common Implementations" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="istio">Istio<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#istio" class="hash-link" aria-label="Direct link to Istio" title="Direct link to Istio" translate="no">​</a></h3>
<p>Istio is an open source Service Mesh framework initiated by Google, IBM, and Lyft. The project launched in 2017 and released version 1.0 in July 2018; as of 2022-12-06 the current version is 1.16.0. Istio is the flagship Service Mesh implementation today. If the Sidecar is the data plane of the whole Service Mesh, Istio's main improvements are on the control plane. Istio uses Envoy as the sidecar, and the entire control plane is written in Golang, bringing a significant performance boost. Istio is first and foremost a service mesh, but it is more than that: on top of classic service meshes like Linkerd and Envoy, Istio provides a complete solution that delivers behavioral insight and operational control over the entire mesh, meeting the diverse needs of microservice applications.</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/en/2022/12/14/chu-tan-service-mesh#linkerd2" class="hash-link" aria-label="Direct link to Linkerd2" title="Direct link to Linkerd2" translate="no">​</a></h3>
<p>Linkerd is a high-performance network proxy open-sourced by Buoyant in 2016 and the industry's first Service Mesh framework, built to address the problems services face when communicating in distributed environments—unreliable and insecure networks, latency, packet loss, and so on. Linkerd was originally written in Scala; its successor, Linkerd2, was rewritten in Go and 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/en/2022/12/14/chu-tan-service-mesh#conduit" class="hash-link" aria-label="Direct link to Conduit" title="Direct link to Conduit" translate="no">​</a></h3>
<p>Conduit, released in December 2017, is another open source project sponsored by Buoyant after Linkerd, positioned as a standalone Kubernetes-focused edition of Linkerd. Conduit aims to radically simplify the complexity of running a service mesh on Kubernetes and improve the user experience, rather than optimizing for every platform the way Linkerd did. Its main goals are to be lightweight, high-performance, secure, and very easy to understand and use. Like Linkerd and Istio, Conduit consists of a data plane and a control plane: the data plane is written in Rust, which lets Conduit run on very little memory, while the control plane is written in 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="architecture-comparison">Architecture Comparison<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#architecture-comparison" class="hash-link" aria-label="Direct link to Architecture Comparison" title="Direct link to Architecture Comparison" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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="recap">Recap<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#recap" class="hash-link" aria-label="Direct link to Recap" title="Direct link to Recap" translate="no">​</a></h3>
<p>Kubernetes has already solved most operational problems—container deployment, high availability, multiple replicas, container migration, elastic scaling, rolling updates, image version management, liveness probing, compute resource allocation, node monitoring—with an excellent automated operations machinery. But K8S does not provide monitoring and traffic management for application-layer Service containers, nor inter-container invocation, monitoring, registration, configuration, or traffic governance. Service Mesh technology emerged to fill that gap and extend K8S in this area, so that microservices no longer worry about specific configuration or environment details, focus on implementing business features, and are freed from language constraints.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="serverless">Serverless<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#serverless" class="hash-link" aria-label="Direct link to Serverless" title="Direct link to Serverless" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview-1">Overview<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#overview-1" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h3>
<p>As the microservice philosophy took hold, more and more companies gradually moved their applications from monoliths to microservice architectures, and the arrival of container technology accelerated that transition. Containers effectively solved the many runtime-environment discrepancies between services, but as the number of services grew, container orchestration and management became the new problem. Kubernetes then answered the challenge of orchestrating and deploying microservice containers at scale, making the industry realize that PaaS could actually land in practice. And as the container count in a microservice system kept climbing, service governance and traffic management became unavoidable—hence Istio, which, through its strategy of separating the network proxy from control, enables effective and sensible management of service control policies.</p>
<p>At this point the architectural evolution seems to have reached a lovely place:</p>
<ul>
<li class=""><strong>Microservices</strong>: solve the problem of bloated, over-coupled applications.</li>
<li class=""><strong>Containers</strong>: solve runtime-environment discrepancies and deployment.</li>
<li class=""><strong>Kubernetes</strong>: solves orchestration and "aggregate" deployment of huge numbers of micro-containers.</li>
<li class=""><strong>Istio</strong>: solves the traffic, release, and governance problems services face once they go live.</li>
</ul>
<p>At first glance, building a container cloud now has a complete chain and solution—everything looks so "perfect."</p>
<p>But step back and dig deeper: does service interaction under the microservice model still have problems? First, whether over HTTP or RPC, it is fundamentally one service remotely calling another; within application code, services cannot be truly transparent to each other. The consequence: no matter how "finely" the business is decomposed into microservices, the business units still cannot run and evolve independently—they are never fully decoupled—and when branching into different development domains, you cannot pick the most suitable implementation for each. So we want to standardize development environments through "templates" and "configuration," and provide an "event" mechanism to reduce the coupling of service-to-service interaction to a minimum. Second, there is the problem of dynamic scaling in production: elasticity in today's Kubernetes environments requires customers to collect monitoring data and implement scaling manually, but we would rather have production services scale automatically and intelligently.</p>
<p>Finally there is service standardization. We want the internal service model to be standard—quickly replicable, quickly buildable; service communication to be standard—standard protocols, standard formats; and the runtime environment to be standard—fast to deploy, fast to migrate.</p>
<p>Knative arrived to solve exactly these problems: direct remote invocation, automatic online scaling, version snapshots, and a whole series of standardization issues.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="knative">Knative<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#knative" class="hash-link" aria-label="Direct link to Knative" title="Direct link to Knative" translate="no">​</a></h3>
<p>Knative is the weapon Google unveiled for the Serverless world in 2018. It brings serverless architecture to any public or private cloud, letting users adopt serverless programming techniques. The main participating companies are Google, Pivotal, IBM, and Red Hat; it was announced on July 24, 2018 and is still evolving rapidly. Unlike Kubernetes, which must keep at least one Pod instance running to serve an application, Knative can scale down to zero (<strong>cold/warm start technology</strong>). Only when a client request hits your application does Knative actually spin up the application's Pods. This can save a great deal of the money spent keeping applications running year-round. For instance, infrequently accessed, low-traffic functional modules are good candidates for cold-started applications to save memory and compute (container startup time can be optimized to reduce cold-start latency—down to milliseconds at best; in China, Alibaba Cloud is probably the leader here).</p>
<p>Official sites:</p>
<p>Documentation at <a href="https://knative.dev/docs/" target="_blank" rel="noopener noreferrer" class="">knative.dev/docs</a> and <a href="https://github.com/knative" target="_blank" rel="noopener noreferrer" class="">github.com/knative</a></p>
<p>Knative's goal is to support the entire development lifecycle on top of Kubernetes. Concretely: first, it lets you as a developer write code in whatever language and style you want; second, it helps you build and package the application; and finally it helps you run and scale it. Knative is built around three core components—Build, Serving, and Eventing—and it is these three that drive the great Serverless ship of Knative forward.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="build">Build<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#build" class="hash-link" aria-label="Direct link to Build" title="Direct link to Build" translate="no">​</a></h3>
<ul>
<li class="">In-cluster builds: builds run inside Kubernetes, compiling functions and shipping them quickly, tightly integrated with the whole Kubernetes ecosystem.</li>
<li class="">Standardization: it aims to be a general, standardized build component usable as part of larger systems; deployment scripts are standardized and structured, making services easier to migrate and deploy.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="serving">Serving<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#serving" class="hash-link" aria-label="Direct link to Serving" title="Direct link to Serving" translate="no">​</a></h3>
<ul>
<li class="">Fast deployment: rapidly deploy serverless containers.</li>
<li class="">Scale on demand: automatic scaling up and down, including scale-to-zero.</li>
<li class="">Routing policies: routing and network programming built on Istio.</li>
<li class="">Version snapshots: deployment snapshots (production container snapshots that can be kept long-term and restored to any snapshot version at will).</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="eventing">Eventing<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#eventing" class="hash-link" aria-label="Direct link to Eventing" title="Direct link to Eventing" translate="no">​</a></h3>
<p><strong>Source, Channel, Subscription</strong></p>
<p>The eventing system makes producing and consuming events easy: it abstracts event sources and lets operators use the messaging layer of their choice. The heart of Serverless is event-based triggering—when something happens, a particular function fires. The event concept decouples functions from their callers: a deployed function does not care who will call it, and an event source does not care who will handle it. In short, our code no longer names the concrete callee Service; it only concerns itself with emitting events and handling events (anyone familiar with MQ queues will feel at home here, though Eventing's system is a bit more complete, with several event-processing patterns). When our Service makes a remote RPC or HTTP call, it needs neither knowledge of a concrete service instance nor a subscription to a registry—just emit the event source, and Eventing handles all of the returned data. This achieves true transparency between services and solves the coupling problem. <strong>(As the saying goes, coupling and decoupling are often just one indirection layer apart.)</strong></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-12-650cc9d17fb6fb116fb265ff782a307308c-1508a550eb4a75c7aff60d1624fa8156.webp" width="1017" height="722" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="overall-strengths">Overall Strengths<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#overall-strengths" class="hash-link" aria-label="Direct link to Overall Strengths" title="Direct link to Overall Strengths" translate="no">​</a></h3>
<p><strong>Convenience</strong>: Knative uses Kubernetes as its underlying framework, so any Kubernetes cluster—online or on-premises, a managed cloud service or self-built—can become a Serverless platform quickly by installing the Knative add-on.</p>
<p><strong>Standardization</strong>: together with the CNCF, Knative standardized all events into CloudEvents, giving events cross-platform portability while decoupling functions from their concrete callers.</p>
<p><strong>Inter-service decoupling</strong>: with Knative, applications are no longer hard-bound to underlying dependent services and can interoperate across clouds.</p>
<p><strong>A mature ecosystem</strong>: Knative is built on the Kubernetes stack and integrates tightly with its ecosystem.</p>
<p><strong>Autoscaling</strong>: it monitors application requests and scales automatically, and—thanks to Istio (or ambassador, gloo, etc.)—natively supports blue-green deployments and rollbacks, streamlining the release process.</p>
<p><strong>Application monitoring</strong>: log collection, search, and analysis are supported, along with metrics dashboards and call-chain tracing.</p>
<p><strong>Snapshot deployment</strong>: every release is recorded as a snapshot that can be retained long-term, and you can seamlessly restore to any snapshot version at any point in time.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>Serverless has become the new technology hotspot, evolved on top of traditional container technology and the Service Mesh. Serverless cloud functions free developers from server deployment and operations: develop just the core business logic or function and it is live, automatically gaining distributed disaster tolerance and load-based autoscaling. On public clouds you are billed by actual invocation count, execution duration, and compute consumed—no wasted resources, and better cost savings for the business.</p>
<p><strong>Serverless broadly falls into two categories: BaaS (Backend as a Service) and FaaS (Function as a Service).</strong></p>
<p><strong>BaaS:</strong> "serverless" was first used to describe applications that significantly or fully incorporate third-party, cloud-hosted applications and services to manage server-side logic and state. These are typically "rich client" applications—single-page web apps or mobile apps—that use the vast ecosystem of cloud-accessible databases (e.g. Parse, Firebase), authentication services (e.g. Auth0, AWS Cognito), and so on. These kinds of services were previously described as "Backend as a Service"—think of the containers and service middleware we commonly use on the backend: Spring Boot, Tomcat, Dubbo, WebLogic, Gin, Flask.</p>
<p><strong>FaaS:</strong> serverless can also mean applications where server-side logic is still written by the application developer. Unlike traditional architectures, it runs in stateless compute containers that are event-triggered—you implement just a function without worrying about the environment—ephemeral (possibly lasting a single invocation), and fully managed by a third party. One way to think of it is "functions as a service," or FaaS. Abroad, AWS Lambda is one of the most popular FaaS platforms; in China, Alibaba Cloud's Function Compute (FC) offers FaaS today.</p>
<p>In short, serverless architecture did not appear to replace traditional applications. Starting from its highly flexible usage model and event-driven nature, it helps us reduce deployment effort, improve scalability, and shrink the burden of maintaining the infrastructure behind the code—giving us more options to pick the deployment approach that best fits the business.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="further-questions">Further Questions<a href="https://tanzhuo.xyz/en/2022/12/14/chu-tan-service-mesh#further-questions" class="hash-link" aria-label="Direct link to Further Questions" title="Direct link to Further Questions" translate="no">​</a></h2>
<ol>
<li class="">
<p>How will deployment architectures evolve? How will the 3H traits—high availability, high performance, high concurrency—spawn new technology stacks? How does an enterprise private cloud reach 6 to 12 nines of availability? (<strong>6 nines: (1-99.9999%)*365*24*60*60 = 31 seconds—no more than 31 seconds of downtime per year. But 12 nines is quite a challenge; from a cost perspective it is unnecessary and perhaps impossible: annual downtime must not exceed (1-99.9999999999%)*365*24*60*60 = 0.00003 seconds.</strong>) Combined with multi-region active-active architectures, how many new middleware technologies might that spawn?</p>
</li>
<li class="">
<p>Does this architecture really have only upsides and no downsides? Are its overhead, footprint, and controllability suitable for every company? Is it sound for a company to jump straight to Serverless without considering its business scenarios?</p>
</li>
<li class="">
<p>Have you noticed that container ecosystems in software and hyper-converged infrastructure in hardware are strikingly similar? Both aim for elasticity, migration, cost savings, flexibility, agility, and greater reliability.</p>
</li>
</ol>
<p>Corrections and additions are welcome—feel free to leave a comment.</p>]]></content:encoded>
            <category>K8s</category>
        </item>
        <item>
            <title><![CDATA[IntelliJ IDEA Default Shortcuts]]></title>
            <link>https://tanzhuo.xyz/en/2022/11/25/idea-mo-ren-kuai-jie-jian</link>
            <guid>https://tanzhuo.xyz/en/2022/11/25/idea-mo-ren-kuai-jie-jian</guid>
            <pubDate>Fri, 25 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A roundup of the most-used default keybindings in IntelliJ IDEA, grouped by navigation, editing, refactoring, and debugging, with memorization tips and conflict troubleshooting.]]></description>
            <content:encoded><![CDATA[<p>A record of IDEA's default keyboard shortcuts. After years of writing Java, what actually leveled up my productivity wasn't plugins—it was getting my hands off the mouse. This post walks through the default keybindings by use case.</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-11-20200928180550215-f90b66344ed049dc5d225ec73fc74153.webp" width="1600" height="702" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-the-default-keymap-is-worth-learning">Why the Default Keymap Is Worth Learning<a href="https://tanzhuo.xyz/en/2022/11/25/idea-mo-ren-kuai-jie-jian#why-the-default-keymap-is-worth-learning" class="hash-link" aria-label="Direct link to Why the Default Keymap Is Worth Learning" title="Direct link to Why the Default Keymap Is Worth Learning" translate="no">​</a></h2>
<p>IDEA supports custom keymaps, and you can switch to Eclipse or VS Code style with one click. But I recommend sticking with the defaults, for a simple reason: when you switch machines, hop onto a coworker's computer, or follow official docs and video tutorials, the default keymap works everywhere, and your muscle memory never goes to waste.</p>
<p>The shortcuts below follow the default Windows/Linux keymap. On macOS, swapping <code>Ctrl</code> for <code>Cmd</code> and <code>Alt</code> for <code>Option</code> usually gets you there; the few exceptions are called out separately.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="search-and-navigation">Search and Navigation<a href="https://tanzhuo.xyz/en/2022/11/25/idea-mo-ren-kuai-jie-jian#search-and-navigation" class="hash-link" aria-label="Direct link to Search and Navigation" title="Direct link to Search and Navigation" translate="no">​</a></h2>
<p>These are the most frequently used—practice them first.</p>
<ul>
<li class=""><strong>Double Shift</strong>: Search Everywhere—globally search classes, files, symbols, and settings. When in doubt, use this.</li>
<li class=""><strong>Ctrl + N</strong>: find a class by name; <strong>Ctrl + Shift + N</strong>: find a file; <strong>Ctrl + Alt + Shift + N</strong>: find a symbol (methods, fields).</li>
<li class=""><strong>Ctrl + Shift + F</strong>: project-wide text search; <strong>Ctrl + Shift + R</strong>: project-wide replace.</li>
<li class=""><strong>Ctrl + B</strong> (or Ctrl + left click): jump to definition; <strong>Ctrl + Alt + B</strong>: jump to implementation—more useful than jumping to the definition when reading interfaces.</li>
<li class=""><strong>Alt + F7</strong>: Find Usages—see who calls a method or references a field.</li>
<li class=""><strong>Ctrl + E</strong>: recently opened files; <strong>Ctrl + Shift + E</strong>: recently edited files.</li>
<li class=""><strong>Ctrl + G</strong>: go to a specific line number—handy when chasing stack traces in logs.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="editing-and-code-generation">Editing and Code Generation<a href="https://tanzhuo.xyz/en/2022/11/25/idea-mo-ren-kuai-jie-jian#editing-and-code-generation" class="hash-link" aria-label="Direct link to Editing and Code Generation" title="Direct link to Editing and Code Generation" translate="no">​</a></h2>
<ul>
<li class=""><strong>Alt + Enter</strong>: context-aware quick fix—importing packages, adding try-catch, creating missing methods all rely on it. Arguably the soul of IDEA.</li>
<li class=""><strong>Alt + Insert</strong> (Cmd + N on macOS): Generate menu—constructors, getters/setters, equals/hashCode, method overrides.</li>
<li class=""><strong>Ctrl + D</strong>: duplicate the current line; <strong>Ctrl + Y</strong>: delete the current line. Note that Ctrl + Y is redo in many editors but deletes a line in IDEA—easy to hit by mistake at first.</li>
<li class=""><strong>Ctrl + W</strong> / <strong>Ctrl + Shift + W</strong>: expand/shrink selection by syntax structure—great for selecting an expression or a code block.</li>
<li class=""><strong>Ctrl + Alt + L</strong>: format code; <strong>Ctrl + Alt + O</strong>: optimize imports.</li>
<li class=""><strong>Ctrl + /</strong>: line comment; <strong>Ctrl + Shift + /</strong>: block comment.</li>
<li class=""><strong>Shift + Alt + Up/Down</strong>: move a line up or down; <strong>Ctrl + Shift + Up/Down</strong>: move by syntax block, so statements never leave the method body.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="refactoring">Refactoring<a href="https://tanzhuo.xyz/en/2022/11/25/idea-mo-ren-kuai-jie-jian#refactoring" class="hash-link" aria-label="Direct link to Refactoring" title="Direct link to Refactoring" translate="no">​</a></h2>
<p>The value of refactoring shortcuts is safety: IDEA updates every reference in sync, far more reliable than manual search-and-replace.</p>
<ul>
<li class=""><strong>Shift + F6</strong>: rename—works for classes, methods, and variables alike.</li>
<li class=""><strong>Ctrl + Alt + M</strong>: Extract Method; <strong>Ctrl + Alt + V</strong>: extract variable.</li>
<li class=""><strong>Ctrl + Alt + C</strong>: extract constant; <strong>Ctrl + Alt + F</strong>: extract field.</li>
<li class=""><strong>Ctrl + Alt + Shift + T</strong>: Refactor This—pops up every refactoring available at the current position. When you forget the ones above, this is your fallback.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="running-and-debugging">Running and Debugging<a href="https://tanzhuo.xyz/en/2022/11/25/idea-mo-ren-kuai-jie-jian#running-and-debugging" class="hash-link" aria-label="Direct link to Running and Debugging" title="Direct link to Running and Debugging" translate="no">​</a></h2>
<ul>
<li class=""><strong>Shift + F10</strong>: run the current configuration; <strong>Shift + F9</strong>: run with the debugger.</li>
<li class=""><strong>F8</strong>: step over; <strong>F7</strong>: step into; <strong>Shift + F8</strong>: step out of the current method.</li>
<li class=""><strong>F9</strong>: resume execution to the next breakpoint.</li>
<li class=""><strong>Ctrl + F8</strong>: toggle a breakpoint on the current line.</li>
<li class=""><strong>Alt + F8</strong>: open Evaluate Expression while debugging to run an ad-hoc expression and inspect the result.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/11/25/idea-mo-ren-kuai-jie-jian#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<p>When a shortcut stops working, don't rush to reinstall—another program has most likely grabbed it:</p>
<ol>
<li class="">
<p>On Windows, resident software like input methods, NetEase Cloud Music, and QQ register global hotkeys, and combos like <code>Ctrl + Alt + L</code> are the most commonly hijacked. Disable the global hotkey in the offending app.</p>
</li>
<li class="">
<p>Linux desktop environments (GNOME/KDE) ship window-management shortcuts that can also conflict with IDEA; rebind them on the system side in your settings.</p>
</li>
<li class="">
<p>In IDEA, press <strong>Ctrl + Shift + A</strong> (Find Action) and type a feature name to see the key currently bound to any action—the universal entry point for troubleshooting conflicts and discovering obscure features.</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>tip</div><div class="admonitionContent_BuS1"><p>Don't try to memorize everything at once. Pick three to five high-frequency keys (double Shift, Alt + Enter, Ctrl + W, Shift + F6) and use them for a week. Once the muscle memory forms, add the next batch. This works far better than cramming from a list.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://tanzhuo.xyz/en/2022/11/25/idea-mo-ren-kuai-jie-jian#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>Shortcuts pay compound interest: each use only saves a second or two, but you search, navigate, and refactor hundreds of times a day. The default keymap travels across machines, and with Ctrl + Shift + A available for lookups, there's barely any memorization burden. I'll keep coming back to this list myself whenever I forget one.</p>]]></content:encoded>
            <category>Java</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Sizing a Thread Pool: How Many Core Threads?]]></title>
            <link>https://tanzhuo.xyz/en/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi</link>
            <guid>https://tanzhuo.xyz/en/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[Estimating a thread pool's core thread count for CPU-bound, IO-bound, and mixed workloads — and why load testing is what ultimately settles the number.]]></description>
            <content:encoded><![CDATA[<p>How many core threads a thread pool should have depends on the workload. There are generally three scenarios: CPU-bound, IO-bound, and mixed. Let's take them one at a time.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cpu-bound">CPU-Bound<a href="https://tanzhuo.xyz/en/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi#cpu-bound" class="hash-link" aria-label="Direct link to CPU-Bound" title="Direct link to CPU-Bound" translate="no">​</a></h2>
<p>Heavy computation, high CPU usage, CPU loading at 90-100%. Aside from the CPU there is some I/O to read/write (disk/memory), but that I/O completes in very little time — most of the work is the CPU crunching large amounts of data and doing math. Think data analytics or stream processing; programs like these typically run at very high CPU utilization.</p>
<p>Suppose you're on a single-core CPU with 6 threads in the pool. Being single-core, only one thread can run at a time, and once you factor in the cost of context switching between threads, it's actually less efficient than a single thread. So on a single-core CPU handling CPU-bound work, skip multithreading altogether.</p>
<p>With a 6-core CPU and 6 threads, throughput can in theory improve 6x (in practice it won't quite get there — threads contend with each other and there's room for optimization). Every thread has a CPU to run on, so no thread waits for a time slice and there's no switching overhead. Multi-core CPUs are the right fit for CPU-bound programs, potentially with no thread context switching at all in between (<strong>one core per thread — the usual configuration doesn't exceed CPU count + 1</strong>).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="io-bound">IO-Bound<a href="https://tanzhuo.xyz/en/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi#io-bound" class="hash-link" aria-label="Direct link to IO-Bound" title="Direct link to IO-Bound" translate="no">​</a></h2>
<p>The opposite of CPU-bound: the work is mostly disk and network IO, CPU usage is low, and most of the time the CPU is waiting on IO responses — threads block while waiting, the CPU sits idle, and the pressure falls on disk or network throughput instead. In this scenario, the crude "CPU cores × 2" rule of thumb isn't really rigorous; IO-bound workloads have a proper formula to apply. Take a typical web application backend: most of the work is CRUD against a database or cache, and much of each endpoint's latency goes to disk and network IO. When sizing the pool's core threads, you can plug the server's allocated CPU resources into the formula.</p>
<p>The formula from <em>Java Concurrency in Practice</em>:</p>
<p><strong>Nthreads = Ncpu × Ucpu × (1 + W/C)</strong></p>
<ul>
<li class="">Ncpu: number of CPU cores</li>
<li class="">Ucpu: target CPU utilization</li>
<li class="">W/C: wait time / compute time</li>
</ul>
<p>The formula does yield an expected thread count, but in a real program <strong>it's usually very hard to get accurate wait and compute times, because programs are complex — they're not just "computation."</strong> A stretch of code mixes memory reads/writes, computation, I/O, and other compound operations, so measuring those two metrics precisely is difficult; sizing threads by formula alone is too idealistic. What we can do is take the formula's result as a baseline and then fine-tune the core thread count through load testing until we hit the best efficiency we can expect.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="mixed">Mixed<a href="https://tanzhuo.xyz/en/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi#mixed" class="hash-link" aria-label="Direct link to Mixed" title="Direct link to Mixed" translate="no">​</a></h2>
<p>Some applications compute over data while simultaneously moving it over disk or network. How do you configure thread pools for maximum performance in such programs? The usual approach is to split by the server's CPU core count and create two pools: one for the compute portion, one for the IO portion — a fairly sound configuration. There's also another formula floating around online:</p>
<p><strong>Core threads = (thread wait time / thread CPU time + 1) × CPU cores</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="thread-counts-in-real-programs">Thread Counts in Real Programs<a href="https://tanzhuo.xyz/en/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi#thread-counts-in-real-programs" class="hash-link" aria-label="Direct link to Thread Counts in Real Programs" title="Direct link to Thread Counts in Real Programs" translate="no">​</a></h2>
<p>So in an actual program — say, a typical Java business system — what's the right number of threads (thread pool size)?</p>
<p><strong>The short answer: there is no fixed answer. Set expectations first — target CPU utilization, load, GC frequency, and similar metrics — then use the formula to set an initial core thread count, and keep adjusting through testing until you land on a reasonable number.</strong></p>
<p>The formula only gets you a ballpark figure; real-world interference comes from all directions, especially contention for the server's compute resources. If your application runs directly on a server with nothing else competing, iterating on load tests will find the right thread count. But many applications are containerized nowadays: in a K8S environment, containers are spread across worker nodes, and a single worker node may run many containers. Without resource limits on the containers, compute contention is almost guaranteed. You need to set proper CPU, memory, disk, and network limits for each container, or actual efficiency can end up far below expectations (with other containers eating your compute).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://tanzhuo.xyz/en/2022/11/21/xian-cheng-chi-he-xin-xian-cheng-shu-ji-suan-fang-shi#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>There is no universally correct core thread count: CPU-bound workloads should hug the CPU core count, IO-bound workloads can be estimated with Nthreads = Ncpu × Ucpu × (1 + W/C), and mixed workloads are worth splitting into separate compute and IO pools. The formula is only a starting point — wait and compute times are hard to measure precisely in real programs, and containerized environments add resource contention on top. So the sensible approach is: pick an initial value from the formula, define your expected metrics for CPU utilization and load, then load-test and adjust step by step until the numbers meet expectations.</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[A Deep Dive into the InnoDB Buffer Pool]]></title>
            <link>https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool</link>
            <guid>https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool</guid>
            <pubDate>Wed, 16 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A detailed look at the InnoDB Buffer Pool's layered structure and page management: the Page Hash, the Free/LRU/Flush lists, hot/cold data partitioning, and the four moments dirty pages get flushed to disk.]]></description>
            <content:encoded><![CDATA[<p>To reduce disk IO and improve performance, the InnoDB storage engine performs all DML operations inside the in-memory BufferPool, with an asynchronous flushing strategy writing the modified data to disk later. This post walks through the BufferPool's data structures, how it manages pages, and its eviction and flushing strategies.</p>
<!-- -->
<p>Official BufferPool documentation:</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>Why design a BufferPool at all? Anyone with a computer science background knows memory and disk are worlds apart in speed — from the CPU's perspective, disk transfer and response times are painfully slow. So MySQL designed the BufferPool to mirror the real data on disk, managing and manipulating data entirely in memory, which makes DML execution take off. As for how the data in the BufferPool survives crashes and power loss (physical damage is another story — if the disk itself is toast, nothing can help), I covered that in an earlier post:</p>
<p><a href="https://tanzhuo.xyz/mysql-zhixing/" target="_blank" rel="noopener noreferrer" class="">The MySQL Execution Pipeline</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="buffer-pool-overview">Buffer Pool Overview<a href="https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool#buffer-pool-overview" class="hash-link" aria-label="Direct link to Buffer Pool Overview" title="Direct link to Buffer Pool Overview" translate="no">​</a></h2>
<p>InnoDB accesses data in units of Pages, each 16KB by default, and the Buffer Pool exists to manage and cache these Pages. InnoDB carves out a contiguous block of memory for the Buffer Pool and splits it into multiple Buffer Pool Instances for better management. Each Instance is the same size, and an algorithm guarantees that a given Page always lands in one specific Instance. This multi-Instance design improves the Buffer Pool's concurrency.</p>
<p>Each Buffer Pool Instance maintains its own set of management structures. InnoDB reads data from disk files into memory in 16KB Pages and caches them via an LRU List: frequently accessed Pages sit near the front of the LRU List, rarely accessed ones near the back. When accessing a Page, InnoDB first looks in the Buffer Pool; on a miss it reads the disk data file, then puts the fetched Page into the LRU List. When an Instance has no free Pages left, Pages on the LRU List get evicted.</p>
<p>The Buffer Pool also contains a fair amount of Page compression logic — compressing the actual 16KB Pages down to 8KB, 4KB, 2KB, or 1KB. We'll skip that here and stick with the default 16KB Pages to trace the main logic.</p>
<p>So the BufferPool is a layered structure:</p>
<ul>
<li class="">The BufferPool consists of multiple Buffer Pool Instances.</li>
<li class="">Each Buffer Pool Instance consists of multiple Buffer Chunks (128MB each by default); a Buffer Chunk manages a contiguous memory region called Buffer Chunk Memory.</li>
<li class="">Each Buffer Chunk consists of multiple Buffer Pages (Pages for short, 16KB by default).</li>
</ul>
<p>This layering exists to reduce mutex contention under high concurrency.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-11-842fb6ff-945d-442d-a28e-0d7a9ef039b1-3f3a18424cb951f5053c4f5ca00a1237.webp" width="1440" height="425" class="img_ev3q"></p>
<p>This post focuses on the BufferPool's data structures, how it manages pages, and its eviction and flushing strategies. Start with the official LRU data-caching diagram:</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>The Buffer Pool's <strong>default total size is 128M</strong>, adjustable via <code>innodb-buffer-pool-size = xxxxxxx</code> as needed (the official recommendation for a dedicated database server is to give it most of the physical memory). It is divided into a New Sublist and an Old Sublist — sound like the JVM's young and old generations? The idea is indeed similar: the New Sublist holds the most active data, the Old Sublist holds infrequently accessed data, and the Old Sublist takes up 3/8 of the BufferPool by default.</p>
<p>Since memory is scarce, you can't load every table on disk into memory — an eviction strategy is required. The BufferPool uses the LRU algorithm plus MySQL's own variant logic to evict in-memory data, also known as hot/cold data handling: frequently accessed data drifts toward the New Sublist, infrequently accessed data toward the Old Sublist, and when memory pressure is high, data in the Old Sublist is evicted first. A newly read page is placed at the Head of the Old Sublist; if it gets accessed often it moves into the New Sublist, otherwise it stays in the Old Sublist.</p>
<p>The BufferPool also contains a region called the ChangeBuffer, as shown:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-11-image-13-7519334f554b72ba0b825625b2d25334.webp" width="581" height="408" class="img_ev3q"></p>
<p>The ChangeBuffer caches DML changes whose target pages are not in the BufferPool, sparing every modification from first pulling the page off disk. When the corresponding page is later read into memory, the buffered changes get merged in, reducing random IO. By default the ChangeBuffer takes 25% of the BufferPool, adjustable up to a maximum of 50%.</p>
<p>A fairly complete picture of the BufferPool's structure (a few lists are omitted, such as the compression lists):</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-11-image-17-f0b9ba6de43eca5efd0e508bef36c291.webp" width="1600" height="763" class="img_ev3q"></p>
<p>Before we start, a few prerequisite concepts:</p>
<ul>
<li class=""><strong>Data page</strong>: a page that has been loaded with data from disk.</li>
<li class=""><strong>Dirty page</strong>: a page that has been modified and needs to be flushed back to disk.</li>
<li class=""><strong>Blank page</strong>: a page holding no data yet.</li>
<li class=""><strong>Control block</strong>: the BufferPool page pointer stored in nodes of the various management lists.</li>
<li class=""><strong>Buffer page</strong>: the general term for any page in the BufferPool (all pages are 16KB).</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="configuring-multiple-buffer-pool-instances">Configuring Multiple Buffer Pool Instances<a href="https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool#configuring-multiple-buffer-pool-instances" class="hash-link" aria-label="Direct link to Configuring Multiple Buffer Pool Instances" title="Direct link to Configuring Multiple Buffer Pool Instances" translate="no">​</a></h2>
<p>When the MySQL server starts up, it requests the BufferPool's memory from the operating system. Under multithreading, every list requires locking, and when the BufferPool is very large and concurrent access is very heavy, a single BufferPool becomes a throughput bottleneck. So the BufferPool is split into several smaller BufferPools, called Buffer Pool Instances, each independently allocating memory and managing its own lists, so concurrent threads don't interfere with each other. The instance count is configurable:</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%)"># Create 2 buffer pool instances</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Memory per instance = total size divided by instance count</span><span class="token plain"></span><br></div><div class="token-line" 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>Since creating and managing multiple instances carries its own overhead, MySQL stipulates: when innodb_buffer_pool_size is below 1G, there is only one instance by default and setting more has no effect; only above 1G is configuring multiple instances encouraged.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-the-buffer-pool-manages-pages">How the Buffer Pool Manages Pages<a href="https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool#how-the-buffer-pool-manages-pages" class="hash-link" aria-label="Direct link to How the Buffer Pool Manages Pages" title="Direct link to How the Buffer Pool Manages Pages" translate="no">​</a></h2>
<p>Data pages are not scattered haphazardly through the BufferPool — they are organized by a hash table and several lists to support the operations described earlier. The nodes in these lists hold the Page Descriptor:</p>
<ol>
<li class=""><strong>page hash (fast access)</strong>: all pages are organized by a hash table called the page hash, used to quickly locate a page in the buffer pool; the page hash key is the page id.</li>
<li class=""><strong>free list (page allocation)</strong>: when loading a page into the buffer pool, a buf_block_t must be allocated as its page descriptor. How do you find a free, unoccupied buf_block_t? They all hang on the free list.</li>
<li class=""><strong>LRU list (page eviction)</strong>: buffer pool space is finite; when there is no room for pages loaded from disk, some existing pages must be evicted, using the LRU algorithm.</li>
<li class=""><strong>flush list (dirty page writeback)</strong>: when a page becomes dirty, the page cleaner thread must periodically write it back to disk; all dirty pages live on the flush list.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="managing-the-free-list">Managing the Free List<a href="https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool#managing-the-free-list" class="hash-link" aria-label="Direct link to Managing the Free List" title="Direct link to Managing the Free List" translate="no">​</a></h2>
<p>When the MySQL server first starts, the BufferPool must be initialized: memory for the BufferPool is requested from the OS and divided into pairs of control blocks and buffer pages. At this point no real disk pages have been cached in the BufferPool (nothing has needed them yet); as the program runs, pages from disk get cached in over time. Which raises a question: when reading a page from disk into the BufferPool, which buffer page slot should it go into? Put differently, how do we tell which buffer pages in the BufferPool are free and which are in use?</p>
<p>Best to record somewhere which buffer pages are available — and this is where each buffer page's control block earns its keep: the control blocks of all free buffer pages are linked into a list as nodes, called the Free list. In a freshly initialized BufferPool, every buffer page is free, so every buffer page's control block joins the Free list.</p>
<p>To manage the Free list better, a dedicated base node is defined, containing the list's head node address, tail node address, and the current node count, among other information. Note that the base node's memory is not part of the big contiguous allocation made for the BufferPool — it is a separately allocated chunk of memory.</p>
<p>With the Free list in place, things get easy: whenever a page needs to be loaded from disk into the BufferPool, take a free buffer page from the Free list, fill in its control block's information (the tablespace the page belongs to, the page number, and so on), and remove that control block from the Free list, marking the buffer page as in use. Be clear that what we actually take from the list is a control block — the real page is reached through the control block. Likewise, "iterating over the buffer pages in the BufferPool" really means "iterating over the buffer pages' control blocks."</p>
<p>In short: manage the Free list well and you always know which pages in the BufferPool are free.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="page-hash-hashing-the-buffer-pages">Page Hash: Hashing the Buffer Pages<a href="https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool#page-hash-hashing-the-buffer-pages" class="hash-link" aria-label="Direct link to Page Hash: Hashing the Buffer Pages" title="Direct link to Page Hash: Hashing the Buffer Pages" translate="no">​</a></h2>
<p>When data in a page needs to be accessed, the page is loaded from disk into the BufferPool; if it's already there, it can simply be used. But how do we know whether the page is in the BufferPool? Surely not by walking through every buffer page one by one — with that many buffer pages, a full scan would be exhausting.</p>
<p>Think about it: we actually locate a page by tablespace number + page number, so tablespace number + page number acts as a Key, and the buffer page's control block is the corresponding Value. Finding a Value quickly by Key — that's naturally a hash table.</p>
<p>So a hash table is built with tablespace number + page number as the Key and the buffer page control block's address as the Value. When a page's data is needed, first look up the hash table by tablespace number + page number: if a corresponding buffer page exists, use it directly; if not, pick a free buffer page from the Free list and load the corresponding page from disk into that slot.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="managing-the-lru-list">Managing the LRU List<a href="https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool#managing-the-lru-list" class="hash-link" aria-label="Direct link to Managing the LRU List" title="Direct link to Managing the LRU List" translate="no">​</a></h2>
<p>The LRU list is a way of managing the BufferPool's cache space. Think about it: if you keep loading data pages into the cache, sooner or later it fills up, and there must be a mechanism to release the pages nobody needs — that mechanism is the LRU (least recently used) algorithm.</p>
<p>You can picture the algorithm as a linked list split into two parts (MySQL's variant): one part stores very frequently used buffer pages — the hot data — called the Young region (New Sublist); the other stores infrequently used buffer pages — the cold data — called the Old region (Old Sublist). InnoDB splits the LRU list proportionally, and you can check the Old region's share via the <strong>innodb_old_blocks_pct</strong> parameter.</p>
<p>With these two regions, InnoDB's designers could optimize for BufferPool hit-rate scenarios:</p>
<ul>
<li class="">Optimizing for read-ahead pages that may never be accessed again. The rule is that when a page from disk is first loaded into a buffer page, its control block goes to the head of the Old region. That way, pages read ahead into the BufferPool but never subsequently accessed drift out of the Old region without disturbing the frequently used buffer pages in the Young region.</li>
<li class="">Optimizing for full table scans that access a burst of low-frequency pages in a short time. During a full table scan, pages loaded for the first time do land at the head of the Old region, but they get accessed again immediately afterward, and each access would move the page to the head of the Young region — still squeezing out the genuinely hot pages. The designers reasoned that during a full table scan, even if a page holds many rows and each row read counts as one page access, the whole process takes very little time. So the rule is: on the first access to a buffer page in the Old region, record the access time in its control block; if subsequent accesses fall within a certain interval of that first access, the page does not move from the Old region to the head of the Young region — only accesses outside the interval move it. This interval is controlled by the system variable innodb_old_blocks_time.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="managing-the-flush-list">Managing the Flush List<a href="https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool#managing-the-flush-list" class="hash-link" aria-label="Direct link to Managing the Flush List" title="Direct link to Managing the Flush List" translate="no">​</a></h2>
<p>If you modify the data in a buffer page, it no longer matches the page on disk — such a buffer page is called a dirty page. You could of course flush it to the corresponding disk page immediately after every modification, but writing to disk that frequently would kill performance. So each modification does not trigger an immediate flush; the page is flushed at some point in the future instead.</p>
<p>But if you don't flush immediately, how do you later know which pages in the BufferPool are dirty and which have never been modified? Hence another list has to be created to hold dirty pages: the control block of every modified buffer page joins this list as a node. Since all these buffer pages need to be flushed to disk, it is called the Flush list. Its construction is much like the Free list. Also note: a free buffer page is definitely not dirty, and a dirty page is definitely not free — meaning a given buffer page's control block cannot be a node of both the Free list and the Flush list at once; it belongs to at most one of them.</p>
<p><strong>In short</strong>: the Free list is simply the list of all free pages in the cache, while the Flush list is the list of all modified pages (dirty pages).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-dirty-page-flushing-mechanism">The Dirty Page Flushing Mechanism<a href="https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool#the-dirty-page-flushing-mechanism" class="hash-link" aria-label="Direct link to The Dirty Page Flushing Mechanism" title="Direct link to The Dirty Page Flushing Mechanism" translate="no">​</a></h2>
<p>Dirty pages are flushed back to disk in the following situations:</p>
<ol>
<li class="">
<p>The Redo Log is full. As the durability guarantee, the Redo Log must record every update. If it runs out of space, pages must be flushed to sync data to disk, after which the Redo Log's CheckPoint pointer moves forward to free up space. If memory is not under pressure at that point, the flushed pages don't need to be evicted.</p>
</li>
<li class="">
<p>Memory can't hold any more data pages. Queries read data pages from disk into memory before returning; if memory runs out, the least recently used pages must be evicted, and if an evicted page is dirty, it must be flushed to disk first.</p>
</li>
<li class="">
<p>The database is idle. When the database considers itself idle, it flushes dirty pages; if memory is not under pressure, flushed pages are not evicted.</p>
</li>
<li class="">
<p>The database shuts down normally. On shutdown, pages are flushed as well, persisting prior modifications to disk.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/11/16/mysql-buffer-pool#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The BufferPool is InnoDB's core design for trading memory for IO: data is cached in memory in 16KB Pages, and DML operations modify memory first, then land on disk asynchronously. To reduce lock contention under concurrency it is split into multiple Instances; internally, the Page Hash provides fast lookup, while the Free, LRU, and Flush lists handle free page allocation, hot/cold data eviction, and dirty page writeback respectively. The LRU's New/Old partitioning plus the innodb_old_blocks_time window solves the problem of read-ahead and full table scans washing out hot data. Once you understand how these lists cooperate, the BufferPool's overall machinery becomes clear.</p>]]></content:encoded>
            <category>Database</category>
        </item>
        <item>
            <title><![CDATA[The MySQL Execution Pipeline]]></title>
            <link>https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing</link>
            <guid>https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing</guid>
            <pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Tracing a SQL statement's full journey through MySQL: from the connector to the executor, how the Buffer Pool cuts disk IO, and the roles of the Redo Log, Bin Log, and Undo Log along with two-phase commit.]]></description>
            <content:encoded><![CDATA[<p>From the moment a client submits a SQL statement to the moment the data actually hits disk, the statement passes through the connector, parser, optimizer, and executor, with the Buffer Pool and three logs working together behind the scenes. This post connects the entire execution pipeline end to end.</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-11-image-11-3b98bcb2a74b924e1f92f8c0c2e9a703.webp" width="1368" height="922" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="from-connector-to-executor">From Connector to Executor<a href="https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing#from-connector-to-executor" class="hash-link" aria-label="Direct link to From Connector to Executor" title="Direct link to From Connector to Executor" translate="no">​</a></h2>
<p>When a client submits a SQL statement to the MySQL server, a connection must be established first. The connector handles the client connection and verifies the user's identity and privileges. If the check passes, the SQL parser performs semantic analysis on the statement (checking whether the SQL is valid and can be parsed). After parsing, the execution plan is optimized — for example, stripping useless conditions like 1=1, and choosing the best index and ordering of WHERE clause columns.</p>
<p>The statement is then handed to the executor, which runs it against whatever storage engine the database uses. MySQL was designed as two layers: the Server layer and the storage engine layer. The benefit is decoupling — you can pick the storage engine that fits each scenario.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="buffer-pool">Buffer Pool<a href="https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing#buffer-pool" class="hash-link" aria-label="Direct link to Buffer Pool" title="Direct link to Buffer Pool" translate="no">​</a></h2>
<p>InnoDB, the most commonly used storage engine, manages storage in units of pages (16KB). Every insert, delete, update, or query ultimately operates on a whole page: the entire page is loaded into the Buffer Pool, and all data operations happen inside the Buffer Pool. The Buffer Pool is typically configured to use around 70-80% of the server's memory, so data operations run directly in memory, which dramatically improves throughput. (If every DML statement required disk IO, the database's disk would quickly become the bottleneck — modifying one row would mean reading from disk and then flushing back, i.e. 2 IOs, one read and one write.)</p>
<p>An example makes this concrete. Assume there is no Buffer Pool, the user table has a single row with age = 1, and we need to run three statements:</p>
<p>Transaction A: update user set age = 2 (one read + one write, 2 IOs)
Transaction B: update user set age = 3 (one read + one write, 2 IOs)
Transaction C: update user set age = 4 (one read + one write, 2 IOs)</p>
<p>Each one has to read the data from disk into memory, modify it, then flush it back to disk — 6 IOs in total.</p>
<p>Now with a Buffer Pool:</p>
<p>Transaction A: update user set age = 2 (read into the Buffer Pool and modify, 1 read IO)
Transaction B: update user set age = 3 (modify the data in the Buffer Pool, 0 IOs)
Transaction C: update user set age = 4 (modify the data in the Buffer Pool and flush to disk, 1 write IO)</p>
<p>Only the first statement needs to load the data page; subsequent operations happen entirely in memory. With the Buffer Pool, the total drops to 2 IOs. (This is a simplified illustration — the real optimization logic isn't quite this simple — but the big picture holds: the Buffer Pool maps data from disk into memory for manipulation, reducing disk IO. The catch is that modifications happen in memory, so a crash or power loss could easily lose data.)</p>
<p>Inspecting Buffer Pool information:</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%)">-- The output includes Buffer Pool size, hit rate, dirty page count, and more</span><span class="token plain"></span><br></div><div class="token-line" 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>This is where several crucial logs come in to help: the Redo Log, the Bin Log (binary log), and the Undo Log (rollback log).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redo-log">Redo Log<a href="https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing#redo-log" class="hash-link" aria-label="Direct link to Redo Log" title="Direct link to Redo Log" translate="no">​</a></h2>
<p>After data is modified, the new value is first recorded in the Redo Log on disk. Even if power is suddenly cut and everything in the Buffer Pool is lost, the Buffer Pool can be rebuilt from the Redo Log when power comes back. This keeps the Buffer Pool's in-memory efficiency while guaranteeing no data loss. The Redo Log has three flush strategies:</p>
<ul>
<li class="">Set to 0: no flush on transaction commit (by default the Master Thread syncs the redo log once per second)</li>
<li class="">Set to 1: a synchronous flush on every transaction commit — the safest option, because if a crash happens at that point, the transaction never committed successfully, so there is nothing to recover (the default)</li>
<li class="">Set to 2: on every commit, the Redo Log Buffer contents are only written to the Page Cache without syncing; the filesystem (OS) decides when to sync to disk</li>
</ul>
<p>Redo Log flushing here is sequential-write WAL (Write-ahead logging). Compared with random disk writes, this is vastly more efficient — message queues like Kafka and RocketMQ also use sequential log writes to boost disk write throughput. Sequential disk writes are already fast, but still nowhere near memory speed. To push Redo Log efficiency further, InnoDB introduced the Change Buffer in memory (16MB by default, located inside the Buffer Pool, with a configurable percentage) to record changes in memory, only performing the sequential disk write when the transaction commits.</p>
<p>The Change Buffer is divided into many Blocks of 512KB each, and all the Redo Log produced by one transaction is called a Group.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bin-log-binary-log">Bin Log (Binary Log)<a href="https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing#bin-log-binary-log" class="hash-link" aria-label="Direct link to Bin Log (Binary Log)" title="Direct link to Bin Log (Binary Log)" translate="no">​</a></h2>
<p>The Bin Log is a binary log recording all table schema changes (CREATE, ALTER TABLE, ...) and table data modifications (INSERT, UPDATE, DELETE, ...). It does not record operations like SELECT and SHOW, since those don't modify data — though you can check the general query log to see every statement MySQL has executed.</p>
<p>One thing to note: <strong>even an update that changes nothing still gets written to the Bin Log</strong>.</p>
<p>Like the Redo Log, the Bin Log has its own flush strategy, controlled by the sync_binlog parameter:</p>
<ul>
<li class="">Set to 0: before each commit, the Bin Log is written to the OS Cache, and the operating system decides when to flush it to disk</li>
<li class="">Set to 1: the Bin Log is written to disk synchronously, bypassing the OS Cache</li>
<li class="">Set to n: after every n transaction commits, Fsync is called once to force the Bin Log in the OS Cache to disk</li>
</ul>
<p>The Bin Log has two common use cases:</p>
<ul>
<li class="">Replication: MySQL Replication enables the Bin Log on the Master, which ships its binary log to Slaves to keep Master and Slave data consistent.</li>
<li class="">Data recovery: restoring data via the mysqlbinlog tool.</li>
</ul>
<p>Which raises a question: both the Bin Log and the Redo Log record post-modification values — what's the difference? If we have the Redo Log, why do we still need the Bin Log?</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="bin-log-vs-redo-log">Bin Log vs. Redo Log<a href="https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing#bin-log-vs-redo-log" class="hash-link" aria-label="Direct link to Bin Log vs. Redo Log" title="Direct link to Bin Log vs. Redo Log" translate="no">​</a></h3>
<ul>
<li class="">The Bin Log belongs to the MySQL Server layer; the Redo Log belongs to the Engine layer</li>
<li class="">The Bin Log works with all engines; the Redo Log is InnoDB-specific</li>
<li class="">The Bin Log records logical operations; the Redo Log records the updated content</li>
<li class="">The Bin Log is append-only across multiple files; the Redo Log is a fixed-size set of files written in a circular fashion</li>
<li class="">During transaction execution, operations are continuously written to the Redo Log; the Bin Log is only written at commit time</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="undo-log-rollback-log">Undo Log (Rollback Log)<a href="https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing#undo-log-rollback-log" class="hash-link" aria-label="Direct link to Undo Log (Rollback Log)" title="Direct link to Undo Log (Rollback Log)" translate="no">​</a></h2>
<p>When data is modified, alongside the Redo Log a corresponding Undo Log is also recorded. If the transaction fails or is rolled back for any reason, the Undo Log makes the rollback possible. Undo Logs are recorded in segments — each Undo Log operation occupies one Undo Log Segment. Their purpose: preserving a version of the data as it was before the transaction, usable both for rollback and for reads under multi-version concurrency control. Note that the Undo Log lives in the global tablespace by default — you can loosely think of the Undo Log as being stored in a MySQL table of its own, so inserting an Undo Log entry is similar to inserting an ordinary row. Which means that writing the Undo Log itself also involves writing the Redo Log.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-execution-pipeline">The Execution Pipeline<a href="https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing#the-execution-pipeline" class="hash-link" aria-label="Direct link to The Execution Pipeline" title="Direct link to The Execution Pipeline" translate="no">​</a></h2>
<p>With the groundwork above in place, here is the full execution pipeline of a SQL statement:</p>
<ol start="0">
<li class="">
<p>We skip the connector, parser, and optimizer steps here (described above).</p>
</li>
<li class="">
<p>First, the Undo Log records the rollback information for this transaction (transaction ID, rollback pointer), enabling rollback and the MVCC transaction isolation that depends on it.</p>
</li>
<li class="">
<p>Next, the data in the Buffer Pool is modified, and the change information is simultaneously added to the Change Buffer. If the target data page is not yet in memory, then — provided <strong>data consistency is not affected</strong> — InnoDB caches these updates in the Change Buffer first, avoiding the need to read the page from disk. The next time a query needs that page, the page is read into memory and the Change Buffer operations related to it are applied. (In some cases, though, the page must be read into memory before the operation can proceed.)</p>
</li>
<li class="">
<p>After the change information lands in the Change Buffer, it must also be flushed to the Redo Log for durable storage, so a crash cannot lose the operations. (Flushing here only happens after the transaction commits — flushing an uncommitted transaction is pointless. MySQL defaults to synchronous Redo Log flushing; if you switch to asynchronous flushing, a sudden crash or power loss will lose data, including everything recorded in the in-memory Change Buffer.)</p>
</li>
<li class="">
<p>The concrete SQL operation is also added to the Bin Log Cache — but the Bin Log Cache records only logical operation information (the actual DML, DDL, and DQL statements executed), not data change details. The Bin Log Cache is a per-thread private memory area (32K by default) sized to hold the Bin Log Events generated by an entire transaction; a large transaction can easily exceed this setting, in which case a temporary Bin Log file is used for storage.</p>
</li>
<li class="">
<p>When the client commits the transaction, two-phase commit begins. First the Redo Log records are flushed to disk and marked with the prepare state; then the Bin Log records are flushed, and once the Bin Log flush succeeds, the corresponding Redo Log state is changed from prepare to commit. The two logs must be kept consistent — if they diverge, you get master-slave inconsistencies and all sorts of problems. At this point the client can be told the commit succeeded, but the data (dirty pages) has not yet hit disk; it still lives in the Buffer Pool. Only the corresponding log records have fully succeeded.</p>
</li>
<li class="">
<p>The MySQL server asynchronously flushes the dirty pages in the Buffer Pool to disk (at <strong>the right flushing moments</strong>). Only once the dirty pages are written to disk is the data truly persisted. If a crash or power loss happens during this asynchronous flushing, InnoDB can replay the Redo Log on startup to rebuild the previous Buffer Pool state.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-right-moments-to-flush-pages">The Right Moments to Flush Pages<a href="https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing#the-right-moments-to-flush-pages" class="hash-link" aria-label="Direct link to The Right Moments to Flush Pages" title="Direct link to The Right Moments to Flush Pages" translate="no">​</a></h2>
<ol>
<li class="">
<p>The Redo Log is full. As the durability guarantee, the Redo Log must record every update. If it runs out of space, pages must be flushed to sync data to disk, after which the Redo Log's CheckPoint pointer moves forward to free up space. If memory is not under pressure at that point, the flushed pages don't need to be evicted.</p>
</li>
<li class="">
<p>Memory can't hold any more data pages. Queries read data pages from disk into memory before returning results. If memory runs out, the least recently used pages must be evicted — and if an evicted page is dirty, it must be flushed to disk before eviction.</p>
</li>
<li class="">
<p>The database is idle. When the database considers itself idle, it flushes dirty pages; if memory is not under pressure, flushed pages are not evicted.</p>
</li>
<li class="">
<p>The database shuts down normally. On shutdown, pages are flushed as well, persisting prior modifications to disk.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/11/15/mysql-zhixing#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>MySQL improves durability performance through sequential log writes, and the Buffer Pool maps disk data into memory for DML operations, dramatically boosting throughput. Asynchronous flushing avoids the poor performance of random disk writes caused by modifications scattered across different tables and rows. The Redo Log guarantees recovery after a crash, the Bin Log powers replication and data recovery, and the Undo Log handles rollback and MVCC — together with two-phase commit, the three logs strike the balance between performance and data consistency.</p>]]></content:encoded>
            <category>Database</category>
        </item>
        <item>
            <title><![CDATA[A History of the HTTP Protocol]]></title>
            <link>https://tanzhuo.xyz/en/2022/11/14/http</link>
            <guid>https://tanzhuo.xyz/en/2022/11/14/http</guid>
            <pubDate>Mon, 14 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Tracing HTTP's evolution from 0.9 to 3.0: what each generation of the protocol solved, what problems it left behind, and how QUIC redesigned the transport layer.]]></description>
            <content:encoded><![CDATA[<p>HTTP is the primary protocol for communication between browsers and servers. It's an application-layer protocol (layer 7): data produced at the application layer rides on a transport-layer protocol to reach other hosts on the internet, and that carrier is TCP (replaced by UDP in 3.0) — establish a connection over TCP, then transfer the content.</p>
<!-- -->
<p>In the 1960s, the U.S. Department of Defense's Advanced Research Projects Agency (ARPA) built ARPANET, widely regarded as the origin of the internet. In the 1970s, drawing on their experience with ARPANET, researchers invented the famous TCP/IP protocol suite. With its clean layered design and stable performance, it made its way into the UNIX kernel in the mid-1980s, bringing many more computers onto the network.</p>
<p>In 1989, Dr. Tim Berners-Lee published a paper proposing a system of hyperlinked documents built on top of the internet. In it he established three key technologies: URI, HTML, and HTTP.</p>
<p>With these three technologies, a hypertext system could run beautifully on the internet, and Berners-Lee named it the "World Wide Web". HTTP began to spread across the internet — today the most common versions are HTTP/1.1 and HTTP/2.0.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-11-image-10-e734574d102a56652a23e4ce91a69df4.webp" width="944" height="356" class="img_ev3q"></p>
<p><strong>Every generation of HTTP has been an optimization built on the shortcomings of the one before it.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http09">HTTP/0.9<a href="https://tanzhuo.xyz/en/2022/11/14/http#http09" class="hash-link" aria-label="Direct link to HTTP/0.9" title="Direct link to HTTP/0.9" translate="no">​</a></h2>
<p>HTTP (HyperText Transfer Protocol) was officially born in 1991, at version 0.9. As the name suggests, its job was to transfer hypertext content — HTML. The protocol defined a communication model where the client sends a request and the server responds. The request message was a single line: GET plus the path of the requested file. With no way to distinguish message types, it could only transfer text.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http10">HTTP/1.0<a href="https://tanzhuo.xyz/en/2022/11/14/http#http10" class="hash-link" aria-label="Direct link to HTTP/1.0" title="Direct link to HTTP/1.0" translate="no">​</a></h2>
<p>As the internet grew and browsers appeared, plain text was no longer enough. Browsers wanted HTTP to carry scripts, stylesheets, images, audio, video — files of all kinds. So the 1.0 version, released in 1996, introduced the following:</p>
<ul>
<li class="">New methods such as HEAD and POST</li>
<li class="">Response status codes to indicate possible error causes</li>
<li class="">The concept of a protocol version number</li>
<li class="">The concept of HTTP headers, making request and response handling much more flexible</li>
<li class="">Transferred data no longer limited to text</li>
</ul>
<p>The most fundamental change was the header mechanism, with header content expressed as key-value pairs. The request headers use the Accept field to tell the server which file types the client can receive, and the response headers use Content-Type to tell the browser what kind of file is being returned. Header fields solved more than content-type negotiation — they enabled caching, authentication, and many other features.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http11">HTTP/1.1<a href="https://tanzhuo.xyz/en/2022/11/14/http#http11" class="hash-link" aria-label="Direct link to HTTP/1.1" title="Direct link to HTTP/1.1" translate="no">​</a></h2>
<p>With the internet's rapid growth, HTTP/1.0 soon fell short too, and the core issue was connections. Every HTTP/1.0 exchange had to go through three phases: <strong>establish the connection</strong>, <strong>transfer the data</strong>, and <strong>tear down the connection</strong>. When a page referenced many external files, all that connecting and disconnecting added substantial network overhead.</p>
<p>HTTP/1.1 made Connection: keep-alive the default, avoiding the cost of repeatedly setting up and tearing down connections. But the server had to return results strictly in the order the client sent its requests, so the client could match each response to its request — using header fields to determine whether the current response had been fully received, with no two responses allowed in flight at once.</p>
<p><strong>To address HTTP/1.0's problems, HTTP/1.1, released in 1999, brought the following:</strong></p>
<ul>
<li class="">Persistent connections (Connection: keep-alive): TCP connection reuse — a TCP connection stays open by default and can be reused across multiple requests</li>
<li class="">Concurrent connections: multiple persistent connections allowed per domain (easing the head-of-line blocking inherent to persistent connections)</li>
<li class="">Pipelining, letting one TCP connection send multiple requests at once (responses must come back in request order, so it saw little use)</li>
<li class="">New methods: PUT, DELETE, OPTIONS, PATCH</li>
<li class="">New caching fields (Cache-Control, ETag)</li>
<li class="">The Range request header, enabling resumable downloads</li>
<li class="">Chunked responses, useful for transferring large files</li>
<li class="">A mandatory Host header, making virtual hosting on the internet possible</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http20">HTTP/2.0<a href="https://tanzhuo.xyz/en/2022/11/14/http#http20" class="hash-link" aria-label="Direct link to HTTP/2.0" title="Direct link to HTTP/2.0" translate="no">​</a></h2>
<p>Persistent connections in HTTP/1.1 eliminated much of the cost of creating and tearing down connections, but its concurrency remained limited in two ways:</p>
<ul>
<li class="">With a persistent connection, HTTP/1.1 can only process one request at a time per connection. Until the current request finishes, all other requests are stuck waiting — the situation known as head-of-line blocking</li>
<li class="">To ease server load, browsers cap the number of HTTP connections per domain, typically at 6 to 8</li>
</ul>
<p>HTTP/2, officially released in 2015, dropped ASCII-encoded transmission by default in favor of <strong>binary data</strong> for better efficiency.</p>
<p>When sending requests, the client packages each request's content into numbered binary frames and sends them all to the server together. On receipt, the server reassembles frames with the same number into the complete request. The same framing and reassembly applies in the other direction, when the server returns results to the client.</p>
<p>With binary framing, a client needs only a single connection per domain to handle all its communication. Sending multiple requests over one connection this way is called <strong>multiplexing</strong>, and each of these lanes is called a stream.</p>
<p>The main changes in HTTP/2.0:</p>
<ul>
<li class="">Data transmitted as a binary protocol rather than plain text</li>
<li class="">Multiplexing, replacing 1.1's pipelining</li>
<li class="">Header compression with a dedicated algorithm, reducing data volume</li>
<li class="">Frame prioritization, letting the server handle certain requests first</li>
<li class="">Server push, allowing the server to proactively send data to the client</li>
<li class="">All header fields lowercase; pseudo-headers introduced, appearing before regular header fields and starting with a colon</li>
<li class="">Stronger security, with encrypted communication (TLS) required "in practice"</li>
</ul>
<p>Although HTTP/2.0 has been out for years, HTTP/1.1 has proven so entrenched and dominant that HTTP/2.0 adoption remains modest — plenty of websites still run HTTP/1.1.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http30">HTTP/3.0<a href="https://tanzhuo.xyz/en/2022/11/14/http#http30" class="hash-link" aria-label="Direct link to HTTP/3.0" title="Direct link to HTTP/3.0" translate="no">​</a></h2>
<p>Of course, HTTP/2 isn't perfect either. If packets are lost during communication, or either side's network drops, the entire TCP connection stalls.</p>
<p>Because HTTP/2 multiplexes via binary framing, it typically uses just one TCP connection — so when packets are lost or the network hiccups, everything behind the loss gets blocked. HTTP/1.1, by contrast, can open multiple TCP connections; if one fails, the others carry on transferring data unaffected. In this scenario HTTP/2 actually performs worse than HTTP/1.</p>
<p>In 2018, HTTP/3 swapped the underlying TCP for UDP, eliminating the problem at its root. UDP's biggest difference from TCP is that it needs no connection setup to send data and can fire off multiple packets simultaneously — very efficient, but with no acknowledgment mechanism to guarantee delivery. So Google started from scratch and built the UDP-based <strong>QUIC protocol</strong>, which HTTP/3 adopted. Many browsers and hardware vendors now support HTTP/3.0.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-quic-protocol">The QUIC Protocol<a href="https://tanzhuo.xyz/en/2022/11/14/http#the-quic-protocol" class="hash-link" aria-label="Direct link to The QUIC Protocol" title="Direct link to The QUIC Protocol" translate="no">​</a></h2>
<p>Baidu Baike: <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 sounds like "quick", and that's precisely the point — <strong>fast</strong>. It's a UDP-based transport protocol proposed by Google, hence its other name: "Quick UDP Internet Connections". QUIC's defining trait is speed. So why is it fast, and where exactly does the speed come from?</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-faster-connection-establishment">1) Faster Connection Establishment<a href="https://tanzhuo.xyz/en/2022/11/14/http#1-faster-connection-establishment" class="hash-link" aria-label="Direct link to 1) Faster Connection Establishment" title="Direct link to 1) Faster Connection Establishment" translate="no">​</a></h3>
<p>As we know, HTTP uses TCP at the transport layer, and HTTPS and HTTP/2.0 add TLS for encryption on top — which means handshake-induced connection latency: the TCP three-way handshake (one RTT) plus the TLS handshake (two RTTs), as shown below.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2025-11-a602adffbd744406a31ccf802fb74569-e939433e4bbc48bcff4c9b3fe613dac5.webp" width="1069" height="601" class="img_ev3q"></p>
<p>For short-lived connections this handshake latency hurts, and it can't be eliminated. RTT is, after all, humanity's eternal battle with efficiency.</p>
<p>QUIC's handshake is faster because it uses UDP as its transport, cutting out the three-way-handshake delay. Its encryption uses the newest version of TLS, TLS 1.3. Compared with TLS 1.1–1.2, TLS 1.3 lets the client start sending application data without waiting for the TLS handshake to complete, supporting 1-RTT and even 0-RTT — achieving <strong>fast connection establishment</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-no-head-of-line-blocking">2) No Head-of-Line Blocking<a href="https://tanzhuo.xyz/en/2022/11/14/http#2-no-head-of-line-blocking" class="hash-link" aria-label="Direct link to 2) No Head-of-Line Blocking" title="Direct link to 2) No Head-of-Line Blocking" translate="no">​</a></h3>
<p>As noted earlier, HTTP/2.0 solved head-of-line blocking at the application layer, but since its connections still ride on TCP, blocking at the transport layer remained.</p>
<p>UDP has no notion of a connection to begin with, and QUIC's streams are isolated from one another — a stall in one stream never blocks the processing of data in others. So running on UDP means no head-of-line blocking.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-reliability-and-more-accurate-rtt">3) Reliability and More Accurate RTT<a href="https://tanzhuo.xyz/en/2022/11/14/http#3-reliability-and-more-accurate-rtt" class="hash-link" aria-label="Direct link to 3) Reliability and More Accurate RTT" title="Direct link to 3) Reliability and More Accurate RTT" translate="no">​</a></h3>
<p>TCP guarantees reliability with a <strong>sequence number + acknowledgment number</strong> mechanism: once a packet carrying a synchronize sequence number reaches the server, the server responds within a certain window; if that window passes with no response, the client retransmits the packet until the server receives it and responds.</p>
<blockquote>
<p>So how does TCP decide its retransmission timeout?</p>
</blockquote>
<p>TCP generally uses an <strong>adaptive retransmission algorithm</strong>, dynamically adjusting the timeout based on the round-trip time, RTT. But because retransmissions reuse the same sequence number, there's no way to tell whether a response corresponds to the original packet or the retransmitted one, making the computed RTT rather inaccurate.</p>
<p>QUIC doesn't use TCP, but it still guarantees reliability. Its mechanism is the <strong>Packet Number</strong> — a sequence number you can think of as the successor to the synchronize sequence number, and likewise monotonically increasing. Unlike syn, the Packet Number increments regardless of whether the server received the packet; syn only increments after the server sends an ack.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2025-11-quic-35e9b29c19f3401c32ef72eaa77d6abb.webp" width="1600" height="946" class="img_ev3q"></p>
<p>Say a packet with PN = 10 gets delayed on its way to the server for whatever reason; the client then retransmits it as PN = 11. When the client later receives the response for PN = 10 and replies in turn, the RTT measured is exactly the time that the PN = 10 packet spent alive in the network — a much more accurate calculation.</p>
<blockquote>
<p>QUIC guarantees packet reliability, but how is data reliability guaranteed?</p>
</blockquote>
<p>QUIC introduces the concept of a <strong>stream offset</strong>. A single stream can carry multiple stream offsets, and each stream offset is essentially the data identified by a PN. Even if the data identified by some PN is lost, the retransmission under PN + 1 still carries the data originally identified by that PN. Once all PN-identified data reaches the server, it gets reassembled — guaranteeing data reliability. Stream offsets arriving at the server are assembled in order, which guarantees data ordering as well.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-pluggable-congestion-control">4) Pluggable Congestion Control<a href="https://tanzhuo.xyz/en/2022/11/14/http#4-pluggable-congestion-control" class="hash-link" aria-label="Direct link to 4) Pluggable Congestion Control" title="Direct link to 4) Pluggable Congestion Control" translate="no">​</a></h3>
<p>As everyone knows, TCP's implementation lives in the operating system kernel — applications can only use it, not modify the kernel. With mobile and ever more devices coming online, performance has become a critical metric. Yet while mobile networks have advanced rapidly, end-user upgrades crawl — I still see machines in many places running XP, long after its development ceased. Server-side systems don't depend on users upgrading, but since OS upgrades touch low-level software and runtime libraries, they too tend to be conservative and slow.</p>
<p>A key trait of QUIC is <strong>pluggability</strong> — it can be updated and upgraded dynamically. QUIC implements congestion control at the application layer, needing no OS or kernel support. Switching congestion control algorithms just takes a reload on the server — no downtime, no restart.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-flow-control-and-security">5) Flow Control and Security<a href="https://tanzhuo.xyz/en/2022/11/14/http#5-flow-control-and-security" class="hash-link" aria-label="Direct link to 5) Flow Control and Security" title="Direct link to 5) Flow Control and Security" translate="no">​</a></h3>
<p>TCP implements flow control with a <strong>sliding window</strong>. QUIC implements flow control too, likewise using window updates (<code>window_update</code>) to tell the peer how many bytes it can accept.</p>
<p>TCP headers are neither encrypted nor authenticated, so they can easily be tampered with in transit. QUIC is different: its packet headers are authenticated and its payloads encrypted. Any modification to a QUIC packet is immediately detectable by the receiver, ensuring security.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-quics-advantages-summed-up">6) QUIC's Advantages, Summed Up<a href="https://tanzhuo.xyz/en/2022/11/14/http#6-quics-advantages-summed-up" class="hash-link" aria-label="Direct link to 6) QUIC's Advantages, Summed Up" title="Direct link to 6) QUIC's Advantages, Summed Up" translate="no">​</a></h3>
<p>All told, QUIC brings these advantages:</p>
<ul>
<li class="">Uses UDP, so no three-way handshake to establish a connection, and shorter TLS setup time as well.</li>
<li class="">Solves head-of-line blocking.</li>
<li class="">Dynamically pluggable — congestion control lives at the application layer and can be swapped anytime.</li>
<li class="">Packet headers authenticated and payloads encrypted, ensuring security.</li>
<li class="">Connections migrate smoothly.</li>
</ul>
<p>Smooth connection migration means that when your phone or mobile device switches between 4G and WiFi, the connection doesn't drop and reconnect — users notice nothing at all as the handover happens seamlessly. QUIC is now standardized in RFC 9000.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/11/14/http#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Looking back at HTTP's evolution: 0.9 could only transfer HTML text; 1.0 introduced headers and diversified what could be transferred; 1.1 used persistent connections to eliminate the cost of repeated connection setup; 2.0 broke through the concurrency ceiling with binary framing and multiplexing. But as long as TCP remained underneath, transport-layer head-of-line blocking could never be eradicated — so HTTP/3 switched to the UDP-based QUIC, redesigning connection establishment, reliable delivery, and congestion control at the application layer. Each generation of the protocol has paid down the debts of the one before it, and HTTP/3.0 will surely see use in more and more scenarios.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[How the JVM Thread Pool Scales Up]]></title>
            <link>https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong</link>
            <guid>https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong</guid>
            <pubDate>Mon, 14 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[In HotSpot, Java threads map one-to-one to kernel threads, making creation and destruction expensive. A walkthrough of ThreadPoolExecutor's core parameters, its task-handling and scaling flow, and the classic pitfall where an unbounded queue silently disables your configuration.]]></description>
            <content:encoded><![CDATA[<p>In the <strong>HotSpot VM threading model</strong>, Java threads map one-to-one to operating system threads (virtual threads arrived in JDK 19, but traditional threads still follow this model). In other words, every Java thread is backed by an OS kernel thread (KLT / LWP).</p>
<!-- -->
<p>When Java creates a thread, it has to call a kernel API to create the corresponding kernel thread, and the OS must allocate stack space, scheduling metadata, and other resources for it. When the Java thread terminates, the kernel thread is reclaimed as well. As a result, <strong>thread creation and destruction are expensive, and the number of threads cannot grow without bound</strong>.</p>
<p>When there are too many threads:</p>
<ol>
<li class="">Creating them incurs significant system overhead</li>
<li class="">The CPU spends time constantly performing <strong>context switches</strong> between threads</li>
<li class="">A large number of threads can <strong>degrade system performance or even cause an OOM</strong></li>
</ol>
<p>That is why, in practice, we use a <strong>thread pool (ThreadPoolExecutor)</strong> to manage thread lifecycles centrally.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-threadpoolexecutor-parameters">Core ThreadPoolExecutor Parameters<a href="https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong#core-threadpoolexecutor-parameters" class="hash-link" aria-label="Direct link to Core ThreadPoolExecutor Parameters" title="Direct link to Core ThreadPoolExecutor Parameters" translate="no">​</a></h2>













































<table><thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>corePoolSize</td><td>int</td><td>Core pool size — the number of threads kept alive in the pool long-term</td></tr><tr><td>maximumPoolSize</td><td>int</td><td>Maximum pool size — the upper bound on threads that may be created once the task queue is full</td></tr><tr><td>keepAliveTime</td><td>long</td><td>Maximum idle time for non-core threads before they are terminated</td></tr><tr><td>unit</td><td>TimeUnit</td><td>Time unit for keepAliveTime</td></tr><tr><td>workQueue</td><td>BlockingQueue</td><td>Task queue holding tasks waiting to be executed</td></tr><tr><td>threadFactory</td><td>ThreadFactory</td><td>Thread factory, used to customize thread names, priorities, and so on</td></tr><tr><td>handler</td><td>RejectedExecutionHandler</td><td>Rejection policy — what to do when a task cannot be executed</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-the-pool-handles-tasks">How the Pool Handles Tasks<a href="https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong#how-the-pool-handles-tasks" class="hash-link" aria-label="Direct link to How the Pool Handles Tasks" title="Direct link to How the Pool Handles Tasks" translate="no">​</a></h2>
<p>When a new task is submitted, the pool processes it in the following order.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-core-threads-run-the-task-first">1) Core threads run the task first<a href="https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong#1-core-threads-run-the-task-first" class="hash-link" aria-label="Direct link to 1) Core threads run the task first" title="Direct link to 1) Core threads run the task first" translate="no">​</a></h3>
<p>If the current thread count is <strong>less than</strong> <code>corePoolSize</code>, the pool creates a new thread right away to run the task.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-core-threads-full--task-goes-into-the-queue">2) Core threads full → task goes into the queue<a href="https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong#2-core-threads-full--task-goes-into-the-queue" class="hash-link" aria-label="Direct link to 2) Core threads full → task goes into the queue" title="Direct link to 2) Core threads full → task goes into the queue" translate="no">​</a></h3>
<p>If the thread count <strong>has already reached</strong> <code>corePoolSize</code>, a new task does not immediately trigger thread creation; instead it goes into the <code>BlockingQueue</code> <strong>task queue</strong> and waits.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-queue-full--pool-scales-up">3) Queue full → pool scales up<a href="https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong#3-queue-full--pool-scales-up" class="hash-link" aria-label="Direct link to 3) Queue full → pool scales up" title="Direct link to 3) Queue full → pool scales up" translate="no">​</a></h3>
<p>If the task queue is at capacity and the current thread count is <strong>less than</strong> <code>maximumPoolSize</code>, the pool <strong>creates new threads to handle tasks</strong>. These are called <strong>non-core threads</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-beyond-the-maximum--rejection-policy">4) Beyond the maximum → rejection policy<a href="https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong#4-beyond-the-maximum--rejection-policy" class="hash-link" aria-label="Direct link to 4) Beyond the maximum → rejection policy" title="Direct link to 4) Beyond the maximum → rejection policy" translate="no">​</a></h3>
<p>If the queue is full and the thread count has reached <code>maximumPoolSize</code>, the pool triggers the <strong>RejectedExecutionHandler rejection policy</strong>.</p>
<p>Common policies:</p>
<ul>
<li class="">AbortPolicy (the default — throws an exception)</li>
<li class="">CallerRunsPolicy</li>
<li class="">DiscardPolicy</li>
<li class="">DiscardOldestPolicy</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-keepalivetime-does">What keepAliveTime Does<a href="https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong#what-keepalivetime-does" class="hash-link" aria-label="Direct link to What keepAliveTime Does" title="Direct link to What keepAliveTime Does" translate="no">​</a></h2>
<p>When a <strong>non-core thread</strong> in the pool <strong>executes no tasks</strong> within <code>keepAliveTime</code>, it gets destroyed. Core threads are not destroyed by default, while non-core threads idle beyond <code>keepAliveTime</code> are reclaimed. This lets the pool <strong>scale up under peak load and shed threads when load is low</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-pitfall-that-is-very-easy-to-hit">A Pitfall That Is Very Easy to Hit<a href="https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong#a-pitfall-that-is-very-easy-to-hit" class="hash-link" aria-label="Direct link to A Pitfall That Is Very Easy to Hit" title="Direct link to A Pitfall That Is Very Easy to Hit" translate="no">​</a></h2>
<p>If <code>workQueue</code> is an <strong>unbounded queue (e.g. LinkedBlockingQueue with its default constructor)</strong>, tasks keep flowing into the queue and it practically never fills up. The pool therefore never reaches the scale-up step, and <code>maximumPoolSize</code> and <code>keepAliveTime</code> <strong>essentially never take effect</strong>. This is a common reason why <strong>thread pool configuration silently does nothing</strong> in many production systems.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://tanzhuo.xyz/en/2022/11/14/jvm-thread-kuorong#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>The pool's scaling path boils down to: core threads → task queue → non-core threads → rejection policy. <code>maximumPoolSize</code> only kicks in after the queue is full, and <code>keepAliveTime</code> determines when non-core threads are reclaimed. When configuring a pool, the choice of queue type matters just as much as the thread count parameters — an unbounded queue renders both scaling and the rejection policy meaningless. Once you understand this full chain, most production "my thread pool settings aren't working" mysteries have an obvious answer.</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Bloom Filters Explained]]></title>
            <link>https://tanzhuo.xyz/en/2022/11/13/bu-long-guo-lu-qi</link>
            <guid>https://tanzhuo.xyz/en/2022/11/13/bu-long-guo-lu-qi</guid>
            <pubDate>Sun, 13 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Starting from bitmaps and multiple hashing, this post walks through the Bloom filter's membership guarantees, where false positives come from and how to trade them off, why the original structure can't support deletion, and how counting Bloom filters fill that gap.]]></description>
            <content:encoded><![CDATA[<p>Anyone working with caches eventually runs into this problem: a request arrives with a key that exists in neither the cache nor the database, yet every lookup punches straight through to the database. To block these requests, you first need a fast way to answer "does this key exist at all?" — and that's exactly what Bloom filters were built for.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2022/11/13/bu-long-guo-lu-qi#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>In a redis caching setup, if someone floods you with requests for keys that don't exist, the cache never hits and all the pressure lands on the database — the classic cache penetration problem. The naive way to intercept these requests is to store every valid key in a set and check it before querying. But once the key count grows large, holding the full key set in something like a HashSet costs far too much memory. A Bloom filter trades a tiny amount of space for an existence check that is "possibly imprecise but plenty fast" — landing exactly on this sweet spot.</p>
<p>The Bloom filter was proposed by Burton Bloom in 1970. It is essentially a very long binary vector combined with a series of random mapping functions. Bloom filters are typically used to prevent redis cache penetration: they save a great deal of space while quickly determining whether an element exists.</p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,UklGRkAbAABXRUJQVlA4IDQbAADwvgCdASr7ApMBPm02mUikIyKhI3OZoIANiWlu/D65UDI9EeZWFWpPlMeOP7Z/vnUH1D9QP7X2n/6n+3/sZ17fqP2m9an+78ckRf5d+Df0nk9/rP7T+3fm/+Rfsn/G9QL8p/l3+d8QHaUWm/5nqC+t31P/m/4XxZP8X0W+yHsAfmZxxfqPsC/0T/J+q3/d//H/befr9G/1H/y/2PwF/0H+7f+DsT+kANpw6mVDIONOHUyoZBxpw6mVDIONOHUyoZBxpw6mVCHU0/7G4iH6zTh1MqGQcaY+JVrV31D+QdTKhkHGnDqZUMg40zR4BL+MacOplQyDjTNJB/BKPUyoZBxpw6mVDIONM87iaarnBKm8s5PwCiyFQAoKoMYTg8wvyQsDFeD0g1LjjNA9V7w2nQTJj+rT0+plQyDjTh1MqGQcacE1Gk4HEk4a5EZY9356hOEYVCzC36pERtLlNBwpTpUOplQyDjTh1MqGP2n5+2wS/WOP1Ms6R+s83BdlB8mIkzw8B+4Ltjg404dTKhEUouUFhBREsVyRV+9t92wDe4PTsfHHcKjdqfo2xwcZrJQ66lgyyJuectA7EDc9VsaDkqe47Bm8Jr26xSUhjg404dTKhj/z3BoqTYKkmG2gj+BsK4vaaqQcacNzhsM26pw/o0kk1AiKmNecVNUcqGQcacOplQxMUkZhYeGB5iefXuXx7T2Ie2ODjNyRdwdpkI4Npcer9M1AvoBt9scHGnDqZUMM7J9IAGFFCyHoKA3/M8G90qPuMWaf9pYaZX91cbqRpkXZKzZ7M23atfYwSodLBXy4HPadt/WH8agxqDXY90qVJ/oocyWtlWx9QtSkSRYQuJEWejtZre+9vtjg404dTKhj+agQ3+0EyDDcTLwdyrTyQ3oGtx1d6dk3Z8RgUcB0YKJ/6T4j0wvy1CDz+jfDnDc2hNwvZUWWSxkHGnDqZUMg4zqCuD7omNCY/HfBVoCEbxaL3u8RSRgHX083EXrD4//ydAe17vwB4WJ/Flrl0G9UAsQY6vMG7jTh1MqGQcacOpkYMuD/IZcvbenmSC91zUpT+UYXoZti3L9oJJ5VsyxalI6yx4ziQq03zdAboI9NLpTYjQVu4VFDRfA9TKhkHGnDqWENNuuwz1OjIR7DlvotgAe0mq9R5nmY38sb1zlzzhR5mpLCuJcf2nXRUpQHbI+4EqGXMcHGnDqZUMf1N47LUMuXzios7/J1v03Sn9rSCersPAUxBv+GMONe5PgdmkfZTwN7Dp6rBhcGLnDqZUMg404dTKhkGbfruPSGJUMPsx3c1Yf9o1LF60Kjl/U7C3ByXn93cZA0eK4xRUi4Evk5tLtBdIgfQWEcEs6bOz4ulQ6mVDH8LRM3xyFt7s3alHn0LNVNv8QTx/l60FRaXthEDgM+8QMEYbl6d817RypVS/X7HH7gkMMnQRxIeLoxwzGiYbH8X4SBhLxb6S7ZXEkPRPGamdY4Md2wwfCmKc4vtZCx7PNxxLNoosz2zfm7oHcNl2OQtvh86rD9cT4NQAAbNiJWloSVnJH9z0SZx8bj/1adX/1oaKQwJHGmezsLOOx+duMFfN7B0mHRCvN0eSHFUZpH5pXDVqqr1oPSonZQc2Uj17gjr61stRcqSOuS0KbkNSqHi0PW6u0eOSxTp2PGBvlP9LHgbsdBu2pcDLRbOUEm9D8vgrH5bJyB+SXfHaCftgMAHgQpDApaKQvQKPTAFHpuS1M83KzPetNSDT0JlMyZAU3CzElwELWhPV8NqbUkBp53UDuxQtkegO5AWG782nfpWYWN32cSO/ug6WrJHlpqI6mvLRc5+ySMu5IA6KcK90dwtCuxP45C3FI8B0+4LtfPQeSJ5ywx/FMgGqIY3T9M+J5Ztpjmb/bAqA5KMNqwDLWTNpJKSOp+vfc9kyoZAyi0yTBWXu392qTMg0DfgO51aKOBxYq93bkHjyZlae78mrEnaLokqQOID7l9iDcnqZUMg404dTKhkHGnDqZUjW6VDqZUMg404dTKhkHGnDqZUMg404dTKhkHGnDqZUMg4cAA/v9+wAAAIuPQEG58i6+izEUm50+9NO9hCWUvljaR3b65y/1liFYfThhWxjRol/+QjMZmFJ/KFh/iIGk9oy/bw1hVbIBZoLXHLBIucSU4lNYZpga+9Du2ixR5vtn/oIgKZrHbfT8E+0pl7zMAMK6ccqrBd1ToDR7O9PUP4sAf/N6TGZB7HHgFtlj4AY6gZNszoopPy6zymn2CANhnyrWanGAI+Jj/0BiQPFERtwxACZbfH4LK1vx4ik/TVmBsg8CgFBjBgbcJ7ubxXcVHEQdS7ULTVtgoC3eSBaDgbtGKgA5TiOD2vk0aAXZRDpn2j5Qm/pYEIqA7fQqfZSy2lO3cv2wmWeLxiNRqftke0GqnZCTyjdn66iJt+AmuByT2ugJBeORr+vZgeBMQ8i0fYNLrJ4+cKn0hV0u0IzsS38nHVs6rCApEm6fRiNAKdoKkjAOto7IVPsd4GAdosd5Hp3Nljb6safIlHzlZXpfkUrFf47pyklhROLVXdfDbUdyqnK2WGbb9bWhS2pEU0bMl7F1ZpmSin0mzXiN9DCUYnhsMyKxFenaRMRjo9x75u87MU/vISjR1x7HEFmcVLRo9Bx88+cZmJxrPSriIL18ThWUDaHvK7fqRZMb+IKqlzer568FQsntGEC+A2U/bhPg1ZnFOwdAxGg8guGa/qn93bHC8gJ8k60IG3PamZXdTk2v/bw8LuQf6LeJjuwLhm/J28yz6fCXV/nPHRSXBoN4lvlOJ/N/mDFvZvNnyOzYvE9UlPvUCthsFsAgccmuR29lFq0O4N4lmv2KsEvAc3DqK70IhtpRwSjW0xs522S2SMHMQdGAB/KR/26WxU6OYTcSOOsUAAFanXaMT3kFA/PadpXQ5II0cxu7VRbffs9NZ5KFyTAG7IkeJCQWY8GMkXB7OPhFO4opogIajEeK+2sDcDyT/AGDYEFGOLmZE/+uTPtXsdg/8uyX7jTb8pwWjo/J62HbYSfZbGfxI2ZEbZHOMnTujhxsBLxrdY8LAEnXf++9mYcCiGAAHvO5bG4PqoGBLzYI7+L6ajPoGt3jjgcwRQrHZ0whuQCAtAGL7ub2WsYSXVHx/D9//BWOV6Fu9o74koyneBUEPC9Nz2Ai/ZgTFalDD8VALf5zYLigIJbf6yq/f7WpT3y3Rg3u01tJLLG8INWJ+DzDwe54JbYJUK0HtR3/4e34N7X1Pvy7l0ZePtTI56aWO5Qc8EYVZyVQ/2i9CUvCeRC/VqCBhsLoG6ATNkxYAB1Gy1J9t/zt9n3kXYsp/L83JmhnxlJ22QijpsvXWG//9UFQJoLf8tM0RH8XP/zt9is0oQ8wt4hYi4q6hqsGugGl8CITAEcn86knwJUKS8ZfYO0bxie62AnCpN3C3OWiaVfsLKmnKb+jj8MgdZ6nMZCBUvadE47K4Ywa8j4ymvGnwlL/RqAGsQNkUMNJ2McXh5FzMbylWNSUdmJVf3N58sOTx1kwiY6DzzFOndfpFXHQ6bzT5I3coryInHyZvLQyDo3exteKSuEoOZvQpduNq8WjqLZUtTQIi4p3UOEciDZ5T+OAIQ/RPIfM+rDyYiOTPx4QwbFuFSEQzQ92oIx10949gqh2QtXw3/T5uirpq3WKD848SUvpGsn0b+C/5TfMg6JkJUldETYMDr2iUMNCc2p74RDaNoHi+uvTYgVLLwNL7aPsGJ5uYjidoTUgo4qh6QbskynkckqtXyL09t6C1MUYPdK7rCtNqeJlV1ncnEl+KP8Pd3Q82hK5ylrIMcJ1p1DMlxNS24iPVCAasQuQeXE33RjCzBlANjj0BQ71mYJEIo1dSs9Www4OCmzSh2OS3GGluL5rkdPT17tvdu6/jpQt3vFEN5hiqogdpW4VytmuS++IZ7XP+U/qWSrRTZNaVzfLIGhZDDIBCj3GFHgqY0xJAKCUFIREF7x83CMOyF4JbNrb7IBwSt/P0puUihUVuJD/i3PNNNI9sYMkwyTqnP3FBHSCVvqTMwPIGuMBVSv/z5f/YG6A/WVBalJbvZZdaH+eGZLlrLyiGeAh8ytqqzutsMe6DgYofnPcgMO1+O8nAo20zg2L7yzSlNXYtMUH34XwHYk8zA3hCybsrpQd2qHWDsrT39xuUlkUUCMioFniSfzJ5ARovdbFe7Eebia+eH7yNDLu4RCCuLPaNz5pYpfdOrRtyLdl43ociXKx3H9AqPKSi7s4HTysL8ls2qY0YFhQSjoaFXS7ZSlKM4ppo+ismI1b5rjYuKespTxHOkxRroM9GuyRMJJE5nnP4SYOaxUT3a/t4mMG7l/QrLMQQSoZeh7eaNbtY8XA/SHjCnKp5lf3DeuiMFKARFda/ifFZ/NoqAdTeJ2yUNn0uvY43IrxQPsCnxUAcEkDuBVT9gV2hUrzQgh6IOe0/wMbFdKZsIxAi1h+tFugcAyOr4zQs4i7Vb591zulL2a/bM5T4Frk7YaKQuAX0jjS0RBu0cUuXNH7xDk4rH7xDmO4ZT+BTFrRUxMcgPwwOagMfS6X7b7yf0JlLaxjasVV2qHw2fQ0HFvxqNL402GmYsg2eb052s9KJhwvyflWZHGGNB1O31wuAeJDDSjd2RxoYhrY2P7IjNYOmRd5CRmNiAn1qmqF1PRYrB7INlJ8G7v6KuLcs2G+oy3jfP3jBPinBRSdsyxx6jA2Gt9O1cF11lXa/kpZwzqfeXNCRNdzr4XRPhLQTthlRKmUj44HYqGm6jOieAO82stC1lPy+pSL1eUOUKi5jj/0DbvQIV1VnIwmQWRucStMCqJa2FnVXu39+0GGRh+A4J+GJSv7myXw9/hOh84pX1rxD+m9uQOx0+FAz0sAMTJRRa/fJ87wrcjWm2JQaUDizaT+WzEg7CiNlg06EuXeb624/H4ZJ4rwid0mwZVw4PaSsjxl9H2c1d3DvPC1uNrEfoBN7BgARRDE10whqOeYWBy5Z5oCoTnld33fh2mFFNaE1+XEnkWuwum1ejnGFSfCGuZRkdIHIPVtFnzqedmyZUNQKIGmuiq3V84IRdXini8szHyzGq0ndRFGfG60LN9FtDOyvQbEVLSgO3efO1++MwTTTzIq6wUkp7LdO3hn9SnQe1ksqpK6kYt6bVRPpHmhJdUClVA0WPbG2RQQBTGLsluQ7oMX/df+ag8V4vEtWrXdM48KH4sIAs7oxjLEcAohIlmWDsCpK6Ww5m6HxnBvWHjxzE0eFhg52yqXfl3tsGTV/oMDolF8wYgtlVbBPJ3lIQT3OiL6p+WMSn9sXxx02b69u30hpFtDtGoYvWlskh13M+EwAiL29e8g5OsD1Jn8u7ws47FwWmIIbuaFDlVB7B93Y/AoUbg6T+M14yHRF40zNLjKxCfZRTUPZ6MernQJz9HX+4UsbcgiZYKwOhwHa18pp3Ig2rCvaxU71c8adBdlDX7A7OSMDaejnvJ3iCcdluTup5dbBY6FxXirexNQU1AMTx7rBUQb/eKnUEbqHlRDBK0x4gNb6hzFu+UP1G4EWJr6mIfDA8O3ev9cmHMnlAphJ5BKpGSwQR1YuWXUDpbdwGHbv91taDN8qT4jXbungG1l87+bZ8lXm01mzohfmfg67eX30iKx1tBrRem5ZYi8RDXSY2n7b7bxN9su8SNHWK4e17xf1XmQZSUg9ceGzqU3DBjvTchlo8IWVp80OXOBnCcmN2Ttb8Ays2nodhcEL2F//vEKb/vstKm5WzHAsYGIIGrdO+1RNTjC6knDJ5Iwtyl2F40+pk9+Mf5ziJUpuZFAOJSJEimC1wPVAZIkjaBpDuzE758iYabbI8S2qW5jn27TFvne7xRaCF2lGJTSMmiMpHvsv8jgL/JzX+s0JqYqppn0fHXjNh/qAEH6kOl8oxdqTzG1KZjQRJbY5REvQ2lz0ISCBLKkZOXrpmQPcBKMpEhvSgjjus7ikm+5xnDzv+EqxCen2iGbnHejLvi18Fp+1Kletn13NditQ63l4ea34ZADI6hvWK0uKt9hb8yvg45IueqFdsMrInkdHZeL9+iWFJ0LB4yOBfp3rlVsaP3ItcXj3nUnhtjmtoaWFR8cr+m0fwp2QAYM6IL73PnepBwc+wWJTGLZgEO6d8FT88BCESoATX0HcEZWVkG0/lj2/3ddKfWGvyAqqY5icv/Mc6N8Am6o3E5wEMYa48yj10FyVL38ofhOe85zrpDBCzgm9DXjlE/tSb+OAqsMCmlicdnkrrAxu1eBhM5UZnsKWexIlCXed50TcCB1Q0o5RnJDp4RZE+AXGZeoLP3znwtukfJB9HAGIW2SgaIU2LVADSUMNkZr+0zd3FplUAUeauQDk+4EwB9L3XBn0ThB5qWTZqarae/f/IYZRu9kF916+OPEuicgJ5I/sR/ulO2pKZpBjaxiPnw6AQzswCNFJs8x+8jj4pnjhjDcmiD4hlujkq/Lt8P5AghosUsnfO+qNiBWZqCAD7xlkIkl0aziR4aGLGXdDTdEZc1bwHjWK+KpXDpjNGcVi1h4zmkQqp2b0Gx6UuwfTZT/0WVgKIyPNDxLYLKE8CGM8nQCpfhaPvnvoTbOrbBqnIBG82IgmX62nDu7hRxvfYYrG4uFsOWxTcBoGZhsNDVZNMtUGQtL26MHJMg3vI1+wHOdvuePSqXzsbxRwG8giNIvd12Q62N036q2S0UENVUzbt4uVFmNUEIsUqFNirhPMDhpCmzTKWUBOEedfgr+zcUkwGxMkcMMV/R8WaJWWv91NBWcHE3dKcAqYH/m2OFIzlmkhkCo+wymwIYWpmgbEvqZAx4neDjr91iUqgVuXs2rBfX2qNDqDxiLZc3mA8j/Su/kIDpUuB3G4zqyuFPMQqrA/3IhF9FffQ+bjgGSWW7N/14d8l3r4Hsff0E3/ABIS9nNgZMEJK3EJU1/AvTWv8jIkk6v7j3tJ1YLFU+R7T7gAQZ7803BcAp5XetNcfhCsIAa3NcMTbfjYgQNSbqkWVjkB4JclcS6tW3HX5TEFcuuGrmt1QPg7CCmhM7s4Hb26KYrbHj3nnM7a9A8mF4aL28ml/JSRbqwbt0CJ6SaYPzkG2l76bqiDCjqOeRyf4XmjzX1AySKs4TjfYmGKX0aJuS+70VIDpwv83+AoqALXbEkGE6vKJUoRL10FfX7Iol+2Qc5kLXbk80ihP8Tt+2BYo9JI10ZOGv2v7oLIy2YIR8GrIuRuV84ThJ6dR46Zq3/7t5l7kilDYFMIUc4BylGLLnd7alZNCZJMWaQVP7Wb03hVePmE7J7mP92E8B6AWr8P3f/uc2/BfQI2SQVWo708UJbJFDAn0+Fnt79Jb4H8eFEwTVm0CNwYJq7JYSM1++SH5jm313B2UhedtCD/7GqjteP2pdDDJTeCnec21Nxsr4TqWY7hxFPlNfSt7j0ADGKwC5xsoYKI9nYTXhh9mls442Ami3wEjROIEN0ClBDBUPwJ0Ttun8fR7ygbj4TRQ+q475AGrYBGAPXIkujVXXK+hzgVqX0lGflPSGVPLLuiwaoQbO9nazaN2vKSM5fZBH9zPaimJX2IxGl94GbJESoo/bjz6S17zHUBX21FOiGVl6oVQt4POPmElWxbGmy7gmHcEkrt2Pnq8pHpgnBqMv/wFkvNpClvTUl8MZ35F9xISUCRPoApCycBqRhytSotKxr7vETm5mBIuQ8rPvUZSTowC5/MWG62un2lZ6vbiR79UOOOIEPl1XQiVfFFJ3/1TMie35SOg+xxFjCVTrobszaNRngtgZLjJUmsZyiRqNDc7R8zUxjBLdxL9+/r9OmipYB1p1YgZbYOSOXzAwzQu83v9am+Z4baQd0dFCbbijmxcRlOHLSNDNpMnXE4HO+ZFvLCmzykMD2wO1amO5JAkcemWz8tYAL1MHXFuXsArP9Bj3ZbF35Sge1CD6sT0ylPU2dvubpBO95J0fU5YWU7r6SBFHoAuKrn+59jNuYdQs67z48giuZOJ1iQacOYXqcgtfm2IaeFVVlGP2Kv+ZK0vDLeFpyWK9CQFFKJn1oSgAXFwohDGDYeY+tlFvucFpEa20VwjtXhNtVi6OgAr/e4Q/7eXRBAvzzH+emc5f6qXknpS1gXyEQXT93H2K5jXr1NggfSW6WtdyeuxFOA2Cc8YyJWetK5kxCWHjvp4AMm2BMbiama+omNJI/OX1cPHkjfS2gPQE3oZrLa1LgZC67lU47jRJH22bE6KIhFUncFCGZV3rsGLZBKyodtz684g0OPNlErnTS+vNwqfnKQE5uVLBXb0zg+hLwFOkvAdb1abXtv+zPn8Gi97qz4kZRAkJ7pIVOnm5slc4tqGu+xCHpGlNKfytJWO0FbIoEnDSti1FqSajfKFNOFW3M9mUmWEfXXZ/m1w4o9svFcU/ges1XrGbA8do43j50BrYXfZa6w6FQfVM9Tz2Sfkd7e2hg6/u7YTEljHOq5yOnCpmMXN0JkHjwzBEEbBWKKG4HToFyoa6mYvRvER5PbN9x5ykwuwI/re3MACruOxVuwrY5DaIYdNcNC6gubnK1pywL7ByN7dW6MdfL/jTD9ANd2SnaWYhrp8K1k2RKPb5i7vmxzC2QWSYkFdBnY3bZdBN7ZZ7jToOFH6P70hnKzL+5C/Kl3fzIVWB4M8Ifm57/P8EUtUFkjUqowQ68wLmTuba7XsfG4AIgk/vancxI80Pfsk0WVQose77ZxW3/dWabSlaGsZkGeRMqcJAVB++Kjsau4yr/7oxO/8gsiOKlMzOQTYc4g13bycXKns7tDPbwG8lhD/E0u7MAXLBgpl2UCtewTEBnKC/X4Si+fIaUTc1DPJIzn6/bXWZ7KkmaAFa6WtXEL/zoqduylCvv746QfBkBf3RVE4aFr9UCTx3N1SjEZ4ON3OFQ/z9OQ9PcMIqV95+tCvi0eXWzuWGC1lhqrixV9gh635F0MCatJy0W6chn3QRYXCzcgDKBxAiYSJMTRhXn0aQ2ju/xxDIOgW6bSMK/H/05YFc5bqGZ4E67ljApxcMI9etQEbxRrZeavG+lwZFnTX8njbDjTlqwdYgyh604mpOQNVzP4t3o0qmvydS5XD3Bj/UyM8T54j4ft4bAPcm7EJuQw0lM6A7MW1RDY/edL4wuiq17+jIObxHXzX1Q96B17eFTaUhHVbyNeR35F0Uo2vbMCfWGsl0ixmfHeSRGEZzysazENnQKNKS9MGDb3NqqNv85+CD0SJc0oZRipBVmYWBiaNetT9/kIOPulWc6jXUt8LZb7eJusCX3yMI0TZ5AAAAAAAAAA" width="763" height="403" class="img_ev3q"></p>
<p>A Bloom filter hashes a key to locate indices in a bitmap (which is just a bit array). Each position in the array has only two states, 0 and 1, and occupies a single bit — 0 means no element is present, 1 means an element is present.</p>
<p>Concretely, when inserting an element, k different hash functions each compute a value for the key, producing k indices, and all k positions in the bitmap are set to 1. Lookups follow the same procedure: if any one of the k positions is 0, the key was definitely never inserted; only when all k positions are 1 does the filter answer "possibly present". The entire process involves nothing but hashing and bit operations, so the time complexity is O(k), independent of how many elements have been stored — which is why lookups are so fast.</p>
<p><strong>Note: the indices produced by hashing two different keys may partially overlap. This overlap reduces memory usage, but it also creates the possibility of hash collisions — a key that was never inserted can hash to positions that all happen to be 1 in the bitmap.</strong></p>
<p><img decoding="async" loading="lazy" src="data:image/webp;base64,UklGRsAfAABXRUJQVlA4ILQfAAAQywCdASrmAoYBPm02mEikIyKhInSKQIANiWlu7l/aXayyfhGCpPj9jP229JvS4Ddra+D/xPVr/c/E364n0zfBr9svVa9Kv+X9Qv/wdT36AHl2/tB8MX9k/5X7b/AB+0H//1pbyl/gPUF44fs+ko+Dba16fsw1OO1/9/5a/93wb+S+oR+T/0Tz7fke1J1v/YegL7Gfev+r+UHv2/WebH8/6gfBh+b+wL+Zv/H6tOhD7C9hH+g/4Hra+k6O4Np0GqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnQ76bB9n5GVZRA9p0GqqqnJuSYFVu4FRXMzMzMzMepaTSerzVBQGREREREREQ5FD5C0GQ/FIVOMlgGZS8KosOfVDMzMzMtWXGLvu/AheG+yjOgrmn5hF+/tbM4WCyxdtcqqqqqlUQ/Jx+5Z3tDAElZ3avyeo09MhmZmZmZmZlmenQaqqqqq5OX8AoZQbToNVVBjZDSsZQixCDadBqqqqMN5odnNroG2ydcyeueJ5mzMzMzMzMywgPyqqqqqKuaoIaDRmGjf4GnpJ/tMa5eywn0NM7+TrmPI2rC6PbpYeJA1EJl+XbQ0LfAufJbOYl+tvSoqsf5W64WZ02vYiB7kMqqqqozKRA5M3K/7QvYzh5XKloNj5Ljow5lctmSaclMoiBbiEjWUhJUK0ArpmXqi8/kKCT7haOWpnvznmaVOOBQCPo7cPkaIbCjDDmZlmJD/mPQxnIwgYrMDAiSEXTMyzEagVKOBm0KysQZmZYXbamWBOhD4u1Y7JlmF2Jp3N6X66AF5JSXAErYe4zSkdxhitFpr0Z7PbnOE9d2AtKQS+q8zUEraOSn9qIyVUiMwvycyKlZgb36bAMSwwnNzWoFGH0ffcx2TdPg+0Fkb9qMmUQerSw5bCckTP2qb2eKaFA6uSRI/YT2ugziil86vsVd7mVkdUs/dRbsnXMzIDJSei+zoaKt+CHNcZzuCT0mBnDQagRIcPmzkutpsTQHQW3oP3Ut1jpeLpJ53HCjle4+CICGsaQZK67eYHCFkerSf11JpyRJ7ZLjFhX+HM0Gqqqqa+A9RKKtzhsVvCN4Ko49KUOjuvY2T9xK4yYpbBQz3+edI/OUpiRTw4PyGaWr7QWV3+VVS4ruMgTMOa3yTluM/a7JMzkOTmXd3d3d3d2978s3OW+SdBnkaHwcthajoblSi6h5zTkDzuc6BxGJkqagbrAc4O+mrck36elnIEt+Mqo1KqumWlYj4qXgbllqtMxYTYiAe/hwtx2RERERERDvMWRIIKrpL9c4W/41lpxtL3zsGXRFrNb716+eYx5CQ1IfZo/2Fv7uEDXZeeqcfuj4MUeBQuie7gglaEnCLarOsx1pfroG2ydczN5dh5ixRG7QPadBIzwuRj6yq/rI7HTLlaJUuOptvjXQWSe02ig0T36bF2djrCdQGSv4GAYY4XqNWXQy6HvCfuDo7iv8OAQixgcUwo/kl+uagk2aHdp42VUhqo7OPaPK6XmT9OjDsCxKYqB2VSO/Sl/2YwSAdzkmZmsW4yYhUMXm7ZKTbB1d/GU18E6ZlOqSwTmQYwhD2DOrdyJPvkrfKW7UPMI2GhTTB0I6h65i5hutqcptxx6ZCEDwhnGtiM1Fx7ToM9U91cjxWkC1sv/Los3YOhip/VD+zMMFVj70N3QKMXTe3i1ZTl0ilxSpbBgg4IamTeAW9TBhszMyweAtbPr0XdrS0FJAr5EnXoBk1+4KqTpSSaJuzOWgzOg+KqqqpI72fW0OOzqvBl3Mm8blEl6PcivC6n8z1HGKi9s6i5R5liUA1fl7BysrGgLM4c1X9qcxlRIkPIbJ1zIy2WA1aaVK+biPrWTMi9SjdJRx6Smr4DP8dkOxM9EqjZyc5Wcvn0RLhe0W8zq84SJn41LVGtoNfCeJqN6tu1UO68VA7rOVnL58hMRgSNDtuctlub7QttnFplrbvEVQS+enCqqqqqm79iDtVDAkey/IZVKypt3LYzTr3iOi53bwJce4bVN3bi0OjKtDYczMzMzK6y5r8vzOS3lzHt4OQDUq1Bm5Ge2uaZKdLSsUrtdbWQ8jVVVVVVVVVVVVVVVVVVVVVVVVVVXA6qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpoAAP7/GhAAAAAAEGMVganH/3DRxmZr+q3fiHoyZHgk8Y0WaWAQ6gtFTB0LVoqMM/fLPD/AM8ZDGUYB85XexWbwrA0VteI6B0T1fN5Ug0A0v/fpG2+Q5R7F4pdYuHgjlOUcnEKh5wSxBkY45nsAKE/7xAA3Gl7tXI5LzWttZcUEjPcYcv89vwzu1HFg9gNt9p4+lUPGz6aXadVyENiHn9cGdAYqKNIF8oD9wudp7o9LBLhyTBKMP02Ylg4UBND8l9QZ0aUBlvJdqVbWqkC7jREvT5GLOq4fjqZ7JgAhyz8GYGfjvGlJMS5MWguB9P0p8xN+ZTlf3A1QI24nB6tEz/QM+WvJ+r1UzOF1Iuj/rJR7eTDGNXejWW5O8MlDMc8P01283twXl48xW5yUyzqTtu8fBeP26+8UdnpkmB92zTDnFlVEssy3bTw/6PUP7yeC+WDUawnqIc6rmHEpddA5LyP5RE8vR2lkEB8otmIe+4bypp6qkcXRRWnCqvDsPb+luMTuIxIzid8U9kSnm8Ppa+dx/ve7/HMYg6wb0MoMjhGvwtdTgDTkSns4b1CZpVJcv86A3iG9csXXwJ4DOUWIDeA3uXf0y06NsbpA2u5BKGHlubs1lJuEpFBjCJQBI2EusiIZEq/Oh+TBoFuEnGC2UFXsMRY46v13sUqzHqiAj9XClQsgoGK2AcVQpCluoiwqDJBhD6pqKcyLU8wjBz8tJmS/hHAz6rRdI1za6CKoh7TJm00rcVWgg3a7reiWL79nuoprQeHhJsh0pJ3V8zzQ8EqAiZ2wJs27V/qnZK37uT8jcf/1y+Kvd/groUc1IMBRAe+TIny4N+PhtKb/lyBTKL8bdgVy60tROqUzibdQnTwMHhQvsTXFDehkIneoUnGAiq8sY4fxk1u4c3cFYqSoeJXJCJ3LQxjMBSdf5MQac65DKtvV1eNkCT1lWJ0YaYpB/FgJO647RV0N3/Dtn3G5XcniCnW6lck+kbmZU0z4LbwxPUZ8co0DDi8+nogB3O3QEgLLycCgrAZx51sAPvyD88lrku7nNHZXM2z5vZME8x3DTlGOOcgL91nx0432Il3FJC07Rwnq0EX08FdIv1sClflt2WFRZh9IzqvWjUP3kFyrr748r7vNs9/4LBaJLSPh1SNHXfLPmNLrVHRsrjkZY+Cp+6wXRFbF5t9BJalvQicAE9YuBwB+E6QAX0dbXOG+hEy/Fdq83mRNCmQCxgOL04/PJ6s1gdJOAHY/bHl3nACQ+83EzjsFKwFHkCGJ2DFoHoHW+KBGJBpvBjS209XfhFdKCecmQtjbLLXC1sOkw494HJXaOFTf14JaA7l6WhlL/XCcBZZBKaPVWGXgTdWPKwv/OQVf3q4rAISc0pNDVpGq5KI/7zk2gA1jC34e7eKWeeh58FhHADteDft3bnx/atJns5CNBEFHXhdgxwZM5DUru30Fb/M4/qekKeeqFccQq656a25N1akSLyF1WlEc3DlK30wPGnhd72rgtmDDvv+vrQn4QBpm9l2q5W1ZCHxSIfEBefO1d7Y++mVKEUIIZOUqJFaJ2r0sze1IcWRH2Ul37uxPKTzgkzy5f+KLArUs9cXOK9LYRS2xCqINI7V3Re/ERDEq7vCDhwYG39JJXTBHsRvSmrkRTBP74qOiZNL/Jga414x0rS5cG/jnuGyjXxMmh6Rs0bjjqIBOnAmO2K/1iUKZpd+wgZvlKQU4pUa7MxOJh0jUP8J5SSBS/SNqdAD/XSKb5sRG1bQNWRCYsMMWNxkDgrGSi0Lj6GY7oKaffcBj8I/04WuFFPRy2fn4bcGYki1rWmWWjQV8AYDqhvUeUqFQJYumkGi+KK+NhTydYe7H/0VoPqkWMyhVtiOgacXOTIZbSXa0vkDTgRAs3vbQeXkUPiTG4l9/GL+8ZlFV7I0i1v3OpDF8PtufS3zi5wPyWAk5mcUPqxX+ZOTczbjkFxojNxW/7RH7rfIo4/pwlRbv7WUJljAN73qVQylSUR48vQUqOjwQGKvizcgtbW82zZcYG4v6sUB1FssGchfLkD6T37qEQ1C++xv4g2PutbtcNs8+8gzDHeN25b9uPj1ApMh8npm2fJXP5rOow1/3BSWKFyrpnzUCGPfPzgqCW3TWo911lA0j/KcFi/n/qBBh3m5MD9yVjC0rZ0+P5Pot8+GAPdVKOy/yj+Hz1RWNQ64zLMm2LJHWbZZRdsx4fu+vBrL4GSc0dej2clqFuSMqzkaBU0uJ4x+xmNsP4Ynqo7qzAKw4QcYprfNWCtPdkzLhUch6Goxv1Zq81xpuc+iRG77ilzjktU7kUBvn2stfJnP7ErVflc501J6wo3PYBvOQbpSUP9h8zAwXen9DdPvApg68PTSxcq/V2+MT702kyW0KlQRGif/nzcumrrHbI8JoUZMmFPaASLzcAIi6De38Nq3gzeWoJhMO+zWf4O+53GfXXVyfMyjp3DToR9NyjrXfY5wMrGRiuzujHOxtMZPratgRXKuSIhjeLcRqbTQ6zwLwzdnQWlHHVxz+1lE/RvmJ+mwqcTD0NvrXSErjhFv2TOE+/oO08y6Qbw23QXFoc18rz9iYqpD7T+lqzvRGs4adz+0/+36IVp6XKTFYhITNpfPUFeUZ4aEoQQbtnPjxJM+PvT8x0uwLvnFp+84k/tWWeT8WLEtq+A4SQkMb975wE2kZ+L+pzEjXlRZc9a7vF1bw8l8QokWYAs1v2absHvK5INI5yEleRdZvA7afWmWLHGVtIwUPeFMnr9i/U4rP7aZUhuMNPOEzQ2sxcJ4c64RzwWZqYNuQnzZgD6Xh0Gu466zqWenmFik3iKLZhxLuMdSZI6NeB4+IuD0pJ95G310p1ph4SwNZHzO2/JT/OA5hyIx3kt4EvXWD/Zk3/o9v/kt0Tf8bfGGXyvikxvBnd/jSzaKJ+5r6d5se6aKu1FHS1OdkUUEKEiECQPVgikktPG2RLvdItqoDPO8RJbwTHjqDmBwJue1GkTsGc9R/ZCnWP+wIecvTTVxUS971CionT5LvxskKc9vf8gHhe7RatT9QUAhMK+IrtK9lvveEkX0X1k8rOP4a+jdRviba3OTd/O7mPrgaoipodHlCn10dR2SeG4R4GsXEKgRz+TsHMepdzXo8OEvUcyXhPAO2PvmKPXVT8dRvU1WJ+dUyWvWcuRTjiTaSrJD8AJXCb088IUWyHYOBgLFqA3+XgmunOkJqgq2joAMAXAPWJn+4z4nwcFSi1el0Kujpiwrb88hnl4RMIUmwgLfDdAtdFnxtBpoE9Va+1A7GRjCk6ajEbnPs/yI7JoWsqgjGSArmsJ3pQHZYb2axdMfF1xMzLK+Igy7aQVIhUqoTIysxkiF/e8FOk1B43Pyx+54v8gsvFN6tMeHoOVWInw6WUexq9aY6753laAawrKaYyr0zqIHWztHxfzTn7r72/GYFKvMIDNVgQX8t4go+tMLV3Cv8Iv5wp/T78NTR4g1KUd/yI+n5j0l45IDGrAWsF1zf5u4YmVN1VPM4Dj1ZtMzujaIaupHST/Ieim5i6yZoaz93reV+aaLtgw+1qnplgva2ALcAw2kRM1lt3q/3M5/2P1+ghN13vQL+AuXz/oCXWaFvxXk+aHjV0pkFOCdaX0M1U7G5lGYxqR4p1EuLZMjvszrxZWmpsC80SQIoGdI9crdKPVNv1L+IPw9WvAtWzC8kJzOd5ZM4gDNTi0jwfvbYU1p+jKEvFquRu2YVurudaOo1v+GFvci1HgltAaluOl/3xURZ5yWfuDqX7/AHZfA4o2aBIWL3z8tyEIAWvL3tohsNDY14lTCdxa/+/FRFnnJd3etG5pumcno521Rdf218moYABYYSXO8Jj915ANBALKwX2xd4ULnlIXasdcmV+RASCnWIEWBX0pEBCV1Dw84hZPxVEWGa2j4TPeMrskN4bsvwkzLUziVWn3CdEx6p+qssvSyzz1gUTrrJqDipSd2qwF2SdGXVs7gQYTS+A66AxM1DSUIEfKUAPSNQ1FbQdd4j8yuTQEY9ujeiXPmUEFVSABD3W+u6JGSlWuA3RPR/tbaVWWaGL7Rs2+7PvEwVic7vO37D81ekLrJVZic4l0gbmc63jAqpy+N1R4g90mwOoz3yvvIHK2NzOXwqFjTN4uUbaGpb8qLb4noWUtVOWgA8P7LJB4M67Dza+536f29y+zCgIxUv/8du4YFhbza5xtK3eBUcx03a0BkKXT+C///B5zWbM4j+sSuLhvO5sL4lco9R/drt7cQU+cpbzJlZN+JNA6hW+x4Pp8NlTZg1jZosHM0LYDMKdjC7hBZkJXYfDJ87bsmQh0fZuqDWaK/zDWZpESQ7TmKGDYaZi1352hkFpytzlNAAiIbRg06pLb4RdId/+SjFkQAowdqIplVi5a7pfJ9YPWoGeftl0IMe8A+Sj/B4/xD1DKEuFLjcFQIz6rg+koK7gBEr38NLrVzLNyFhXsu/Ng+gkSVUhXsw9amKf7VM+ENo4nM1jRnDLYbyFBTvR5l3R7ikVEU3pOiVRQ6S/QFSAw12LNmeIZKxuAiPPYGV8vACuHUpeAg8SwllBXjMVe7wyxdWqrPd6QvcCExwVHQxJO+OB2tmsGhTojvoiHuNkiMXVhvGogd3vmyJlLJsW9DobsI68jB+KG25K6PJeUQAEINLz/q6jsscl2zbxFwXkqZyslMCGt7ObvEsbpU/gCuqB6K7KdhdtbUUqa7tsUNRIee06ZtUInKWE2n3Jwl73ha4XzE2lllUL56QlzxHaagKi/zLcFKXN991QGx+7q5O7Qqz7q042VfOO9ae+S36P/VJT7iAtsueoVAiUyaRTr0xaor/UdKpXrLOUN3eRPSOnqBqeTOL2WLWXye6u5MBUM/JyeKr6njxJNRnoflLUf7NBbk2ZTZKYqpd8yjEBFHaY2PO41SCTlxwtdAV7Mr/VzAjnfYWiLEmqh5fKA94hDEwJMPoaUUfUz2c1pLc7YyDnzDarrsqtSxDbXZ/9NZn4qckMdTvx8mTxpmiSyKziCqDQwFYHnKrMhe3J8PKBrRJF+WNKEFGyuKuwbodOKACmV4reBbbBiLApVYegr58hk0piF/iR3TG/Dvamg3EJCTL4O6qqhMXDhnR8FLKYOfTc79nd2/jMcVPRb9EyK9ZxzBEZxYs5UGDG/YMo2NA87sTRKSn3nkqnmaoCCIbgry/OYiFiz5MiR4y2Iq9Vg8XVFwX0RTpA+CwfwnYENtByZhGZBh8hNbj54RL7VYtbmZmhRdFG7kG3f+Bth2pDNZ3kRP3XU0phGCl43zgIs0TwGUnAIZQ540GEI/dTggAD+S9xJ+ILhy/bfwKBG7NgLHV7tRF8R/5Or78omHawioJgyI1R3eYdSYtaA06EYG+PXPxSugV7l6LPtuiqKF+zlBK5Z7hq44Bsr/xmFhtbiqeM5T2ab02MnDNuf+fIG7rYiM50FzkuW8oe+fZAaJJld3z80S99BKWHcOoYLESZnc7thsXwjWk1O0W4hrMsVydUwe59G0KOJu6SlV0OSSQkCd52IK0KUSveMujRQmUgzaoYwJE7li0JnP1glMy6mrJn+KFPUb4frpM+JO66njt7ItubuzLwpe9KHAbCSqsm3BHKvs/0rStV4FxEVM3SM99h6vW9ohgNniyTMT6vcWEIatLaZWmA9E6o/Saftr+hRywEB/YxVJZc+P7pEZ5KiHbjqrWsmbFHCGhOXmzWGUCgdk2EPareazUTPNRcyl+GqhoFCl+uOsZxVw3DCeYz6kQcL5poL49Z6MEfbBig4X0NkbVyuCA4T0KN74C9Ne+1sBZ4vBaoREdKLO4Gg3HeIS2QPLaA/szg9+5yCWSX/ZMpD8i/Clzr12KRhzMcTQhZJnQ/zB51uiJ6kT5FJ3+ufLAwM6XcnBGACKHzKW24KJZDLxgNqPL333q6ASDj+3cEChXq3pCDwcPjsjUuDkosSFxV8cwmsEP36JQhFXTx8kjyGFy35ipGXSjXl7/J9sUcSxfQKt9mzif95CZIZZQr9dfOc5nahxzA8vgH15ZESyj7EyqyIyiDqoJdmtXVJSQmVtRCSRUCMeqde9w6ljtUcam8pOCFaJE2AMsu/eg3lM+qql9/fGUQ4m8zjURfIens2uKHxI4+H6MNS5bhrRK09p0JtB7zEaJjHmHtkpDHh5IPRjEwv6+7f+CCLiWBWJh5Xj7uSi1W5zkQ0D1ia+QAxf+l2ckGysfqTQ5kbJv7J/2fQ8PHAELdrpvhMToKzQ62EpjC5b4yORl9Y1niYDlZ4HF8ILF0kS8VhOmIdAos8adQljqzIIKRXJOuzgVdUQiypLr03R4Nf6/7yDRLg6HFcvyd4sq0K4368huRxIn/5VXqejmE260WyW8oC1yyjhZfj7S8oJL4QidATsc8lkr9kqb+r2DLCq2tF8d8tWg8piObW8cVmBjWv84MzZSJ/cgNVYduAOMKLuxa2A1ppcvR11eYSXDfVEY2BeWO5m6QJtNFHjf8+NOuZXVonSpnqRV/E6wAp9L9TvddKa3Q5wBNJPUSKCbY22p83RTgjsKdjPwr/I0BDDIf5zTD6Y5gThNOXUjwCKwXTuvJuuNd+kdnCV1NtpddkXWhP4nRw4dquvC7UxhFx8G8q7VRgs4dBmLKn+vf7BWnA1/btDyDuNvrKFI658D9ZzlYTTAegTSltveiXr77tADLOmZ0MCMpyYoeNaOmt1AJGvC1a4Joc1g63h2NzAi+XkXKjHTfQjEyJJE3JTW6/Q20qKb3OjxrT0knuAEIihA6bDFg8zDZyyjP+BJPOpr3dZhezW/87b/WnkP4PWbb1dOlSzphpwoWVUk35K/BDx4NDUG0j2dDVdtsMSG20/ilM2JSp8ORGG6tlBDv+wDTCH5vTuhv6TxcC2c9+ah3Q3ypyVfRRbIKFoy2Ko01p/na2Yvj6uG/MbuJB+FXYPhT+lZr1crhYPFDKBJlnqG7sfD4QTOsRhQz3ivifcKu4cOcCecGhiYJmrnfbF63YBVCTgb4c8Fnn8Dp008rNZlfCLz0OB/M3ICkHmGhS7AE9PEfEvjOXPfZoBFRgcEexBmakmhbKrVtMgpCCf2kZBWt6rhuKNlpxtK3Yo+dT9ys/RAwQmruvUyROJQH9T4dCBScnOD7NjkXwe6zUYUi2gY8oad79JrmrPjdsYEE3IFgUI/DbqfdvP2NFCHhUMeWZTq1nDwMMRMwy/gDzXgiBkMRgaoeZcd8EPTl4FqGTxEqb8luQ/9tRXbIvc7fxRArl70JscnnkNdBr/KTxL3TsoiMiBFCihY3SyEGHTAxTTmIUESuUYiI9OA7MPbRDyc5b3r/fwdyGmwVKgbjiTxG4/LakcmZYtvl3d02oUwMg/QyT29I6/Vg4lk6ScAAfyvGzbRhVs/hEbpAwoAgRuDmWyVv4Hg4KBIWXyd4wop/WEv45/mfK69UXrduN5sai8sNxF0pJqg8LoK15INDnVYpfRVfg/zQ+dXnnTlX+G9dc1CDf0unyKudOZjOVAFwQgzjD9FZiU69hwbCZVbCDNzAGD3qiY0pKC2TOlQmk12OukvZ+YggeQRwpyP3jSd+XtObnvBSxJNCEaWCwhNGpA7Gfrc3JE3yT5Pvgi4VdVahFn5XTT2INe+/vMFYAaXWKp2ohOgpFcjrBCNblgBpeL8Outud0Gr2Yd13mZbaAnKqdkGO5mIcnL8xijIpZEFO1IGwqawQ5WrI++WHkgkEhjLUvyzGKMhLDhn1S6g5rlQVxon232BSpIrAnyuH47j/r4ykONWxvFkwmdTmE3VrMkj4cGd4lJr59kWFkgUhpFtviFgiRXHJg7CIBVglIArcOtn1Z52f6us4Rqn9gbUQf8RJv+VeDlBM6MA0GLIFlqLKovopAWB8dANIVDFP/r4obAF8pSFXWxd9YgqSZVSJZA/5uakz1UFK4JoYqndoioWwwmo5puDs3z+DWsBcmm8i2j7sp+p6SAinNiTOyxDAOSV9FHES+bMUurg7VwVEtzqGqgw5Lu3JkOtkLWVUaqy10FJwZWa/ZT78L9jcQxP4E3zrDhaX2DF9UyP/sXTKHEAFyFaQibyrbwAAAA5e+KbSrmbY/gD4wf1YYrbQ99JzfK4PMGBVsY8aNEfafxWXAxnMi2S0wnVtdbNOd0bsPQ0jfajLVnDK2t7GQnY52dsEKHzgxodlWEKu2qymO+imENDh2YG2JlHSGFtdXPC6x/EZFwjQ4dIZL4CcpHuluF1j+2IpzCld+kx56nzt2yUMIy9nUqlUuaPhupeTfHaltxIyK41RjQFe8v7gVgK99qyjNzHEJn5VK38GzKu0PFGGXUc53QuUMj4Y5w9cSp1Kz1fhoQL1+0pbMDlVVV1DiFTUFJLc4WbDRH59RsxD1kismAX/0HMqc5BFyG2zeiS9NtGyiY5Bd12oiR+rORcRb4+eX3/yyhr+Oe9+/5bzEv9xkJezBEwlReK245lcZ4hHXLh6WG3l6D56q/D7/kBF9bcLItjc7I2/zQDPoaTWdKT59hHqG1RbKjkX9chOZfa6p896L/aSDMlEVbMLZ+kgXENxCctbcWYIzE7Jt+UvwFZdlbjhHO5gFWVSodMwUc4aFBaLrKi5PU98/YQSBpdlEB/6TYwGM1WwTJF6J1YQAAAAAAAAAAA" width="742" height="390" class="img_ev3q"></p>
<p>From this behavior we can derive the two defining properties of a Bloom filter:</p>
<p><strong>1. If a Bloom filter says an element exists, the element may exist.</strong></p>
<p><strong>2. If a Bloom filter says an element does not exist, the element definitely does not exist.</strong></p>
<p>These two properties dictate how it should be used: it works well as a front-line gate that blocks requests for keys that definitely don't exist, but its "exists" verdict must never be taken at face value — after a positive answer, you still go to the cache or database for confirmation.</p>
<p>A Bloom filter always carries some false positive rate, because hash collisions can never be avoided completely. This misjudgment rate is called the False Positive Probability, or fpp for short.</p>
<p>The fpp is determined mainly by three factors: the bitmap length m, the number of hash functions k, and the number of elements already inserted, n. The more elements you insert, the more bits in the bitmap are set to 1, and the more likely it becomes that a stranger key's k positions "just happen to all be 1". This is why Bloom filter implementations (such as Guava's BloomFilter) typically ask for an estimated element count and a target fpp at creation time, and derive suitable values of m and k from them.</p>
<p>To reduce the fpp, you can enlarge the bitmap or hash more times to lower the collision probability — but a bigger bitmap needs more memory, and more hashing burns more CPU. Budget the resource costs accordingly and pick what fits. At its core this is a three-way trade-off between space, CPU, and accuracy; there is no free lunch.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-about-deleting-a-key">What About Deleting a Key?<a href="https://tanzhuo.xyz/en/2022/11/13/bu-long-guo-lu-qi#what-about-deleting-a-key" class="hash-link" aria-label="Direct link to What About Deleting a Key?" title="Direct link to What About Deleting a Key?" translate="no">​</a></h2>
<p>As we've seen, membership is determined by checking whether the corresponding index positions are 1. But you can't delete an element by simply flipping those 1s back to 0, because other elements may map to the same positions — <strong>which is why the original Bloom filter cannot support deletion</strong>. So what do we do if deletion is needed? The simplest approach is to add a counter: instead of storing just a 1, each position in the bit array stores the number of elements mapped there (0 if none). This raises an obvious cost — a plain 1 fits in a single bit, but storing an actual count like 2 needs 2 bits, so a Bloom filter with counters takes up more space.</p>
<p>This improved variant is generally called a Counting Bloom Filter: insertion increments the counters at all k positions by 1, deletion decrements them by 1, and only when a counter drops to 0 is that position truly empty. Beyond the space blow-up (each position grows from 1 bit to several bits), there's another hazard — if you mistakenly delete an element that was never inserted, counters get wrongly decremented, and elements that actually exist can be "deleted away", introducing false negatives. Deletion should therefore only ever be performed on elements you've confirmed were inserted.</p>
<p>If your business doesn't really need deletion, there's an even simpler route: don't delete at all. Periodically rebuild a fresh Bloom filter from the latest full dataset and swap it in for the old one. The old filter keeps serving during the rebuild, and the switch is atomic — many cache penetration setups work exactly this way.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/11/13/bu-long-guo-lu-qi#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Estimate capacity generously. Once a Bloom filter is created, its bitmap length is fixed. If insertions exceed the estimated scale, the fpp degrades sharply — and there's no online resize, only a full rebuild.</p>
</li>
<li class="">
<p>"Exists" doesn't mean it actually exists. Keys that slip through as false positives still hit the database. A Bloom filter greatly reduces penetration but never eliminates it entirely, so database-side safety nets (such as caching empty values) are still needed.</p>
</li>
<li class="">
<p>Hash functions must match the writer's. If multiple services share one Bloom filter (say, stored in redis), every client's hash implementation and parameters must be exactly identical, or the results will simply be wrong.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/11/13/bu-long-guo-lu-qi#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>With one bitmap and k hash functions, a Bloom filter buys you a membership check with tiny space usage and O(k) lookups — at the cost of false positives and, in its original form, no deletion. Its two defining answers — "definitely not present" versus "only possibly present" — make it best suited as a front-line gate, with cache penetration prevention as the textbook use case. When deletion is needed, switch to a counting Bloom filter or just take the periodic-rebuild route; as for capacity estimates and the fpp setting, that's a calculation each business has to make for itself, balancing memory, CPU, and accuracy.</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[False Sharing: The Hidden Performance Killer]]></title>
            <link>https://tanzhuo.xyz/en/2022/11/03/wei-gong-xiang</link>
            <guid>https://tanzhuo.xyz/en/2022/11/03/wei-gong-xiang</guid>
            <pubDate>Thu, 03 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Starting from CPU cache hierarchies and cache lines, this post explains how false sharing arises in multi-core concurrent code, and covers common mitigations such as cache line padding.]]></description>
            <content:encoded><![CDATA[<p>As CPU manufacturing has advanced, high-end CPUs now pack dozens of cores and over a hundred threads, backed by L1, L2, and L3 caches. With these caches, CPU cores can process data faster and access main memory less often, pushing compute performance even higher.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cpu-cache-hierarchy-and-access-latency">CPU Cache Hierarchy and Access Latency<a href="https://tanzhuo.xyz/en/2022/11/03/wei-gong-xiang#cpu-cache-hierarchy-and-access-latency" class="hash-link" aria-label="Direct link to CPU Cache Hierarchy and Access Latency" title="Direct link to CPU Cache Hierarchy and Access Latency" translate="no">​</a></h2>
<p>The CPU cache hierarchy, and how it relates to memory and disk, looks like this:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-11-image-7-55a2ece8a91bbe178b8999954644bf4f.webp" width="1156" height="1166" class="img_ev3q"></p>
<p>As is well known, a single memory access is expensive for the CPU, and fetching data from disk means waiting far longer still. Techniques like mmap help mitigate this, but overall the CPU's compute performance is the ceiling of the whole machine — every other component lags behind in data transfer speed. Here are the latencies for the CPU accessing each tier of storage:</p>









































<table><thead><tr><th>Storage</th><th>Medium</th><th>Cost (USD)</th><th>Random access latency</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>The pattern is clear: the larger and cheaper a storage device is, the more data it holds but the slower it is to access; the faster the device, the more it costs. The CPU accesses L1 Cache 100 times faster than main memory — which is exactly why CPUs have L1 through L3 caches: they sit between the CPU and memory as a caching layer, reducing how often memory needs to be touched.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cache-lines">Cache Lines<a href="https://tanzhuo.xyz/en/2022/11/03/wei-gong-xiang#cache-lines" class="hash-link" aria-label="Direct link to Cache Lines" title="Direct link to Cache Lines" translate="no">​</a></h2>
<p>When the CPU reads data from memory into cache, it doesn't fetch it byte by byte — it fetches it in blocks. These blocks are called cache lines. In other words, <strong>the cache line is the unit in which the CPU reads data from memory into cache</strong>.</p>
<p>On Linux, you can check the cache line size with:</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%)"># Check the L1 cache line size of cpu0, in bytes</span><span class="token plain"></span><br></div><div class="token-line" 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>Mainstream CPUs typically use 64-byte cache lines, while Apple's M1 chip already uses 128-byte cache lines.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-false-sharing">What Is False Sharing<a href="https://tanzhuo.xyz/en/2022/11/03/wei-gong-xiang#what-is-false-sharing" class="hash-link" aria-label="Direct link to What Is False Sharing" title="Direct link to What Is False Sharing" translate="no">​</a></h2>
<p>Now that we know data is copied from memory into the CPU cache in cache-line units, let's look at what false sharing is and how to avoid it.</p>
<p>Imagine a dual-core CPU where the two cores run two different threads in parallel, each reading a different piece of data from memory: two <code>long</code> variables, A and B (8 bytes each). CPU1 wants to fetch A and modify it, CPU2 wants to fetch B and modify it — but the two variables sit at <strong>contiguous addresses in physical memory (DRAM)</strong>. If the cache line is 64 bytes and variable A sits at the start of a cache line, then B immediately follows A, and both variables land in <strong>the same cache line</strong>. Since the cache line is the unit of transfer from memory to cache, both variables get pulled into each core's private cache together.</p>
<p>Now consider: what happens when the two threads on different cores each modify their own variable — say CPU1's thread only changes A, and CPU2's thread only changes B?</p>
<p>At that point, the copies of the cache line in the two cores' caches diverge, which could easily corrupt the final result. To keep both copies consistent, the MESI protocol for multi-core cache coherence was born.</p>
<p>To comply with MESI, a core that modifies data must notify the other cores: the cache line you hold is stale, go fetch it from memory again. That notification and reload is where the performance cost comes from.</p>
<p>When multiple CPU cores concurrently modify unrelated variables that happen to live in the same cache line, you get false sharing. Each core is working on different data, yet they drag each other down because they share a cache line: one core updates data in the line, and every other core has to reload the entire line — a process that takes considerable time.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-avoid-false-sharing">How to Avoid False Sharing<a href="https://tanzhuo.xyz/en/2022/11/03/wei-gong-xiang#how-to-avoid-false-sharing" class="hash-link" aria-label="Direct link to How to Avoid False Sharing" title="Direct link to How to Avoid False Sharing" translate="no">​</a></h2>
<p>The most common technique is cache line padding. The idea is to insert unused filler data between variables, isolating hot variables into separate cache lines so that data accessed by different CPU cores no longer lands in the same cache line — eliminating false sharing.</p>
<p>There are also programming-level tricks, such as deliberately laying out frequently modified data across different cache lines, or using Thread Local Storage (TLS) to avoid data contention between threads. At the hardware level, Intel offers Cache Allocation Technology (CAT), which lets programmers explicitly control how cache is allocated; there's also cache partitioning, which divides the cache into regions assigned to different threads — likewise sidestepping cross-thread contention and false sharing.</p>
<p>Beyond false sharing, other cache-related performance issues deserve attention. Take cache misses: when the CPU needs data that isn't in cache, it must fetch it from memory, which takes far longer. Cache misses can be reduced through prefetching and well-chosen cache replacement algorithms.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/11/03/wei-gong-xiang#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Caching is a cornerstone of performance in computer architecture, but the cache line as a unit of transfer brings hidden traps like false sharing along with it. When multiple cores concurrently modify different variables in the same cache line, the coherence overhead of the MESI protocol drags performance down noticeably. The fix is equally direct: use padding or careful data layout to give each hot variable its own cache line. When writing highly concurrent code, paying attention to how your data is laid out in memory often saves you from this kind of invisible performance killer.</p>]]></content:encoded>
            <category>Java</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Kata: A Secure Container Runtime]]></title>
            <link>https://tanzhuo.xyz/en/2022/11/01/an-quan-rong-qi-zhi-k-a-fa</link>
            <guid>https://tanzhuo.xyz/en/2022/11/01/an-quan-rong-qi-zhi-k-a-fa</guid>
            <pubDate>Tue, 01 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Traditional containers share the host kernel, which leaves a security gap in isolation. This post introduces how Kata achieves kernel-level isolation through lightweight virtual machines, along with its strengths and shortcomings.]]></description>
            <content:encoded><![CDATA[<p>Traditional lightweight containers rely on Linux namespaces and cgroups for isolation. Along with being lightweight, simple, and efficient, this also brings security risks.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-risk-of-a-shared-kernel">The risk of a shared kernel<a href="https://tanzhuo.xyz/en/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#the-risk-of-a-shared-kernel" class="hash-link" aria-label="Direct link to The risk of a shared kernel" title="Direct link to The risk of a shared kernel" translate="no">​</a></h2>
<p>A container provides an execution environment whose resources are isolated from other processes on the system, but it shares the kernel with the host. If one container is compromised and the attacker manages to take down the host kernel, every other container inevitably crashes with it — the consequences are hard to overstate.</p>
<p>So is there a container technology that is both secure and reasonably efficient? That's where Kata containers come in.</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="how-kata-approaches-it">How Kata approaches it<a href="https://tanzhuo.xyz/en/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#how-kata-approaches-it" class="hash-link" aria-label="Direct link to How Kata approaches it" title="Direct link to How Kata approaches it" translate="no">​</a></h2>
<p>Kata actually isolates resources between containers by creating lightweight virtual machines, then running a container runtime inside each VM. Containers thus run on a dedicated kernel, gaining isolation of network, I/O, and memory, and can leverage hardware-enforced isolation via virtualization VT extensions. While providing this security, it still delivers high performance. Some performance is sacrificed (the current release has reached 3.0, with substantial performance improvements), but the benefit is obvious: a single container failing or being maliciously attacked can never affect the host, which indirectly protects the other running containers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="source-code">Source code<a href="https://tanzhuo.xyz/en/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#source-code" class="hash-link" aria-label="Direct link to Source code" title="Direct link to Source code" 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/en/assets/images/2022-11-image-80526d4327e0fdb37b1097e86ec4fe97.webp" width="640" height="309" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="strengths">Strengths<a href="https://tanzhuo.xyz/en/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#strengths" class="hash-link" aria-label="Direct link to Strengths" title="Direct link to Strengths" translate="no">​</a></h2>
<p>Kata Containers also has the following characteristics:</p>
<ol>
<li class="">Flexibility: Kata Containers runs across a variety of operating systems and container runtime environments. It supports multiple operating systems such as Linux, Windows, and FreeBSD, and is compatible with multiple container runtimes such as Docker, CRI-O, and containerd.</li>
<li class="">Scalability: Kata Containers scales easily to large deployments. It integrates with container orchestration platforms like Kubernetes, making it possible to manage large numbers of containers.</li>
<li class="">Easy installation: Installing Kata Containers is very straightforward. It can be installed through package managers (such as apt, yum, or zypper), and comes with a convenient command-line tool that helps users quickly configure and launch containers.</li>
<li class="">Community support: Kata Containers has an active open source community maintained and advanced by developers around the world. The community provides documentation, tutorials, and support to help users better understand and use Kata Containers.</li>
<li class="">Security: runs on a dedicated kernel, providing isolation of network, I/O, and memory, and can leverage hardware-enforced isolation with virtualization VT extensions.</li>
<li class="">Compatibility: supports industry standards, including the OCI container format, the Kubernetes CRI interface, and traditional virtualization technologies.</li>
<li class="">Lightweight: eliminates the need to nest containers inside full-blown VMs.</li>
<li class="">High performance: delivers performance close to that of standard Linux containers (some overhead still exists).</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="shortcomings">Shortcomings<a href="https://tanzhuo.xyz/en/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#shortcomings" class="hash-link" aria-label="Direct link to Shortcomings" title="Direct link to Shortcomings" translate="no">​</a></h2>
<p>Some of Kata Containers' downsides:</p>
<ol>
<li class="">Performance loss: although Kata Containers' performance has improved substantially, it still carries some overhead compared to traditional Linux containers. This is because Kata Containers uses lightweight virtual machines to achieve container isolation, which adds extra cost.</li>
<li class="">More complex installation and configuration: compared to traditional Linux containers, installing and configuring Kata Containers can be more involved. Users need to install and configure virtualization software such as QEMU and KVM, along with components like the Kata Containers runtime.</li>
<li class="">Higher hardware requirements: Kata Containers requires hardware with virtualization VT extension support, which may limit its use on some older servers.</li>
<li class="">A less mature ecosystem: while Kata Containers has an active open source community, its ecosystem may not be as mature as some other container technologies. That means users may need to write their own tools or plugins to meet specific needs.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/11/01/an-quan-rong-qi-zhi-k-a-fa#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>Kata's core idea is trading lightweight virtual machines for kernel-level isolation: each container runs on its own dedicated kernel, so compromising one container never spills over to the host or to other containers. The price is some performance overhead, a more complex installation and configuration process, and a requirement for hardware virtualization support. It isn't out to replace traditional containers — it offers a different trade-off. If your workloads involve multi-tenancy or demand strong isolation, enabling Kata for those workloads is worth it; otherwise, standard Linux containers remain the simpler choice.</p>]]></content:encoded>
            <category>Docker</category>
            <category>K8s</category>
        </item>
        <item>
            <title><![CDATA[Traffic Mirroring with Istio]]></title>
            <link>https://tanzhuo.xyz/en/2022/11/01/is-ti-o</link>
            <guid>https://tanzhuo.xyz/en/2022/11/01/is-ti-o</guid>
            <pubDate>Tue, 01 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Using Istio's Envoy proxy in a k8s cluster to mirror traffic: duplicate real requests to a shadow service while responses are discarded, completely transparent to the application. Covers the mechanics, VirtualService configuration, and a few pitfalls from real-world use.]]></description>
            <content:encoded><![CDATA[<p>Once Istio is deployed as a Service Mesh in a k8s cluster, the Envoy proxy that forwards traffic can also mirror it — a few extra lines in a config file are all it takes. Mirrored responses never reach the client, so it's completely transparent to your application code, and used well it can deliver a lot of value in production.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-traffic-mirroring">What Is Traffic Mirroring<a href="https://tanzhuo.xyz/en/2022/11/01/is-ti-o#what-is-traffic-mirroring" class="hash-link" aria-label="Direct link to What Is Traffic Mirroring" title="Direct link to What Is Traffic Mirroring" translate="no">​</a></h2>
<p>Traffic mirroring is also known as shadow traffic: while forwarding a client request to the real service, Envoy sends a copy of it to a designated mirror service. Two things matter here:</p>
<ol>
<li class="">
<p>Mirrored requests are fire-and-forget. Envoy never waits for the mirror service's response, let alone returns it to the client. The mirror service can be slow, throw errors, or even go down without affecting the main path at all.</p>
</li>
<li class="">
<p>Mirroring happens before traffic reaches the application container — it's handled by the sidecar proxy. Your application code needs zero changes; you just deploy a separate instance to receive the mirrored traffic.</p>
</li>
</ol>
<p>Official docs: <a href="https://istio.io/latest/zh/docs/tasks/traffic-management/mirroring/" target="_blank" rel="noopener noreferrer" class="">Mirroring</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="typical-use-cases">Typical Use Cases<a href="https://tanzhuo.xyz/en/2022/11/01/is-ti-o#typical-use-cases" class="hash-link" aria-label="Direct link to Typical Use Cases" title="Direct link to Typical Use Cases" translate="no">​</a></h2>
<ol>
<li class="">
<p>Testing environments: a test build can run against real production traffic without touching the production critical path. A staging environment, for instance, gets much better real-time validation, fewer surprises after release, and a development team with far more confidence going live 😂 — releases become almost boringly predictable, no more late nights pushing hotfixes.</p>
</li>
<li class="">
<p>Data collection: capture request data in parallel and feed it to other containers for risk analysis or logging, building out user profiles along the way.</p>
</li>
<li class="">
<p>Performance and compatibility validation: a new service version, a refactored API, or an implementation on a new framework or dependency can "shadow-run" against mirrored traffic for a while. Compare logs and metrics on both sides, confirm the behavior matches, and only then cut over for real.</p>
</li>
<li class="">
<p>Reproducing bugs: those rare, intermittent bad requests in production are nearly impossible to reconstruct in a test environment. Mirror the traffic to an instance with verbose logging or debug flags enabled and you get a first-hand capture of the scene.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="configuration">Configuration<a href="https://tanzhuo.xyz/en/2022/11/01/is-ti-o#configuration" class="hash-link" aria-label="Direct link to Configuration" title="Direct link to Configuration" translate="no">​</a></h2>
<p>In Istio, traffic mirroring is declared via the <code>mirror</code> field of a VirtualService. Suppose the <code>httpbin</code> service has two versions, v1 and v2, and you want traffic hitting v1 to also be mirrored to 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%)"># Real traffic: all forwarded to v1, response returned to the client</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Mirrored traffic: a copy is sent to v2, response discarded</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># Sampling percentage for mirroring, defaults to 100% if omitted</span><span class="token plain"></span><br></div><div class="token-line" style="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>What the fields mean:</p>
<ol>
<li class="">
<p><code>route.destination</code> is the primary route. It decides where the real request goes, and its response is what the client receives.</p>
</li>
<li class="">
<p><code>mirror</code> specifies the mirror target, also as a host + subset pair. The subset must be defined in a DestinationRule beforehand.</p>
</li>
<li class="">
<p><code>mirrorPercentage</code> controls the sampling rate. When the mirror target has less capacity than production, ramping up gradually — 1%, then 10% — is far safer than going straight to 100%.</p>
</li>
</ol>
<p>Apply the config with <code>kubectl apply -f</code> and it takes effect immediately, no application Pod restarts required — one of the perks of the sidecar model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/11/01/is-ti-o#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Mirrored traffic gets a <code>-shadow</code> suffix appended to its Host header — <code>httpbin:8000</code> becomes <code>httpbin-shadow:8000</code>. If the mirror service validates or routes on the Host header, handle this difference up front. On the flip side, the suffix is a handy way to tell real traffic from shadow traffic in your logs.</p>
</li>
<li class="">
<p>What gets mirrored is the request, not idempotency. A mirrored write endpoint will execute again in the shadow environment, and if the mirror service talks to the same database or the same downstream, you can end up with duplicate writes or duplicate third-party calls. Either point the mirror service at an isolated data source, or intercept write operations on the mirror side.</p>
</li>
<li class="">
<p>Mirroring amplifies egress bandwidth and Envoy overhead — especially noticeable for services with large request bodies or high QPS. Validate with a small <code>mirrorPercentage</code> first, then scale up.</p>
</li>
<li class="">
<p>Errors in the mirror service never reach the client. That's a strength, but also a blind spot — when the shadow environment goes down, no user notices. Give it its own monitoring and alerting, or the "shadow run" may have silently stopped long ago without anyone finding out.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/11/01/is-ti-o#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Traffic mirroring turns "validate with production traffic" — normally a high-risk endeavor — into a low-cost operation: one VirtualService config, zero application changes, zero impact on the main path. Staging validation, risk analysis, and bug reproduction all benefit. The main things to watch are side effects of write operations, the <code>-shadow</code> Host header suffix, and monitoring on the mirror side itself. Handle those three well, and it's a tool worth keeping within arm's reach.</p>]]></content:encoded>
            <category>K8s</category>
        </item>
        <item>
            <title><![CDATA[Distributed Transactions with Seata]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/31/seata</link>
            <guid>https://tanzhuo.xyz/en/2022/10/31/seata</guid>
            <pubDate>Mon, 31 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[In a microservice architecture, every business module gets its own DB storage node, and cross-database calls bring the distributed transaction problem. This post walks through Seata's roles and the principles and trade-offs of its four modes: AT, TCC, Saga, and XA.]]></description>
            <content:encoded><![CDATA[<p>In enterprise application development, as distributed frameworks have matured, production environments have come to hold many database instances. In the microservices world in particular, we usually give each business Service module its own DB storage node for its own data, and then deploy that node for high availability.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-transaction-problem">The transaction problem<a href="https://tanzhuo.xyz/en/2022/10/31/seata#the-transaction-problem" class="hash-link" aria-label="Direct link to The transaction problem" title="Direct link to The transaction problem" translate="no">​</a></h2>
<p>When writing Service business logic, a single business operation often makes remote calls to other business modules, and the resulting data lands in different storage nodes. Ensuring the ACID properties of a transaction across multiple database instances is where we run into the distributed transaction problem.</p>
<p>What a distributed transaction has to accomplish boils down to two things:</p>
<ol>
<li class="">
<p>If every step in the business call chain succeeds, all the databases involved in the chain commit their transactions.</p>
</li>
<li class="">
<p>If an exception is thrown anywhere in the call chain, all the databases involved in the chain roll back.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-roles-in-seata">The roles in Seata<a href="https://tanzhuo.xyz/en/2022/10/31/seata#the-roles-in-seata" class="hash-link" aria-label="Direct link to The roles in Seata" title="Direct link to The roles in Seata" translate="no">​</a></h2>
<p>The Seata distributed transaction solution generally involves the following roles:</p>
<ul>
<li class="">TC (Transaction Coordinator): coordinates the transaction.</li>
<li class="">TM (Transaction Manager): the transaction manager, integrated into the AP.</li>
<li class="">RM (Resource Manager): manages the resources handled by branch transactions; talks to the TC to register branch transactions and report their status, and drives branch transactions to commit or roll back.</li>
<li class="">AP (Application Program): the application that accesses the RM.</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-11-145942191-7a2d469f-94c8-4cd2-8c7e-46ad75683636-604f44d73d72d865af48e8fc188c9a9e.webp" width="1534" height="908" class="img_ev3q"></p>
<p><strong>The TC coordinator component in the diagram must be deployed separately and joined to the registry of the whole microservice cluster. All the common registries are supported — Nacos, Zookeeper, Etcd, Eureka, Consul — and the AP needs the corresponding connection configuration. The TC module must be highly available: if it goes down, distributed transactions are off the table entirely.</strong></p>
<p><a href="http://seata.io/zh-cn/docs/user/txgroup/transaction-group-and-ha.html" target="_blank" rel="noopener noreferrer" class="">Transaction Groups and High Availability</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="rigid-vs-flexible-transactions">Rigid vs. flexible transactions<a href="https://tanzhuo.xyz/en/2022/10/31/seata#rigid-vs-flexible-transactions" class="hash-link" aria-label="Direct link to Rigid vs. flexible transactions" title="Direct link to Rigid vs. flexible transactions" translate="no">​</a></h2>
<p>Rigid transactions: usually require no business-code changes, offer strong consistency with native rollback and isolation support, handle low concurrency, and suit short transactions. Corresponding solutions: the XA protocol (2PC, JTA, JTS) and 3PC.</p>
<p>Flexible transactions: require business-code changes, offer eventual consistency, need you to implement compensation and resource-locking interfaces, handle high concurrency, and suit long transactions. Corresponding solutions: TCC/FMT, Saga (state machine mode, AOP mode), local transactional messages, and message transactions (half messages).</p>
<p>So what counts as a long transaction versus a short one?</p>
<p>Long transaction: holds some resource (exclusive locks, gap locks, table locks, page locks) for an extended time, modifies a lot of data or runs slowly, and spans a long business flow. Other transactions accessing the resource are blocked waiting, which easily leads to deadlocks.</p>
<p>Short transaction: holds resources only briefly, modifies little data or completes quickly, spans a short business flow, and finishes fast, so the chance of deadlock is small.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-four-transaction-modes">The four transaction modes<a href="https://tanzhuo.xyz/en/2022/10/31/seata#the-four-transaction-modes" class="hash-link" aria-label="Direct link to The four transaction modes" title="Direct link to The four transaction modes" translate="no">​</a></h2>
<p>Seata currently supports four modes for managing distributed transactions.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="at-mode">AT mode<a href="https://tanzhuo.xyz/en/2022/10/31/seata#at-mode" class="hash-link" aria-label="Direct link to AT mode" title="Direct link to AT mode" translate="no">​</a></h3>
<p>AT mode is arguably Seata's signature mode. In essence it is an optimized 2PC — a form of two-phase commit. Using it requires creating an undolog table in the local database node. A DataSourceProxy intercepts the DML SQL statements the application executes, parses their semantics, converts them into query SQL, and stores an image of the data as it was before execution in the undolog table for use in phase-two rollback. All of these steps are handled by the proxy, so it achieves zero intrusion into business code.</p>
<p>But the approach is far from perfect, and the performance cost is considerable: each DML SQL statement incurs an extra round of semantic conversion and query execution, plus writing a data image into the undolog table; if a phase-two rollback happens, a local lock also has to be acquired before restoring the original data image. There is another fairly serious issue as well: if, at rollback time, another local transaction bypasses Seata's global transaction management (and its global lock) and directly modifies data covered by an image in the undolog, Seata will find during rollback that the image no longer matches the current local data, resulting in data loss — somewhat like the ABA problem in CAS.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tcc-mode">TCC mode<a href="https://tanzhuo.xyz/en/2022/10/31/seata#tcc-mode" class="hash-link" aria-label="Direct link to TCC mode" title="Direct link to TCC mode" translate="no">​</a></h3>
<p>TCC mode is comparatively simple — it too is 2PC two-phase commit. The difference from AT mode is that you implement the phase-one prepare, phase-two commit, and rollback logic yourself, and Seata's transaction manager invokes these implementations in turn.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="saga-mode">Saga mode<a href="https://tanzhuo.xyz/en/2022/10/31/seata#saga-mode" class="hash-link" aria-label="Direct link to Saga mode" title="Direct link to Saga mode" translate="no">​</a></h3>
<p>A Saga consists of a series of local transactions. After each local transaction updates its database, it publishes a message or event that triggers the next local transaction in the Saga. If a local transaction fails because a business rule cannot be satisfied, the Saga runs compensation operations for all the transactions that had already committed successfully before the failure.</p>
<p>That makes Saga the most laborious mode to implement, but a Saga-based state machine gives you very flexible control over every step of the distributed transaction. For long-transaction scenarios you especially want that fine-grained control over every situation: you don't track the state of the whole distributed transaction — you just handle each event properly and keep your own methods idempotent. However, there is no notion of staged commit in its transactions; everything commits locally and directly. If other transactions read and modify that data mid-rollback, you get dirty reads and dirty writes. For that case, I think you'd still need to build your own txid scheme to guard against it.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="xa-mode">XA mode<a href="https://tanzhuo.xyz/en/2022/10/31/seata#xa-mode" class="hash-link" aria-label="Direct link to XA mode" title="Direct link to XA mode" translate="no">​</a></h3>
<p>XA mode first requires the database to support the XA protocol. Its implementations include 2PC and 3PC, provided entirely by the database itself — the application doesn't need to do anything. It is fundamentally quite similar to AT mode; the difference is that during an XA transaction the database, for the sake of strong consistency, keeps holding data resources (exclusive locks, gap locks, table locks, page locks, depending on the data range). With long transactions under high concurrency, deadlocks arise very easily and database performance degrades quite badly. If everything is short transactions, this mode is usable.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/31/seata#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>The essence of a distributed transaction is making a cross-database call chain commit together or roll back together. Seata productizes this through the division of labor among TC, TM, and RM, and offers four modes: AT, TCC, Saga, and XA. AT achieves zero intrusion via data images and fits most everyday scenarios; TCC and Saga hand the compensation logic to the business in exchange for higher concurrency and flexibility; XA relies on native database support and only suits short transactions. When choosing, first work out whether your business runs long or short transactions and whether it needs strong or eventual consistency — then match the mode accordingly.</p>]]></content:encoded>
            <category>Database</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Helm: The Package Manager for Kubernetes]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/27/ku-be</link>
            <guid>https://tanzhuo.xyz/en/2022/10/27/ku-be</guid>
            <pubDate>Thu, 27 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[When the yaml files in your cluster become too many to maintain, Helm packages configuration into reusable, versioned Charts. This post covers the problem it solves, its core concepts and everyday commands, plus a UI management tool worth trying.]]></description>
            <content:encoded><![CDATA[<p>As we all know, k8s manages container resources across multiple nodes, but every container needs a corresponding pod yaml file to manage it.</p>
<!-- -->
<p>What this post is really about is yaml sprawl. Writing one Deployment and one Service for a single application is no big deal, but once the cluster fills up with applications, the number of config files balloons at a visible rate: changing one image tag means digging through several files, and spinning up a new environment means copying the whole batch of configs and tweaking parameters one by one. At that point, maintaining yaml by hand is no longer realistic.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="starting-from-a-pile-of-yaml">Starting from a pile of yaml<a href="https://tanzhuo.xyz/en/2022/10/27/ku-be#starting-from-a-pile-of-yaml" class="hash-link" aria-label="Direct link to Starting from a pile of yaml" title="Direct link to Starting from a pile of yaml" translate="no">​</a></h2>
<p>Consider the problem first: suppose a namespace has dozens of pods that need to be deployed together — a mysql cluster, a kafka cluster (which itself requires a zookeeper cluster first), a Redis cluster for the application, some MQ queue clusters, plus the application deployments themselves. With microservices, you might be looking at 50–100 applications or even more.</p>
<p>If someone asked you to hand-write yaml config files for a scenario like that, I suspect you'd refuse. Even if you managed to get through one deployment, what happens when the cluster in a remote data center needs to be deployed all over again? And how do you guarantee that the configuration across clusters in different data centers stays perfectly consistent?</p>
<p>These yaml files also carry implicit deployment ordering and plenty of duplicated settings — image registry addresses, resource quotas, storage class names — copied into nearly every file. Once a shared parameter changes, missing a single spot becomes a liability.</p>
<p>So the problem is clear: we need a tool that can centrally manage all the yaml config files and make them reusable.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="helm-the-package-manager-for-k8s">Helm: the package manager for k8s<a href="https://tanzhuo.xyz/en/2022/10/27/ku-be#helm-the-package-manager-for-k8s" class="hash-link" aria-label="Direct link to Helm: the package manager for k8s" title="Direct link to Helm: the package manager for k8s" translate="no">​</a></h2>
<p>That's where Helm comes in. Its role is similar to the Yum package manager on CentOS, or apt on Debian — except this time the packages are for the k8s environment.</p>
<p>Each package in Helm is called a Chart, and a Chart is a directory. Its core contents boil down to three parts: <code>Chart.yaml</code> describes the package's metadata such as name and version; <code>values.yaml</code> holds the default configuration values, which can be overridden at install time; and the <code>templates/</code> directory contains k8s resource files written with template syntax. At install time, Helm renders the values into the templates, generates the actual yaml, and submits it to the cluster. The result of one installation is called a Release.</p>
<p>The key to configuration reuse lies in this templating mechanism: different data centers and environments share the same Chart, each maintaining only a small values file of its own, so the problem of inconsistent configuration between data centers simply dissolves. Charts can also declare dependencies — relationships like "zookeeper must exist before kafka is deployed" can be handed off to sub-charts instead of relying on humans to remember the order.</p>
<p>Application publishers can use Helm to package applications, manage dependencies, manage versions, and publish applications to a software repository for unified management. When it's time to deploy on k8s, a single command brings up the entire set of pod applications.</p>
<p>The commands you need day to day are few:</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%)"># Add a Chart repository (using the popular bitnami repo as an example)</span><span class="token plain"></span><br></div><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%)"># Refresh the locally cached repository 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">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%)"># Deploy a full application with one command; the Chart creates all required resources automatically</span><span class="token plain"></span><br></div><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%)"># List the Releases deployed in the current 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">helm list</span><br></div></code></pre></div></div>
<p>Upgrades and rollbacks after deployment are also handled by Helm, with every change recorded as a new revision:</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%)"># Override the defaults with a custom values file and upgrade</span><span class="token plain"></span><br></div><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%)"># Roll back to the previous revision if the upgrade goes wrong</span><span class="token plain"></span><br></div><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's official site:</p>
<p><a href="https://helm.sh/" target="_blank" rel="noopener noreferrer" class="">Helm</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-graphical-option-kubeapps">A graphical option: Kubeapps<a href="https://tanzhuo.xyz/en/2022/10/27/ku-be#a-graphical-option-kubeapps" class="hash-link" aria-label="Direct link to A graphical option: Kubeapps" title="Direct link to A graphical option: Kubeapps" translate="no">​</a></h2>
<p>The official docs are quite thorough and worth studying. On top of that, let me recommend a UI management tool for Helm that makes managing enterprise Helm packages more convenient.</p>
<p>kubeapps</p>
<p><a href="https://kubeapps.dev/" target="_blank" rel="noopener noreferrer" class="">Kubeapps - Kubeapps</a></p>
<p>Kubeapps is itself a web application deployed inside the cluster. From its pages you can browse Charts in your repositories, install them by filling in a form, and view or upgrade existing Releases. For teams where giving everyone command-line access to the cluster isn't practical, a tool like this saves a lot of hassle.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and caveats<a href="https://tanzhuo.xyz/en/2022/10/27/ku-be#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and caveats" title="Direct link to Pitfalls and caveats" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-keep-your-custom-configuration-in-your-own-values-file">1) Keep your custom configuration in your own values file<a href="https://tanzhuo.xyz/en/2022/10/27/ku-be#1-keep-your-custom-configuration-in-your-own-values-file" class="hash-link" aria-label="Direct link to 1) Keep your custom configuration in your own values file" title="Direct link to 1) Keep your custom configuration in your own values file" translate="no">​</a></h3>
<p>Prefer passing a standalone values file with <code>-f</code> to override defaults, rather than editing the Chart's own <code>values.yaml</code>. The former carries over cleanly when the Chart is upgraded; changes made the latter way are easily overwritten and lost when the Chart is updated.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-think-before-you-uninstall">2) Think before you uninstall<a href="https://tanzhuo.xyz/en/2022/10/27/ku-be#2-think-before-you-uninstall" class="hash-link" aria-label="Direct link to 2) Think before you uninstall" title="Direct link to 2) Think before you uninstall" 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>warning</div><div class="admonitionContent_BuS1"><p><code>helm uninstall</code> deletes all the resources associated with a Release. For stateful applications like mysql, check the storage volume retention policy before pulling the trigger, or you may wipe out the data along with everything else.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-chart-version-and-app-version-are-two-different-things">3) Chart version and app version are two different things<a href="https://tanzhuo.xyz/en/2022/10/27/ku-be#3-chart-version-and-app-version-are-two-different-things" class="hash-link" aria-label="Direct link to 3) Chart version and app version are two different things" title="Direct link to 3) Chart version and app version are two different things" translate="no">​</a></h3>
<p>In <code>Chart.yaml</code>, <code>version</code> refers to the Chart's own packaging version, while <code>appVersion</code> corresponds to the version of the software inside. Before upgrading, be clear about which one is changing — otherwise you might think you're only tweaking templates when you're actually upgrading the application too.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/27/ku-be#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>Helm gathers the yaml scattered everywhere into Charts, solves configuration reuse and multi-environment consistency with templates plus values, and collapses deployment, upgrade, and rollback into single commands. Hand-written yaml can hold up while things are small, but once applications multiply, adopting Helm's package management layer early saves an enormous amount of repetitive work down the road. Pair it with a UI tool like Kubeapps and the barrier for team collaboration drops considerably as well.</p>]]></content:encoded>
            <category>K8s</category>
        </item>
        <item>
            <title><![CDATA[Kubernetes Pod YAML Explained Field by Field]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/27/kubernetes-2</link>
            <guid>https://tanzhuo.xyz/en/2022/10/27/kubernetes-2</guid>
            <pubDate>Thu, 27 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A field-by-field annotated Pod YAML reference: metadata, containers, probes, resource limits, volumes and other common settings, plus spelling traps and tips for querying with kubectl explain.]]></description>
            <content:encoded><![CDATA[<p>A Pod is the smallest schedulable unit in Kubernetes, and almost every workload ultimately comes down to a Pod's YAML definition. These notes collect a fully annotated Pod YAML reference, with <code>kubectl explain</code> as the authoritative source — for example: <code>kubectl explain pod.spec.volumes</code>.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-bother-reading-pod-yaml">Why bother reading Pod YAML<a href="https://tanzhuo.xyz/en/2022/10/27/kubernetes-2#why-bother-reading-pod-yaml" class="hash-link" aria-label="Direct link to Why bother reading Pod YAML" title="Direct link to Why bother reading Pod YAML" translate="no">​</a></h2>
<p>Day to day we deploy applications with Deployments and StatefulSets, but their <code>spec.template</code> section is essentially a Pod template. When you're debugging containers that won't start, failing probes, or wrong mount paths, you always end up going through the Pod definition field by field. Rather than flipping through the docs every time, it pays to go over the common fields once and for all.</p>
<p>When you're unsure about a field, the most reliable approach is to ask the API itself:</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%)"># Show the documentation for a specific field</span><span class="token plain"></span><br></div><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%)"># Recursively list all sub-fields under a field</span><span class="token plain"></span><br></div><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> reads the cluster's OpenAPI schema directly, so it matches your exact cluster version — more accurate than any annotation table copied from somewhere.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fully-annotated-reference">Fully annotated reference<a href="https://tanzhuo.xyz/en/2022/10/27/kubernetes-2#fully-annotated-reference" class="hash-link" aria-label="Direct link to Fully annotated reference" title="Direct link to Fully annotated reference" translate="no">​</a></h2>
<p>Below is the complete annotated reference (the comments are for quick lookup only; <code>kubectl explain</code> is authoritative):</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            //API 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"></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                 //resource kind</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%)">metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">                 //metadata</span><br></div><div class="token-line" style="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"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">String            //metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> the pod's name</span><br></div><div class="token-line" style="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"> </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">String       //metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> the pod's 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">  </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 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"> list of labels</span><br></div><div class="token-line" 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 key atrule" style="color:hsl(29, 54%, 61%)">String        //metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> label name</span><br></div><div class="token-line" style="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 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"> list of custom annotations</span><br></div><div class="token-line" 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 key atrule" style="color:hsl(29, 54%, 61%)">String        //metadata</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> custom annotation name</span><br></div><div class="token-line" style="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">                     //detailed definition of the containers in the 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">             //list of containers in the pod; there can be several</span><br></div><div class="token-line" 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          //container name</span><br></div><div class="token-line" style="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         //image name for the container</span><br></div><div class="token-line" style="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">//image pull policy</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> always pull</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> never pull</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> or pull only when not present locally</span><br></div><div class="token-line" style="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">     //container startup command list (if unset</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> the startup command baked into the image is used)</span><br></div><div class="token-line" style="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">        //container startup argument list</span><br></div><div class="token-line" style="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    //container working directory</span><br></div><div class="token-line" style="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">         //storage volumes mounted inside the container</span><br></div><div class="token-line" 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        //name of a shared storage volume defined in the Pod; must match a name defined in the 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"> section</span><br></div><div class="token-line" style="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   //absolute mount path of the volume inside the container; should be under 512 characters</span><br></div><div class="token-line" style="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   //whether the mount is read</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">only; defaults to read</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">write</span><br></div><div class="token-line" style="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">                //list of ports the container needs to expose</span><br></div><div class="token-line" 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        //port name</span><br></div><div class="token-line" style="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  //port the container exposes</span><br></div><div class="token-line" style="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       //port the host listens on (the container port mapped onto the host); defaults to the same as containerPort. When hostPort is set</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> a second replica of this container cannot start on the same host</span><br></div><div class="token-line" style="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    //port protocol</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> TCP or UDP; defaults to 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">                  //environment variables to set before the container runs</span><br></div><div class="token-line" 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        //environment variable name</span><br></div><div class="token-line" style="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       //environment variable 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">    </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">            //resource limits and resource requests</span><br></div><div class="token-line" style="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">             //resource limit settings</span><br></div><div class="token-line" style="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 limit in cores; used for the 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 parameter</span><br></div><div class="token-line" style="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    //memory limit</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> in units like MiB or GiB; used for the 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 parameter</span><br></div><div class="token-line" style="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">           //resource request settings</span><br></div><div class="token-line" style="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 request in cores; the initial amount available at container startup</span><br></div><div class="token-line" style="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    //memory request in MiB or GiB; the initial amount available at container startup</span><br></div><div class="token-line" style="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><span class="token key atrule" style="color:hsl(29, 54%, 61%)">//health check for containers in the pod; after a number of failed probes the container is restarted automatically. Probe methods</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 probe method</span><br></div><div class="token-line" style="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"> //command or script for the exec method</span><br></div><div class="token-line" style="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">            //health check via httpGet; requires path and 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 path (the part after the domain or IP address)</span><br></div><div class="token-line" style="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      //port to probe</span><br></div><div class="token-line" style="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      //domain name or IP address</span><br></div><div class="token-line" style="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   //protocol for the check</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> e.g. 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">      //custom request headers</span><br></div><div class="token-line" 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    //header name</span><br></div><div class="token-line" style="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   //header 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">      </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">          //health check via 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      //port number to probe</span><br></div><div class="token-line" style="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//delay before the first probe after the container starts</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> in seconds</span><br></div><div class="token-line" style="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     //probe response timeout in seconds</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> default 1; on timeout the container is considered unhealthy and will be restarted</span><br></div><div class="token-line" style="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      //probe interval in seconds</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> default 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   //number of consecutive successes before the probe is considered successful</span><br></div><div class="token-line" style="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   //number of consecutive failures before the probe is considered failed</span><br></div><div class="token-line" style="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">        //security settings</span><br></div><div class="token-line" style="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">//restart policy</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> always restart on termination; never restart; or restart only on abnormal exit (non</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">-</span><span class="token plain">zero exit code) — on Pod termination the exit code is reported to the master and the Pod is not restarted</span><br></div><div class="token-line" style="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 labels specified as key</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">value pairs; the Pod will be scheduled onto Nodes carrying these labels</span><br></div><div class="token-line" style="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">         //Secrets to use when pulling images</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> specified as name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain">secretkey</span><br></div><div class="token-line" 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            //referenced Secret name</span><br></div><div class="token-line" style="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        //whether to use host networking; defaults to false. When true</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> the Pod uses the host's network instead of the docker bridge</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> and a second replica cannot start on the same machine</span><br></div><div class="token-line" style="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">                  //list of shared storage volumes defined on this 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            //shared volume name; within a Pod each volume gets one name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> referenced by 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. Many types exist</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> e.g. 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 volume</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> a temporary directory sharing the Pod's lifecycle; empty object</span><br></div><div class="token-line" style="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">               </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">//hostPath volume</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> mounts a directory from the Pod's host</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> specified via 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          //directory on the Pod's host</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">,</span><span class="token plain"> used as the mount directory inside the container</span><br></div><div class="token-line" style="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">                 </span><span class="token key atrule" style="color:hsl(29, 54%, 61%)">//secret volume</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> mounts a predefined cluster secret object into the container</span><br></div><div class="token-line" style="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    //volume name</span><br></div><div class="token-line" style="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">                 //used when mounting only specific keys from a Secret object</span><br></div><div class="token-line" 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         //the 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        //relative path of the mapped file</span><br></div><div class="token-line" style="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><span class="token key atrule" style="color:hsl(29, 54%, 61%)">//configMap volume</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">:</span><span class="token plain"> mounts a predefined cluster configMap object into the container</span><br></div><div class="token-line" style="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          //name of the configMap to 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 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">                //used when mounting only specific keys from a ConfigMap object</span><br></div><div class="token-line" 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         //the 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        //relative path of the mapped file</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-closer-look-at-a-few-key-fields">A closer look at a few key fields<a href="https://tanzhuo.xyz/en/2022/10/27/kubernetes-2#a-closer-look-at-a-few-key-fields" class="hash-link" aria-label="Direct link to A closer look at a few key fields" title="Direct link to A closer look at a few key fields" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-command-and-args">1) command and args<a href="https://tanzhuo.xyz/en/2022/10/27/kubernetes-2#1-command-and-args" class="hash-link" aria-label="Direct link to 1) command and args" title="Direct link to 1) command and args" translate="no">​</a></h3>
<p><code>command</code> corresponds to the container runtime's entrypoint, and <code>args</code> are the arguments passed to it. When neither is set, the image's own ENTRYPOINT and CMD are used; when only <code>args</code> is set, the image's ENTRYPOINT is kept and CMD is overridden; when <code>command</code> is set, both the image's ENTRYPOINT and CMD are ignored. When debugging a container that exits immediately on startup, first check whether these two fields have accidentally overridden the image's default startup command.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-resources-requests-vs-limits">2) resources: requests vs. limits<a href="https://tanzhuo.xyz/en/2022/10/27/kubernetes-2#2-resources-requests-vs-limits" class="hash-link" aria-label="Direct link to 2) resources: requests vs. limits" title="Direct link to 2) resources: requests vs. limits" translate="no">​</a></h3>
<p><code>requests</code> drives scheduling — the scheduler uses it to decide whether a node has enough capacity left. <code>limits</code> is the runtime ceiling — exceeding the memory limit gets the container OOMKilled, while exceeding the CPU limit only throttles it (the container is not killed). With neither set, the Pod runs at the BestEffort QoS class and is the first to be evicted when the node runs short on resources. In production, at least fill in requests.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-health-probes">3) Health probes<a href="https://tanzhuo.xyz/en/2022/10/27/kubernetes-2#3-health-probes" class="hash-link" aria-label="Direct link to 3) Health probes" title="Direct link to 3) Health probes" translate="no">​</a></h3>
<p><code>livenessProbe</code> determines whether a container is still alive; once failures reach <code>failureThreshold</code>, the kubelet restarts the container. Alongside it there is <code>readinessProbe</code> (determines readiness; on failure the Pod is only removed from the Service endpoints, the container is not restarted), with an identical field structure. Give <code>initialDelaySeconds</code> enough headroom for the application to start — otherwise the probe declares the app dead before it's even up, and it gets stuck in a restart loop.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-volumes-and-volumemounts">4) volumes and volumeMounts<a href="https://tanzhuo.xyz/en/2022/10/27/kubernetes-2#4-volumes-and-volumemounts" class="hash-link" aria-label="Direct link to 4) volumes and volumeMounts" title="Direct link to 4) volumes and volumeMounts" translate="no">​</a></h3>
<p>Storage is declared in two stages: first define the volume in <code>spec.volumes</code> (types like emptyDir, hostPath, secret, configMap), then reference it by <code>name</code> in the container's <code>volumeMounts</code> and specify the mount path. The <code>name</code> on both sides must match exactly — one of the most common sources of errors for newcomers. An emptyDir is destroyed when the Pod is deleted, so it only suits temporary data; hostPath mounts a host directory directly, so the data is "lost" once the Pod moves to another node — it's generally reserved for node-level scenarios like log collection.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and caveats<a href="https://tanzhuo.xyz/en/2022/10/27/kubernetes-2#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and caveats" title="Direct link to Pitfalls and caveats" translate="no">​</a></h2>
<p>The annotation table above circulates widely in the community, but several spelling mistakes have crept into copied versions. Copying it verbatim will cause apply errors or silently ignored fields:</p>
<ul>
<li class=""><code>kind: pod</code> should be <code>kind: Pod</code>; resource kinds are capitalized;</li>
<li class=""><code>imagesPullPolicy</code> — the correct field name is <code>imagePullPolicy</code>, no s;</li>
<li class=""><code>requeste</code> under <code>resources</code> should be spelled <code>requests</code>;</li>
<li class="">hostPath's path field is <code>volumes[].hostPath.path</code>; the <code>hostNetwork.path</code> in the comments is a typo.</li>
</ul>
<p>When a field name is misspelled, depending on the cluster's validation policy it may be rejected outright, or it may be ignored as an unknown field — the latter is sneakier: the configuration looks applied but has no effect. So once more: verify field names with <code>kubectl explain</code> before you start, or run <code>kubectl apply --dry-run=server -f pod.yaml</code> to let the API Server validate first.</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>tip</div><div class="admonitionContent_BuS1"><p>Comments in YAML should use <code>#</code>, not <code>//</code>. The <code>//</code> comments in the table above are quick-reference annotations only — delete them or convert them to <code>#</code> before copying anything into a real YAML file.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/27/kubernetes-2#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>Pod YAML has many fields, but the ones you actually use cluster in a few areas: labels under metadata, the containers' image and startup command, resources, the three kinds of probes, and volume mounts. The annotation table is handy for quickly locating a field, but for field names and default values always defer to the output of <code>kubectl explain</code> — it reads the current cluster's schema, so it never goes stale and never carries transcription errors.</p>]]></content:encoded>
            <category>K8s</category>
        </item>
        <item>
            <title><![CDATA[Installing Fiddler and Capturing HTTPS Traffic]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu</link>
            <guid>https://tanzhuo.xyz/en/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu</guid>
            <pubDate>Thu, 20 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[How to install Fiddler, configure the system proxy, and capture encrypted HTTPS requests with a forged certificate — covering both how it works and the step-by-step setup.]]></description>
            <content:encoded><![CDATA[<p>Fiddler is a well-known and extremely practical packet-capture tool (mainly for HTTP requests), with plenty of extensions available on its official site. During development we inevitably need a capture tool to inspect API data or test API security — for instance, tampering with values on unsigned requests. This post covers installing Fiddler and configuring its certificate to capture encrypted HTTPS traffic.</p>
<!-- -->
<p>When building APIs, the most common questions are: did the request actually go out, are the parameters right, and what did the server return? The browser's F12 devtools only show requests the browser itself makes — calls from mobile apps, desktop programs, and backend services are invisible to it. Logs, meanwhile, usually record only business fields and miss the raw payload you actually want to verify. A capture tool fills exactly this gap: it sits between the client and the server as a proxy and lays every request and response out in front of you, in full. Among these tools, Fiddler has the lowest setup cost and the smoothest HTTP support.</p>
<p>The tricky part is HTTPS. Nearly all APIs run over HTTPS now, with payloads encrypted end to end — even if the proxy intercepts the traffic, all it sees is ciphertext. To see plaintext, Fiddler has to insert itself into the TLS handshake as a "man in the middle" — and that's exactly what this post explains in detail.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="download-and-install">Download and Install<a href="https://tanzhuo.xyz/en/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu#download-and-install" class="hash-link" aria-label="Direct link to Download and Install" title="Direct link to Download and Install" translate="no">​</a></h2>
<p>Fiddler's official site:</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>Pick the appropriate version to download. For personal debugging, the free Fiddler Classic is plenty — no need to sign up for the enterprise edition.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-18-a3c29b8f26df9a8ea23e14cbb0b92512.webp" width="1600" height="783" class="img_ev3q"></p>
<p>Run it after installation:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-19-be4e61d0ca02ec3e5799e4a490c813e8.webp" width="1600" height="869" class="img_ev3q"></p>
<p>At this point, with no configuration at all, we can already capture HTTP requests (since HTTP is plaintext — though the traffic does need to go through the system proxy, and Firefox requires "manual proxy configuration"). But if our project uses HTTPS, everything is encrypted for security. So how do we capture that?</p>
<p>A quick note on how the proxy works: when Fiddler starts, it registers itself as the system proxy, listening on <code>127.0.0.1:8888</code> by default. Programs that honor the system proxy (most Windows applications, Chrome, Edge) automatically route their traffic through it. Firefox, however, has its own independent proxy settings and ignores the system proxy, so you need to manually configure a proxy in its network settings, pointing at the same address and port:</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 proxy:  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">Port:        8888</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-https-capture-works">How HTTPS Capture Works<a href="https://tanzhuo.xyz/en/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu#how-https-capture-works" class="hash-link" aria-label="Direct link to How HTTPS Capture Works" title="Direct link to How HTTPS Capture Works" translate="no">​</a></h2>
<p>First we need to configure Fiddler's certificate. Why bother with that, you might ask? Let's take a quick look at the mechanism:</p>
<ol>
<li class="">
<p>Fiddler intercepts the HTTPS request from our client.</p>
</li>
<li class="">
<p>It forwards the intercepted request to the target server.</p>
</li>
<li class="">
<p>The server responds and sends its certificate back to our client. Fiddler intercepts it, keeps the server's real certificate, and sends our configured forged certificate to the client instead.</p>
</li>
<li class="">
<p>The client accepts the forged certificate and sends its data again — now encrypted with the forged certificate.</p>
</li>
<li class="">
<p>Fiddler intercepts and reads all the request data, then re-encrypts it with the real certificate captured in step 3 and sends it on to the server.</p>
</li>
</ol>
<p>In short, Fiddler acts as a relay in the middle — which is why it can see the data both sent by our client and returned by the server.</p>
<p>Put plainly, this is a "benign man-in-the-middle attack". HTTPS security rests on the certificate trust chain: when a client receives a server certificate, it verifies that the certificate was issued by a root CA it trusts. The reason Fiddler can swap the certificate without the client rejecting it is that you've installed Fiddler's root certificate into the system's "Trusted Root Certification Authorities" store. Once it's there, the forged certificates Fiddler mints on the fly pass validation, the client agrees to encrypt subsequent data with them, and the plaintext lands in Fiddler's hands. This is also why step 2 below — trusting the root certificate — cannot be skipped.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="configuring-the-certificate">Configuring the Certificate<a href="https://tanzhuo.xyz/en/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu#configuring-the-certificate" class="hash-link" aria-label="Direct link to Configuring the Certificate" title="Direct link to Configuring the Certificate" translate="no">​</a></h2>
<p><strong>Fiddler certificate setup:</strong></p>
<ol>
<li class="">Tools -&gt; Options -&gt; HTTPS</li>
</ol>
<p>Check Decrypt HTTPS traffic and select from all processes</p>
<p>This is the master switch for HTTPS decryption. <code>from all processes</code> decrypts HTTPS traffic from every process; if you only want to capture one program, switch to <code>from browsers only</code> or use process filtering later, so unrelated traffic doesn't flood the session list.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-20-404125e5766d5ffd58605f55e31804eb.webp" width="1600" height="869" class="img_ev3q"></p>
<ol start="2">
<li class="">Actions -&gt; Trust Root Certificate</li>
</ol>
<p>This is the key move described in the mechanism above: installing Fiddler's root certificate into the system trust store.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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/en/assets/images/2022-10-image-22-7f14841bfa953c40fea063f5d9037588.webp" width="1600" height="869" class="img_ev3q"></p>
<ol start="3">
<li class="">Click Yes and the certificate is configured — start capturing and you can view HTTPS request contents</li>
</ol>
<p>Click <code>Yes</code> in the dialog to confirm trust, and the certificate is installed. Back in the main window, HTTPS sessions that previously showed as encrypted gibberish now reveal their plaintext request headers, bodies, and responses.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-23-507eb4cb4ef1dcdbd24c643d2132007d.webp" width="1600" height="869" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ul>
<li class="">Can't capture a particular app's traffic: most likely it doesn't use the system proxy, or it bypasses certificate validation checks in its own way. Since Android 7, apps no longer trust user-installed certificates by default — you need the app itself to opt in or use other means; installing the certificate alone won't get Fiddler in.</li>
<li class="">The client uses SSL pinning: these apps hard-code the server certificate's fingerprint, so the moment they see Fiddler's substituted certificate they refuse the connection. Ordinary configuration can't get around this.</li>
<li class="">Firefox capture not working: double-check that its independent proxy points at <code>127.0.0.1:8888</code> — an easy step to forget.</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>warning</div><div class="admonitionContent_BuS1"><p>Once Fiddler's root certificate is in your system store, this machine effectively has a backdoor that can decrypt all HTTPS. When you're done debugging, go to Tools -&gt; Options -&gt; HTTPS and click <code>Remove Interception Certificates</code>, or delete <code>DO_NOT_TRUST_FiddlerRoot</code> from the system certificate manager. Don't leave it sitting on your dev machine long-term.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/10/20/fiddler-an-zhuang-bing-zhua-qu-httpsqing-qiu#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Capturing plaintext HTTP takes almost zero setup. Capturing HTTPS boils down to just two things: enabling Decrypt HTTPS traffic, and trusting Fiddler's root certificate. Once you grasp the "man in the middle + forged certificate" through-line, you can also roughly diagnose where things are stuck when capture fails — traffic not going through the proxy, certificate not trusted, or the other side doing pinning.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Redis GUI Clients Worth Using]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/20/redis-chang-yong-uigong-ju</link>
            <guid>https://tanzhuo.xyz/en/2022/10/20/redis-chang-yong-uigong-ju</guid>
            <pubDate>Thu, 20 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Inspecting Redis data from the command line isn't very visual. Here are a few Redis GUI clients I keep coming back to — RDM, AnotherRedisDesktopManager, and QuickRedis — with picks and caveats.]]></description>
            <content:encoded><![CDATA[<p>You can certainly inspect Redis data by running commands in the CLI, but it's nowhere near as efficient as a graphical interface — so here's a record of a few Redis GUI tools I use regularly.</p>
<!-- -->
<p>Day-to-day Redis lookups tend to be small and scattered: confirming a key actually got written, checking what a serialized value looks like, verifying a TTL matches expectations. <code>redis-cli</code> can do all of that, but bouncing between <code>keys</code> / <code>scan</code>, <code>type</code>, <code>ttl</code>, and <code>get</code> / <code>hgetall</code> is slow going, and structured data like Hashes and ZSets is a pain to read as plain text. A good GUI client folds all of that into one screen — key list, data structure, TTL, all at a glance — so it's worth writing down the ones I've used, if only to make setup faster on the next machine.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-desktop-manager">Redis Desktop Manager<a href="https://tanzhuo.xyz/en/2022/10/20/redis-chang-yong-uigong-ju#redis-desktop-manager" class="hash-link" aria-label="Direct link to Redis Desktop Manager" title="Direct link to Redis Desktop Manager" translate="no">​</a></h2>
<p>Paid, with a two-week trial. Cracked versions float around online, though they tend not to be the latest release.</p>
<p>RDM is the veteran of the bunch. Early versions were open source and free; it later moved to a paid subscription and rebranded as RESP.app, which is exactly why many people jumped ship to free alternatives. Feature-wise it's comprehensive: SSH tunneling, SSL connections, a built-in console for running commands directly, and mature rendering for all the data types. If your company is willing to pay, it remains a safe choice.</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/en/2022/10/20/redis-chang-yong-uigong-ju#anotherredisdesktopmanager" class="hash-link" aria-label="Direct link to AnotherRedisDesktopManager" title="Direct link to AnotherRedisDesktopManager" translate="no">​</a></h2>
<p>Open source, free, pleasant to use, with a clean good-looking UI.</p>
<p>The name itself is a bit of a jab at RDM going paid. Its headline trait is stability: it won't freeze or crash when loading huge numbers of keys, which matters a lot when a production database has millions of them — it uses <code>scan</code> for progressive iteration instead of pulling everything at once. Everything you need day to day is there: multiple connection management, SSH/SSL, cluster mode, a command-line terminal, a dark theme, and keys that fold into a tree view by delimiter. It's the one I mainly use these days.</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/en/2022/10/20/redis-chang-yong-uigong-ju#quickredis" class="hash-link" aria-label="Direct link to QuickRedis" title="Direct link to QuickRedis" translate="no">​</a></h2>
<p>QuickRedis is a forever-free Redis visual management tool. It supports direct connections, sentinel, and cluster mode, handles keys in the hundreds of millions, and has a genuinely exciting UI. QuickRedis runs on Windows, Mac OS X, and Linux. It's a productivity tool: while others are busy hammering out commands, you're already sipping tea.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-6874747-d065e1e6a63c9c853df4c8faa4fb757b.webp" width="1600" height="936" class="img_ev3q"></p>
<p>Its sentinel and cluster support works out of the box — no need to handle <code>MOVED</code> redirects yourself or hunt down the master node the way you would on the command line, which saves real effort when connecting to a production cluster. The interface uses a tree-directory style, organizing keys into a hierarchy by colon delimiter — very comfortable if you're in the habit of naming keys by business prefix.</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="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/10/20/redis-chang-yong-uigong-ju#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Be restrained with production. In a GUI, deleting a key or editing a value is just a few clicks — the cost of a slip-up is lower than on the command line. Use a read-only account for production connections, or at least name the connections in a clearly distinguishable way, so test-database habits don't leak into production.</p>
</li>
<li class="">
<p>Watch out for full scans. Some clients use <code>keys *</code> instead of <code>scan</code> when rendering the key list, which blocks the Redis main thread on large databases. It's worth checking for this when picking a tool — AnotherRedisDesktopManager has a good reputation here.</p>
</li>
<li class="">
<p>Garbled values usually aren't the tool's fault. Binary data written by application code via JDK serialization or Protobuf can only be displayed as raw bytes. Some tools let you choose a deserialization format or pretty-print JSON — check whether that option exists before use.</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>tip</div><div class="admonitionContent_BuS1"><p>When connecting to a remote Redis, prefer an SSH tunnel over exposing port 6379 to the public internet. All the tools above support SSH connections.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/10/20/redis-chang-yong-uigong-ju#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Of the three: RDM (RESP.app) is the most feature-complete but paid; AnotherRedisDesktopManager is open source, free, and reliably boring in the best way — a solid default choice; QuickRedis is also free, with its tree UI and sentinel/cluster support as the highlights. None of them is absolutely better than the others — pick whichever feels right and pocket the time you save looking up data.</p>]]></content:encoded>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[The 7-Layer Network Model]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi</link>
            <guid>https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi</guid>
            <pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A walkthrough of the OSI seven-layer model: what problem each layer solves, what its data unit is called, which common devices and protocols map to it, and how it relates to the TCP/IP model.]]></description>
            <content:encoded><![CDATA[<p>When troubleshooting network problems, the go-to approach is to isolate the issue layer by layer: is the cable connected, can you ping the IP, is the port listening, what did the application return. That whole approach traces back to the OSI seven-layer model. Here I go through what problem each layer solves, as a reference note for myself.</p>
<!-- -->
<p>First, why layering at all. Network communication has too many concerns to handle at once: how signals travel over the medium, what to do about errors, how to find the other party across networks, how applications agree on data formats. By splitting these concerns by responsibility — each layer depending only on the services of the layer below and exposing an interface only to the layer above — every layer can be designed and swapped out independently. The OSI model slices the whole process into seven layers, bottom to top: physical, data link, network, transport, session, presentation, and application.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="physical-layer">Physical layer<a href="https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi#physical-layer" class="hash-link" aria-label="Direct link to Physical layer" title="Direct link to Physical layer" translate="no">​</a></h2>
<p>This layer solves the problem of how two pieces of hardware communicate. Common physical media include optical fiber, cables, and repeaters. It mainly defines physical device standards, such as connector types for network cables and fiber, and the transmission rates of various media.</p>
<p>Its main job is transmitting bit streams (1s and 0s converted into stronger or weaker electrical signals for transmission, then converted back into 1s and 0s at the destination — the familiar digital-to-analog and analog-to-digital conversion). The data unit at this layer is the bit.</p>
<p>In other words, the physical layer only cares about electrical and mechanical characteristics — what voltage counts as a 1, what the connector looks like, how many bits per second — with no understanding whatsoever of what is being transmitted. A NIC's physical interface and hubs operate at this layer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-link-layer">Data link layer<a href="https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi#data-link-layer" class="hash-link" aria-label="Direct link to Data link layer" title="Direct link to Data link layer" translate="no">​</a></h2>
<p>Because of all kinds of interference in computer networks, physical links are unreliable. This layer's main job is to use control protocols to turn an error-prone physical channel into an error-free data link that can reliably transmit data frames.</p>
<p>Concretely, it takes the bit-stream data from the physical layer, packages it into frames, and passes them up; likewise, it unpacks frames from the layer above into bit streams and forwards them to the physical layer. The data unit at this layer is the frame.</p>
<p>Only after framing is there a boundary defining where a piece of data starts and ends, which also makes it possible to append a checksum at the end of the frame for error detection — corrupted frames are simply dropped. This layer uses MAC addresses to identify devices on the same link; Ethernet and switches operate here, which is why switches are often called layer-2 devices.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="network-layer">Network layer<a href="https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi#network-layer" class="hash-link" aria-label="Direct link to Network layer" title="Direct link to Network layer" translate="no">​</a></h2>
<p>With many computers on a network, how do you find the one you want to send to? And with multiple nodes in between, how do you choose the path? That is what routing does.</p>
<p>This layer's main task is to use routing algorithms to select the most suitable path through the communication subnet for packets (this layer's data unit, packaged from the layer above). This layer defines IP addresses and addresses hosts by them, which is where the IP protocol comes from.</p>
<p>The difference from the link layer is scope: MAC addresses are only meaningful within a single link, while IP addresses work across the whole network. Every time a packet passes through a router, the link-layer frame is stripped and rebuilt, but the IP-layer source and destination addresses stay the same end to end. Note that IP itself is "best effort" — it only tries to move packets toward the destination, guaranteeing neither delivery nor ordering; reliability is left to the layers above. Routers are the classic layer-3 device.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="transport-layer">Transport layer<a href="https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi#transport-layer" class="hash-link" aria-label="Direct link to Transport layer" title="Direct link to Transport layer" translate="no">​</a></h2>
<p>When sending large amounts of data, packet loss is likely, and the receiving machine needs to report whether it got everything. If something is missing, it says which packets were lost so they can be resent, repeating until everything arrives.</p>
<p>Simply put, the transport layer's main job is to monitor the quality of the data transmission service and ensure packets are delivered correctly.</p>
<p>The transport layer also solves another problem: an IP address only identifies a host, but a host runs many processes — who should actually receive the data? The answer is port numbers; IP plus port pins down a specific process. The two most famous protocols at this layer are TCP and UDP: TCP is connection-oriented and achieves reliability through acknowledgments, retransmission, and flow control; UDP is connectionless, offering no reliability guarantees but low overhead, suited to latency-sensitive scenarios where losing a little data doesn't matter.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="session-layer">Session layer<a href="https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi#session-layer" class="hash-link" aria-label="Direct link to Session layer" title="Direct link to Session layer" translate="no">​</a></h2>
<p>At this point we can already deliver correctly packaged data to the right computer. But we can't be expected to manually invoke the transport protocol to package data and then invoke IP to find a route every single time — we need automatic send/receive and automatic addressing. Hence the session layer: its role is to establish and manage communication between applications.</p>
<p>It handles session establishment, maintenance, and teardown — the entire lifecycle of a conversation between two parties, from "start talking" to "stop talking," is managed here.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="presentation-layer">Presentation layer<a href="https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi#presentation-layer" class="hash-link" aria-label="Direct link to Presentation layer" title="Direct link to Presentation layer" translate="no">​</a></h2>
<p>The presentation layer handles data format conversion: transforming application data into a format suitable for network transmission, or converting data from the layer below into a format the layer above can process.</p>
<p>Typical work includes character-encoding conversion, encryption/decryption, and compression/decompression. Two machines may represent data differently internally; the presentation layer ensures that data sent by one side is correctly understood by the other.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="application-layer">Application layer<a href="https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi#application-layer" class="hash-link" aria-label="Direct link to Application layer" title="Direct link to Application layer" translate="no">​</a></h2>
<p>The application layer is the interface between computer users (and their applications) and the network. Its function is to provide services directly to users, accomplishing whatever they want to do over the network.</p>
<p>The protocols we deal with most day to day all live here: HTTP for browsing the web, FTP for transferring files, SMTP for sending mail, DNS for name resolution.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-----1--96518b37b423d77157eafd5d667beaaa.webp" width="1120" height="1587" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="relationship-to-the-tcpip-model">Relationship to the TCP/IP model<a href="https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi#relationship-to-the-tcpip-model" class="hash-link" aria-label="Direct link to Relationship to the TCP/IP model" title="Direct link to Relationship to the TCP/IP model" translate="no">​</a></h2>
<p>OSI is a conceptual model; the protocol stack the real Internet runs on is TCP/IP. TCP/IP is usually divided into four layers: the network interface layer maps to OSI's physical and data link layers, the internet layer to the network layer, the transport layer to the transport layer, and the session, presentation, and application layers are merged into a single application layer. That's why you rarely see standalone session- or presentation-layer protocols in practice — their responsibilities are mostly absorbed by application-layer protocols or by the applications themselves.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and caveats<a href="https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and caveats" title="Direct link to Pitfalls and caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>The data units are easy to mix up: bits at the physical layer, frames at the data link layer, packets at the network layer, segments at the transport layer. This comes up constantly in both debugging and interviews.</p>
</li>
<li class="">
<p>The device-to-layer mapping is worth memorizing: hubs work at the physical layer, switches at the data link layer, routers at the network layer. The terms "layer-2 switch" and "layer-3 switch" come from exactly this.</p>
</li>
<li class="">
<p>To decide which layer a protocol belongs to, look at what problem it solves rather than guessing from the name. DNS, for instance, is infrastructure but is an application-layer protocol; ARP maps IP addresses to MAC addresses and sits between the network and link layers.</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>tip</div><div class="admonitionContent_BuS1"><p>You can standardize the layer-by-layer troubleshooting order: first check physical connectivity (cable, NIC lights), then link and IP (ping the gateway, ping the target), then the transport layer (is the port listening, does the firewall allow it), and finally the application's own logs. Eliminating layer by layer beats random guessing by a wide margin.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/17/7ceng-wang-luo-xie-yi#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>The heart of the seven-layer model is separation of concerns: each layer solves one class of problems, provides services upward, and consumes services downward. The physical layer moves bits, the link layer makes a single link reliable, the network layer handles network-wide addressing and routing, the transport layer guarantees end-to-end delivery quality, and the top three layers serve the application itself. Once you understand what problem each layer solves, any specific protocol slots naturally into place — and you know which layer to start from when things break.</p>]]></content:encoded>
            <category>Linux</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Docker Basic Commands]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling</link>
            <guid>https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling</guid>
            <pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A reference for the Docker commands you use every day: pulling images, starting containers, listing images and containers, removing, stopping, restarting, and entering containers — plus how images relate to containers and a few common pitfalls.]]></description>
            <content:encoded><![CDATA[<p>When you're new to Docker, the most common confusion isn't the concepts — it's "which command do I actually type for this." This post collects the most frequently used basic commands, each with its syntax and an example, for quick reference.</p>
<!-- -->
<p>Before the commands themselves, one thing worth getting straight: <strong>images and containers are two different things</strong>. An image is a read-only template containing the filesystem and configuration a program needs to run; a container is a running instance of an image, with a writable layer added on top. One image can spawn multiple containers that don't interfere with each other. Nearly every command below operates on one of these two objects — some on images (pull, images, rmi), some on containers (run, ps, rm, kill, restart, start, exec). Keep the object straight and the commands are much easier to remember.</p>
<p>Also, the Docker daemon runs with root privileges by default, so regular users need <code>sudo</code> to run docker commands (or need to be added to the docker group), which is why most examples below include <code>sudo</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="help">Help<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#help" class="hash-link" aria-label="Direct link to Help" title="Direct link to Help" 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>When you forget a command or a flag, check the help first. You can also get help for a specific subcommand — <code>docker run --help</code>, for example, lists every option that subcommand supports.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pulling-an-image">Pulling an image<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#pulling-an-image" class="hash-link" aria-label="Direct link to Pulling an image" title="Direct link to Pulling an image" 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%)"># Syntax: NAME is the image name, TAG is the version 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%)"># Example: pull the latest tag of 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"> pull centos:latest</span><br></div></code></pre></div></div>
<p><code>pull</code> downloads an image from a registry (Docker Hub by default) to your local machine. If you omit the TAG, <code>latest</code> is pulled. Images are stored in layers, so during a pull you'll see per-layer download progress, and layers already present locally are reused rather than downloaded again.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="starting-a-container">Starting a container<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#starting-a-container" class="hash-link" aria-label="Direct link to Starting a container" title="Direct link to Starting a 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%)"># Syntax</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Example: start an interactive container from an image (the original post wrote "contos"; it should be 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>What <code>run</code> actually does is two steps: it creates a new container from the image, then starts it. A few common flags:</p>
<ol>
<li class="">
<p><code>-i</code>: keep stdin open so you can type into the container;</p>
</li>
<li class="">
<p><code>-t</code>: allocate a pseudo-terminal; combined with <code>-i</code> you get an interactive shell;</p>
</li>
<li class="">
<p>the <code>/bin/bash</code> after the image name is the command executed when the container starts — when that command exits, the container stops.</p>
</li>
</ol>
<p>If the image isn't present locally, <code>run</code> automatically performs a pull first.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="listing-images">Listing images<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#listing-images" class="hash-link" aria-label="Direct link to Listing images" title="Direct link to Listing images" translate="no">​</a></h2>
<p>List all local 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%)"># Syntax</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Example: only show centos-related images</span><span class="token plain"></span><br></div><div class="token-line" 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>The output shows each image's repository name, tag, image ID, creation time, and size. The name or ID shown here is what you'll use later when removing an image.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="listing-containers">Listing containers<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#listing-containers" class="hash-link" aria-label="Direct link to Listing containers" title="Direct link to Listing containers" translate="no">​</a></h2>
<p>See every container we've created:</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%)"># Syntax</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Example: -a lists all containers, including stopped ones</span><span class="token plain"></span><br></div><div class="token-line" 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>Note that <code>docker ps</code> only shows <strong>running</strong> containers by default. A container that just exited won't appear in <code>ps</code>, which makes it easy to think it "disappeared" — add <code>-a</code> to see everything. Both the CONTAINER ID and NAMES columns can be used to identify a container in later commands.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="removing-an-image">Removing an image<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#removing-an-image" class="hash-link" aria-label="Direct link to Removing an image" title="Direct link to Removing an image" translate="no">​</a></h2>
<p>Delete a downloaded image from the local machine:</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%)"># Syntax: you can delete several at once</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Example</span><span class="token plain"></span><br></div><div class="token-line" 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>If any container (even a stopped one) was created from the image, <code>rmi</code> will refuse with an error — you need to <code>rm</code> the related containers first.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="removing-container-instances">Removing container instances<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#removing-container-instances" class="hash-link" aria-label="Direct link to Removing container instances" title="Direct link to Removing container instances" translate="no">​</a></h2>
<p>Remove one or more containers:</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%)"># Syntax</span><span class="token plain"></span><br></div><div class="token-line" 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>Remove all containers:</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%)"># The original post wrote it as below; in practice the inner command needs $() or backticks for command substitution:</span><span class="token plain"></span><br></div><div class="token-line" 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>Here the <code>-q</code> in <code>docker ps -aq</code> means output container IDs only; passing those IDs as arguments to <code>docker rm</code> gives you batch deletion. A running container cannot be <code>rm</code>'d directly — stop it first, or add <code>-f</code> to force removal.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stopping-a-running-container">Stopping a running container<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#stopping-a-running-container" class="hash-link" aria-label="Direct link to Stopping a running container" title="Direct link to Stopping a running 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%)"># Syntax</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Example: the first few characters of the container ID are enough, as long as they uniquely identify it</span><span class="token plain"></span><br></div><div class="token-line" 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>By default <code>kill</code> sends SIGKILL to the container's main process, killing it immediately with no chance to clean up. If you want the program to exit gracefully, <code>docker stop</code> is the better choice — it sends SIGTERM first and only sends SIGKILL after a grace period.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="restarting-a-running-container">Restarting a running container<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#restarting-a-running-container" class="hash-link" aria-label="Direct link to Restarting a running container" title="Direct link to Restarting a running 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%)"># Syntax</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Example</span><span class="token plain"></span><br></div><div class="token-line" 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> amounts to a stop followed by a start; data in the container's writable layer is preserved.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="starting-a-stopped-container">Starting a stopped container<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#starting-a-stopped-container" class="hash-link" aria-label="Direct link to Starting a stopped container" title="Direct link to Starting a stopped 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%)"># Syntax</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Example</span><span class="token plain"></span><br></div><div class="token-line" 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>Mind the difference between <code>start</code> and <code>run</code>: <code>run</code> creates a <strong>new</strong> container, while <code>start</code> just brings an existing, stopped container back up — every change you previously made inside it is still there.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="entering-a-container">Entering a container<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#entering-a-container" class="hash-link" aria-label="Direct link to Entering a container" title="Direct link to Entering a 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 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">container name or ID</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> starts an additional process inside a running container — here, another bash. It's more commonly used than <code>attach</code>: typing <code>exit</code> in a shell you entered via <code>exec</code> only ends that extra process and doesn't affect the container itself.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and caveats<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and caveats" title="Direct link to Pitfalls and caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>Don't mix up image IDs and container IDs.</strong> <code>rmi</code> removes images, <code>rm</code> removes containers; pass the wrong kind of ID and you'll get a "no such object" error.</p>
</li>
<li class="">
<p><strong>Stopped is not removed.</strong> A stopped container still holds its name and its writable layer on disk. A new container using the same name will fail to create — <code>rm</code> the old one first.</p>
</li>
<li class="">
<p><strong>Prefer <code>stop</code> over <code>kill</code>.</strong> Unless the process is hung and unresponsive, give the application a chance to clean up after receiving SIGTERM.</p>
</li>
<li class="">
<p><strong>Data inside a container is ephemeral.</strong> Once a container is <code>rm</code>'d, the data in its writable layer goes with it. Anything important should be mounted to the host via volumes.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/17/docker-ji-ben-ming-ling#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>This set of commands covers Docker's minimal working loop: <code>pull</code> to get an image, <code>run</code> to start a container, <code>images</code> / <code>ps</code> to check state, <code>start</code> / <code>stop</code> (<code>kill</code>) / <code>restart</code> to control the lifecycle, <code>exec</code> to get inside a container for debugging, and finally <code>rm</code> / <code>rmi</code> to clean up containers and images. Keep the through-line in mind — images are templates, containers are instances — and the rest is just practice.</p>]]></content:encoded>
            <category>Docker</category>
        </item>
        <item>
            <title><![CDATA[Useful IDEA Plugins and Dev Tools]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian</link>
            <guid>https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian</guid>
            <pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A roundup of the IDEA plugins and tools I rely on for daily Java development: SonarQube, FindBugs, CheckStyle, Maven Helper, Lombok, Key Promoter X, and more, with notes on when to use each.]]></description>
            <content:encoded><![CDATA[<p>IDEA is powerful enough on its own, but the right handful of plugins can shave real time off everyday chores like code-quality checks, dependency debugging, and style enforcement. This post rounds up the plugins and tools I use in Java development, grouped by purpose so you can grab what you need.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="static-code-quality-management-platform">Static code quality management platform<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#static-code-quality-management-platform" class="hash-link" aria-label="Direct link to Static code quality management platform" title="Direct link to Static code quality management platform" 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>Strictly speaking, SonarQube isn't an IDEA plugin but a standalone code-quality management platform you deploy yourself. It scans code via static analysis for bugs, vulnerabilities, and code smells, and produces quantified quality reports. The typical setup is to run one instance for the team and hook it into CI so every commit or merge gets scanned automatically, catching quality issues before they land. There's also a companion IDEA plugin that connects to the server so you can see scan results locally.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="code-quality-inspection-plugin-findbugs">Code quality inspection plugin: FindBugs<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#code-quality-inspection-plugin-findbugs" class="hash-link" aria-label="Direct link to Code quality inspection plugin: FindBugs" title="Direct link to Code quality inspection plugin: FindBugs" translate="no">​</a></h2>
<p>Installs directly as a plugin inside the 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 works by analyzing compiled bytecode and matching it against known defect patterns — null-pointer dereferences, unclosed resources, equals without a matching hashCode, and so on. It complements SonarQube nicely: SonarQube leans toward team-level continuous quality management, while FindBugs is better for a quick local scan right after you finish writing code.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="translation-plugin-a-must-if-your-english-is-shaky">Translation plugin (a must if your English is shaky)<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#translation-plugin-a-must-if-your-english-is-shaky" class="hash-link" aria-label="Direct link to Translation plugin (a must if your English is shaky)" title="Direct link to Translation plugin (a must if your English is shaky)" translate="no">​</a></h2>
<p>Translation lets you translate directly inside the code editor.</p>
<p>Select a variable name, comment, or docstring, hit the shortcut, and the translation pops up — no switching out to a browser dictionary. Especially handy when reading English source comments or naming variables.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="code-style-enforcement-plugin">Code style enforcement plugin<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#code-style-enforcement-plugin" class="hash-link" aria-label="Direct link to Code style enforcement plugin" title="Direct link to Code style enforcement plugin" translate="no">​</a></h2>
<p>CheckStyle-IDEA checks code against rules for formatting, naming conventions, Javadoc, class design, and more, effectively nudging developers toward consistent coding standards.</p>
<p>Its focus differs from FindBugs: FindBugs hunts for "code that might have bugs," while CheckStyle polices "code that isn't written to standard." A team can share one rules file (say, adapted from the Alibaba or Google Java style guides) so everyone's output looks consistent, and reviews stop getting bogged down in formatting back-and-forth.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="maven-dependency-conflict-detector">Maven dependency conflict detector<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#maven-dependency-conflict-detector" class="hash-link" aria-label="Direct link to Maven dependency conflict detector" title="Direct link to Maven dependency conflict detector" translate="no">​</a></h2>
<p>Maven Helper detects dependency conflicts across the whole project's pom.xml.</p>
<p>Maven dependencies are transitive, so in any sizable project it's almost inevitable that the same library gets pulled in at different versions by different dependencies. The default <code>mvn dependency:tree</code> output is painful to read; with Maven Helper, opening a pom.xml in IDEA adds a dependency-analysis view where conflicting dependencies are highlighted in red, and a right-click excludes the version you don't want. Extremely useful when chasing down NoSuchMethodError or ClassNotFoundException caused by version conflicts.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="code-minimap-plugin">Code minimap plugin<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#code-minimap-plugin" class="hash-link" aria-label="Direct link to Code minimap plugin" title="Direct link to Code minimap plugin" translate="no">​</a></h2>
<p>CodeGlance works like the minimap in VSCode.</p>
<p>It shows a thumbnail of the whole file along the right edge of the editor — much more intuitive than dragging the scrollbar when jumping around long files.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hot-reload-plugin-paid">Hot-reload plugin (paid)<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#hot-reload-plugin-paid" class="hash-link" aria-label="Direct link to Hot-reload plugin (paid)" title="Direct link to Hot-reload plugin (paid)" translate="no">​</a></h2>
<p>JRebel cuts down on restarts after changing code or mapper.xml files, boosting productivity.</p>
<p><a href="https://tanzhuo.xyz/idea-2020-jrebelpo-jie/" target="_blank" rel="noopener noreferrer" class="">Cracking JRebel for IntelliJ IDEA 2020</a></p>
<p>In the traditional workflow, changing one line of Java means restarting the app, and in a big project a restart can take tens of seconds or minutes. JRebel's approach is hot class replacement at the JVM level: changes take effect without a restart, and it even covers MyBatis mapper.xml changes — broader coverage than Spring Boot's built-in devtools restart mechanism. The downside is it's commercial software, and not cheap for individual use.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="json-to-java-class-plugin">JSON-to-Java class plugin<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#json-to-java-class-plugin" class="hash-link" aria-label="Direct link to JSON-to-Java class plugin" title="Direct link to JSON-to-Java class plugin" translate="no">​</a></h2>
<p>GsonFormat</p>
<p>When integrating a third-party API, hand-writing the Java bean for a JSON response is slow and it's easy to mistype a field. GsonFormat lets you paste the JSON string in and generates an entity class with all fields and types mapped correctly, nested structures included.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="productivity-tool">Productivity tool<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#productivity-tool" class="hash-link" aria-label="Direct link to Productivity tool" title="Direct link to Productivity tool" translate="no">​</a></h2>
<p>Lombok is a plugin that uses annotations to simplify writing JavaBeans in Java, eliminating redundant boilerplate and keeping classes lean.</p>
<p>It works by generating code at compile time through an annotation processor: <code>@Getter</code>/<code>@Setter</code> generate accessors, <code>@Builder</code> generates a builder, <code>@Slf4j</code> generates the logger declaration. You won't see these methods in the source, but they're all in the compiled class files. Note that Lombok needs both ends set up — the dependency in your project and the plugin in IDEA. Miss either one and your screen fills with red.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="shortcut-hint-plugin-basically-a-must-install">Shortcut hint plugin (basically a must-install)<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#shortcut-hint-plugin-basically-a-must-install" class="hash-link" aria-label="Direct link to Shortcut hint plugin (basically a must-install)" title="Direct link to Shortcut hint plugin (basically a must-install)" translate="no">​</a></h2>
<p>Key Promoter X is a plugin for IntelliJ-based products (IDEA, Android Studio, CLion) that helps you learn essential keyboard shortcuts while you work. When you use the mouse on a button inside the IDE, Key Promoter X shows the keyboard shortcut you should have used instead. It's an easy way to replace tedious mouse work with keystrokes and transition toward faster, mouse-free development. It currently supports toolbar buttons, menu buttons, tool windows, and the actions inside them.</p>
<p>Plugin page: <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>The nice thing about this kind of "teaching" plugin is passive learning: no need to memorize a shortcut cheat sheet. Whatever you click most with the mouse is exactly what it keeps reminding you about, so the most-used shortcuts stick on their own.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and caveats<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and caveats" title="Direct link to Pitfalls and caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>More plugins is not better. Every plugin eats memory and slows down indexing and startup. If IDEA feels sluggish, first check whether you've installed too many, and disable the ones you rarely use.</p>
</li>
<li class="">
<p>For Lombok-related compile errors, first confirm both the dependency and the plugin are installed, and that Annotation Processing is enabled.</p>
</li>
<li class="">
<p>Quality-check plugins (FindBugs, CheckStyle) are slow on full scans of large projects; run incremental checks on just the changed modules or files instead.</p>
</li>
<li class="">
<p>After tracking down a conflict with Maven Helper, exclude with care — make sure the excluded version really isn't needed by another module, or you may just be deferring a compile-time problem into a runtime one.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/17/idea-chang-yong-cha-jian#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>These ten tools roughly cover a few categories of daily needs: quality and standards (SonarQube, FindBugs, CheckStyle), dependency management (Maven Helper), coding efficiency (Lombok, GsonFormat, Translation, CodeGlance), and developer experience (JRebel, Key Promoter X). No need to install them all at once — pick the ones that address your project's actual pain points, get comfortable, then add more. That's the right way to use plugins.</p>]]></content:encoded>
            <category>Java</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[JVM Garbage Collectors: A Field Guide]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/17/jvm-gc</link>
            <guid>https://tanzhuo.xyz/en/2022/10/17/jvm-gc</guid>
            <pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[The common garbage collectors in the JVM include Serial, Parallel, ParNew, CMS, G1, and ZGC. They differ in concurrency, pause times, throughput, and the scenarios they suit best.]]></description>
            <content:encoded><![CDATA[<p>The common garbage collectors in the JVM include Serial, Parallel, ParNew, CMS, G1, and ZGC. They differ in <strong>concurrency, pause times, throughput, and the scenarios they suit best</strong>. This post walks through each of the major collectors in turn.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="serial-collector">Serial Collector<a href="https://tanzhuo.xyz/en/2022/10/17/jvm-gc#serial-collector" class="hash-link" aria-label="Direct link to Serial Collector" title="Direct link to Serial Collector" translate="no">​</a></h2>
<p>Flags: <code>-XX:+UseSerialGC -XX:+UseSerialOldGC</code></p>
<p>The Serial collector is the most basic and oldest garbage collector in the JVM. It performs garbage collection <strong>on a single thread</strong>. During collection, the JVM triggers a <strong>STW (Stop The World)</strong> pause: all user threads are suspended and only resume once collection finishes.</p>
<p>Algorithmically, Serial uses <strong>mark-copy (Copying) in the young generation</strong> and <strong>mark-compact in the old generation</strong>. Since the entire collection runs on a single GC thread, the implementation is simple and stable, but collection efficiency drops off noticeably on large heaps or machines with many CPU cores.</p>
<p>Serial is therefore best suited to <strong>applications with small heaps and few CPU cores</strong>, such as the old Client-mode JVM or resource-constrained systems.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="parallel-scavenge-collector">Parallel Scavenge Collector<a href="https://tanzhuo.xyz/en/2022/10/17/jvm-gc#parallel-scavenge-collector" class="hash-link" aria-label="Direct link to Parallel Scavenge Collector" title="Direct link to Parallel Scavenge Collector" translate="no">​</a></h2>
<p>Flags: <code>-XX:+UseParallelGC -XX:+UseParallelOldGC</code></p>
<p>Parallel Scavenge is the <strong>multi-threaded version</strong> of the Serial collector, and its core goal is to <strong>maximize throughput</strong>. During collection it runs multiple GC threads in parallel, improving overall collection efficiency.</p>
<p>By default the number of GC threads roughly matches the CPU core count, though you can set it explicitly with <code>-XX:ParallelGCThreads</code>. Algorithmically, Parallel matches Serial: <strong>mark-copy in the young generation, mark-compact in the old generation</strong>.</p>
<p>Because Parallel cares about <strong>throughput rather than pause time</strong>, it is a great fit for <strong>background computation services and batch processing systems</strong>. For applications with medium-sized heaps (around 3–4 GB), it delivers solid performance.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="parnew-collector">ParNew Collector<a href="https://tanzhuo.xyz/en/2022/10/17/jvm-gc#parnew-collector" class="hash-link" aria-label="Direct link to ParNew Collector" title="Direct link to ParNew Collector" translate="no">​</a></h2>
<p>Flag: <code>-XX:+UseParNewGC</code></p>
<p>ParNew can be thought of as <strong>the multi-threaded version of the Serial collector</strong>, but it is rarely used on its own — its main role is to <strong>pair with the CMS collector</strong>. ParNew handles <strong>young generation collection</strong>, while CMS handles the <strong>old generation</strong>.</p>
<p>ParNew uses a <strong>multi-threaded parallel collection mechanism</strong> and, like the others, uses <strong>mark-copy</strong> in the young generation. Since CMS targets low pause times and Serial is single-threaded, real-world CMS deployments typically pair it with ParNew to speed up young generation collection.</p>
<p>The ParNew + CMS combination was <strong>widely used in early web services and other pause-sensitive systems</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cms-collector">CMS Collector<a href="https://tanzhuo.xyz/en/2022/10/17/jvm-gc#cms-collector" class="hash-link" aria-label="Direct link to CMS Collector" title="Direct link to CMS Collector" translate="no">​</a></h2>
<p>Flag: <code>-XX:+UseConcMarkSweepGC</code></p>
<p>CMS (Concurrent Mark Sweep) collects the <strong>old generation</strong>, and its design goal is to <strong>minimize pause time during garbage collection</strong>. Its defining feature is that <strong>GC threads run concurrently with user threads</strong>, cutting down application pauses.</p>
<p>CMS uses the <strong>mark-sweep algorithm</strong> and relies on <strong>tri-color marking</strong> for reachability analysis. Its collection cycle typically has five phases:</p>
<ol>
<li class=""><strong>Initial Mark</strong>
Triggers a STW pause, marking only objects directly referenced by GC Roots. Since these are few, this phase is very fast.</li>
<li class=""><strong>Concurrent Mark</strong>
Runs concurrently with user threads, traversing the entire object graph and marking all reachable objects.</li>
<li class=""><strong>Remark</strong>
Triggers another STW pause to correct any drift caused by reference changes during concurrent marking.</li>
<li class=""><strong>Concurrent Sweep</strong>
Runs concurrently with user threads, sweeping unmarked objects.</li>
<li class=""><strong>Concurrent Reset</strong>
Resets CMS's internal state to prepare for the next GC cycle.</li>
</ol>
<p>CMS flow diagram:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10----b8e31d638671b82e0bfc6e3e2f62f4b7.webp" width="1105" height="396" class="img_ev3q"></p>
<p>CMS breaks collection down from one big step into several, splitting both marking and sweeping, and moves the most expensive part — marking objects — into a phase that runs concurrently with user threads. The application barely notices the pauses: only the initial mark and remark phases have very short STW windows, while everything else runs concurrently. That's how CMS achieves its goal of <strong>the shortest collection pauses</strong>. Note that shortest pause time does not mean shortest total collection time — CMS's overall collection actually takes longer than other collectors; it just keeps the pauses minimal.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="g1-collector">G1 Collector<a href="https://tanzhuo.xyz/en/2022/10/17/jvm-gc#g1-collector" class="hash-link" aria-label="Direct link to G1 Collector" title="Direct link to G1 Collector" translate="no">​</a></h2>
<p>Flag: <code>-XX:+UseG1GC</code></p>
<p>G1 (Garbage First) has been the <strong>default garbage collector since JDK 9</strong>, targeting <strong>large-memory server environments</strong>. Unlike traditional collectors that physically partition the heap into young and old generations, G1 divides the entire heap into many <strong>Regions</strong>.</p>
<p>Each Region can dynamically serve as an <strong>Eden, Survivor, or Old</strong> region as needed. G1 tracks the garbage ratio in each Region and prioritizes collecting <strong>the Regions with the most garbage and the highest collection payoff</strong> — hence the name "Garbage First".</p>
<p>Another key feature of G1 is its <strong>predictable pause-time model</strong>. You can set a target maximum pause with <code>-XX:MaxGCPauseMillis</code>, and G1 automatically adjusts its collection strategy to meet it.</p>
<p>The overall collection cycle includes <strong>initial mark, concurrent mark, final mark, and Region evacuation</strong> phases. Compared with CMS, G1 does a better job of reducing memory fragmentation, supporting large heaps, and controlling pause times, which is why it is so widely used in modern JVM deployments.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zgc-collector">ZGC Collector<a href="https://tanzhuo.xyz/en/2022/10/17/jvm-gc#zgc-collector" class="hash-link" aria-label="Direct link to ZGC Collector" title="Direct link to ZGC Collector" translate="no">​</a></h2>
<p>ZGC is a <strong>low-latency garbage collector introduced in JDK 11</strong>, with the core goal of <strong>millisecond-level GC pauses</strong>. In most cases, ZGC keeps STW pauses <strong>under 10ms</strong>, and stays stable even on terabyte-scale heaps.</p>
<p>ZGC's implementation rests on a few key techniques: <strong>colored pointers</strong>, <strong>load barriers</strong>, and a Region-based <strong>ZPage memory management scheme</strong>. With these, ZGC can perform most of its garbage collection work concurrently with user threads.</p>
<p>ZGC's main collection cycle includes <strong>concurrent marking, concurrent relocation, and concurrent reference remapping</strong>. Since the vast majority of the work happens in concurrent phases, the impact on application threads is tiny.</p>
<p>That makes ZGC ideal for <strong>servers with huge heaps, real-time systems, and latency-critical workloads like financial trading</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="comparing-the-common-gc-collectors">Comparing the Common GC Collectors<a href="https://tanzhuo.xyz/en/2022/10/17/jvm-gc#comparing-the-common-gc-collectors" class="hash-link" aria-label="Direct link to Comparing the Common GC Collectors" title="Direct link to Comparing the Common GC Collectors" translate="no">​</a></h2>
<ul>
<li class=""><strong>Serial</strong>: single-threaded collection, good for small applications</li>
<li class=""><strong>Parallel</strong>: multi-threaded collection, throughput first</li>
<li class=""><strong>ParNew + CMS</strong>: the low-pause combo (common on JDK 8)</li>
<li class=""><strong>G1</strong>: the modern JVM default, suited to large-memory services</li>
<li class=""><strong>ZGC</strong>: ultra-low-latency collector for terabyte-scale heaps</li>
</ul>





































































<table><thead><tr><th>Collector</th><th>Type</th><th>Threading</th><th>Main Algorithm</th><th>Pause Profile</th><th>Best For</th></tr></thead><tbody><tr><td>Serial</td><td>Young + Old</td><td>Single-threaded</td><td>Copying + mark-compact</td><td>Long STW pauses</td><td>Small apps, single-core CPUs</td></tr><tr><td>Parallel Scavenge</td><td>Young</td><td>Multi-threaded</td><td>Copying</td><td>Throughput first</td><td>Background computation, batch jobs</td></tr><tr><td>Parallel Old</td><td>Old</td><td>Multi-threaded</td><td>Mark-compact</td><td>Throughput first</td><td>High-throughput services</td></tr><tr><td>ParNew</td><td>Young</td><td>Multi-threaded</td><td>Copying</td><td>Short pauses</td><td>Paired with CMS</td></tr><tr><td>CMS</td><td>Old</td><td>Concurrent</td><td>Mark-sweep</td><td>Low pause</td><td>Web services, low-latency systems</td></tr><tr><td>G1</td><td>Whole heap (Regions)</td><td>Concurrent + parallel</td><td>Copying + mark-compact</td><td>Predictable pauses</td><td>Large-memory servers</td></tr><tr><td>ZGC</td><td>Whole heap (Regions)</td><td>Highly concurrent</td><td>Marking + relocation</td><td>&lt;10ms pauses</td><td>Huge heaps, low-latency systems</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bonus-how-golang-gc-works">Bonus: How Golang GC Works<a href="https://tanzhuo.xyz/en/2022/10/17/jvm-gc#bonus-how-golang-gc-works" class="hash-link" aria-label="Direct link to Bonus: How Golang GC Works" title="Direct link to Bonus: How Golang GC Works" translate="no">​</a></h2>
<p>Go introduced its <strong>tri-color concurrent mark-and-sweep GC in version 1.5</strong> and has kept refining it since. Today it is a mature system built on <strong>concurrent marking + concurrent sweeping + write barriers</strong>.</p>
<p>The core traits of Golang's GC:</p>
<ul>
<li class=""><strong>Tri-color marking algorithm</strong></li>
<li class=""><strong>Concurrent marking</strong></li>
<li class=""><strong>Write barriers to keep object references consistent</strong></li>
<li class=""><strong>Extremely short STW pauses (typically &lt; 1ms)</strong></li>
<li class=""><strong>Automatic GC triggering (based on heap growth ratio)</strong></li>
</ul>
<p>The main flow of a Golang GC cycle:</p>
<ol>
<li class=""><strong>STW Start</strong>: pause all goroutines and initialize the GC roots.</li>
<li class=""><strong>Concurrent Mark</strong>: GC runs concurrently with user goroutines, traversing object references with tri-color marking.</li>
<li class=""><strong>Mark Termination</strong>: a brief STW pause to ensure all objects are fully marked.</li>
<li class=""><strong>Concurrent Sweep</strong>: reclaim unmarked objects, running concurrently with the program.</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-core-differences-between-jvm-gc-and-golang-gc">The Core Differences Between JVM GC and Golang GC<a href="https://tanzhuo.xyz/en/2022/10/17/jvm-gc#the-core-differences-between-jvm-gc-and-golang-gc" class="hash-link" aria-label="Direct link to The Core Differences Between JVM GC and Golang GC" title="Direct link to The Core Differences Between JVM GC and Golang GC" translate="no">​</a></h3>
<p>The JVM offers <strong>multiple garbage collectors (Serial, CMS, G1, ZGC, and so on) with generational collection, adapting to different scenarios through combinations of algorithms and tuning flags</strong>. Golang, by contrast, was designed from the start around a single tri-color concurrent mark-and-sweep system with no generations, achieving low-latency collection through concurrent marking, write barriers, and extremely short STW pauses — a simpler overall design with a much lower tuning cost.</p>
<p>Why doesn't Go do generational GC? Because the Go team concluded that generational GC brings little benefit to latency-sensitive systems while adding runtime complexity.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/10/17/jvm-gc#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>From Serial to ZGC, the through-line of JVM garbage collector evolution is a constant trade-off between throughput and pause time. Single-threaded Serial wins on simplicity and stability, Parallel chases throughput, ParNew + CMS traded concurrency for low pauses, G1 made pauses predictable through Region-based collection, and ZGC squeezed pauses down to milliseconds with colored pointers and load barriers. When choosing a collector, there's no need to chase the newest one — what matters is your application's heap size and latency requirements. And comparing all this with Golang's single, non-generational GC design makes the different trade-offs the two runtimes made between complexity and flexibility that much clearer.</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Deploying a Private Cloud with StarVCenter]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/17/si-you-yun-bu-shu</link>
            <guid>https://tanzhuo.xyz/en/2022/10/17/si-you-yun-bu-shu</guid>
            <pubDate>Mon, 17 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[StarVCenter is a Chinese hyper-converged IaaS platform that consolidates compute, storage, and networking onto the same hardware nodes. This post covers its hyper-converged design, free-tier policy, advanced features, and what to prepare before deploying.]]></description>
            <content:encoded><![CDATA[<p>StarVCenter is a Chinese hyper-converged cloud platform (IaaS) that consolidates compute, storage, and networking resources into a single hardware node, forming a complete virtualization infrastructure. This hyper-converged architecture improves resource utilization, cuts deployment and maintenance costs, and delivers better performance and reliability.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-a-private-cloud">Why a Private Cloud<a href="https://tanzhuo.xyz/en/2022/10/17/si-you-yun-bu-shu#why-a-private-cloud" class="hash-link" aria-label="Direct link to Why a Private Cloud" title="Direct link to Why a Private Cloud" translate="no">​</a></h2>
<p>If you want to build your own private cloud at home or in a small server room, you can't avoid the platform-selection question. Commercial virtualization licenses aren't cheap, while hand-maintaining raw KVM leaves you without a unified management interface — once the VM count grows, creating, migrating, and monitoring them all becomes a chore. Hyper-converged software sits right in between and fills the gap: you get graphical, centralized management without having to buy dedicated centralized storage hardware. StarVCenter is one of the options I came across while surveying Chinese-made solutions — its community edition is free for individuals and small-scale deployments, so it's worth writing down.</p>
<p>Official docs: <a href="http://www.starvcs.com/doc/vcenter/index.html" target="_blank" rel="noopener noreferrer" class="">StarVCenter Documentation (starvcs.com)</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-hyper-convergence-means">What Hyper-Convergence Means<a href="https://tanzhuo.xyz/en/2022/10/17/si-you-yun-bu-shu#what-hyper-convergence-means" class="hash-link" aria-label="Direct link to What Hyper-Convergence Means" title="Direct link to What Hyper-Convergence Means" translate="no">​</a></h2>
<p>In a traditional virtualization architecture, compute nodes and storage devices are separate: servers run the VMs, data lives on a centralized storage array, and a dedicated storage network connects the two. This architecture performs well and is reliable, but the hardware is expensive and the cabling is complex — unfriendly for small deployments.</p>
<p>Hyper-convergence (HCI) puts storage back on the servers themselves: every node contributes both compute power and its local disks. A software layer aggregates the disks across nodes into one distributed storage pool, with VM disk data sharded across multiple nodes and kept in replicas. If one node fails, no data is lost; when you scale out, adding a server adds both compute and storage capacity at once.</p>
<p>StarVCenter's hyper-converged technology has the following characteristics:</p>
<ol>
<li class="">Hardware node consolidation: compute, storage, and networking resources are integrated into a single hardware node, forming a complete virtualization infrastructure.</li>
<li class="">Simplified deployment: automated deployment and configuration simplify setting up and maintaining the entire virtualization environment.</li>
<li class="">High performance and reliability: consolidating compute, storage, and networking improves system performance and reliability.</li>
<li class="">Flexible scaling: supports online scale-out and scale-in, so resources can be adjusted flexibly as the business requires.</li>
</ol>
<p>Overall, StarVCenter's hyper-converged technology helps users deploy and manage virtualization infrastructure efficiently, improving resource utilization while lowering costs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="free-tier-and-advanced-features">Free Tier and Advanced Features<a href="https://tanzhuo.xyz/en/2022/10/17/si-you-yun-bu-shu#free-tier-and-advanced-features" class="hash-link" aria-label="Direct link to Free Tier and Advanced Features" title="Direct link to Free Tier and Advanced Features" translate="no">​</a></h2>
<p>StarVCenter aims to be as easy to use as possible: the community edition is free (up to 20 physical servers), with free community technical support and free upgrades forever. For home labs and small teams, a 20-physical-server cap is effectively no limit at all.</p>
<p>Beyond basic virtualization management, StarVCenter also offers some advanced features:</p>
<ol>
<li class="">Multi-cloud management: manage multiple cloud platforms, both public and private, with cross-cloud resource management and scheduling.</li>
<li class="">Container management: manage containerized applications such as Docker and Kubernetes, covering container deployment, management, and monitoring.</li>
<li class="">Automated operations: automated deployment, configuration management, fault diagnosis, performance optimization, and more — improving ops efficiency and reducing human error.</li>
<li class="">Security management: access control, auditing, and log management to keep the virtualization environment secure.</li>
</ol>
<p>Project white paper: <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="before-you-deploy">Before You Deploy<a href="https://tanzhuo.xyz/en/2022/10/17/si-you-yun-bu-shu#before-you-deploy" class="hash-link" aria-label="Direct link to Before You Deploy" title="Direct link to Before You Deploy" translate="no">​</a></h2>
<p>Before actually rolling this out, a few things are worth confirming first:</p>
<ol>
<li class="">Enable hardware virtualization in the BIOS (Intel VT-x or AMD-V) — a prerequisite for any virtualization platform.</li>
<li class="">Plan the network ahead of time. In a hyper-converged architecture, management traffic, workload traffic, and storage traffic are best kept separate; at minimum, decide which NIC and which subnet each will use, because changing this afterwards is painful.</li>
<li class="">Plan the disks too: keep system disks and data disks separate, and don't pre-partition or RAID the disks that will join the distributed storage pool — check the official docs for the exact requirements.</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>tip</div><div class="admonitionContent_BuS1"><p>If this is your first time with this kind of platform, do a nested-virtualization trial deployment inside a VM first, get familiar with the install flow and the management UI, and only then install on physical servers.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/10/17/si-you-yun-bu-shu#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ul>
<li class="">The community edition's free tier covers up to 20 physical servers; beyond that you need to contact the vendor, so factor future scaling into your platform choice.</li>
<li class="">Hyper-convergence gets its reliability from data replicas. A single-node deployment misses the whole point — failure domains and replica mechanisms only become meaningful with at least three nodes.</li>
<li class="">Distributed storage is sensitive to disk and network quality. Back up anything important before trusting it, and don't put all your eggs in a storage pool you haven't validated yet.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/10/17/si-you-yun-bu-shu#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>StarVCenter offers a home-grown hyper-converged path: compute, storage, and networking all live on the same set of nodes, deployment and operations are simpler than the traditional three-tier architecture, and the community edition's free tier is generous enough for small-scale setups.</p>
<p>Right now I only have a single Inspur blade server at home. Once I get my hands on a few more rack servers, I'll try building a private cloud with StarVCenter 😒 — pair it with K8s and life would be sweet.</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Zookeeper Visual UI Tools]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/16/zookeeper-ke-shi-hua-uigong-ju</link>
            <guid>https://tanzhuo.xyz/en/2022/10/16/zookeeper-ke-shi-hua-uigong-ju</guid>
            <pubDate>Sun, 16 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A roundup of popular Zookeeper GUI tools: the commercial Zookeeper Assistant, the open-source PrettyZoo, plus zktools and zkui, with hands-on impressions and picking advice.]]></description>
            <content:encoded><![CDATA[<p>A few handy Zookeeper UI tools that can save you real time in day-to-day work.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-bother-with-a-ui-tool">Why bother with a UI tool<a href="https://tanzhuo.xyz/en/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#why-bother-with-a-ui-tool" class="hash-link" aria-label="Direct link to Why bother with a UI tool" title="Direct link to Why bother with a UI tool" translate="no">​</a></h2>
<p>Zookeeper's built-in command-line client <code>zkCli.sh</code> covers the basics, but it is clunky: browsing the node tree means running <code>ls</code> level by level, reading node data takes a separate <code>get</code>, and once nodes pile up it's hard to get any intuitive picture of the overall structure. When you're troubleshooting things like registry entries, distributed locks, or config nodes, a graphical tool that lays out the Znode tree, node data, and stat metadata in one view spares you a lot of typing.</p>
<p>Zookeeper's data model is simple at heart: a tree of Znodes, each holding a small amount of data along with stat info such as version numbers, ACLs, and create/modify timestamps. So the core feature set of these UI tools is largely the same — node CRUD, data editing, and connection management — and the real differences come down to UI polish and how actively the project is maintained. Here are the ones I've used.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zookeeper-assistant-paid">Zookeeper Assistant (paid)<a href="https://tanzhuo.xyz/en/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#zookeeper-assistant-paid" class="hash-link" aria-label="Direct link to Zookeeper Assistant (paid)" title="Direct link to Zookeeper Assistant (paid)" translate="no">​</a></h2>
<p><a href="http://www.redisant.cn/za" target="_blank" rel="noopener noreferrer" class="">ZooKeeper Assistant - a visual management and monitoring tool for ZooKeeper</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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/en/assets/images/2022-10-image-17-1aeb7611ecabc744bb394677184f3ed7.webp" width="1600" height="681" class="img_ev3q"></p>
<p>This is the desktop client from Redisant, and the interface follows the same style as the rest of their products: node tree on the left, data panel on the right, with node data and metadata visible at a glance.</p>
<p>Personally I don't think it's worth paying for — ZK isn't complicated, and the work is mostly CRUD on Znodes. That said, you can tell they put real care into the UI, which deserves credit. If your company will pick up the tab and you want an out-of-the-box experience, it's worth a look; for personal use, the free options below are plenty.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prettyzoo-open-source-free">PrettyZoo (open source, free)<a href="https://tanzhuo.xyz/en/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#prettyzoo-open-source-free" class="hash-link" aria-label="Direct link to PrettyZoo (open source, free)" title="Direct link to PrettyZoo (open source, free)" translate="no">​</a></h2>
<p>This is the one I actually use — free, and the UI is genuinely easy on the eyes.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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>It's a desktop app built on JavaFX, with installers for Windows, Mac, and Linux. Beyond basic tree browsing and data editing, it shows node stat info and ships with a built-in command-line terminal, so anything the GUI can't do you can just type. For me, "free + cross-platform + decent-looking + still maintained" adds up to an easy default choice.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zktools">zktools<a href="https://tanzhuo.xyz/en/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#zktools" class="hash-link" aria-label="Direct link to zktools" title="Direct link to zktools" translate="no">​</a></h2>
<p>Unmaintained for a long time — the GitHub repo was last touched four years ago — but thanks to the author for the effort all the same.</p>
<p><a href="https://github.com/zhangyawei117/zktools" target="_blank" rel="noopener noreferrer" class="">GitHub - zhangyawei117/zktools: zk visual tool zktools</a></p>
<p>Back when there weren't many options, it helped a lot of people. Now that more active alternatives exist, I wouldn't recommend it to new users; it's listed here mostly as a tribute.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zkui">zkui<a href="https://tanzhuo.xyz/en/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#zkui" class="hash-link" aria-label="Direct link to zkui" title="Direct link to zkui" translate="no">​</a></h2>
<p>ZKUI is a web-based tool, also last updated two years ago, and I rarely reach for it these days.</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>Unlike the desktop clients above, zkui is a web app deployed on a server, accessed by the team through a browser — a good fit for the "deploy once, share with everyone" scenario. The cost is that you now have a service to maintain, plus login and permission concerns; if you just want to poke at nodes yourself, a desktop client is less hassle.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and caveats<a href="https://tanzhuo.xyz/en/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and caveats" title="Direct link to Pitfalls and caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Be extra careful when connecting to production ZK. In these tools, deleting a node or editing data is often a single click, with none of the psychological friction of a confirmation step — accidentally deleting a registry node can take down a whole batch of services. In production, use these tools for reading only, and route writes through scripts or a change process.</p>
</li>
<li class="">
<p>Watch out for ACLs. If the cluster has ACL authentication enabled, a connected tool may only show some nodes or fail to read data. That's not a bug in the tool — first check whether you connected with the right credentials.</p>
</li>
<li class="">
<p>Long-unmaintained tools (zktools and zkui above) can hit compatibility issues on newer Zookeeper releases or newer JDKs. When you see a bizarre error, suspect version mismatches before you suspect the cluster.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/16/zookeeper-ke-shi-hua-uigong-ju#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>Among these tools, my pick is clear: for everyday personal use, go straight to PrettyZoo — free, cross-platform, good-looking, and still maintained. If you want a commercial-software experience and have the budget, take a look at Zookeeper Assistant; if the team needs a shared entry point, then consider the web-based zkui. None of these tools is complicated — pick one that feels right and spend the saved time on real problems.</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Setting Up a Zookeeper Cluster]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun</link>
            <guid>https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun</guid>
            <pubDate>Sat, 15 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Building a 5-node cluster with Zookeeper 3.8.0 on Debian 11: from the basics of Znodes and the ZAB protocol to zoo.cfg configuration, myid setup, and a Leader election experiment.]]></description>
            <content:encoded><![CDATA[<p>This post walks through the full process of building a 5-node Zookeeper cluster on Debian — first covering the fundamentals like Znodes and the ZAB protocol, then getting hands-on with configuration and running a Leader failure election experiment.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deployment-environment">Deployment environment<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#deployment-environment" class="hash-link" aria-label="Direct link to Deployment environment" title="Direct link to Deployment environment" translate="no">​</a></h2>
<p>OS: Debian 11.5.0</p>
<p>Runtime: OpenJDK-1.8.0_332 64-Bit Server VM</p>
<p>Zookeeper version: 3.8.0</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="official-overview">Official overview<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#official-overview" class="hash-link" aria-label="Direct link to Official overview" title="Direct link to Official overview" translate="no">​</a></h2>
<p>Official Zookeeper documentation (the downloaded zip also ships docs in its \docs directory): <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 is a distributed, open-source coordination service for distributed applications. It exposes a simple set of primitives that distributed applications can build upon to implement higher-level services for synchronization, configuration maintenance, and groups and naming. It is designed to be easy to program against, and uses a data model organized like a familiar filesystem directory tree. It runs in Java and has bindings for both Java and C.</p>
<p>The ZooKeeper implementation puts a premium on high performance, high availability, and strictly ordered access. High performance means it can be used in large distributed systems; reliability keeps it from becoming a single point of failure; and strict ordering means sophisticated synchronization primitives can be implemented at the client.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-zkservice-7e54d04876edac10ed3249727ed99fb4.webp" width="600" height="185" class="img_ev3q"></p>
<p>From the official description we can conclude that Zookeeper is mainly used for <strong>meta-information storage and configuration synchronization</strong> — a <strong>high-performance, highly available</strong> service with <strong>no single point of failure</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="my-take">My take<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#my-take" class="hash-link" aria-label="Direct link to My take" title="Direct link to My take" translate="no">​</a></h2>
<p>The name literally means the keeper of a zoo. Most Apache projects use animals for their logos and names, which is why the Zookeeper logo looks like a caretaker holding a shovel (doge). Zookeeper's own architecture is quite simple and easy to pick up.</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 is also a member of the broader Apache Hadoop architecture, where it serves as middleware for unified configuration management, naming services, and storage of critical data. In one sentence: Zookeeper = filesystem + notification mechanism.</p>
<p>Zookeeper (ZK from here on) stores data in something like a Key-Value form, except that in ZK data is stored as Znodes, managed much like the filesystem in Linux: every key is expressed as a file path, with multiple levels of directories. This resembles keys in Redis, except that Redis keys are conventionally namespaced with the : character, while ZK uses the Linux / separator throughout. ZK keeps its data in memory and persists it to disk, maintaining the tree-shaped Znode data structure (the filesystem) in memory.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-structure">Data structure<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#data-structure" class="hash-link" aria-label="Direct link to Data structure" title="Direct link to Data structure" 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 values don't come with rich data structures (unlike Redis, which offers list, set, hashmap, zset, string, and more) — a ZK value is only ever a String. ZK was never meant to be a database; it was designed to store the critical configuration of an architecture and provide notification and synchronization on top, <strong>which is why each Znode can store at most 1M of data</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="znode-types">Znode types<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#znode-types" class="hash-link" aria-label="Direct link to Znode types" title="Direct link to Znode types" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-persistent-nodes">1) Persistent nodes<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#1-persistent-nodes" class="hash-link" aria-label="Direct link to 1) Persistent nodes" title="Direct link to 1) Persistent nodes" translate="no">​</a></h3>
<p>Once created, a persistent node exists until someone explicitly runs a Delete command on it.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-ephemeral-nodes">2) Ephemeral nodes<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#2-ephemeral-nodes" class="hash-link" aria-label="Direct link to 2) Ephemeral nodes" title="Direct link to 2) Ephemeral nodes" translate="no">​</a></h3>
<p>An ephemeral node is tied to the session of the client that created it: as long as the client stays connected, the node exists; once the client disconnects, the node is removed. See where this is going? It's a perfect fit for a service registry — Dubbo connects on startup, and when it goes down its Znode is deleted and the other clients are notified.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-sequential-nodes">3) Sequential nodes<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#3-sequential-nodes" class="hash-link" aria-label="Direct link to 3) Sequential nodes" title="Direct link to 3) Sequential nodes" translate="no">​</a></h3>
<p>When you create a sequential node, a 10-digit counter is automatically appended to the node path.</p>
<p>For example, if I create the sequential node /node, the file path becomes: /node0000000001.</p>
<p>Create /node again and the counter increments by 1, creating a new Znode (the old /node0000000001 is not deleted — it stays), so the file path is now: /node0000000002.</p>
<p>However, once the counter exceeds 2147483647, it overflows.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-ephemeral-sequential-nodes">4) Ephemeral sequential nodes<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#4-ephemeral-sequential-nodes" class="hash-link" aria-label="Direct link to 4) Ephemeral sequential nodes" title="Direct link to 4) Ephemeral sequential nodes" translate="no">​</a></h3>
<p>The properties of ephemeral nodes + the properties of sequential nodes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zk-guarantees">ZK guarantees<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#zk-guarantees" class="hash-link" aria-label="Direct link to ZK guarantees" title="Direct link to ZK guarantees" translate="no">​</a></h2>
<ul>
<li class="">Sequential consistency — updates from a client are applied in the order they were sent.</li>
<li class="">Atomicity — updates either succeed or fail. No partial results.</li>
<li class="">Single system image — (globally consistent data) every server in the cluster holds the same copy of the data, so a client sees the same data no matter which server it connects to. [In theory it's consistent, though during data synchronization there can be moments of inconsistency — this can be resolved with the sync command.]</li>
<li class="">Reliability — once an update has been applied, it persists from that point on until a client overwrites it.</li>
<li class="">Timeliness — the client's view of the system is guaranteed to be up to date within a certain time bound.</li>
</ul>
<p><strong>Atomicity here is guaranteed by the ZAB protocol — short for Zookeeper Atomic Broadcast.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-zab-protocol">The ZAB protocol<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#the-zab-protocol" class="hash-link" aria-label="Direct link to The ZAB protocol" title="Direct link to The ZAB protocol" translate="no">​</a></h2>
<p>ZAB has two modes: message broadcast mode and crash recovery mode.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="message-broadcast-mode">Message broadcast mode<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#message-broadcast-mode" class="hash-link" aria-label="Direct link to Message broadcast mode" title="Direct link to Message broadcast mode" translate="no">​</a></h3>
<p>In a Zookeeper cluster, data replicas are propagated using message broadcast. ZK's replica synchronization resembles 2PC but is not the same: 2PC requires the coordinator to wait for ACK confirmations from every participant before sending the commit message, demanding that all participants either all succeed or all fail — which makes 2PC prone to severe blocking.</p>
<p>In Zookeeper, by contrast, the Leader waiting for Follower ACKs only means waiting for a majority of Followers to respond successfully — it doesn't need every Follower's reply — which cuts down the waiting time.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="crash-recovery-mode">Crash recovery mode<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#crash-recovery-mode" class="hash-link" aria-label="Direct link to Crash recovery mode" title="Direct link to Crash recovery mode" translate="no">​</a></h3>
<p>Once the Leader server crashes, or network issues cause it to lose contact with a majority of Followers, the cluster enters crash recovery mode and elects a new Leader.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-the-cluster-5-nodes">Building the cluster (5 nodes)<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#building-the-cluster-5-nodes" class="hash-link" aria-label="Direct link to Building the cluster (5 nodes)" title="Direct link to Building the cluster (5 nodes)" translate="no">​</a></h2>
<p>A ZK cluster needs at least 3 nodes, and the node count should be <strong>odd</strong>. The number of node failures it can tolerate is <strong>node count / 2</strong>: a 3-node cluster can keep serving with 1 node down, a 5-node cluster with 2 down. The rule is: <strong>as long as a majority of nodes are healthy, the cluster keeps serving requests</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-roles-in-a-zk-cluster">The three roles in a ZK cluster<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#the-three-roles-in-a-zk-cluster" class="hash-link" aria-label="Direct link to The three roles in a ZK cluster" title="Direct link to The three roles in a ZK cluster" translate="no">​</a></h3>
<p>Leader: only one node in the entire cluster is elected Leader, and only the Leader can perform writes. (This design inherently makes ZK's write throughput a bottleneck, but since most operations happen in memory and each Znode holds very little data, write performance stays reasonably high.)</p>
<p>Follower: a cluster can have many Followers. They maintain heartbeat connections with the Leader, can only read, not write, and take part in electing a new Leader when the current one goes down.</p>
<p>Observer: the Observer sits at the very edge of the cluster — it can only read and cannot participate in Leader elections. Why does this role exist? Because a write in a ZK cluster only succeeds after a majority of Followers confirm the sync, so the more nodes in the cluster, the lower the write performance. Observers were designed to scale the cluster's read capacity without hurting write performance: they only replicate data from the Leader and serve reads.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-428604b1d2f3ba5ce2ecc4fde939121b-fad0baa9cebc4c2505c42de855a2494b.webp" width="500" height="350" class="img_ev3q"></p>
<p>The official performance chart for ZK clusters of different sizes shows request throughput rising as the node count grows. To be precise, though, I'd say it's read capacity that rises while write capacity drops: with more nodes, a Leader write has to be synced to other Followers via the ZAB protocol, and only counts as successful once a majority of Followers have replicated it — so more nodes inevitably means lower write throughput. This design also shows that ZK isn't suited to write-heavy workloads; the officially recommended read/write ratio is around 7:3 or 8:2.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="configuring-zoocfg-download-and-extraction-skipped-here">Configuring zoo.cfg (download and extraction skipped here)<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#configuring-zoocfg-download-and-extraction-skipped-here" class="hash-link" aria-label="Direct link to Configuring zoo.cfg (download and extraction skipped here)" title="Direct link to Configuring zoo.cfg (download and extraction skipped here)" translate="no">​</a></h3>
<p>After extracting the zip, go into the conf folder and rename zoo_sample.cfg to zoo.cfg, or copy it out as zoo.cfg.</p>
<p>Adjust some of the defaults in zoo.cfg — the most important change is the dataDir path. ZK defaults to a tmp path, but Linux periodically wipes that temporary directory, so we need to move it somewhere else.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-1665826644086-c156c215baba24939267268b3a9cf3ea.jpg" width="1294" height="892" class="img_ev3q"></p>
<p>Remember to save after editing. I set the path to /opt/zookeeper/data; the other nodes all need the same setup later.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-the-myid-file">Creating the myid file<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#creating-the-myid-file" class="hash-link" aria-label="Direct link to Creating the myid file" title="Direct link to Creating the myid file" translate="no">​</a></h3>
<p>In the folder at the updated dataDir path, create a myid file and write the node's unique ID into it.</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>Each of the other nodes also needs its own ID set — no duplicates allowed.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="configuring-the-cluster-member-list">Configuring the cluster member list<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#configuring-the-cluster-member-list" class="hash-link" aria-label="Direct link to Configuring the cluster member list" title="Direct link to Configuring the cluster member list" translate="no">​</a></h3>
<p>Add the server entries to zoo.cfg on every node:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-1-0293760e7e0428371702a80c7559a9d5.png" width="1113" height="1010" class="img_ev3q"></p>
<p>The 1 in server.1 corresponds to the ID in myid; the value after it can be a node IP or a hostname. Port 2888 is used for data synchronization, and port 3888 for election voting.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="starting-all-the-nodes">Starting all the nodes<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#starting-all-the-nodes" class="hash-link" aria-label="Direct link to Starting all the nodes" title="Direct link to Starting all the nodes" translate="no">​</a></h3>
<p>If fewer than half the nodes are up, the cluster won't serve anything:</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABDoAAAC2CAYAAAA4EO0JAAAACXBIWXMAAAsTAAALEwEAmpwYAAAgAElEQVR4nO3di3GrPLcAUFfnhn5X4wJuF8etfO6EO07ihIeQtkB+wVozmTnHkZEQgqCNJA7dx7h0p+O5u3bv5tqdj4fudHl1OVjv0p0Op86h/NRzce9atd+tnQdb259Pt4Xrx6vbVG3+ry7vu1Iv6qXUFrQR5xqf7NB9yI3Q5XTojufPvjXi3d3+oB26w9ePm8LfOnEu7qzd3oK3x247l1s3qi+t+01eP17dpgQ6XlOPe7HHehHoeKjruTt+fICbT3R465Pi9+b90B0MmQDnIs9xOW0o4LfHm/Y3ssm/5a9uUwIdr6nHvdhjvQh0PK5ev6/9m7j083HeN9ABAKvt8aadx/q0NvVp5X0W9aJetAXYMoEOAAAAYDMEOgAAAIDNEOgAAAAANkOgAwAAANgMgQ4AAABgMwQ6AAAAgM0Q6AAAAAA2Q6ADAAAA2AyBDgAAAGAzNh7ouHSnw6E7HE7dZS7F6fb7v5/TZW4buTRLinbqDsdzd+3ewEvKcqvX+eOyKYP6rdnvRnXUz/9Zx7o6n+C+3rY7OB930oZm7eg8AgCAzw90xG7gB4GKQcfq/v357VzPx9F3rt35eOzO11GQY9Rhu+V5/Eu0wC2fRgGT1Z2fR5dl7x20cf0+P9Dx1c5/CtD/9+MsaVPlfZ2er/fzfw/t6J3OoxZ57uX8BwDgFT440PHdmep32r46PZPe1dx2xkGN+8fn7vi7jUt3TgY0Vt6kP/ypekX5XjayZCcdnUn9Pnu/+0GHJwW1FrWpUr3kApbn7bejWQIdAADQINBxv7H+CTT8jKa4j3D4euraG1qeGvmQTzOdKpIcop7qTN2CFJMO1lxHIPN5sZOW22bpCfN8Z7NcL6fuMhq6P9xOsO6yZZnbxnQ76451og5/9m0aqxruc3I0TTZNpO6C+RTaf6x+a45jpu3e2nv/+PTb7fh34Tax1tI2VWoPlcG7VccxEwTt13Hz9jJn+N3vn3v5Am2hST73vAJtKVuWyDai1+xSeQEA2KuFgY5jdxx1BG5PVf8vORVkeFOfni6SuvHPd2xuozd+O1P3G+vjsTtOvrNgREdkCH3ysXgg0DHztLtcL6lpNHNBk4r1DqIdolvaXkaR45hPMyzjbJ1+dSb7xymxz8U0gbqL5FNo/5dQ/bY4jukpVck6fPb6HIvbVLk9hKapNDqOt/zH16TBZ49oL0n5tvGvpi2syCd2Tf35XagshW0UAx0rr30AAGzawkBH6gYz8hQ0+KT0N5/cSIT7dno3vF+dj/hN+VxHvBioWHwjPXdzHqmX7w7T+rorlSWRctJRWX+s/+uVcX7Nk5kyTvIppcnX3X+hbZTaf7DsTY7jzDYS6Z+7PseaNhVpD6VRRA2P4+Q7M9ectfk0mdITawvr8mm97bWBDgENAAAeMnVlyec1343eCJe2n78hHnacbh2GTPrJU9xWT7ufWXelsgylnmy3KO+/3yfdkWH/y/OJl6Vh3a4+1kvSpj/vBw3WL6JbsLpNldpDaZ2elsdxFKibBM4at5dFCy7XlmVNPtFtp6aTpILHawMdNeUFAGBvdh/omEg+lW0Q5Mg+7X52oCP25D3dIW0b6PhbZyD2at/02g4L1n+YlCW6lsXa0TKPD3Tk9+dR6xi0aFOl9pAybnPtjmO/rMNyt24vcYPA7PcwtmaBjnw+kW3XTENcH+iIlxcAgL1pGOj4vKkryb1LPfVeHeQoPe1+8tSVwJP3/NP/tlNXfj+f7N9tG5GgQilNZOpKw2H72fptNXUl8Hl/vZlHr8+xuk2V2kPkDUiNj+PvMRm35cb5LLJkNNLyfFYF4JLrHi0IdATWTzKlBQCAxoGOFgtUDraW7ig3W4z00p1GT/6mZft7Uhh/upx6qlt+2h1ejHSw7co1P4JlGdTt4vLWLUb6k3Hs7Trj7xbTBOouks/sZzX1W3Mc1wU62q7PMde2c+WvaVOl9vCTf2qB0v5nzY5jb79OiW02zae3f8nvJ87ln/wnAcPqfGP5XEPXl9wbd1LXxPz1PbX+yrA9RMsLAMDeNA10tHm9bD+r/usbIx3gcTkLw8pHr4dMD9GueT3nzE19xdoF8/USfEVqct9KQYE3eb3sb/pRB2byqtS5t+XkXosZqLtIPqtHy5TKsn4qzl/spv+61DULYvbLlci3SZuKtIfEdtLvaV5/HAf5z1yjGuaTDyQl9nvReiHRMgTWvpi9No+3kVn3KHd9n6z1kdpORXkBANiVBYGO9/H1NPftb2xbdDJbDUVvVZZP86jpA2N7rN897jMAAPDOPjrQMR0x8IzO7NY761uk7gAAAPbi4wMd+6Gzru4AAAAoEegAAAAANkOgAwAAANgMgQ4AAABgMwQ6AAAAgM0Q6AAAAAA2Q6ADAAAA2AyBDgAAAGAzBDoAAACAzRDoAAAAADZDoAMAAADYjPWBjsupOxzP3bV7Az9l+a+7dKfDqbvEv1iZfmm9PCAfAAAAoFWg49qdj4fu9BY9935ZXh3omKsXgQ4AAAB430DHG47muL5DQOGd6gUAAAB25NBu1MJccGH8+ff3Dof+z7E7X+e+N0x/nCacL8st4NDLJz3C4v77TNmv5+7YL282iJEazVHK5/7VXD637Sbq6fadcXmqygsAAADbcWg3aiES6KiZ0nH77NgdR5376/k8zSNZllSgYG6aTa7s00DB9XzsDnPzdbKjOXIjTS7dqZDP7f/jQM/0s/J2AAAAYKsWBjrmRi2UAh01U0q+gwzl/vlcWYKjH4plz402iZQl8r1g+kn5Z0Z5rM4XAAAA9hToqHqjyPDzyyk6pSLYOV9RluafF9fmyO1TakrPeKrLKLCRDNxEtgMAAADbdHj8G0XmO/dfUyruHfHJBiOBjlZlafF55A00+ZEg0/VHpun7U1Wm01bi2wEAAIAtqg90zI5amOlMfy2MWepkz63RUfhetiytpq4EPw+9aaUyn1Td/e5DatrKmmMAAAAAuwt05Ect3KalpEYY/E2bmAlAVE0/iZTl/paT6dte6hcjjb9JpryeSM3aHql96KU9peqsZjsAAACw90BHcdTCeH2IW1Bj3Lnvv2o1t1ZHIdARebtJ9vWyiXJM1rMIBjpCozlK+zQuT6ruhlN+0q/ajW8HAAAAdhzoiI5aeIZPLYuAAwAAALzh62VZJDzyAwAAAFhCoOPhhlNJ3mMUCgAAAGyTQAcAAACwGQIdAAAAwGYIdAAAAACbIdABAAAAbIZABwAAALAZAh0AAADAZgh0AAAAAJsh0AEAAABshkAHAAAAsBkCHQAAAMAeAx2X7nQ4dIfkz6m7NCvSLZ/E9i6n7nA4dudrt2HjOu7Xw8/vjufurwqu3fnYT/f9/9MlUXeD772DmeM8+P2j2li0DEvTPsLK/L/On9Z1+YxjBAAA8OQRHdfzsXEnOtGh++qk7a0TNa6He6eyF+y5nrvjuIN5+2wSDHnHANE7BBneoQxRa/J/RhuIlO/VdQgAAOzBukDHV0e7dQdq1BnaZZBjLtBx6k6nQ3f8qfCvINPpNOk8Xk69UR23+psM8XgH7xBkeIcyRK3J/xllF+gAAAA+PtDxPU3i3un+/fTW+e4NZx//vpym12H6CqRkOmiD4fiJvLK//8lnlCYZEyjlMyj3fTpJLu3yQMdXeb9GbNynqSQ6mL+jOlZ0cCN19zui5OcnNbJnNk1F/Uf2I3KMJnktCHRkyzszdWgyyiaSXaCsxX2em26WaC+5YzRXF8XPKssBAADwykBHasrK9LNpMKScpt+JzowWmazZMepkln6fWfNi0FEtbqe/vWN3HE0RuJ7PjZ7C9wMpx+58uQeB0h3M26iOW1kWBVoW1d19hMmg8rrTbJpg/Sfrora8qXVKxuubFCsl3l5GO3Db56rjEClruF0GghDZY9Qi0FGTBgAA4BWBjmQQYmYdgMHT7Eiav7Uo/ve/ufU/8k/O/ws9Wf8OTJTLG31C/13udrNE5gIdvRExuc7oz9P++vKsqLuKzu6/UP1Hthspb6bdVQU6IuUdp6tdHyNS1tqRI7UBhv4xEugAAAA2H+iofere/zya5t7RS0+PKW0n1kFrlaZUpvaBjmGgKZXvzzE6LXnbSst6GU7j6U9ZaNeJbn0c58S38TWa5t5mq6etPGJ/yoGixx6jmjQAAABPDnRMpyfcPSi4kBw9kn/V7b/Qq3CjZYm+UveJgY5SvpN1PGrzbVF3+SBV20BHi/JG6iW4jV5wo3raSvN2mdvms45RTRoAAIBnBjqyQ/1bTl2Z67j388p1mEq/v+cTKe8z3spx6S7FaQh1oygGa5VUjSiI1t3CoNZPG2o7daVU3mdOXekfgyWvdY1OXamfihIOgAyO0fzvBToAAIAPD3SURwc0XYx0tO3BdIDZDnzvu6Xf/z4VzwQJQtvJfRb1XZb+/k32N9pZnVnIsmpUR6juYiM6hvn+1fm/aP1H9iFyjCZpUvnn1JT3nt9tcdraqUPBsobbZe7z/DG6JNvh3CKukXa2JPADAADwsEBHbLh809fLJvIfdKTGr8UcP3HP/r7i9aalfLLlDhrnMSnI0uDCklEdwbrLluX+//E2RtMiQq+XHZdnbpRB4Rg9/PWy4/QrXi8cKWuoXfbKniln6hj129T87+fKM5PfYL9MYwEAAN7o9bKfz3oBPJLRCwAAAK8g0PGSamfzloyiAQAAYDWBjvV1CImpINaiAAAAeIUdBzoAAACArRHoAAAAADZDoAMAAADYDIEOAAAAYDMEOgAAAIDNEOgAAAAANkOgAwAAANgMgQ4AAABgMwQ6AAAAgD0GOi7d6XDqLuHPl25v4y6n7nA4/Py0rM8nlPt47q7dXsvypsflgfX7X/U+P6COnnWs36F9F68Nb1beZ+8zAAAECXQ81bU7H4/d+fppHepbuQ/d6SGFqt3fR5bl047LI/Tr99WBjmcd61e1qdprwzuV99n7DAAAHxHo2KMPrauHPj2urJOXPcn+0GNXa1C/L97nPY3mqKnrtyhvCzs5pwAA+PRAx/eTxr+hyLef8RO7W/rA1I3ruTv2t5O6sR8Me7494Vxw4zzZxjTJ9XwcpDkOdihS3v4+93/6ZS3VS+0+37e3tiMx//Q4VC+z9Rupk0hZ5rYz3Va+vMH9GWT9vW+Tuhnt8ziffJpSvVXm81vu4bmZTjuu32hZGp7Ts2V5lEw+hf1ef11Ych6kjlGb9h9NU1TVXjL73OL6DgDALjULdPyb7TDUjgT5uQkedQi+bsD7G/+6Ce5//96RqxwdMEp/OSU6B4OyfOcz7JgGypvd52Caqn1uFOiYeXq8rF7mAhUPeJJ9S9vLqFTe2P78lTN9fO/HqB/cS+xzNk2w3iL5/Jb72B1HUwSu5/O0zif1Gz2Gjc7pbFkeZC6fwrWh7XWh4jyI1ktl+4+maXFNbX+tAwCAhwU6Ws3n/+6YTe+txyNHEmm+np5GyxB5YpzJZzC8v1Te3GeR7xXK8rCb/7k6WlEvgzT3dJHyx5/wTzuT+fL+F96f73JeTtEREant5NN8LwBaKkskn27Q4S7X21wQKnIMW5zTpbI8u33n8m99Xch9XlOuZe3/r03VHOvl5XvPax0AAFvSdOrKrQMYG5Ye29785y3WC1kTdFhallZ51m5zodmnxy3rpe2T7FsnbxqEWBqkG+/PbWREZJpIaTstytKyzc3Vb6u2WPn5q0dzFOvsEdeowHEK1MuS9v/ca2ptudZsHwCAvXrYYqSDud5VQ7Xf6aZ8z4GOpVMUHhHoiD/JXhKAqAl0fG9+bpREZL2EfJp4WWLrMqyr31cEOl49mmOuXK8OdJTrZWn7F+gAAGDHgY7bjfbMzXL2KeMjOkXvNnXlGYGOJw/nzj49fvLUlcCT7PnpJO2nrvz+brJ/c+dITZro1JWGAb3syIZWU1eCn798NEfLqSsNAx2FelnT/k1dAQBgx4GOn6kpo7v/v89mOkWrpj9kPn+rxUifEeio3ef7U/8lQZDY0+PQYoyJ8g63O9MJqyjLrQ2G1i1ouBjpT8bpN0WUpoBk0vyL1lt4qsmakQLRY1jKK/p5dDRHpG3n0gTyabIYaatAR768Ldp/NM2wvHNvSslfU9tf6wAAYEWgoxuvwzGZlpIYUh96zep4aH/F0993eb1spLzZtMGpCM94vWzFehjLXy87t0+1T/hf+HrZ3++MAjXjV4rOjYpIpqmot0g+q0YKRMrS8JwOj+ZYGeioeXNJ5hi0uy7kPo8co1e8XrZQv6W2W9rn5Has0QEAwIMCHe+tNI1mi1rvc6s1Elp0Sp61XsM7eWZn7p3q9x3W5nhHn1beR9rj9R0AgJ0FOqY3vfl56lvwSfvs6at6gz1c6wAAeDcfHOhIDN3fw2PPj9lngQ71Bnu41gEA8G4+O9ABAAAA0CPQAQAAAGyGQAcAAACwGQIdAAAAwGYIdAAAAACbIdABAAAAbIZABwAAALAZAh0AAADAZgh0AAAAAJsh0AEAAADsO9BxOR26w+Hv53//Lt2p9//D4dRdylvpTqF0LHY5VR6TZ3jEcR+3v0N3uuTSpfMft+tDciP5bbQ0Ls93cZxr0+NROhZbvdYsaQuvLstWj8VO/zYO/sbM7N9smhfuZ/Xfxnc61wCABwQ6rt35eOiO5+vohqH/Rz968/KMm5wPumFsXt7bsTp2/UP1Hlofk58b0OO5u446HIN2+ptvOv+vDsoosDH9LL+NdiLnWb88ez7X9hzoeNd9zJXl2eV8Vn77+9t4PR+T193+PufS/HtZm137t/GdzjUAoEmg43bTMuw8/rjcun7veBPwTmV5dnnfdd/bBzrOyTvWuXxSn9ekzX3eRuw8e3w56gh07LP+U/YX6Njf38b571/P5+KIjVua1wU6HrfvAMBHBjqiT0FKNwH9IaCZdKPhrsmn89dzd+wPJ/19cjSdyrBsuOlPPqOypGY0fD25mi1vv16+n/wN07Uqb2zf82WNdO5L9d//2rju5gINrYcDf2qgo+Zp46PPtchxbtl2vxpnvk1F2lO4zc2dj6V6+Sro4HvfP+PjFkkzrsel7arcJsvXqFTdJTdUOO8DgY7AtaNc3lz9Nm6XWTv+27h6f2vybXGOFPY98jetWO7geZ89jgDAEwMdrYfdZtJ93QD0bwy+bxz+brrT0xW+boyTUw2WSuUzLktqeG5iGPPXto7dcXRD/Pf0q0V5x2Wfbqtc1migI1D/t+OYyCs9BaNtJ2TaFlL7kU8/nf6S30YbNdt+9LkWPc9qy50pZy6vSHuqanOZ8zFbL9NrwLQOImmeF+gIXaMS06Mup0S9rbruxtpUubzR+n2naSBb+ts4naZSn+bZgY5SvpG2nft+RbssHkcAYIOBjpk/+LcnLYOnUqknaK1vcmfyGZRl5mneIM19W6UbmUcHOiJljQY6SvWfyevRHY/kehapMi54ml3axkcFOvLn2n/h86y23DW+t/sv1J5q2lzufIzUy7utC1LqfEWuY0s7WjXX3ZXXjuJ1qlS2R9jj38b4KKH5NJ8wdaX2WhfZduQ4AgAbDHRUTJ3onhHoeGZZHh3oaLU/LeulscmTslj+scVI89v4vEBH/vP5OfSPCnSkhnyXFi5c2uaWdFb+Ph+8XWOmcxJJ0866/Ykfv/ljFA90tDmOsfrdUqDjnf42ZtpGNlrWT/NugY5I2859P9ouX/S3EQB25gVrdJTS3Yen5m44BDrKdhjoKAY55vJf07F45zU61p1rzw10pKZ7/W33+YGO0jVo5on1TCcvkub9Ax35Y/TsQEesfp/Redzj38bask/TvNdipNG2PfdZWrpd1l1fAIAnvXUleZO+aGX53FOVpSMf3n3qyqtHdKwYEj4Y/h+p/ydPXQkFOcZlzH225PM2YufZM861ZwY68u3uMVNX5sobuQZV7EN1miWeMXUlcm0olWXltaM4MuZZHfpx0fb3t7H8tqt8mvcKdETbdiZtOM+l1xcA4GGBjtgCdtG53pmbhcmCguP00Zu5mqfjc2UcP2VZsxhp5InpmvKOyz7z9LlQ1ukinOMFHYP1PzmOqfrMfR5zf2oWe1D+rKkra/YptlDko8+1uhEda9tual9GdRhpT1VtLnNsivUy8xaF0loJye3myhmVbwttFiMNHKNiWWJtKrZocqR+a0ZbrK//3f1tzF4zS2lqAwZrj1F/O5Fr6Vx+pfYdaJfF41gqAwDQONCRmIOa6mAOXtE2fooUHLI5fs3b4OahogM2WGDyHV4vW8pyTXlj+dW/tvFW9wuHlj/89bLRNpVKN+7UjX6fXG0/OtR4/Q1q8Tx7+LlWOaJlddsdl3nc7h7xetmM4jVoVK/JjkspzTjtinN+ti20fL1s4BhlyxJvU7FraqB+Q+2yTYdyP38bZ8ocuWYunsLxyEBHRdsunmvBdpk9jq33GQD2Z1GgYz9eNbQWAAAAWEKgI0ugAwAAAD6JQEeWQAcAAAB8EoEOAAAAYDMEOgAAAIDNEOgAAAAANkOgAwAAANgMgQ4AAABgMwQ6AAAAgM0Q6AAAAAA2Q6ADAAAA2AyBDgAAAGAzBDoAAACAPQY6Lt3pcOgOyZ9Td3lkKXmA2/F03D63XlLn4yPL/Sn18oz9KG1jfGy2UG+fbCttFwCABwU63Cwu98z6i+TleG6jXiKd7hb782n18spAR+v8nunTyrvXfQIAIEegY7eBDrbhWYGOrRDoeHz9AADA5gId93TX7nz8G759PF+r0lzPx8Gw/OH3a/IqFfc0yOeU2MF8WX7KcD13x/5w9eO5u2an/CTqcnYb0fJG8uqnSR3P0v7kylLbSRoet++fYzc5fKN86tvC7bPEdm/7mDxOmX0ItJdYeVuZq/OKdlfcfum7weMY2Y9i/a69dkTzyZ2PFdvIXje/92HyvUG7DNZb4Hwt10uraYm5Y5Rrr/fPI/sUTfMu1zoAADYQ6Dh2x1HH8no+jzrb82m+bsgHN5vfN8zTDmMkr1xRbzevw326nIb/L5fl52Z6dHP89b1BDybw5L20jUB5Y3nl0gT3Z1KWe6cmevM/08kbl+srn/7xzX0v36bG7Sf1WbbuIvUfLu9Wpq4Ej2No++N2l9r22mtHRT6z50B0G4F6uLWX0ZfS7XJ2A6Hzte6a2mJqT/oY/QsHOkr7FL3uvsO1DgCADSxG+p0u37HLpYk8fa/Ja06kAxodCZB6ej2+uV7Sgeh/p6bDvDbQUdqfTL00Xbug5ol3oS1MvjOzD9Ud+qXl3Uqgo+X2I+f92mtHNJ+5Mp5+Ous116jS8RkHxmpGw6w8X6vLGy1T7QiXcaCjtE/R6+47XOsAANjQ1JWlaWo6VGtuytcGA0bDrIvfLeWXGv7fDyK1XgCxtuO6ZJ8LpTwVhos3bQujDstsx3bNvj06GFCTZ+u857dTPo5rtl9zHrVsu/PnY2xUQqS8PylOvZEV1UGx1udrq0DHo49Ri3163rUOAIDnEuh4i0BHZBj59gIds+sHjIenh9YOKOfdnxIwPz1gbaAjWt7tBDrKx/HTAh3587F1oKMf3KibthLdH4GOd7rWAQCwy0DHs4ZZt5y68qAO2mB49DOnrkQ6go8azj3Op2Fw5/e45aYHzG0n2l6e3fl5r0DHuvTRqStrrh2RfPLnY9upK/22VTttpcH5+rCpK2uude80osPUFQCAT/SGgY4nLpzXbDHS6FD4uU7M3KKLo5EA4cVIIx2mNTf/LRbom+ks3rbbr+/x/2fLWNGhPKW22XAx0nDnMXGMq1VO2WmeT/A4hrY/rou582LNtSOST/58/Bcua+7zVH3dFvCsnfoTO1/j19QW7SV/jAZTdXplObxloKP2WtfinAYA4E0XI10X6Kh/vewKrV4vGynbIK/Uzfj49ZylbWQ6UMm8Isdw6f4seeViojypjt749Y/hRQjnj2Xs1ZqJ47TktaSzr1pd0ylKlTkXoFkyhabmnA8cx6avl53X5vWy8+fj79SV6Lk4aA+5kQ5LXkUcP19j19Q17aVUprn1T8bXuvWBjn8vu9YJdAAAfFigA0qmr+SEmL2vedBq5A3P4VoHAPDOBDpodqM/HZIOFe1pz4GO6qk+PI9rHQDApxHoYLnxFI2qt2xA314DHfepDkZzvDXXOgCAjyLQAQAAAGyGQAcAAACwGQIdAAAAwGYIdAAAAACbIdABAAAAbIZABwAAALAZAh0AAADAZgh0AAAAAJsh0AEAAABshkAHAAAAsBkbD3RcutPh0B0Op+4yl+J0+/3fz+kyt41cmiVFO3WH47m7dm/gJWW51ev8cdmUQf3W7HejOurn/07tDgAAYF+BjlgnbxCoGHTg7t+f3871fBx959qdj8fufB0FOUYdw1uex79EC9zyaRQwWd1BfnRZ9h7oGNfv8wMdX+38pwD9fwMAAGzRBwc6vjuQ/U7bV9Bj0omb2844qHH/+Nwdf7dx6c7JgMbKDujDn6pXlO9lT/h3EuiY1O+z97sfaHlVUAsAAOCtAx33jtpPoOFnNMV9hMPXE+PeNI/UyId8mulUke+fUecw1UG/BSkmnfa5jmXm82LHP7fN/FSZXGezXC+n7nLb79lpNMG6y5ZlbhvT7aw71ok6/Nm3aaxquM/J0TTZNJG6C+ZTaP+x+q05jpm2e2vv/ePTb7fj34XbBAAAwC4DHcfuOBoNcT2fu/9LTgUZdgLT00VSHcX8k+/b6I3fzuG9U3c8dsfJdxaM6Ch0AueH/wcCHTMjKMr1kppGMxc0CY4aqBnNcUvbyyhyHPNphmWcrdOvgED/OCX2uZgmUHeRfArt/xKq3xbHMT2lKlmH1ucAAAB2ZmGgI9UpywQOfjtkkTT9fHIjEe7b6XUSvzqr0UDHfEe8GKhY/DR8rkMbqZfvDvb6uiuVJZFy0oFef6z/65Vxfs2TmTJO8imlydfdf6FtlNp/sOxNjuPMNhLprc8BAADszYqpK0s+r/lurrM+t83a7YynVtw6j5n0k6f+lSi7PBgAAAH6SURBVGZHUDyz7kplmdZPfLRNvLz/fkdGRKaJLM8nXpaGdbv6WC9Jm/68H0Rav4guAADA+9t9oGMi+WS9QZAjO4Li2YGO2GiOdJCjbaDje/NzoyQi64VE05TKEl3LYu1omccHOvL7cw/oZXcAAADgYzUMdHze1JXk3qWeeq8OcpRGUDx56kpgNEf+6X/bqSu/n0/277aNSFChlCYydaXhK1+z9dtq6krg8/56My97uw4AAMDHBjpaLFA52Fq6o9xsMdJLdxo9cp+W7W9qS+yVnHOLkZZHUIQXIx1su3LNj2BZBnW7uLx1i5H+ZBx7u874u8U0gbqL5DP7WU391hzHdYEO63MAAAB71DTQ0eb1sv2s+q/gjHSAx+UMvKK2//vp+0YrpjT009eWNVIvwVekJvetFBR4k9fL/qYfBWkmr0qde1vOXJpg3UXyWT1aplSW9VNx/mI39/qOLzwLAACww0DH+7h15N5/OH6rTmbdFJzHluXTtKi7iL3WLwAAwPv46EDHdMTAMzqzW++sb5G6AwAA2IuPD3Tsh866ugMAAKBEoAMAAADYDIEOAAAAYDMEOgAAAIDNEOgAAAAANkOgAwAAANgMgQ4AAABgMwQ6AAAAgM0Q6AAAAAA2Q6ADAAAA2AyBDgAAAGAzBDoAAACAzRDoAAAAALqt+H8sC4hirl7A5QAAAABJRU5ErkJggg==" width="1082" height="182" class="img_ev3q"></p>
<p>At this point it throws an exception and refuses ZK connections, so all nodes need to be started.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="verifying-the-cluster">Verifying the cluster<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#verifying-the-cluster" class="hash-link" aria-label="Direct link to Verifying the cluster" title="Direct link to Verifying the cluster" translate="no">​</a></h3>
<p>Once every node is up:</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>You can check each node's role and running state with zkServer.sh's status command.</p>
<p>Connect to the cluster with zkCli.sh and run some commands to make sure everything works:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-5-0820db64e27c2089db99d00fd216ab45.png" width="1133" height="646" class="img_ev3q"></p>
<p>Then test with commands on the other nodes:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-6-ac15ca49fe2fa89961cabb52a29c41b5.png" width="1097" height="574" class="img_ev3q"></p>
<p>Everything checks out — the simplest possible ZK cluster is up and running. This build followed the most basic flow; you can keep tuning the cluster through configuration later, <strong>and harden it with SSL communication and passwords for better security</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-failure-experiment">A failure experiment<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#a-failure-experiment" class="hash-link" aria-label="Direct link to A failure experiment" title="Direct link to A failure experiment" translate="no">​</a></h3>
<p><strong>Let's push the testing further: stop the Leader node and see whether the cluster automatically elects a new one.</strong></p>
<p>Find the Leader node and shut down its service.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-15-d408ba5e077788935ad4ebe8f7cdcd02.png" width="1023" height="267" class="img_ev3q"></p>
<p>The current Leader is the debian4 server, so we immediately run zkServer.sh stop to shut down its ZK service.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-1665900721220-61d412e62856fbc99e948132720c2acf.jpg" width="1010" height="363" class="img_ev3q"></p>
<p>Checking with zkServer.sh status, the debian5 server has been elected as the new Leader — cluster elections are working exactly as they should.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-16-eea82f99208fce0d17c1684ccbddce2b.png" width="1060" height="272" class="img_ev3q"></p>
<p>Official configuration parameters: <a href="https://zookeeper.apache.org/doc/r3.8.0/zookeeperAdmin.html#sc_zkMulitServerSetup" target="_blank" rel="noopener noreferrer" class="">ZooKeeper: Because Coordinating Distributed Systems is a Zoo (apache.org)</a></p>
<p><a href="https://tanzhuo.xyz/zookeeper-ke-shi-hua-uigong-ju/" target="_blank" rel="noopener noreferrer" class="">Zookeeper visual UI tool</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/15/da-jian-zookeeperji-qun#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>This build followed the most basic flow: change dataDir, write myid, register all the server nodes in zoo.cfg, then start them one by one. The core rule governing whether the cluster can serve requests boils down to one thing — a majority of nodes must be alive. The failure experiment confirmed it: after stopping the Leader, the remaining nodes quickly elected a new one with no service interruption. Once you understand the Znode model and ZAB's majority-acknowledgment mechanism, the design intent behind all these configuration options falls into place. Before using this in production, you should still add SSL and authentication.</p>]]></content:encoded>
            <category>Java</category>
            <category>Tech</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Installing Containerd on Debian]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/15/debian-an-zhuang</link>
            <guid>https://tanzhuo.xyz/en/2022/10/15/debian-an-zhuang</guid>
            <pubDate>Sat, 15 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[The complete process of installing Containerd on Debian 11: download the cri-containerd-cni release package, set up environment variables and a domestic registry mirror, then start the service with systemd and enable it at boot.]]></description>
            <content:encoded><![CDATA[<p>Containerd official site: <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>Official installation guide: <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="environment">Environment<a href="https://tanzhuo.xyz/en/2022/10/15/debian-an-zhuang#environment" class="hash-link" aria-label="Direct link to Environment" title="Direct link to Environment" translate="no">​</a></h2>
<p>Debian 11.5.0</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="downloading-the-containerd-release-package">Downloading the Containerd release package<a href="https://tanzhuo.xyz/en/2022/10/15/debian-an-zhuang#downloading-the-containerd-release-package" class="hash-link" aria-label="Direct link to Downloading the Containerd release package" title="Direct link to Downloading the Containerd release package" translate="no">​</a></h2>
<p>Download page: <a href="https://github.com/containerd/containerd/releases" target="_blank" rel="noopener noreferrer" class="">https://github.com/containerd/containerd/releases</a></p>
<p>Since containerd needs to invoke runc, we would normally have to install runc first. Fortunately, containerd provides a bundle called cri-containerd-cni that includes the required dependencies, saving us the trouble of installing everything one by one.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-9-f8bc3132a7d5be8680b45c4f5bb2f749.webp" width="1291" height="1099" class="img_ev3q"></p>
<p>Run the download command:</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>If the download is too slow from inside China, try this address (a domestic proxy site):</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="extracting-the-archive-to-the-root-directory">Extracting the archive to the root directory<a href="https://tanzhuo.xyz/en/2022/10/15/debian-an-zhuang#extracting-the-archive-to-the-root-directory" class="hash-link" aria-label="Direct link to Extracting the archive to the root directory" title="Direct link to Extracting the archive to the root directory" translate="no">​</a></h2>
<p>The archive's directory layout already mirrors the system hierarchy, so just extract it straight into the <code>/</code> root directory:</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="setting-environment-variables">Setting environment variables<a href="https://tanzhuo.xyz/en/2022/10/15/debian-an-zhuang#setting-environment-variables" class="hash-link" aria-label="Direct link to Setting environment variables" title="Direct link to Setting environment variables" translate="no">​</a></h2>
<p>Append <code>/usr/local/bin</code> and <code>/usr/local/sbin</code> to the <code>PATH</code> environment variable in <code>~/.bashrc</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>Add the following:</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%)"># Let the shell find containerd's executables</span><span class="token plain"></span><br></div><div class="token-line" 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>Then apply the change:</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="generating-containerds-default-config-file">Generating containerd's default config file<a href="https://tanzhuo.xyz/en/2022/10/15/debian-an-zhuang#generating-containerds-default-config-file" class="hash-link" aria-label="Direct link to Generating containerd's default config file" title="Direct link to Generating containerd's default config file" translate="no">​</a></h2>
<p>The config file can live under 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%)"># Export the default configuration as 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="configuring-a-domestic-registry-mirror">Configuring a domestic registry mirror<a href="https://tanzhuo.xyz/en/2022/10/15/debian-an-zhuang#configuring-a-domestic-registry-mirror" class="hash-link" aria-label="Direct link to Configuring a domestic registry mirror" title="Direct link to Configuring a domestic registry mirror" translate="no">​</a></h3>
<p>With the default config generated, we manually add a domestic registry mirror address:</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>Find this line
<strong>[plugins."io.containerd.grpc.v1.cri".registry.mirrors]</strong></p>
<p>around line 153:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-11-405c4770b2814e518247e3a124317568.png" width="1136" height="798" class="img_ev3q"></p>
<p>Add the following, minding the indentation:</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%)"># Point docker.io and k8s.gcr.io at their respective domestic mirrors</span><span class="token plain"></span><br></div><div class="token-line" 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/en/assets/images/2022-10-image-12-8d68b3bb6dbc5662e9ee1c379201e1a5.png" width="1165" height="662" class="img_ev3q"></p>
<p>Save the file after configuring.</p>
<p>In the config, root is where container data is stored — feel free to change it:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-image-13-8de1c0fc48a088a3650e2f3bdeaf9831.png" width="1032" height="392" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="starting-the-containerd-service-and-enabling-it-at-boot">Starting the containerd service and enabling it at boot<a href="https://tanzhuo.xyz/en/2022/10/15/debian-an-zhuang#starting-the-containerd-service-and-enabling-it-at-boot" class="hash-link" aria-label="Direct link to Starting the containerd service and enabling it at boot" title="Direct link to Starting the containerd service and enabling it at boot" 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 starts the service immediately while also enabling it at boot</span><span class="token plain"></span><br></div><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>Verify the service started successfully:</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>With that, containerd is installed and running.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/15/debian-an-zhuang#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>Installing containerd on Debian really comes down to four steps: download the official cri-containerd-cni release package, extract it to the root directory, configure PATH and the default config file, and start the service with systemd. The bundle already includes runc and the CNI plugins, sparing you from installing each dependency separately. In China it's worth configuring a registry mirror while you're at it — pulling images will be a much better experience. And if you later want to hook this up to Kubernetes, this same installation approach carries over directly.</p>]]></content:encoded>
            <category>Docker</category>
        </item>
        <item>
            <title><![CDATA[Common CI/CD Tools]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/14/chang-jian-de-ci-cd</link>
            <guid>https://tanzhuo.xyz/en/2022/10/14/chang-jian-de-ci-cd</guid>
            <pubDate>Fri, 14 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A roundup of popular CI/CD tools — Jenkins, GitLab CI, CircleCI, Buddy, Jenkins X, and Alibaba Cloud's Yunxiao — with a quick look at what each one is good at and where it fits.]]></description>
            <content:encoded><![CDATA[<p>CI/CD combines continuous integration with continuous delivery/deployment, with one goal: shipping software faster and more reliably. This post rounds up several popular CI/CD tools on the market, as a reference for when you need to pick one.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2022/10/14/chang-jian-de-ci-cd#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>CI/CD is a software development practice that combines two concepts — Continuous Integration and Continuous Delivery/Deployment — to speed up software delivery and deployment while improving quality and stability.</p>
<p>Continuous integration means automatically merging code submitted by developers into the mainline, then running a series of tests and checks to ensure quality and stability. This avoids code conflicts, reduces the complexity of merging, and surfaces problems early so they can be fixed promptly, making the codebase more maintainable and testable.</p>
<p>Continuous delivery/deployment means automatically deploying code that has passed testing and checks into production, enabling fast software delivery. It relies on automated tooling and processes — automated testing, automated builds, automated deployment — to reduce human error and delays, improving both the speed and quality of delivery.</p>
<p>CI/CD helps development teams respond faster to market demands and user feedback, shortens development cycles, cuts costs, and improves team collaboration and the reliability of code management. Implementing CI/CD requires a stack of tools and technologies: version control, automated build tools, automated testing, containerization, cloud computing, and so on. There are many CI/CD tools and platforms on the market today — Jenkins, GitLab CI, Travis CI, CircleCI, and others — offering rich features and plugins with support for many languages and frameworks.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="circleci">CircleCI<a href="https://tanzhuo.xyz/en/2022/10/14/chang-jian-de-ci-cd#circleci" class="hash-link" aria-label="Direct link to CircleCI" title="Direct link to CircleCI" translate="no">​</a></h2>
<p>Paid for closed source, free for open source: open-source projects can use it for free. Pricing depends on the number of concurrent build instances you need; a single instance is free.</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/en/2022/10/14/chang-jian-de-ci-cd#jenkins" class="hash-link" aria-label="Direct link to Jenkins" title="Direct link to Jenkins" translate="no">​</a></h2>
<p>Free, and one of the most widely used CI/CD tools, built on 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/en/2022/10/14/chang-jian-de-ci-cd#buddy" class="hash-link" aria-label="Direct link to Buddy" title="Direct link to Buddy" translate="no">​</a></h2>
<p>Free for 14 days after sign-up. It has a modern UI, a rich feature set, and a gentle learning curve.</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/en/2022/10/14/chang-jian-de-ci-cd#gitlab" class="hash-link" aria-label="Direct link to GitLab" title="Direct link to GitLab" translate="no">​</a></h2>
<p>The Community Edition is free. GitLab is not just a repository management tool — it has CI/CD capabilities built in, so you can write pipeline scripts directly to handle compiling, packaging, releasing, and more.</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/en/2022/10/14/chang-jian-de-ci-cd#jenkins-x" class="hash-link" aria-label="Direct link to Jenkins X" title="Direct link to Jenkins X" translate="no">​</a></h2>
<p>Built to fit naturally into Kubernetes clusters, it integrates Helm, Draft, GitOps, plus systems and tools like Nexus, chartmuseum, and monocular. This enables automated build environments, container image generation, pipelines, and automated deployment, with promotion between environments driven by a simple review step.</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="yunxiao">Yunxiao<a href="https://tanzhuo.xyz/en/2022/10/14/chang-jian-de-ci-cd#yunxiao" class="hash-link" aria-label="Direct link to Yunxiao" title="Direct link to Yunxiao" translate="no">​</a></h2>
<p>Alibaba Cloud's one-stop cloud-native development platform, with built-in git repositories, bug tracking, task assignment, pipeline releases, and more. It is highly integrated and connects to all your cloud resources, making it a good fit for enterprises that want fast development and deployment.</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="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/14/chang-jian-de-ci-cd#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>None of these tools is absolutely better than the others — it mostly comes down to matching the tool to your situation. For personal or open-source projects, hosted services that are free for open source, like CircleCI, are a good first choice. If your team already manages code in GitLab, using its built-in GitLab CI is the path of least resistance. Jenkins wins on ecosystem and plugin count, offering the most freedom for self-hosted pipelines; if your infrastructure already runs on Kubernetes, Jenkins X fits the cloud-native workflow better. And for companies in China with their resources on Alibaba Cloud, Yunxiao's one-stop integration is worth a try.</p>]]></content:encoded>
            <category>CI/CD</category>
        </item>
        <item>
            <title><![CDATA[Switching Debian apt to the Aliyun Mirror]]></title>
            <link>https://tanzhuo.xyz/en/2022/10/14/debian-tian-jia-a-li-yuan-apt</link>
            <guid>https://tanzhuo.xyz/en/2022/10/14/debian-tian-jia-a-li-yuan-apt</guid>
            <pubDate>Fri, 14 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[How to replace Debian's default apt sources with the Aliyun mirror: back up and edit sources.list, add the mirror URLs, refresh the index with apt-get update, plus notes on the source format and common pitfalls.]]></description>
            <content:encoded><![CDATA[<p>A fresh Debian install uses the official sources by default, and from inside China package downloads often crawl along at a few dozen KB/s — installing anything means staring at a stalled progress bar. Switching the apt sources to Aliyun's domestic mirror is usually the first thing I do on a new machine, so here are the steps and the things to watch out for.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-switch-mirrors">Why switch mirrors<a href="https://tanzhuo.xyz/en/2022/10/14/debian-tian-jia-a-li-yuan-apt#why-switch-mirrors" class="hash-link" aria-label="Direct link to Why switch mirrors" title="Direct link to Why switch mirrors" translate="no">​</a></h2>
<p>When apt installs software, it downloads packages and indexes from the repository URLs configured in <code>/etc/apt/sources.list</code>. The official servers are overseas, so access from China means high latency and low bandwidth — <code>apt-get update</code> and <code>apt-get install</code> are slow and sometimes time out entirely. Mirror sites like Aliyun, Tsinghua, and USTC regularly sync the complete repository from the official source. The package contents are identical; only the address changes to a domestic node, and the speedup is an order of magnitude.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="anatomy-of-a-source-entry">Anatomy of a source entry<a href="https://tanzhuo.xyz/en/2022/10/14/debian-tian-jia-a-li-yuan-apt#anatomy-of-a-source-entry" class="hash-link" aria-label="Direct link to Anatomy of a source entry" title="Direct link to Anatomy of a source entry" translate="no">​</a></h2>
<p>Before touching anything, it helps to understand what each line in sources.list means. The format is fixed:</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 [repository URL] [release codename] [components...]</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>deb</code> denotes a binary package repository; <code>deb-src</code> is the corresponding source repository. If you only install software, you can skip the <code>deb-src</code> lines.</li>
<li class="">The release codename maps to the Debian version — <code>stretch</code>, for example, is Debian 9. The codename must match your system's version; you can confirm with <code>cat /etc/os-release</code>.</li>
<li class="">Among the components, <code>main</code> is the official main repository, while <code>contrib</code> and <code>non-free</code> contain packages that depend on or are themselves non-free software, such as certain drivers and firmware — usually worth including.</li>
<li class=""><code>stretch/updates</code> is the security update repository, <code>stretch-updates</code> carries regular updates, and <code>stretch-backports</code> provides packages backported from newer releases.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="steps">Steps<a href="https://tanzhuo.xyz/en/2022/10/14/debian-tian-jia-a-li-yuan-apt#steps" class="hash-link" aria-label="Direct link to Steps" title="Direct link to Steps" translate="no">​</a></h2>
<p>1）Open the source list file</p>
<p>vim /etc/apt/sources.list</p>
<p>Back it up first so you can restore it if anything goes wrong:</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%)"># Back up the existing source configuration</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Edit the source 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%)">vim</span><span class="token plain"> /etc/apt/sources.list</span><br></div></code></pre></div></div>
<p>2）Add the Aliyun mirror URLs</p>
<p>Comment out or clear the existing content and add the following lines (using stretch, i.e. Debian 9, as an example):</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>Plain-text version for easy copying:</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）Refresh the sources</p>
<p>Save and exit, then refresh the local package index so the new configuration takes effect:</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>This step re-fetches all repository index files from the Aliyun mirror; you should see <code>mirrors.aliyun.com</code> addresses in the output. From then on, <code>apt-get install</code> downloads go through the domestic mirror.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-notes">Pitfalls and notes<a href="https://tanzhuo.xyz/en/2022/10/14/debian-tian-jia-a-li-yuan-apt#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and notes" title="Direct link to Pitfalls and notes" translate="no">​</a></h2>
<ul>
<li class="">The codename must match your system version. Point the stretch sources at a different release and, at best, update throws 404s; at worst, you mix packages from different releases and wreck your dependencies. For other versions, just replace every <code>stretch</code> above with the corresponding codename.</li>
<li class="">On newer Debian releases, the security source format changed from <code>codename/updates</code> to <code>codename-security</code>. Copying from old articles will get you a repository-not-found error — trust the configuration shown on the mirror site's homepage.</li>
<li class="">If update fails with GPG signature errors, it's usually a missing key or a wrong system clock. Check the clock first, then the keys — don't force the install by skipping verification.</li>
<li class="">On internal networks or environments where stability matters, after switching sources run <code>apt-get update</code> and install a small package as a sanity check before doing any large-scale installs or upgrades.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2022/10/14/debian-tian-jia-a-li-yuan-apt#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>Switching mirrors is really just three steps: back up and edit <code>/etc/apt/sources.list</code>, add the mirror URLs, and refresh the index with <code>apt-get update</code>. The key is making sure the codename matches your system version, and keeping a backup so you can roll back at any time. The same approach works for the Tsinghua and USTC mirrors — just a different domain name.</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[JUC Review (Part 2): Understanding Locks]]></title>
            <link>https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er</link>
            <guid>https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er</guid>
            <pubDate>Sat, 27 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Part 2 of my JUC review: a survey of common lock concepts — fair locks, reentrant locks, read-write locks — plus how the CAS algorithm works, the ABA problem, and where AQS fits in.]]></description>
            <content:encoded><![CDATA[<p>Locks are unavoidable in concurrent programming. This post runs through the common lock concepts in one place, from fair locks and read-write locks to CAS and AQS, as the second part of my JUC review.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-basic-idea-of-locking">The Basic Idea of Locking<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#the-basic-idea-of-locking" class="hash-link" aria-label="Direct link to The Basic Idea of Locking" title="Direct link to The Basic Idea of Locking" translate="no">​</a></h2>
<p>A lock mechanism guarantees that in a multithreaded environment, only one thread can enter a critical section at any given moment, ensuring the consistency of the data manipulated inside it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-locks">Common Locks<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#common-locks" class="hash-link" aria-label="Direct link to Common Locks" title="Direct link to Common Locks" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-fair-lock">1) Fair Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#1-fair-lock" class="hash-link" aria-label="Direct link to 1) Fair Lock" title="Direct link to 1) Fair Lock" translate="no">​</a></h3>
<p>Prevents thread starvation — that is, prevents a thread from never getting a CPU time slice and being unable to make progress. Guaranteeing fairness requires extra bookkeeping of thread state and more context switches, so the overhead is higher and throughput drops somewhat. ReentrantLock can be made fair via a constructor parameter; Synchronized is non-fair by default and cannot be made fair.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-non-fair-lock">2) Non-Fair Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#2-non-fair-lock" class="hash-link" aria-label="Direct link to 2) Non-Fair Lock" title="Direct link to 2) Non-Fair Lock" translate="no">​</a></h3>
<p>Threads compete directly for compute resources, which can lead to starvation. But throughput is higher and thread switching less frequent — whoever grabs the CPU time slice first gets to run.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-reentrant-lock">3) Reentrant Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#3-reentrant-lock" class="hash-link" aria-label="Direct link to 3) Reentrant Lock" title="Direct link to 3) Reentrant Lock" translate="no">​</a></h3>
<p>Also called a recursive lock: a thread can re-acquire a lock it already holds, which helps avoid certain deadlock scenarios. Both ReentrantLock and Synchronized are reentrant.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-non-reentrant-lock">4) Non-Reentrant Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#4-non-reentrant-lock" class="hash-link" aria-label="Direct link to 4) Non-Reentrant Lock" title="Direct link to 4) Non-Reentrant Lock" translate="no">​</a></h3>
<p>If a thread that holds the lock tries to acquire it again inside the synchronized block, it deadlocks — a non-reentrant lock may only be acquired once within the block.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-mutex-lock">5) Mutex Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#5-mutex-lock" class="hash-link" aria-label="Direct link to 5) Mutex Lock" title="Direct link to 5) Mutex Lock" translate="no">​</a></h3>
<p>While one thread holds the resource, other threads are suspended and consume no CPU; when the lock is released, the CPU schedules the suspended threads. Suitable for resources that aren't accessed at high frequency — otherwise the constant thread scheduling becomes inefficient.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-read-write-lock">6) Read-Write Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#6-read-write-lock" class="hash-link" aria-label="Direct link to 6) Read-Write Lock" title="Direct link to 6) Read-Write Lock" translate="no">​</a></h3>
<p>ReentrantReadWriteLock consists of a read lock and a write lock, which are mutually exclusive. Use the read lock for read-only access to the shared resource and the write lock when modifying it — a good fit for read-heavy, write-light workloads. When no thread holds the write lock, multiple threads can hold the read lock concurrently; but once a thread holds the write lock, all other attempts to acquire either lock block.</p>
<p><strong>Write priority:</strong> if write threads keep acquiring the write lock, read threads can be starved.</p>
<p><strong>Read priority:</strong> if read threads keep acquiring the read lock, write threads may never get in — starving the writers.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-fair-read-write-lock">7) Fair Read-Write Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#7-fair-read-write-lock" class="hash-link" aria-label="Direct link to 7) Fair Read-Write Lock" title="Direct link to 7) Fair Read-Write Lock" translate="no">​</a></h3>
<p>A simple approach to a fair read-write lock: queue up the threads requesting the lock and grant it first-in, first-out regardless of whether they're readers or writers. Readers can still run concurrently, and starvation is alleviated to a degree.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-stamped-lock">8) Stamped Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#8-stamped-lock" class="hash-link" aria-label="Direct link to 8) Stamped Lock" title="Direct link to 8) Stamped Lock" translate="no">​</a></h3>
<p>StampedLock is a faster lock than ReentrantReadWriteLock, supporting optimistic reads, pessimistic read locks, and write locks. Unlike ReentrantReadWriteLock, StampedLock allows one thread to acquire the write lock even while multiple threads are performing optimistic reads.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="perspectives-on-concurrent-synchronization">Perspectives on Concurrent Synchronization<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#perspectives-on-concurrent-synchronization" class="hash-link" aria-label="Direct link to Perspectives on Concurrent Synchronization" title="Direct link to Perspectives on Concurrent Synchronization" translate="no">​</a></h2>
<p>The "locks" below are not concrete lock implementations; they're more like perspectives on concurrent synchronization, or states a lock can be in.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-optimistic-locking">1) Optimistic Locking<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#1-optimistic-locking" class="hash-link" aria-label="Direct link to 1) Optimistic Locking" title="Direct link to 1) Optimistic Locking" translate="no">​</a></h3>
<p>Implemented with version numbers or the CAS algorithm, assuming conflicts are rare. It works like this: when modifying a resource, assume you're the only one modifying it; after the modification, verify — and if the check fails, redo it. Despite the name, optimistic locking never actually takes a lock, which is why it's also called lock-free programming.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-pessimistic-locking">2) Pessimistic Locking<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#2-pessimistic-locking" class="hash-link" aria-label="Direct link to 2) Pessimistic Locking" title="Direct link to 2) Pessimistic Locking" translate="no">​</a></h3>
<p>Assumes multiple threads will modify the resource, so every modification takes a lock. Database locking mechanisms are generally built on pessimistic locking.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-segmented-locking">3) Segmented Locking<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#3-segmented-locking" class="hash-link" aria-label="Direct link to 3) Segmented Locking" title="Direct link to 3) Segmented Locking" translate="no">​</a></h3>
<p>Segmented locking exists to make locking finer-grained: when an operation doesn't need to update the whole array but only a single element, we lock just that element. In JDK 1.7, ConcurrentHashMap used segmented locking for concurrent access, locking individual Segments for higher concurrency — but in JDK 1.8 this was replaced by the CAS algorithm.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-biased-locking">4) Biased Locking<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#4-biased-locking" class="hash-link" aria-label="Direct link to 4) Biased Locking" title="Direct link to 4) Biased Locking" translate="no">​</a></h3>
<p>When a synchronized block is only ever accessed by one thread, that thread acquires the lock automatically, lowering the cost of lock acquisition.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-lightweight-lock">5) Lightweight Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#5-lightweight-lock" class="hash-link" aria-label="Direct link to 5) Lightweight Lock" title="Direct link to 5) Lightweight Lock" translate="no">​</a></h3>
<p>When a biased lock is accessed by a second thread, it upgrades to a lightweight lock; other threads then try to acquire it by spinning, without blocking.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-heavyweight-lock">6) Heavyweight Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#6-heavyweight-lock" class="hash-link" aria-label="Direct link to 6) Heavyweight Lock" title="Direct link to 6) Heavyweight Lock" translate="no">​</a></h3>
<p>When a lightweight lock is spun on by another thread past a certain number of attempts without success, it upgrades to a heavyweight lock, which blocks the other threads.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-spin-lock">7) Spin Lock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#7-spin-lock" class="hash-link" aria-label="Direct link to 7) Spin Lock" title="Direct link to 7) Spin Lock" translate="no">​</a></h3>
<p>A thread trying to acquire the lock doesn't block immediately; instead it loops, retrying the acquisition. This burns more CPU — the thread is essentially idling. Usually a spin limit is set, after which the thread is suspended.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-deadlock">8) Deadlock<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#8-deadlock" class="hash-link" aria-label="Direct link to 8) Deadlock" title="Direct link to 8) Deadlock" translate="no">​</a></h3>
<p>Deadlock isn't a kind of lock but a phenomenon: two running threads each need a lock the other already holds in order to proceed, so the program freezes and can't move forward. Deadlocks can be diagnosed with <code>jstack</code>: first get the application's pid with <code>jps -l</code>, then run <code>jstack &lt;pid&gt;</code> to inspect for deadlocks. You can also open the UI tool via the <code>jconsole</code> command to view deadlock information.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-the-cas-algorithm">What Is the CAS Algorithm<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#what-is-the-cas-algorithm" class="hash-link" aria-label="Direct link to What Is the CAS Algorithm" title="Direct link to What Is the CAS Algorithm" translate="no">​</a></h2>
<p>CAS stands for Compare And Swap — exactly what the name says. It's a lock-free algorithm that guarantees thread safety without taking any locks: variables are synchronized without any thread blocking, which puts it in the category of <strong>non-blocking synchronization</strong>.</p>
<p>CAS involves three operands: the memory value <strong>V</strong>, the expected value <strong>A</strong>, and the new value <strong>B</strong>. The operation is: if and only if V equals A, CAS atomically replaces V with B; otherwise it does nothing. In general, CAS is a <strong>spin</strong> operation — it keeps retrying until it succeeds.</p>
<p><strong>CAS has the following characteristics: the ABA problem, spin overhead, and atomicity for only a single shared variable.</strong></p>
<ul>
<li class="">The ABA problem. If variable V reads as A initially and still checks out as A when we're about to assign, that doesn't prove no other thread modified it — in the meantime it could have been changed to something else and then back to A, and CAS would wrongly conclude it was never touched. This is CAS's ABA problem. In practice, judge the actual scenario: if ABA doesn't matter much for your use case, you can leave it alone; otherwise, attach a version number to every modification, or just fall back to pessimistic locking.</li>
<li class="">Spin overhead. As mentioned, CAS is a spin operation with a head-through-the-wall persistence — on failure it retries until it succeeds. If the spinning goes on for a long time, it puts a heavy load on the CPU; that's the spin overhead.</li>
<li class="">Atomicity for only one shared variable. CAS works on a single shared variable; when an operation spans multiple shared variables, CAS can't guarantee synchronization. One fix is to wrap those variables into a single object and apply CAS to that.</li>
</ul>
<p>The underlying assembly instruction is <code>lock cmpxchgl</code>, where the lock prefix is especially important. What it does:</p>
<ol>
<li class="">
<p>Ensures the read-modify-write operation on memory executes atomically. On Pentium and earlier processors, an instruction with the lock prefix locked the bus for the duration of its execution, temporarily preventing other processors from accessing memory over the bus — obviously an expensive proposition. Starting with the Pentium 4, Intel Xeon, and P6 processors, Intel added a meaningful optimization on top of bus locking: if the area of memory being accessed is already locked in the processor's internal cache during the lock-prefixed instruction (i.e. the cache line containing it is in the exclusive or modified state) and the memory area fits entirely within a single cache line, the processor executes the instruction directly. Because the cache line stays locked for the duration, other processors can't read or write the memory area the instruction touches, so atomicity is preserved. This is called cache locking, and it dramatically reduces the cost of lock-prefixed instructions — though under heavy contention between processors, or when the accessed memory address is misaligned, the bus still gets locked.</p>
</li>
<li class="">
<p>Forbids reordering of this instruction with the reads and writes before and after it.</p>
</li>
<li class="">
<p>Flushes all data in the write buffer to memory.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-aqs">What Is AQS<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#what-is-aqs" class="hash-link" aria-label="Direct link to What Is AQS" title="Direct link to What Is AQS" translate="no">​</a></h2>
<p>Abstract Queued Synchronizer. Every implementation of the Lock interface in JUC is built on the AQS abstract class.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/08/27/jucfu-xi-pian-er#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>This post gathered the common lock concepts in one pass: fair vs. non-fair and reentrant vs. non-reentrant describe a lock's behavior; read-write locks and StampedLock target read-heavy workloads; and the progression from optimistic and pessimistic locking through biased, lightweight, and heavyweight locks is more about perspectives on synchronization and lock states. CAS is the cornerstone of lock-free programming in JUC, and understanding its three operands and the ABA problem is well worth the effort. As for AQS, it's the common skeleton behind every Lock implementation in JUC — its internals deserve a post of their own.</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[JUC Review (Part 1): Thread Fundamentals]]></title>
            <link>https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi</link>
            <guid>https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi</guid>
            <pubDate>Fri, 26 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Part 1 of my JUC review: starting from the JVM memory model and thread stacks, covering threading models, the five thread states, and how to create thread pools and their rejection policies.]]></description>
            <content:encoded><![CDATA[<p>The JUC chapter of Java covers a huge number of topics. To better organize what I know, I'm writing it up as blog posts, working through it from the underlying principles and distilling my own understanding of concurrent programming.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-concurrent-programming">What Is Concurrent Programming<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#what-is-concurrent-programming" class="hash-link" aria-label="Direct link to What Is Concurrent Programming" title="Direct link to What Is Concurrent Programming" translate="no">​</a></h2>
<p>The reason traces back to the history of computing. Per Moore's law, CPU compute power kept climbing over time, but Moore's law soon approached its physical limits and single-core CPU performance hit a wall. In pursuit of more compute, the industry turned to multi-core CPU architectures; multiple cores plus hyper-threading dramatically increased CPU throughput. The principle is like having several people execute different instructions at the same time — overall efficiency naturally beats one person working alone. (Note that this means higher <em>overall</em> efficiency; using multiple threads does not guarantee an application executes instructions faster than a single-threaded one.)</p>
<p>To make better use of multi-core CPU resources (squeezing everything out of the CPU), multithreaded programming was born. While multithreading does improve overall program efficiency in some scenarios, it is <strong>not a silver bullet</strong>: it brings along thread safety, locking, deadlocks, thread context-switching overhead, and a whole series of other problems.</p>
<p><strong>After all this talk about threads — what exactly is a thread?</strong></p>
<p>A thread is the smallest unit of execution that the operating system can schedule. It lives inside a process and is the actual working unit within it. A thread is a single sequential flow of control within a process; one process can run multiple threads concurrently, each executing a different task in parallel, and every process has at least one running thread.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-threads-live-in-the-jvm">Where Threads Live in the JVM<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#where-threads-live-in-the-jvm" class="hash-link" aria-label="Direct link to Where Threads Live in the JVM" title="Direct link to Where Threads Live in the JVM" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-56-6dc9d01d54aecf1fb5b2c41ecf1908a7.webp" width="1006" height="603" class="img_ev3q"></p>
<p>From the JVM memory model diagram, we can see exactly where a Thread sits within the JVM. In the VM Stack space, every thread has its own thread stack, which holds many Stack Frames; each frame in turn holds the LVA, OS, FD, and other data.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="lva-local-variable-array">LVA: Local Variable Array<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#lva-local-variable-array" class="hash-link" aria-label="Direct link to LVA: Local Variable Array" title="Direct link to LVA: Local Variable Array" translate="no">​</a></h3>
<ul>
<li class="">The local variable section of a stack frame is a byte array indexed from 0.</li>
<li class="">It contains all the method parameters and local variables.</li>
<li class="">Each slot or entry in the array is 4 bytes.</li>
<li class="">int, float, and reference types each occupy one entry (slot) in the array, i.e. 4 bytes.</li>
<li class="">double and long values occupy 2 consecutive entries, i.e. 8 bytes total.</li>
<li class="">byte, short, and char are converted to int before being stored and take 1 slot, i.e. 4 bytes.</li>
<li class="">How boolean values are stored varies across JVM implementations. In most JVMs, a boolean occupies one slot in the local variable array.</li>
<li class="">Parameters are placed into the local variable array first, in the order they are declared in the method.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="os-operand-stack">OS: Operand Stack<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#os-operand-stack" class="hash-link" aria-label="Direct link to OS: Operand Stack" title="Direct link to OS: Operand Stack" translate="no">​</a></h3>
<ul>
<li class="">The JVM uses the operand stack as its working space at runtime — a place to store intermediate results of computations.</li>
<li class="">Like the local variable array, the operand stack is organized as an array. But it isn't accessed by index; instead, instructions access it by pushing values onto the stack or popping them off to perform whatever operations we need.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="fd-frame-data">FD: Frame Data<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#fd-frame-data" class="hash-link" aria-label="Direct link to FD: Frame Data" title="Direct link to FD: Frame Data" translate="no">​</a></h3>
<ul>
<li class="">The frame data area contains all the symbolic references (constant pool resolution) and the return address for normal methods; that return address is tied to a specific method and used to jump back.</li>
<li class="">It also holds a reference to the exception table, which provides the catch-block information when an exception occurs.</li>
</ul>
<p>In JDK 1.8, the default thread stack size is 1MB; it can be adjusted via <code>-XX:ThreadStackSize=256k</code>. If the thread stack runs out of space, a StackOverflowError is thrown.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-thread-stack">The Thread Stack<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#the-thread-stack" class="hash-link" aria-label="Direct link to The Thread Stack" title="Direct link to The Thread Stack" translate="no">​</a></h2>
<p><strong>Thread stack:</strong> every time a thread executes a function, a stack frame is pushed. The thread stack is first-in, last-out — the earliest frame sits at the bottom, the latest at the top, and execution proceeds from the top downward. For example, if function A calls B, and B calls C, the stack looks like this:</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>The stack structure is simple and needs no garbage collector, because a frame's memory is destroyed the moment it finishes executing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="threading-models">Threading Models<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#threading-models" class="hash-link" aria-label="Direct link to Threading Models" title="Direct link to Threading Models" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="one-to-one-model-one-lwp-maps-to-one-klt">One-to-One Model (one LWP maps to one KLT)<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#one-to-one-model-one-lwp-maps-to-one-klt" class="hash-link" aria-label="Direct link to One-to-One Model (one LWP maps to one KLT)" title="Direct link to One-to-One Model (one LWP maps to one KLT)" translate="no">​</a></h3>
<p>This is the model used in JDK 1.8.</p>
<p><strong>KLT: Kernel Level Thread.</strong> In this implementation, thread support, thread switching, and scheduling are handled directly by the operating system kernel — threads are mapped onto the processors.</p>
<p><strong>LWP: Light Weight Process.</strong> Programs generally don't use kernel threads directly; instead they schedule and operate kernel threads through a higher-level interface, the LWP. An LWP differs from an ordinary process in that it carries only a minimal execution context plus the statistics the scheduler needs — which is why it's called lightweight — and its relationship with a KLT is strictly 1:1.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-58-eb43fb8e4e480589dae73d43e0fd3a74.webp" width="951" height="636" class="img_ev3q"></p>
<p>Pros:</p>
<ol>
<li class="">Simple to implement and suitable for most multithreading scenarios; all mainstream JVMs use this approach today.</li>
</ol>
<p>Cons:</p>
<ol>
<li class="">
<p>Blocking and waking a user thread maps directly onto the kernel thread, and as the thread count grows, frequent switching between threads drives up CPU overhead. JDK 1.8 introduced the CAS algorithm to avoid frequent thread switching and locking, which did substantially improve the JVM's concurrency performance.</p>
</li>
<li class="">
<p>The kernel can only create a limited number of threads. If an application creates too many, system performance drops sharply, with most CPU time slices burned on switching between the excess threads.</p>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="many-to-many-model-virtual-user-threads-ut-built-on-top-of-lwps">Many-to-Many Model (virtual user threads, UT, built on top of LWPs)<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#many-to-many-model-virtual-user-threads-ut-built-on-top-of-lwps" class="hash-link" aria-label="Direct link to Many-to-Many Model (virtual user threads, UT, built on top of LWPs)" title="Direct link to Many-to-Many Model (virtual user threads, UT, built on top of LWPs)" translate="no">​</a></h3>
<p>This is the model used by the Go language and by JDK 19.</p>
<p><strong>UT: User Thread.</strong> Built in user space, invisible to the system kernel, with low costs for creation, destruction, and switching.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-59-36418701a1fc2ee67b61c7e5c35ed456.webp" width="796" height="657" class="img_ev3q"></p>
<p>The many-to-many model, also called the two-level threading model, absorbs the strengths of the other models while dodging their weaknesses as much as possible. Under this model, user threads and kernel threads have a many-to-many (M : N, usually M &gt;= N) mapping.</p>
<p>Pros:</p>
<ol>
<li class="">
<p>The application manages the switching between UTs itself, so the underlying CPU doesn't have to switch threads as often and can run the current thread more continuously, improving program efficiency.</p>
</li>
<li class="">
<p>UT scheduling priority can be controlled by the program itself.</p>
</li>
<li class="">
<p>Massively higher concurrency — with this model you can spin up tens of thousands of UTs and still serve traffic well, making it a natural fit for high-concurrency scenarios.</p>
</li>
</ol>
<p>Cons:</p>
<ol>
<li class="">It's fairly complex to implement. Go's GMP threading model is a many-to-many implementation of exactly this kind, and it's one of the reasons goroutines can deliver such high concurrency. Java's Project Loom has been exploring the same territory and landed in JDK 19 — feel free to give it a try. <a href="https://openjdk.org/jeps/425" target="_blank" rel="noopener noreferrer" class="">JEP 425: Virtual Threads (Preview) (openjdk.org)</a></li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-five-thread-states">The Five Thread States<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#the-five-thread-states" class="hash-link" aria-label="Direct link to The Five Thread States" title="Direct link to The Five Thread States" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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/en/2022/08/26/jucfu-xi-pian-yi#1-new" class="hash-link" aria-label="Direct link to 1) New" title="Direct link to 1) New" translate="no">​</a></h3>
<p>Once a thread object is created, it enters the New state.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-runnable">2) Runnable<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#2-runnable" class="hash-link" aria-label="Direct link to 2) Runnable" title="Direct link to 2) Runnable" translate="no">​</a></h3>
<p>Also called the "ready-to-run" state. After the thread object is created, another thread calls its start() method — e.g. thread.start() — to launch it. A thread in the Runnable state may be scheduled onto the CPU at any moment, but exactly when it runs is up to the CPU.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-running">3) Running<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#3-running" class="hash-link" aria-label="Direct link to 3) Running" title="Direct link to 3) Running" translate="no">​</a></h3>
<p>The thread has acquired CPU time and is executing. Note that a thread can only enter the Running state from the Runnable state.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-blocked">4) Blocked<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#4-blocked" class="hash-link" aria-label="Direct link to 4) Blocked" title="Direct link to 4) Blocked" translate="no">​</a></h3>
<ul>
<li class="">(01) Wait blocking — calling the thread's wait() method makes it wait for some work to complete. The thread can be woken via notify(), after which it returns to the Runnable state.</li>
<li class="">(02) Synchronization blocking — when a thread fails to acquire a synchronized lock (because another thread holds it), it enters the synchronization-blocked state.</li>
<li class="">(03) Other blocking — calling sleep() or join(), or issuing an I/O request, puts the thread into a blocked state. When sleep() times out, join() sees the target thread terminate or times out, or the I/O completes, the thread returns to the Runnable state.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-dead">5) Dead<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#5-dead" class="hash-link" aria-label="Direct link to 5) Dead" title="Direct link to 5) Dead" translate="no">​</a></h3>
<p>The thread has finished executing or exited the run() method due to an exception, ending its life cycle.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="thread-pools-in-java">Thread Pools in Java<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#thread-pools-in-java" class="hash-link" aria-label="Direct link to Thread Pools in Java" title="Direct link to Thread Pools in Java" translate="no">​</a></h2>
<p>From the thread states above, it's clear that constantly creating and destroying threads burns extra compute in the New and Dead states. To use threads more effectively, thread pools were introduced to manage thread resources. A pool creates a set number of threads up front; when work arrives, a thread is taken from the pool to run it, and once the task completes the thread is tossed back into the pool. This neatly avoids the repeated create-and-destroy cycle and improves program efficiency.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-60-11f1b3fb7fc52f39dfcec972cc9df124.webp" width="894" height="682" class="img_ev3q"></p>
<p>Advantages:</p>
<ol>
<li class="">
<p>Better control over the number of threads and tasks in the application.</p>
</li>
<li class="">
<p>Faster task execution — shorter request response times (no creation and destruction steps).</p>
</li>
<li class="">
<p>Parallel computation across multiple threads for higher efficiency, plus other enhanced features.</p>
</li>
<li class="">
<p>Thread reuse, lowering the system's compute resource overhead.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ways-to-create-a-thread-pool">Ways to Create a Thread Pool<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#ways-to-create-a-thread-pool" class="hash-link" aria-label="Direct link to Ways to Create a Thread Pool" title="Direct link to Ways to Create a Thread Pool" translate="no">​</a></h2>
<ul>
<li class="">Manually, via ThreadPoolExecutor.</li>
<li class="">Automatically, via the Executors factory.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-ways-to-create-a-thread-pool">7 Ways to Create a Thread Pool<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#7-ways-to-create-a-thread-pool" class="hash-link" aria-label="Direct link to 7 Ways to Create a Thread Pool" title="Direct link to 7 Ways to Create a Thread Pool" translate="no">​</a></h3>
<ol>
<li class="">Executors.newFixedThreadPool: creates a fixed-size pool that caps the number of concurrent threads; excess tasks wait in the queue.</li>
<li class="">Executors.newCachedThreadPool: creates a cached pool — threads beyond what's needed are reclaimed after being idle for a while, and new threads are created when there aren't enough.</li>
<li class="">Executors.newSingleThreadExecutor: creates a single-thread pool that guarantees first-in, first-out execution order.</li>
<li class="">Executors.newScheduledThreadPool: creates a pool that can run delayed tasks.</li>
<li class="">Executors.newSingleThreadScheduledExecutor: creates a single-threaded pool that can run delayed tasks.</li>
<li class="">Executors.newWorkStealingPool: creates a work-stealing pool (task execution order is nondeterministic).</li>
<li class="">ThreadPoolExecutor: the manual approach, which takes up to 7 parameters at construction: <strong>core pool size, maximum pool size, blocking queue type (bounded or unbounded), keep-alive time, time unit, thread factory, and rejection policy</strong>.</li>
</ol>
<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%)">// core thread count</span><span class="token plain"></span><br></div><div class="token-line" 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%)">// maximum thread count</span><span class="token plain"></span><br></div><div class="token-line" 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%)">// idle keep-alive time for non-core threads</span><span class="token plain"></span><br></div><div class="token-line" 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%)">// unit for the keep-alive time</span><span class="token plain"></span><br></div><div class="token-line" 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%)">// blocking queue (bounded / unbounded)</span><span class="token plain"></span><br></div><div class="token-line" 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%)">// thread factory</span><span class="token plain"></span><br></div><div class="token-line" 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%)">// rejection policy</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>tip</div><div class="admonitionContent_BuS1"><p>The recommended way to create a thread pool is the last one, ThreadPoolExecutor, because it makes the pool's operating rules explicit and avoids the risk of resource exhaustion.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="rejection-policies">Rejection Policies<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#rejection-policies" class="hash-link" aria-label="Direct link to Rejection Policies" title="Direct link to Rejection Policies" translate="no">​</a></h2>
<p>There are 4 rejection policies:</p>
<ol>
<li class="">ThreadPoolExecutor.AbortPolicy: the default — rejects the task and throws an exception.</li>
<li class="">ThreadPoolExecutor.CallerRunsPolicy: runs the task directly on the calling thread.</li>
<li class="">ThreadPoolExecutor.DiscardPolicy: silently rejects the task without throwing.</li>
<li class="">ThreadPoolExecutor.DiscardOldestPolicy: once rejection kicks in, for every new task that arrives, keeps discarding the oldest task in the blocking queue and enqueues the new one.</li>
</ol>
<p><strong>Maximum tasks a pool can hold = thread count + queue length.</strong></p>
<p>If a pool has a maximum of 10 threads and a queue length of 20, it can accept at most 30 tasks at once; anything beyond that hits the configured rejection policy. You can also implement the RejectedExecutionHandler interface yourself to customize the rejection flow.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/08/26/jucfu-xi-pian-yi#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Starting from the JVM memory model, this post walked through the internal structure of the thread stack, the trade-offs between the one-to-one and many-to-many threading models, and the five thread states. Once you understand the cost of creating and destroying threads, the reason thread pools exist becomes obvious. In practice, create pools manually with ThreadPoolExecutor, making the core parameters and rejection policy explicit, to avoid the resource risks of the Executors defaults. These fundamentals are the prerequisite for understanding locks and the various JUC synchronizers in the posts to come.</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Deploying Istio on Kubernetes]]></title>
            <link>https://tanzhuo.xyz/en/2022/08/22/kubernetes</link>
            <guid>https://tanzhuo.xyz/en/2022/08/22/kubernetes</guid>
            <pubDate>Mon, 22 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[An overview of Istio's architecture and core components, along with a walkthrough of installing Istio on a Kubernetes cluster with Helm and deploying the Bookinfo sample application.]]></description>
            <content:encoded><![CDATA[<p>Kubernetes has already solved most operational problems, with excellent built-in automation: dynamic scaling, scheduling, image management, restarting crashed containers, monitoring compute nodes, and so on. But k8s doesn't provide application-layer monitoring and traffic management, coordination between services calling each other, a service registry, or a configuration center. That gap is where Istio, the flagship product of the Service Mesh architecture, comes in.</p>
<!-- -->
<p>Istio is a framework jointly developed by Google, IBM, and Lyft. Its main job is to control, connect, secure, configure, and observe traffic between microservices in a k8s environment, letting us ditch the cumbersome, complex, hard-to-maintain, tightly coupled low-level microservice SDKs when building microservices.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="istio-architecture">Istio Architecture<a href="https://tanzhuo.xyz/en/2022/08/22/kubernetes#istio-architecture" class="hash-link" aria-label="Direct link to Istio Architecture" title="Direct link to Istio Architecture" translate="no">​</a></h2>
<p><strong>Architecturally, Istio is split into a data plane and a control plane.</strong></p>
<p>The core idea of the data plane is the Sidecar pattern: a proxy container is added to the same pod to take over the traffic of our business container, delivering all kinds of functionality without any intrusion into the business code. Examples include load balancing, traffic encryption, monitoring, policies (canary releases, blue-green deployments), circuit breaking, degradation, remote service calls, configuration center, service registry, and other common microservice features (it does not handle distributed transaction management, which arguably isn't something Istio should take over anyway).</p>
<p>The control plane is responsible for managing and configuring the proxies to route traffic. It also configures Mixer to enforce policies and collect telemetry data for display, providing features like call-chain analysis and service topology graphs.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-50-29cab4357ba6d65501863969da0b1a2a.webp" width="1057" height="715" class="img_ev3q"></p>
<p>Since business code no longer depends on a specific microservice SDK — tightly coupled frameworks like SpringCloud, Dubbo, Motan, or ServiceComb — we can pick the right language for each business scenario and let teams working in different languages build features side by side, improving overall efficiency while handing all the fiddly registration, configuration, and remote-call management over to Istio.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="component-overview">Component Overview<a href="https://tanzhuo.xyz/en/2022/08/22/kubernetes#component-overview" class="hash-link" aria-label="Direct link to Component Overview" title="Direct link to Component Overview" translate="no">​</a></h2>
<p>citadel: the core security component, responsible for identity authentication and certificate management.</p>
<p>galley: the configuration management component — it validates the format and content of configuration files and feeds that configuration to pilot and mixer.</p>
<p>pilot: the control hub, covering service discovery plus rule translation and distribution.</p>
<p>proxy: implemented by Envoy (written in C++) together with Pilot-agent, providing dynamic service discovery, load balancing, TLS, circuit breaking, health checks, traffic splitting, canary releases, and more. It also generates telemetry data, giving microservices observability.</p>
<p>Ingressgateway: the gateway at the entry point — traffic from outside the mesh reaches services inside the mesh through this gateway.</p>
<p><strong>Full official documentation:</strong> <a href="https://istio.io/latest/zh/docs/" target="_blank" rel="noopener noreferrer" class="">Istio / Docs</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deployment-steps">Deployment Steps<a href="https://tanzhuo.xyz/en/2022/08/22/kubernetes#deployment-steps" class="hash-link" aria-label="Direct link to Deployment Steps" title="Direct link to Deployment Steps" translate="no">​</a></h2>
<p>I'm going with the simplest option here: installing with Helm.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-why-helm">1) Why Helm<a href="https://tanzhuo.xyz/en/2022/08/22/kubernetes#1-why-helm" class="hash-link" aria-label="Direct link to 1) Why Helm" title="Direct link to 1) Why Helm" translate="no">​</a></h3>
<p>Helm plays the role of a package manager for a K8S cluster. Declaring K8S resources — Services, Pods, Ingresses — means writing Yaml files. As microservices multiply, and you add Redis clusters, MySQL HA clusters, Hadoop clusters, and so on, the pile of Yaml files keeps growing, along with the risk of mistakes. More importantly, if the business grows and you deploy K8S clusters in other regions, a multi-region active-active setup means deploying yet another batch of Pods. This is exactly the kind of problem Helm solves: a Helm Chart bundles all the Yaml configuration a complete service needs.</p>
<p>Helm's advantages:</p>
<ul>
<li class="">Efficient reuse of yaml files.</li>
<li class="">Managing a large number of yaml files as a single unit.</li>
<li class="">Application-level version management.</li>
</ul>
<p>Later on, you can also set up an internal Chart repository for your company. Whenever a service is needed, instead of writing Yaml files by hand, you simply run the Chart through Helm and the corresponding Pods are deployed to the K8S cluster, giving you the service ready to go.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-installing-helm">2) Installing Helm<a href="https://tanzhuo.xyz/en/2022/08/22/kubernetes#2-installing-helm" class="hash-link" aria-label="Direct link to 2) Installing Helm" title="Direct link to 2) Installing Helm" translate="no">​</a></h3>
<p>Helm needs to be installed first (official site): <a href="https://helm.sh/docs/" target="_blank" rel="noopener noreferrer" class="">Helm | Docs (helm.sh)</a></p>
<p>When installing, mind the version compatibility between Helm and Kubernetes: <a href="https://helm.sh/zh/docs/topics/version_skew/" target="_blank" rel="noopener noreferrer" class="">Helm | Helm Version Support Policy</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-installing-istio">3) Installing Istio<a href="https://tanzhuo.xyz/en/2022/08/22/kubernetes#3-installing-istio" class="hash-link" aria-label="Direct link to 3) Installing Istio" title="Direct link to 3) Installing Istio" translate="no">​</a></h3>
<p><strong>Let's run the installation: <a href="https://istio.io/latest/zh/docs/setup/install/helm/" target="_blank" rel="noopener noreferrer" class="">Istio / Install with Helm</a></strong></p>
<p>Create the <code>istio-system</code> namespace for the Istio components:</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>Install the Istio base chart, which contains the cluster-wide resources used by the Istio control plane:</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%)"># The official docs don't list the chart repository, so I've added it here</span><span class="token plain"></span><br></div><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>Install the Istio discovery chart, which deploys the <code>istiod</code> service:</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>(Optional) Install Istio's ingress gateway:</p>
<p><strong>The Linux kernel must be 4.11 or later — check your kernel version to make sure it qualifies.</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%)"># Create the namespace for the ingress gateway and enable automatic sidecar injection</span><span class="token plain"></span><br></div><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>With the Istio ingress in place, you can deploy Istio's official demo project, the Bookinfo microservices, to try out Istio's various capabilities.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bookinfo">Bookinfo<a href="https://tanzhuo.xyz/en/2022/08/22/kubernetes#bookinfo" class="hash-link" aria-label="Direct link to Bookinfo" title="Direct link to Bookinfo" translate="no">​</a></h2>
<p>Official page: <a href="https://istio.io/latest/zh/docs/examples/bookinfo/" target="_blank" rel="noopener noreferrer" class="">Istio / Bookinfo Application</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-10-noistio-6757dcc885fcdd61bb4011d88efc5dd7.svg" width="662" height="453" class="img_ev3q"></p>
<p>The Bookinfo microservices are deployed with Istio sidecars and are written in 4 different languages. The Reviews service, written in Java, ships in 3 different versions, which makes it a great showcase of Istio's capabilities in multi-version control, traffic management, migration, and integration.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/08/22/kubernetes#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Istio pulls service governance out of the business code: the data plane takes over traffic with Sidecars, the control plane distributes routes and policies centrally, and applications are no longer tied to any language-specific microservice SDK. On the deployment side, with Helm, three install commands get you base, istiod, and the ingress gateway — far less work than hand-writing a pile of Yaml. Once installed, running through the official Bookinfo example gives you a solid feel for multi-language, multi-version traffic management. From there, you can gradually adopt capabilities like canary releases and circuit breaking in your own systems.</p>]]></content:encoded>
            <category>K8s</category>
        </item>
        <item>
            <title><![CDATA[Frequently Used kubectl Commands]]></title>
            <link>https://tanzhuo.xyz/en/2022/08/21/k</link>
            <guid>https://tanzhuo.xyz/en/2022/08/21/k</guid>
            <pubDate>Sun, 21 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A scenario-based collection of the kubectl commands I use most in day-to-day operations: viewing resources, creating and deleting them, node maintenance, and exporting configs, plus caveats for force-deleting Pods and draining nodes.]]></description>
            <content:encoded><![CDATA[<p>Working with Kubernetes clusters day to day, almost everything goes through kubectl. The commands themselves aren't hard — the hard part is remembering them when you actually need them, especially the low-frequency but critical ones like node maintenance and force deletion. This post organizes the commands I use most, grouped by scenario, so I can come back and look them up whenever I forget.</p>
<!-- -->
<p>Every kubectl operation is essentially a REST request to kube-apiserver: viewing is a GET, applying a config is a declarative update of desired state, and deleting removes the object from etcd, after which the controllers converge the actual state. Once you understand this, the behavior of most commands becomes easy to explain.</p>
<p>Start with the basics. When you're not sure how to write a flag, the help output is always the first place to check:</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%)"># Show help; also works for subcommands, e.g. 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="inspecting-the-cluster-and-nodes">Inspecting the Cluster and Nodes<a href="https://tanzhuo.xyz/en/2022/08/21/k#inspecting-the-cluster-and-nodes" class="hash-link" aria-label="Direct link to Inspecting the Cluster and Nodes" title="Direct link to Inspecting the Cluster and Nodes" 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%)"># List cluster nodes and their status (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%)"># Show detailed node information: pods on the node and hardware resource usage</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Commonly used when diagnosing insufficient node resources or unschedulable Pods</span><span class="token plain"></span><br></div><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">node-name</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><br></div></code></pre></div></div>
<p>In the <code>describe node</code> output, the Conditions, Allocated resources, and Events sections carry the most information — check those first when a node is misbehaving.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="inspecting-namespaces-and-pods">Inspecting Namespaces and Pods<a href="https://tanzhuo.xyz/en/2022/08/21/k#inspecting-namespaces-and-pods" class="hash-link" aria-label="Direct link to Inspecting Namespaces and Pods" title="Direct link to Inspecting Namespaces and Pods" translate="no">​</a></h2>
<p>Kubernetes uses namespaces for resource isolation. Most query commands need <code>-n</code> to specify a namespace; without it, they default to <code>default</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%)"># List 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 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%)"># List pods in a given 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 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">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%)"># List pods across all namespaces; -A is short for --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%)"># List pods across all namespaces (the full flag is --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>Once you've located a specific Pod, troubleshooting mainly relies on two commands — describe for events, logs for output:</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%)"># Show detailed information about one or more resource objects</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Scheduling failures, image pull failures, etc. all show up under 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%)"># Print the logs of a container in a 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%)"># Multi-container Pods need -c to specify the container 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">kubectl logs</span><br></div></code></pre></div></div>
<p>A rule of thumb: if a Pod won't start, describe it first and read the events; if it starts but misbehaves, then check the logs. Doing it the other way around often wastes half your time.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-and-deleting-resources">Creating and Deleting Resources<a href="https://tanzhuo.xyz/en/2022/08/21/k#creating-and-deleting-resources" class="hash-link" aria-label="Direct link to Creating and Deleting Resources" title="Direct link to Creating and Deleting Resources" translate="no">​</a></h2>
<p>kubectl manages resources in two styles: <code>create</code> is imperative — you tell the cluster exactly what to create; <code>apply</code> is declarative — you submit the desired state described in a yaml file, and existing resources get incrementally updated. For day-to-day config maintenance, apply is the way to go, since the same file can be run repeatedly.</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%)"># Create a resource object from a yaml/json file or stdin</span><span class="token plain"></span><br></div><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%)"># Apply a pod config file to set up resources; if already applied, does an incremental 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">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%)"># Un-apply a pod config file, deleting its resources</span><span class="token plain"></span><br></div><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%)"># Delete a Deployment in a given 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"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># Note: deleting a Deployment cascades to the ReplicaSets and Pods it manages</span><span class="token plain"></span><br></div><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">namespace</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-name</span><span class="token operator" style="color:hsl(207, 82%, 66%)">&gt;</span><br></div></code></pre></div></div>
<p>A few commands for bulk cleanup and force deletion, in increasing order of destructiveness:</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%)"># Force-delete a pod stuck in Terminating</span><span class="token plain"></span><br></div><div class="token-line" 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 skips the graceful termination wait</span><span class="token plain"></span><br></div><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%)"># Delete all pods in a 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"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># Pods managed by controllers like Deployments will be recreated automatically</span><span class="token plain"></span><br></div><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%)"># Delete the namespace itself; everything in it is cascade-deleted</span><span class="token plain"></span><br></div><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>There are also two modification commands, for labeling resources and making ad-hoc config changes:</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%)"># Set resource labels; labels are how Services select backends and how node affinity scheduling works</span><span class="token plain"></span><br></div><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%)"># Edit a server-side resource object in the default editor; saving takes effect immediately</span><span class="token plain"></span><br></div><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> is handy for emergency debugging, but the change is not synced back to your local yaml file. Remember to fold the change back into your config file afterward, or the next apply will overwrite it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="node-maintenance">Node Maintenance<a href="https://tanzhuo.xyz/en/2022/08/21/k#node-maintenance" class="hash-link" aria-label="Direct link to Node Maintenance" title="Direct link to Node Maintenance" translate="no">​</a></h2>
<p>The standard procedure for taking a node offline for maintenance (kernel upgrades, hardware swaps) is cordon first, then drain: cordon only marks the node unschedulable, leaving existing Pods untouched; drain goes further and evicts the Pods on the node, letting controllers recreate them elsewhere.</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%)"># Cordon a node: mark it unschedulable so new Pods stop landing on it</span><span class="token plain"></span><br></div><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">node-name</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%)"># Evict pods from a 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 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%)"># Evict all pods on the 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"></span><span class="token comment" style="color:hsl(220, 10%, 40%)"># --ignore-daemonsets: skip Pods managed by DaemonSets (they run one per node and would be recreated anyway)</span><span class="token plain"></span><br></div><div class="token-line" 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: also delete Pods using emptyDir local data</span><span class="token plain"></span><br></div><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">node-name</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%)"># After maintenance is done, remove the node from the 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">kubectl delete nodes </span><span class="token operator" style="color:hsl(207, 82%, 66%)">&lt;</span><span class="token plain">node-name</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="exporting-resource-configs">Exporting Resource Configs<a href="https://tanzhuo.xyz/en/2022/08/21/k#exporting-resource-configs" class="hash-link" aria-label="Direct link to Exporting Resource Configs" title="Direct link to Exporting Resource Configs" translate="no">​</a></h2>
<p>When a resource has been modified by hand in production and you want to capture its current state as a yaml file, use <code>-o yaml</code> with a redirect:</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%)"># Export an existing pod's yaml config to a file</span><span class="token plain"></span><br></div><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">namespace</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-name</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">filename</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>The exported yaml carries cluster runtime fields like status, resourceVersion, and uid. Strip those out before applying it elsewhere — keep only the spec-related parts.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/08/21/k#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p><code>--all-namespace</code> is easy to misremember — the full flag is <code>--all-namespaces</code> (with an s). In daily use, just stick with the <code>-A</code> shorthand.</p>
</li>
<li class="">
<p><code>--force --grace-period=0</code> only makes the API Server remove the object immediately; the container process may not actually have exited. Use with caution on stateful services — you could end up with two copies of the same instance running in a split-brain fashion.</p>
</li>
<li class="">
<p>Deleting a namespace is a cascade operation: all Deployments, Services, and ConfigMaps inside it disappear together. Run <code>kubectl get all -n &lt;namespace&gt;</code> first to confirm what's still in there.</p>
</li>
<li class="">
<p>Without <code>--ignore-daemonsets</code>, <code>drain</code> errors out and aborts the moment it hits a DaemonSet-managed Pod. Also, <code>--delete-local-data</code> has been renamed to <code>--delete-emptydir-data</code> in newer versions — if the old flag errors, switch to the new name.</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>warning</div><div class="admonitionContent_BuS1"><p>drain really does evict workload Pods. Before running it in production, confirm you have enough replicas and spare capacity on other nodes, so service capacity doesn't drop off a cliff.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/08/21/k#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>This list covers four high-frequency scenarios: inspecting, creating and deleting, node maintenance, and config export. It's fine not to memorize every command — two workflows are enough to remember: for troubleshooting, go <code>get → describe → logs</code>; for taking a node offline, go <code>cordon → drain → delete node</code>. For everything else, <code>--help</code> is always there.</p>]]></content:encoded>
            <category>K8s</category>
        </item>
        <item>
            <title><![CDATA[Building a Kubernetes Cluster with Kubeadm]]></title>
            <link>https://tanzhuo.xyz/en/2022/08/20/k8s</link>
            <guid>https://tanzhuo.xyz/en/2022/08/20/k8s</guid>
            <pubDate>Sat, 20 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A complete walkthrough of building a highly available Kubernetes 1.24 cluster with kubeadm: environment prerequisites, the Containerd runtime, cluster initialization, CNI networking, and the pitfalls hit along the way.]]></description>
            <content:encoded><![CDATA[<p>Notes from my recent experience building a K8S cluster with kubeadm, including some of the problems I ran into along the way.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deployment-environment">Deployment Environment<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#deployment-environment" class="hash-link" aria-label="Direct link to Deployment Environment" title="Direct link to Deployment Environment" translate="no">​</a></h2>
<ul>
<li class="">Version: kubernetes-1.24.4 (HA)</li>
<li class="">Container runtime: containerd-1.6.8</li>
<li class="">Operating system: CentOS7</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerequisites">Prerequisites<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites" translate="no">​</a></h2>
<ol>
<li class="">
<p>Cluster nodes need at least 2 CPU cores and 2 GB of RAM, and must be reachable from each other over the private or public network.</p>
</li>
<li class="">
<p>Each machine's hostname and MAC address must be unique, and product_uuid must be unique as well. Nodes must be reachable by hostname, which requires configuring /etc/hosts.</p>
</li>
<li class="">
<p>Starting with Kubernetes 1.24, the default container runtime is no longer Docker but Containerd. If you still want Docker, you need to install <a href="https://github.com/Mirantis/cri-dockerd" target="_blank" rel="noopener noreferrer" class="">cri-dockerd</a>.</p>
</li>
<li class="">
<p>An HA setup requires 3 master control-plane nodes. High availability comes in two topologies — external etcd and stacked etcd — shown below.</p>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="external-etcd">External etcd<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#external-etcd" class="hash-link" aria-label="Direct link to External etcd" title="Direct link to External etcd" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-46-2b17f77d2252cf1f1b37c1c98c81e317.png" width="1281" height="879" class="img_ev3q"></p>
<p>Pros: losing a control-plane instance or an etcd member has less impact and doesn't hurt cluster redundancy the way the stacked HA topology does.</p>
<p>Cons: requires more machines — 3 dedicated servers for a separate etcd HA cluster.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="stacked-etcd">Stacked etcd<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#stacked-etcd" class="hash-link" aria-label="Direct link to Stacked etcd" title="Direct link to Stacked etcd" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-45-18ff9d55a0109ad3d07f388f439d385a.webp" width="1307" height="837" class="img_ev3q"></p>
<p>Pros: this topology couples the control plane and etcd members on the same nodes. Compared to an external etcd cluster, it's simpler to set up and easier to manage replicas for.</p>
<p>Cons: a stacked cluster carries the risk of coupled failure. If one node goes down, both an etcd member and a control-plane instance are lost, and redundancy suffers. You can mitigate this by adding more control-plane nodes.</p>
<p><strong>load balancer:</strong> An internal load balancer that must itself be HA. It reverse-proxies the apiServer for worker nodes, providing a single IP for accessing the apiServer, and can sense the availability of each master.</p>
<ol start="5">
<li class="">Disable the Linux swap partition. (Swap uses disk as storage when memory runs low; it degrades system performance, but in exchange lets you hold more data in "memory" and prevents the server from immediately stalling when RAM fills up.)</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%)"># Disable swap on 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%)"># Permanently:</span><span class="token plain"></span><br></div><div class="token-line" 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 and comment out the following line</span><span class="token plain"></span><br></div><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. Comment it out and save</span><span class="token plain"></span><br></div><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%)"># Temporarily:</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Run 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>
<ol start="6">
<li class="">Turn off the firewall, or open the ports specified in the official docs</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%)"># Check firewall status</span><span class="token plain"></span><br></div><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. Open firewall ports — master nodes need 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%)"># Open firewall ports — worker nodes need 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%)"># Reload firewall configuration</span><span class="token plain"></span><br></div><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%)"># Verify the ports are configured</span><span class="token plain"></span><br></div><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. Or just turn the firewall off — insecure, but avoids a lot of problems,</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># such as the ports required later by the CNI network component</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Stop firewalld</span><span class="token plain"></span><br></div><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%)"># Disable firewalld on boot</span><span class="token plain"></span><br></div><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>
<ol start="7">
<li class="">Configure the yum repo at /etc/yum.repos.d/kubernetes.repo (the official repo may be unreachable without a proxy, so the Aliyun mirror is used here)</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%)">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>
<ol start="8">
<li class="">Enable IP forwarding and bridge filtering, install ipset and ipvsadm, and load the required kernel modules</li>
</ol>
<p>Official docs: <a href="https://kubernetes.io/zh-cn/docs/setup/production-environment/container-runtimes/" target="_blank" rel="noopener noreferrer" class="">Container Runtimes | 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%)"># Declare the kernel modules to load</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Enable bridge filtering and IP forwarding</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Apply the configuration</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Install ipset and 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%)"># Declare the kernel modules required by 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%)"># Make it executable and run it</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Verify the modules are loaded</span><span class="token plain"></span><br></div><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%)"># Console output</span><span class="token plain"></span><br></div><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>
<ol start="9">
<li class="">Set SELinux to permissive mode (effectively disabling it)</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%)">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="installing-the-container-runtime">Installing the Container Runtime<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#installing-the-container-runtime" class="hash-link" aria-label="Direct link to Installing the Container Runtime" title="Direct link to Installing the Container Runtime" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="containerd">Containerd<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#containerd" class="hash-link" aria-label="Direct link to Containerd" title="Direct link to Containerd" translate="no">​</a></h3>
<p>Reference: <a href="https://blog.csdn.net/weixin_39246554/article/details/120930966" target="_blank" rel="noopener noreferrer" class="">Hands-on: installing containerd on CentOS 7 - 20211023 (CSDN blog)</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>warning</div><div class="admonitionContent_BuS1"><p>With Containerd 1.6 and later, you must upgrade the local libseccomp dependency to 2.4 or above, or containers may fail to start. I hit this myself — the error message is very hard to track down — and the CentOS yum repo only goes up to 2.3.1, so you have to download and install it from upstream yourself.</p></div></div>
<p>Relevant commands:</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%)"># Check the current libseccomp 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 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%)"># Remove the old 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 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%)"># Download the new 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%)"># Install 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="cluster-initialization">Cluster Initialization<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#cluster-initialization" class="hash-link" aria-label="Direct link to Cluster Initialization" title="Direct link to Cluster Initialization" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-install-the-three-core-components-kubeadm-kubectl-kubelet">1) Install the three core components: kubeadm, kubectl, kubelet<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#1-install-the-three-core-components-kubeadm-kubectl-kubelet" class="hash-link" aria-label="Direct link to 1) Install the three core components: kubeadm, kubectl, kubelet" title="Direct link to 1) Install the three core components: 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%)"># Install the components</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Enable kubelet on boot</span><span class="token plain"></span><br></div><div class="token-line" 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-initialize-the-control-plane-node">2) Initialize the control-plane node<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#2-initialize-the-control-plane-node" class="hash-link" aria-label="Direct link to 2) Initialize the control-plane node" title="Direct link to 2) Initialize the control-plane node" translate="no">​</a></h3>
<p>Initialize the node with kubeadm using <code>kubeadm init [flags]</code>.</p>
<p><strong>See the official docs for more flags: <a href="https://kubernetes.io/zh-cn/docs/reference/setup-tools/kubeadm/" target="_blank" rel="noopener noreferrer" class="">Kubeadm | Kubernetes</a></strong></p>
<p>The flags below use my local environment's configuration; for production, just swap in your online private-network IPs.</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%)"># Initialize the control plane with this command</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Or use an init config file: generate it first, then adjust its parameters for your scenario</span><span class="token plain"></span><br></div><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>Key flags explained:</p>
<p>--control-plane-endpoint: the IP address of the internal <strong>load balancer</strong> or an internal DNS name. Worker nodes access the cluster's master nodes through this name or IP, which is what keeps the masters highly available.</p>
<p>--apiserver-advertise-address: the private IP address of the current control-plane node.</p>
<p>--pod-network-cidr: the pod network CIDR.</p>
<p>--service-cidr: the service network CIDR.</p>
<p>--image-repository: the image registry; set it to a domestic mirror — I use the Aliyun one here for much faster pulls.</p>
<p><strong>During initialization, kubeadm needs to pull the k8s.gcr.io/pause image, which is blocked in China and will very likely fail to download, causing initialization to fail. If your init fails, check the container runtime's logs — if you find the image can't be pulled, this is your problem (it cost me several days of debugging). The fix:</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%)"># Check the container runtime logs</span><span class="token plain"></span><br></div><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 requires external network access, so the base pause container</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># for k8s often can't be fetched.</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Pull from a domestic mirror with crictl, then fix it up with 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%)"># Pull from the domestic mirror</span><span class="token plain"></span><br></div><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 the image</span><span class="token plain"></span><br></div><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-output-after-successful-initialization">3) Output after successful initialization<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#3-output-after-successful-initialization" class="hash-link" aria-label="Direct link to 3) Output after successful initialization" title="Direct link to 3) Output after successful initialization" 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>The output above gives you the join commands for the remaining nodes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="joining-nodes-to-the-cluster">Joining Nodes to the Cluster<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#joining-nodes-to-the-cluster" class="hash-link" aria-label="Direct link to Joining Nodes to the Cluster" title="Direct link to Joining Nodes to the Cluster" translate="no">​</a></h2>
<p>Run <code>kubeadm join</code> with the token to add more master and worker nodes. Tokens are valid for 6 hours by default; regenerated tokens are valid for 24 hours. If a token has expired, regenerate one with:</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%)"># For joining a master (control-plane) node: fetch the certificate encryption info</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># — control-plane joins need both the token and the 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%)"># For joining a worker node: generate the 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%)"># List existing tokens</span><span class="token plain"></span><br></div><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="installing-the-cni-network-component">Installing the CNI Network Component<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#installing-the-cni-network-component" class="hash-link" aria-label="Direct link to Installing the CNI Network Component" title="Direct link to Installing the CNI Network Component" translate="no">​</a></h2>
<p>Choose a CNI component that fits your company's business scenario and scale, weighing security against performance. Note: a cluster can only run one CNI component.</p>
<p><strong>More detail in the official docs:</strong> <a href="https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/" target="_blank" rel="noopener noreferrer" class="">Creating a cluster with kubeadm | Kubernetes</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-47-e74df237f2373cb24825389fd1ff8792.png" width="1336" height="679" class="img_ev3q"></p>
<p>Here I go with a popular CNI component: Calico.</p>
<p>Calico's official site: <a href="https://projectcalico.docs.tigera.io/getting-started/kubernetes/self-managed-onprem/onpremises" target="_blank" rel="noopener noreferrer" class="">Install Calico networking and network policy for on-premises deployments (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%)"># First, install the operator on the 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">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%)"># Download the custom resources needed to configure 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%)"># Customize the custom-resources.yaml file</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Change the pod network CIDR in it</span><span class="token plain"></span><br></div><div class="token-line" 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 (set this to the same pod CIDR used with kubeadm)</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Create the manifest to install 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%)"># Check the status of the Calico pods — expect to wait around 4 minutes</span><span class="token plain"></span><br></div><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="deploying-a-dashboard">Deploying a Dashboard<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#deploying-a-dashboard" class="hash-link" aria-label="Direct link to Deploying a Dashboard" title="Direct link to Deploying a Dashboard" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="kuboard">kuboard<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#kuboard" class="hash-link" aria-label="Direct link to kuboard" title="Direct link to kuboard" translate="no">​</a></h3>
<p><strong>Official docs:</strong> <a href="https://www.kuboard.cn/" target="_blank" rel="noopener noreferrer" class="">https://www.kuboard.cn/</a></p>
<p>Start the container with a single docker command, then follow the guided cluster-import flow.</p>
<p>Cluster monitoring up and running:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-48-a39e9ce841bd338e83876a2d26160fd8.webp" width="1600" height="783" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/08/20/k8s#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>At this point the cluster is basically usable: kubeadm has initialized the control plane, nodes have joined via tokens, Calico provides the pod network, and kuboard handles visual monitoring. The two biggest traps in the whole process were the outdated libseccomp version for Containerd and the pause image failing to pull due to network restrictions — neither produces an obvious error, and you have to dig through the container runtime logs for clues. Next steps: add an NAS file service for in-cluster file storage, and consider whether to introduce Istio and an Ingress network entry point, configuring them appropriately for your organization.</p>
<p>Next post: <a href="https://tanzhuo.xyz/kubernetes/" target="_blank" rel="noopener noreferrer" class="">Deploying Istio on Kubernetes (tanzhuo.xyz)</a></p>]]></content:encoded>
            <category>K8s</category>
        </item>
        <item>
            <title><![CDATA[An Introduction to HBase, Compared with MongoDB]]></title>
            <link>https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb</link>
            <guid>https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb</guid>
            <pubDate>Sat, 13 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[HBase is a column-oriented, scalable distributed storage system that can handle PB-scale data on clusters of commodity PC servers. This post walks through its architecture, read/write paths, LSM-tree structure, strengths and weaknesses, and compares its use cases with MongoDB.]]></description>
            <content:encoded><![CDATA[<p>HBase, short for Hadoop Database, is a highly reliable, high-performance, column-oriented, scalable distributed storage system that lets you build large-scale structured storage clusters on inexpensive PC servers.</p>
<!-- -->
<p>HBase is designed to store and process massive amounts of data. Using nothing but standard hardware, it can handle enormous datasets with thousands of rows and columns, supporting PB-scale storage and queries at hundreds of millions of QPS.</p>
<p>It suits business scenarios that don't demand strict real-time behavior. HBase stores byte arrays and doesn't care about data types, which allows for a dynamic, flexible data model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>HBase consists of HMaster and HRegionServer nodes and follows a master-slave architecture. HBase splits a logical table into multiple data blocks called HRegions and stores them on HRegionServers.</p>
<p>The HMaster manages all HRegionServers. It stores no data itself — only the mapping from data to HRegionServers (the metadata).</p>
<p>All nodes in the cluster are coordinated by Zookeeper, which handles the various issues HBase may run into at runtime. The basic architecture looks like this:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-ad2553610635ee0adc362fe27c2a7537-a4b0200d1bfb0f00802e6f2aaf28ab93.webp" width="640" height="370" class="img_ev3q"></p>
<p><strong>Client:</strong> Communicates with the HMaster and HRegionServers via HBase's RPC mechanism, submitting requests and receiving results. Administrative operations go through RPC calls to the HMaster; data reads and writes go through RPC calls to the HRegionServers.</p>
<p><strong>Zookeeper:</strong> Every node in the cluster registers its state with Zookeeper, so the HMaster can always know the health of each HRegionServer. It also removes the HMaster as a single point of failure.</p>
<p><strong>HMaster:</strong> Manages all HRegionServers, tells each one which HRegions it should maintain, and monitors their health. When a new HRegionServer registers with the HMaster, the HMaster tells it to wait for data assignment; when an HRegion fails, the HMaster marks all the HRegions it was responsible for as unassigned and reassigns them to other HRegionServers. The HMaster has no single-point problem — HBase can start multiple HMasters, and Zookeeper's election mechanism ensures one is always running, improving cluster availability.</p>
<p><strong>HRegion:</strong> When a table grows beyond a preset threshold, HBase automatically splits it into regions, each holding a subset of the table's rows. To the user, a table is one collection of data distinguished by primary key (RowKey); physically, a table is split into multiple blocks, each of which is an HRegion, identified by "table name + start/end key." An HRegion holds one contiguous range of a table's data, and the full table is stored across multiple HRegions.</p>
<p><strong>HRegionServer:</strong> All HBase data normally lives in the underlying HDFS, and users access it through HRegionServers. Typically one node in the cluster runs one HRegionServer, and each HRegion is maintained by exactly one HRegionServer. The HRegionServer is responsible for serving user I/O requests and reading/writing data in HDFS — it is the core module of HBase. Internally, it manages a series of HRegion objects, each corresponding to a contiguous range of the logical table. An HRegion consists of multiple HStores, each of which stores one column family of the logical table. This means each column family is a concentrated storage unit, so for efficiency it's best to place columns with similar I/O characteristics in the same column family.</p>
<p><strong>HStore:</strong> The heart of HBase storage, made up of a MemStore and StoreFiles. The MemStore is an in-memory buffer where incoming writes land first; when it fills up, it is flushed into a StoreFile (backed by HFile). When the number of StoreFiles crosses a threshold, a Compact operation merges multiple StoreFiles into one, performing version merging and data deletion along the way. In other words, HBase only ever appends data — all updates and deletes happen during later Compact operations — so a user write can return as soon as it lands in memory, which is what gives HBase its write performance. Continual compaction gradually produces larger and larger StoreFiles; once a single StoreFile crosses a size threshold, a Split operation divides the current HRegion into two. The parent HRegion goes offline, the HMaster assigns the two child HRegions to appropriate HRegionServers, and the load on the original HRegion is spread across the two new ones.</p>
<p><strong>HLog:</strong> Every HRegionServer has an HLog object — the class that implements the write-ahead log. Each time a user writes to the MemStore, a copy of the data is also written to the HLog file. HLog files roll periodically and old files are deleted (their data having been persisted to StoreFiles). When the HMaster detects via Zookeeper that an HRegionServer has terminated unexpectedly, it first processes the leftover HLog files: it splits the log data by HRegion and places it into the corresponding HRegion directories, then reassigns the failed HRegions. The HRegionServers taking over these HRegions notice during loading that there are historical HLogs to process, replay the HLog data into the MemStore, then flush to StoreFiles, completing data recovery.</p>
<p>HBase is based on the BigTable model: a sparse, long-term (HDFS-backed), multidimensional, sorted map. The table is indexed by row key, column key, and timestamp. All data in HBase is strings, with no types.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-bdc5394355d37057a381069b139db6c7-fffe9699725f0292d1ef21fd0831af44.webp" width="1200" height="234" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hbase-read-and-write-paths">HBase Read and Write Paths<a href="https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb#hbase-read-and-write-paths" class="hash-link" aria-label="Direct link to HBase Read and Write Paths" title="Direct link to HBase Read and Write Paths" translate="no">​</a></h2>
<p>The diagram below shows the data storage relationships inside an HRegionServer. As described above, HBase uses the MemStore and StoreFiles to store table updates. When data is updated, it is first written to the HLog and the MemStore; data in the MemStore is kept sorted.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-4f79359da1203e9a7f16754087434d53-70190020f2acab2fae91ebe0a5b1484c.webp" width="640" height="394" class="img_ev3q"></p>
<p>When the MemStore accumulates to a threshold, a new MemStore is created and the old one is added to a flush queue, where a dedicated thread flushes it to disk as a StoreFile. At the same time, the system records a CheckPoint in Zookeeper, indicating that all data changes before that point have been persisted. If the system fails unexpectedly, data in the MemStore may be lost — in that case, the HLog is used to recover the data after the CheckPoint.</p>
<p>StoreFiles are read-only; once created they can never be modified, so an HBase update is really an append. When the number of StoreFiles in an HStore reaches a threshold, a merge combines modifications to the same key into one large StoreFile; when a StoreFile's size reaches a threshold, it is split into two StoreFiles.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="write-path">Write Path<a href="https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb#write-path" class="hash-link" aria-label="Direct link to Write Path" title="Direct link to Write Path" translate="no">​</a></h3>
<ul>
<li class="">Step 1: The client, coordinated through Zookeeper, sends a write request to the HRegionServer, writing the data into an HRegion.</li>
<li class="">Step 2: The data goes into the HRegion's MemStore until the MemStore reaches its preset threshold.</li>
<li class="">Step 3: The data in the MemStore is flushed into a StoreFile.</li>
<li class="">Step 4: As the number of StoreFiles grows past a threshold, a Compact operation merges multiple StoreFiles into one, performing version merging and data deletion in the process.</li>
<li class="">Step 5: Through continual Compact operations, StoreFiles gradually grow larger and larger.</li>
<li class="">Step 6: When a single StoreFile exceeds the size threshold, a Split operation divides the current HRegion into two new HRegions. The parent goes offline, the HMaster assigns the two children to appropriate HRegionServers, and the load on the original HRegion is distributed away.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="read-path">Read Path<a href="https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb#read-path" class="hash-link" aria-label="Direct link to Read Path" title="Direct link to Read Path" translate="no">​</a></h3>
<ul>
<li class="">Step 1: The client contacts Zookeeper, finds the -ROOT- table, and from there obtains the .META. table information.</li>
<li class="">Step 2: From the .META. table, it looks up which HRegion holds the target data and finds the corresponding HRegionServer.</li>
<li class="">Step 3: It fetches the data through that HRegionServer.</li>
<li class="">Step 4: An HRegionServer's memory is divided into the MemStore and the BlockCache — the MemStore mainly serves writes, the BlockCache mainly serves reads. A read first checks the MemStore, then the BlockCache, and only then goes to the StoreFiles, placing the fetched result into the BlockCache.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-to-use-hbase">When to Use HBase<a href="https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb#when-to-use-hbase" class="hash-link" aria-label="Direct link to When to Use HBase" title="Direct link to When to Use HBase" translate="no">​</a></h2>
<p>Semi-structured or unstructured data: for data whose fields can't be defined in advance or are messy, a fixed schema is hard to model, making it a good fit for HBase. As the business grows and needs more fields, an RDBMS requires downtime to alter the table structure, whereas HBase supports adding columns dynamically.</p>
<p>Very sparse records: an RDBMS table has a fixed set of columns, and empty columns simply waste storage; in HBase, empty columns take no storage at all, saving space and improving read performance.</p>
<p>Multi-versioned data: the value located by a RowKey and column qualifier can have any number of versions (with different timestamps), so HBase is very convenient for data whose change history needs to be kept.</p>
<p>Massive data volumes: as data keeps growing, an RDBMS gradually buckles. First you split reads from writes — one primary for writes, several replicas for reads, doubling server costs. As pressure keeps mounting and the primary can't keep up, you start sharding by database, splitting out nearly unrelated data, which breaks some join queries and forces you to introduce a middle layer. As volume grows further and single tables balloon, queries slow to a crawl, and you're forced to shard tables too (e.g., splitting by ID modulo) to keep per-table row counts down. Anyone who has lived through this knows how tedious it is.</p>
<p>HBase is far simpler: just add new nodes to the cluster and HBase scales out automatically, while seamless integration with Hadoop provides data reliability (HDFS) and high-performance analytics over massive data (MapReduce).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hbase-and-mapreduce">HBase and MapReduce<a href="https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb#hbase-and-mapreduce" class="hash-link" aria-label="Direct link to HBase and MapReduce" title="Direct link to HBase and MapReduce" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-ed27cafbd33b8be27e8810b45de839e5--1--ed4338a1786827b76930ee4962011de8.webp" width="554" height="277" class="img_ev3q"></p>
<p>The relationship between a Table and its Regions in HBase resembles that between a File and its Blocks in HDFS. Because HBase provides APIs for interacting with MapReduce — such as TableInputFormat and TableOutputFormat — HBase tables can serve directly as the input and output of Hadoop MapReduce jobs, simplifying MapReduce application development without requiring knowledge of HBase internals.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-structures">Data Structures<a href="https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb#data-structures" class="hash-link" aria-label="Direct link to Data Structures" title="Direct link to Data Structures" translate="no">​</a></h2>
<p>HBase uses an LSM (Log-Structured Merge) tree, whereas the traditional database engine InnoDB uses a B+ tree.</p>
<p>Characteristics of the <strong>LSM tree</strong>:</p>
<ol>
<li class="">Optimized for writes</li>
<li class="">Reads must consult multiple trees, requiring more I/O operations than a B+ tree, with higher variance</li>
<li class="">Writes are all sequential I/O; random reads are random I/O, sequential reads are sequential I/O</li>
</ol>
<p>Advantages:</p>
<ol>
<li class="">Dramatically better write performance</li>
<li class="">Unaffected by SSD random-write amplification</li>
<li class="">Unaffected by space amplification</li>
</ol>
<p>Disadvantages:</p>
<ol>
<li class="">Read performance is sacrificed — more I/O operations than a B+ tree</li>
<li class="">Requires periodic compaction, which amplifies overall network/disk I/O</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="strengths-of-hbase">Strengths of HBase<a href="https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb#strengths-of-hbase" class="hash-link" aria-label="Direct link to Strengths of HBase" title="Direct link to Strengths of HBase" translate="no">​</a></h2>
<ol>
<li class="">Data is stored by column, so queries touch only the columns involved, dramatically reducing system I/O; data in the same column shares a type and empty values aren't stored, enabling efficient compression.</li>
<li class="">The key/value storage model means query performance barely degrades even as data grows to massive scale.</li>
<li class="">As a columnar database (versus a traditional row-oriented one), when a table has many fields, different columns (in units of Regions) can be spread across different server instances to distribute load.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="weaknesses-of-hbase">Weaknesses of HBase<a href="https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb#weaknesses-of-hbase" class="hash-link" aria-label="Direct link to Weaknesses of HBase" title="Direct link to Weaknesses of HBase" translate="no">​</a></h2>
<ol>
<li class="">No native secondary indexes — access is by primary key only; community-built secondary indexes lag behind data updates, causing painful consistency issues</li>
<li class="">The wide-table model is awkward and hard to grasp, and it demands up-front modeling, limiting flexibility</li>
<li class="">Few supported programming interfaces (Java, Thrift, RESTful API); no SQL — the API is the only option</li>
<li class="">Complex cluster structure, with 8 different node types</li>
<li class="">Primitive data types — only byte streams — which is unfriendly for development</li>
<li class="">No consistent snapshot capability</li>
<li class="">Requires periodic compaction, which severely affects continuous read/write workloads</li>
<li class="">No table joins, so data analysis is a weak point — common operations like group by or order by can only be done by writing MapReduce jobs</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/08/13/hbasehe-mongodb#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>HBase excels at extremely high-volume write scenarios: ingest the data, then process and analyze it asynchronously. The matching business cases are those with loose real-time requirements but very heavy write volumes, such as monitoring and collecting data from hardware and vehicle systems, or user profiling. It primarily solves massive-scale storage; paired with downstream analytics components, it can power big-data analysis, user behavior tracking, real-time recommendations, risk control, and more.</p>
<p>This post hasn't said much about MongoDB. On equivalent cluster hardware, MongoDB's write throughput trails HBase's; but MongoDB builds indexes as data is written, so both its query speed and the dimensions it can query on exceed HBase's — HBase can only query along limited dimensions. In terms of query flexibility and efficiency under varied conditions, MongoDB has the edge and better suits scenarios needing real-time query and analysis. To raise MongoDB's write capacity, you add more high-performance cluster nodes. When actually choosing between them, analyze the fit against your own business scenario.</p>]]></content:encoded>
            <category>Database</category>
        </item>
        <item>
            <title><![CDATA[Redis Cache Penetration, Breakdown, and Avalanche]]></title>
            <link>https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng</link>
            <guid>https://tanzhuo.xyz/en/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[Cache penetration, breakdown, and avalanche are the three most common problems with Redis caching. This post explains what causes each one, how they differ, and the corresponding solutions: Bloom filters, caching empty objects, mutex locks, staggered expiration, and two-tier caching.]]></description>
            <content:encoded><![CDATA[<p>Penetration, breakdown, and avalanche are three problems you can't avoid when using Redis as a cache. The names sound similar, but the causes and remedies are quite different. This post walks through them one by one.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cache-penetration">Cache Penetration<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#cache-penetration" class="hash-link" aria-label="Direct link to Cache Penetration" title="Direct link to Cache Penetration" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-33-93577c026a68c565599c164e0eb90088.webp" width="862" height="597" class="img_ev3q"></p>
<p>Cache penetration happens when the data a user requests exists neither in the cache (a cache miss) nor in the database. Every request for that data goes all the way to the database, which then returns nothing.</p>
<p>If a malicious attacker keeps requesting data that doesn't exist in the system, a flood of requests hits the database in a short time, putting it under heavy pressure and potentially bringing the whole database system down.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="solutions">Solutions<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#solutions" class="hash-link" aria-label="Direct link to Solutions" title="Direct link to Solutions" translate="no">​</a></h3>
<p><strong>1) Bloom filter</strong></p>
<p>A Bloom filter is essentially a very long binary vector plus a set of random hash functions, used to test whether an element is a member of a set. Its strengths are space efficiency and query time far better than typical algorithms; its weaknesses are a certain false-positive rate and difficulty with deletion.</p>
<p>To check whether an element is in a set, the usual approach is to store all the elements and compare against them — linked lists, trees, and similar data structures all work this way. But as the set grows, the required storage keeps increasing and lookups get slower (O(n) or O(log n)). A hash table, on the other hand, can map an element through a hash function to a single position in a bit array, so you only need to check whether that bit is 1 to know whether the element is in the set — and that is the basic idea behind a Bloom filter.</p>
<p><strong>2) Cache empty objects</strong></p>
<p>When the cache misses and the database query also comes back empty, you can write the empty result into the cache. The next request for that key gets the empty object straight from the cache instead of hitting the database. To avoid accumulating too many empty objects, you usually give them an expiration time.</p>
<p>This approach has two problems:</p>
<ol>
<li class="">If a large number of keys penetrate the cache, the cached empty objects take up memory.</li>
<li class="">Until the key expires, there is a window where the cache and the database may be inconsistent.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cache-breakdown">Cache Breakdown<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#cache-breakdown" class="hash-link" aria-label="Direct link to Cache Breakdown" title="Direct link to Cache Breakdown" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-34-518f21065331aa6695c8f45bcbc2c799.webp" width="840" height="522" class="img_ev3q"></p>
<p>Cache breakdown occurs when a hot key expires at the exact moment a large number of requests are accessing it. Because the cache has just been invalidated, all of those requests hit the persistent database simultaneously to query the data and write it back to the cache, putting the database under instant pressure.</p>
<p>This is where the difference between <strong>breakdown</strong> and <strong>penetration</strong> becomes clear:</p>
<p>Breakdown is about one extremely hot key. Massive traffic concentrates on that single key, and the instant it expires, every request slams into the database — punching a hole straight through, hence "breakdown." Penetration, by contrast, is about requesting data that doesn't exist at all: large volumes of requests for nonexistent data.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="solutions-1">Solutions<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#solutions-1" class="hash-link" aria-label="Direct link to Solutions" title="Direct link to Solutions" translate="no">​</a></h3>
<p><strong>1) Use a mutex lock</strong></p>
<p>Let the first thread that arrives query the database and write the result back to the cache; all other threads wait for the write-back to finish and then re-read from the cache.</p>
<p><strong>2) Make hot data never expire</strong></p>
<p>For extremely hot data, don't set an expiration time at all; instead, have the business side refresh the cached content asynchronously.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cache-avalanche">Cache Avalanche<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#cache-avalanche" class="hash-link" aria-label="Direct link to Cache Avalanche" title="Direct link to Cache Avalanche" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-35-fdaac8501a626f8c372ae833bbdf7e6e.webp" width="800" height="520" class="img_ev3q"></p>
<p>Cache avalanche happens when a large batch of hot keys in the cache expires at the same time while query volume is huge, so requests fall directly on the database, overwhelming it or even taking it down. Unlike cache breakdown, which is concurrent queries for the same piece of data, an avalanche means many different keys expire at once — lots of data becomes unavailable in the cache, so everything goes to the database.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="solutions-2">Solutions<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#solutions-2" class="hash-link" aria-label="Direct link to Solutions" title="Direct link to Solutions" translate="no">​</a></h3>
<p><strong>1) Stagger expiration times</strong></p>
<p>Set different expiration times so that cache invalidation is spread evenly over time. Common approaches are adding a random offset to the TTL, or planning expiration times centrally.</p>
<p><strong>2) Add a mutex lock</strong></p>
<p>Same idea as with cache breakdown: only one thread rebuilds the cache at a time, while the others block and queue.</p>
<p><strong>3) Never expire the cache</strong></p>
<p>Same idea as with cache breakdown: the cache physically never expires, and an asynchronous thread keeps it updated.</p>
<p><strong>4) Two-tier cache strategy</strong></p>
<p>Use a primary/backup pair of caches:</p>
<ul>
<li class="">Primary cache: TTL set based on experience; this is the cache reads normally hit. When the primary cache expires, load the latest value from the database.</li>
<li class="">Backup cache: long TTL; used for reads when acquiring the lock fails. Whenever the primary cache is updated, the backup cache must be updated in sync.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-chuan-tou-ji-chuan-xue-beng#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>At their core, all three problems are requests bypassing the cache and landing on the database: penetration is querying data that doesn't exist, breakdown is a single hot key expiring, and an avalanche is a large batch of keys expiring at once. The remedies share common threads too — either intercept invalid requests at the cache layer (Bloom filters, empty objects), control the concurrency of requests going back to the source (mutex locks), or make expiration predictable (staggered TTLs, never-expiring keys, two-tier caching). In practice, combine them based on how hot the data is and how strict your consistency requirements are.</p>]]></content:encoded>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[Redis Cache Consistency]]></title>
            <link>https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-yi-zhi-wen-ti</link>
            <guid>https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-yi-zhi-wen-ti</guid>
            <pubDate>Thu, 11 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A look at why Redis caches drift out of sync with the database, comparing write-through, single delete, and delayed double delete strategies — and their trade-offs under high concurrency.]]></description>
            <content:encoded><![CDATA[<p>The moment you put a cache in front of a database, the problem of "the data in the cache doesn't match the data in the database" becomes unavoidable. This post organizes my understanding of cache consistency and the pitfalls of several common strategies.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cache-consistency">Cache Consistency<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#cache-consistency" class="hash-link" aria-label="Direct link to Cache Consistency" title="Direct link to Cache Consistency" translate="no">​</a></h2>
<p>First, let's talk about what the cache consistency problem is, why it needs solving, and what the options are.</p>
<p>Cache consistency problems arise when using a cache: because the cached data and the database data can diverge, you may end up with incorrect data or even data loss. This is especially common under high concurrency, where multiple threads reading and writing the same data make consistency hard to guarantee. The question is how to keep the data in Redis in sync with the data in the database and prevent inconsistency (in high-concurrency scenarios) — in some situations, inconsistent data can cause serious trouble, so the solution matters a great deal.</p>
<p>The root of the problem: the cache and the database are two independent stores, and writing to both is not an atomic operation. No matter which one you write first, there is a time window between the two steps, and any thread reading during that window may see a "half new, half old" state. Add in the unpredictability of thread scheduling, the timing of database transaction commits, and network latency, and all sorts of interleavings become possible. So discussing consistency strategies is really discussing: can this window be eliminated, and if not, how long can stale data survive — and can the business tolerate it?</p>
<p>Below are the common strategies I've collected, along with the problems each one has under high concurrency.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="write-directly-to-the-cache-not-recommended">Write Directly to the Cache (Not Recommended)<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#write-directly-to-the-cache-not-recommended" class="hash-link" aria-label="Direct link to Write Directly to the Cache (Not Recommended)" title="Direct link to Write Directly to the Cache (Not Recommended)" translate="no">​</a></h2>
<p>The most intuitive approach: right after updating the database, write the latest value into Redis. The cache always has a value, and reads never fall through to the database.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-40-749de52be98887e346de853674fc613e.webp" width="942" height="615" class="img_ev3q"></p>
<p>During steps 3 and 4, under high concurrency there is no guarantee that the threads writing to Redis will execute in order (because of CPU time slicing — and it gets even worse with distributed, microservice deployments). As a result, the newest data may be overwritten by older data from another thread, and if no further writes happen afterward, the cache can be stuck holding stale data indefinitely.</p>
<p>In other words: thread A writes to the database first and thread B second, but the writes to Redis can easily land in the reverse order — B first, then A — leaving A's stale value in the cache. Worse, this overwrite has no self-healing mechanism: unless a new write comes along later to "flush" that key, the stale data stays in the cache and keeps getting read.</p>
<p><strong>Some people reach straight for a distributed lock here (a JVM lock is enough for a single application). The lock has to cover the entire flow from the database write through the Redis write, you have to manage the function scope for each key operation yourself, and throughput definitely takes a serious hit — though you can tune the lock granularity. A distributed lock is best suited to strong-consistency scenarios, trading performance for consistency (recommended when strong consistency is required).</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="delete-the-cache-after-writing-the-database-single-delete--not-recommended">Delete the Cache After Writing the Database (Single Delete — Not Recommended)<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#delete-the-cache-after-writing-the-database-single-delete--not-recommended" class="hash-link" aria-label="Direct link to Delete the Cache After Writing the Database (Single Delete — Not Recommended)" title="Direct link to Delete the Cache After Writing the Database (Single Delete — Not Recommended)" translate="no">​</a></h2>
<p>Since "writing the cache" has the overwrite problem, flip the approach: after updating the database, just delete the cache entry and let the next read go back to the database and load the fresh value. Deletion is idempotent, so there's no risk of a new value being overwritten by an old one.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-41-579fa302b29cd904d2a261b4c31e3bf1.webp" width="1431" height="779" class="img_ev3q"></p>
<p>Single delete looks like it neatly avoids the overwrite problem, but things break once reader threads enter the picture. While a reader is fetching data from the database, another thread may be in the middle of committing a database transaction; the reader then gets the pre-commit stale value and stores it in the cache, causing inconsistency.</p>
<p>The exact interleaving goes like this: the reader thread gets a cache miss and queries the database, seeing the old value from before the write transaction commits; the writer thread then commits the transaction and deletes the cache; finally, the reader writes the stale value it's holding back into the cache. The delete happened before the stale value was backfilled, so it accomplished nothing — the cache is dirty again, and once again there is no self-healing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="delayed-double-delete-recommended">Delayed Double Delete (Recommended)<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#delayed-double-delete-recommended" class="hash-link" aria-label="Direct link to Delayed Double Delete (Recommended)" title="Direct link to Delayed Double Delete (Recommended)" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-44-8c4f7a424001dd9bbbe338d1daab955f.webp" width="1550" height="825" class="img_ev3q"></p>
<p>By adding a delay queue and deleting the key a second time after a certain wait, this strategy fixes the problem single delete leaves behind — without any locking. That's why I personally think delayed double delete is a better fit for applications that can tolerate brief inconsistency in some of their data.</p>
<p>The point of the second delete is to act as a safety net: the interleaving above, where the stale value gets backfilled after the first delete, is cleaned up by the delayed second delete when it fires. The delay needs to cover the full duration of "reader queries the database + writes back to the cache" — in practice you estimate a generous value based on the actual latency of the endpoint. The cost is equally clear: within the delay window, reads may still return stale data, so what this guarantees is eventual consistency, not strong consistency.</p>
<p>Beyond the strategies above, there are plenty of other options:</p>
<ol>
<li class="">Read-write locks. Acquire a write lock during write operations so no other thread can read or write the cache until the write completes. This guarantees consistency but may add read latency and lock contention.</li>
<li class="">Version numbers. Store a data version number in the cache and bump it with every update. On reads, compare version numbers and reload the data if they don't match. This guarantees consistency but costs extra storage and read overhead.</li>
<li class="">Caching with TTLs. Set an expiration time when storing data in the cache; once it expires, the entry is invalidated automatically and must be reloaded. This reduces the likelihood of cache/database divergence, but may add storage and read overhead.</li>
<li class="">Asynchronous updates via the database. When writing to the database, don't update the cache directly; instead, publish the update to a message queue and let a consumer update the cache. This offloads the database, but adds message-queue complexity and latency.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Whichever delete-based strategy you pick, give cache entries an expiration time as a backstop. Even if some interleaving produces dirty data, expiration will eventually force a reload from the source and correct it, capping how long the inconsistency can survive.</p>
</li>
<li class="">
<p>If the second delete in delayed double delete is done in memory (e.g., a thread sleeps and then deletes), it's lost when the application restarts. If reliability matters, dispatch the delete task through a message queue or delay queue so failures can be retried.</p>
</li>
<li class="">
<p>The cache delete itself can fail (network hiccups, Redis briefly unavailable). Failed deletes need a retry mechanism, or dirty data will likewise linger indefinitely.</p>
</li>
<li class="">
<p>Don't try to achieve "cache and database perfectly consistent at every instant" in ordinary business logic. The cache and the database are two separate stores; without locks or transaction-level coordination, strong consistency is simply unattainable. First figure out how much inconsistency your business can tolerate and for how long — then pick a strategy.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/08/11/redis-huan-cun-yi-zhi-wen-ti#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>There is no silver bullet for cache consistency; it's fundamentally a trade-off between consistency, performance, and implementation complexity. Writing directly to the cache gets overwritten under concurrency with no self-healing; single delete plugs the overwrite hole but can't stop stale values from being backfilled; delayed double delete adds one deferred deletion as a safety net, buying eventual consistency — good enough for most businesses that can tolerate brief dirty reads. For truly strong-consistency scenarios, just take the lock and trade performance for correctness. Before choosing, answer one question: if this data is stale for a few hundred milliseconds, does the business actually hurt?</p>]]></content:encoded>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[CentOS 7: Enabling the Network Interface]]></title>
            <link>https://tanzhuo.xyz/en/2022/08/10/contos7-kai-qi-wang-qia</link>
            <guid>https://tanzhuo.xyz/en/2022/08/10/contos7-kai-qi-wang-qia</guid>
            <pubDate>Wed, 10 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[After a minimal install, CentOS 7 leaves the network interface disabled by default. How to find the interface config file, set ONBOOT to yes, and restart networking so the machine is online right after install.]]></description>
            <content:encoded><![CDATA[<p>Fresh CentOS 7 install, can't ping the outside world, and ip addr shows no IP — the network isn't broken; the system simply doesn't enable the network interface by default. These notes cover the full steps to enable it and the reason behind it.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2022/08/10/contos7-kai-qi-wang-qia#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>During a Minimal Install of CentOS 7, the network toggle in the installer is off by default. If you don't switch it on during installation, the kernel will still recognize the NIC after install, but it won't be activated at boot and won't request an IP address. The symptoms: everything works locally, but you can't ping the gateway, yum can't install anything, and ssh can't connect.</p>
<p>The fix is simple: find the interface's config file and flip the "enable at boot" switch on.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How It Works<a href="https://tanzhuo.xyz/en/2022/08/10/contos7-kai-qi-wang-qia#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works" translate="no">​</a></h2>
<p>CentOS 7 keeps its network interface config files in <code>/etc/sysconfig/network-scripts/</code>. Each NIC has one file starting with <code>ifcfg-</code>, with the interface name as the suffix — e.g. <code>ifcfg-ens33</code>, <code>ifcfg-eth0</code>.</p>
<p>Whether the interface comes up at boot is controlled by the <code>ONBOOT</code> parameter in that file:</p>
<ul>
<li class=""><code>ONBOOT=no</code>: don't activate this interface at boot — the default when the network toggle was left off during install;</li>
<li class=""><code>ONBOOT=yes</code>: activate automatically at boot; combined with <code>BOOTPROTO=dhcp</code>, it will request an IP from the router automatically.</li>
</ul>
<p>So the whole exercise boils down to changing <code>ONBOOT</code> from <code>no</code> to <code>yes</code> and having the network service re-read the config.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-by-step">Step by Step<a href="https://tanzhuo.xyz/en/2022/08/10/contos7-kai-qi-wang-qia#step-by-step" class="hash-link" aria-label="Direct link to Step by Step" title="Direct link to Step by Step" translate="no">​</a></h2>
<ol>
<li class="">Go to the network scripts directory</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 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>
<ol start="2">
<li class="">Find the name of the interface to enable</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%)"># The ip command accepts prefix abbreviations; addres / addr are both equivalent to the full 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>In the output, any interface other than <code>lo</code> (the local loopback) is a physical NIC; common names are <code>ens33</code>, <code>ens160</code>, or <code>eth0</code>. At this point its state will typically be <code>DOWN</code> with no inet address.</p>
<ol start="3">
<li class="">Edit the matching config file</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%)"># The filename is ifcfg- plus the interface name from the previous step, e.g. for 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>
<ol start="4">
<li class="">Find the line <code>ONBOOT=no</code>, change <code>no</code> to <code>yes</code>, and save the file</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-21-721dec15f4564323c825f9777afbf564.webp" width="652" height="278" class="img_ev3q"></p>
<p>If the machine sits behind a router with DHCP, also confirm <code>BOOTPROTO=dhcp</code>, so it picks up an IP automatically after the network restart.</p>
<ol start="5">
<li class="">Restart the network service to apply the config</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%)">service</span><span class="token plain"> network restart</span><br></div></code></pre></div></div>
<p>Run <code>ip addr</code> again — if the interface is now <code>UP</code> with an inet address, you're done.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/08/10/contos7-kai-qi-wang-qia#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>The config filename is easy to get wrong. The files are named <code>ifcfg-&lt;interface&gt;</code> (no extra hyphen in between). Run <code>ls</code> before opening with vi, so you don't accidentally create a new empty file and wonder why your changes have no effect.</p>
</li>
<li class="">
<p>You're changing "configuration," not "state." <code>ONBOOT=yes</code> only governs boot behavior; after editing, you must restart the network service (or reboot) for it to take effect. You can also bring the interface up temporarily with <code>ifup ens33</code>, but without editing the config file, the machine will be offline again after the next reboot.</p>
</li>
<li class="">
<p>In a VM, if you can't get an IP, check the virtual network mode first. In VMware/VirtualBox, a misconfigured bridged or NAT setup means no outside connectivity even with <code>ONBOOT</code> set correctly — that's a host-level issue, unrelated to the switch this post covers.</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>tip</div><div class="admonitionContent_BuS1"><p>During installation, turn on the network toggle in the installer's "Network &amp; Host Name" page and the system comes with networking out of the box — skipping all of the steps above.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/08/10/contos7-kai-qi-wang-qia#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>When a fresh CentOS 7 install has no network, it's usually not a driver or hardware problem — just <code>ONBOOT</code> defaulting to <code>no</code>. Confirm the interface name with <code>ip addr</code>, edit the matching <code>ifcfg-</code> file under <code>/etc/sysconfig/network-scripts/</code> to set <code>ONBOOT=yes</code>, then <code>service network restart</code>, and you're online. It's a fixed ritual for nearly every new CentOS 7 machine — worth writing down so I don't have to look it up every time.</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Setting Up Redis Replication, Sentinel, and Cluster]]></title>
            <link>https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi</link>
            <guid>https://tanzhuo.xyz/en/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[Setup walkthroughs and configuration notes for Redis's three deployment modes — replication, Sentinel, and Cluster — with pros and cons of each, plus the causes of and remedies for data skew.]]></description>
            <content:encoded><![CDATA[<p>Notes on setting up Redis in its three deployment modes: replication (master-replica), Sentinel, and Cluster. None of them is hard to deploy — it mostly comes down to writing the config files — plus an analysis of each mode's pros and cons.</p>
<!-- -->
<p>Official site: <a href="https://redis.io/" target="_blank" rel="noopener noreferrer" class="">Redis</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="environment">Environment<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#environment" class="hash-link" aria-label="Direct link to Environment" title="Direct link to Environment" translate="no">​</a></h2>
<p>OS: CentOS7</p>
<p>Redis version: Redis7.0.5</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="replication-master-replica-mode">Replication (Master-Replica) Mode<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#replication-master-replica-mode" class="hash-link" aria-label="Direct link to Replication (Master-Replica) Mode" title="Direct link to Replication (Master-Replica) Mode" 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>Pros:</strong> Simple and quick to configure. Read/write splitting fully offloads read pressure from the master, and replicas can be chained — a replica can itself have replicas, nesting-doll style.</p>
<p><strong>Cons:</strong> Not highly available — if the master goes down, you lose write capability. Heavy data redundancy: every replica holds a 100% copy of the master's data. As a non-HA architecture, it's rarely used on its own in production.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="setup-steps">Setup Steps<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#setup-steps" class="hash-link" aria-label="Direct link to Setup Steps" title="Direct link to Setup Steps" translate="no">​</a></h3>
<ol>
<li class="">Configure the replica's redis.conf.</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-2-cfff0cf45887fffb34a8f4514cf67b8c.png" width="800" height="396" class="img_ev3q"></p>
<p>It's best to keep the master and replica passwords identical.</p>
<ol start="2">
<li class="">Configure the master's redis.conf — just set the auth password and remote connection settings as usual.</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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>The result:</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="sentinel-mode">Sentinel Mode<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#sentinel-mode" class="hash-link" aria-label="Direct link to Sentinel Mode" title="Direct link to Sentinel Mode" translate="no">​</a></h2>
<p>Official docs: <a href="https://redis.io/docs/manual/sentinel/" target="_blank" rel="noopener noreferrer" class="">High availability with Redis Sentinel</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-12-52dc39f41e38d8ff5835cf731c4b255c.webp" width="762" height="526" class="img_ev3q"></p>
<p><strong>Pros:</strong> Sentinel solves the problem of losing writes when the master in a replication setup goes down. When the master fails, the sentinels detect that it's unavailable and elect a new master from the remaining replicas, restoring write capability. When the old master comes back up, it rejoins the cluster as a replica.</p>
<p><strong>Cons:</strong> Data redundancy is still heavy, and sentinel nodes don't store data, so compute resources are wasted. <strong>Storage capacity doesn't improve</strong>, <strong>write capacity doesn't improve</strong>, and it's still centralized.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="deployment-notes">Deployment Notes<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#deployment-notes" class="hash-link" aria-label="Direct link to Deployment Notes" title="Direct link to Deployment Notes" translate="no">​</a></h3>
<ul>
<li class="">Sentinel nodes themselves store no data.</li>
<li class="">You need at least 3 sentinel nodes, and the count must be odd (leader election is Raft-based).</li>
<li class="">Sentinel nodes must be deployed on their own, running only the sentinel process — no other applications. This avoids the sentinel entering TILT mode; other applications competing for hardware resources degrade the reliability of sentinel monitoring.</li>
</ul>
<p><strong>Sentinel mode is built on top of replication.</strong> One master, two replicas, plus three sentinels already adds up to six compute instances. In production, sentinels must be deployed separately — never on the master or replica nodes — to keep the whole cluster stable.</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>This mode is production-ready: a highly available architecture that preserves all of Redis's features.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="sentinel-node-setup-steps">Sentinel Node Setup Steps<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#sentinel-node-setup-steps" class="hash-link" aria-label="Direct link to Sentinel Node Setup Steps" title="Direct link to Sentinel Node Setup Steps" translate="no">​</a></h3>
<p>Prerequisite: replication is already configured.</p>
<ol>
<li class="">Configure sentinel.conf with the master address to monitor. The sentinel automatically discovers the replicas from the master's info output.</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-13-6b1ea6129087baad8494b2a35524cd70.png" width="1115" height="296" class="img_ev3q"></p>
<ol start="2">
<li class="">Make sure all master and replica nodes share the same password.</li>
</ol>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="1012" height="162" class="img_ev3q"></p>
<ol start="3">
<li class="">
<p>Start the sentinel with the redis-sentinel command.</p>
</li>
<li class="">
<p>Configure the sentinel connection in spring-boot.</p>
</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-17-3bdd041211172d2edb77c33a0d6bfd06.webp" width="1087" height="487" class="img_ev3q"></p>
<p>If there are many sentinel nodes, you can configure them one per line:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-20-b041c7b3f51196de178e10d5c3f7adf1.webp" width="689" height="497" class="img_ev3q"></p>
<p>Sentinels judge node failure in two stages — subjective down and objective down. Only when enough sentinels agree the master is unavailable does an election vote start. <strong>During the election, the whole cluster cannot serve requests.</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="subjective-down-vs-objective-down">Subjective Down vs. Objective Down<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#subjective-down-vs-objective-down" class="hash-link" aria-label="Direct link to Subjective Down vs. Objective Down" title="Direct link to Subjective Down vs. Objective Down" translate="no">​</a></h3>
<p>SDOWN (subjectively down): the current sentinel instance, on its own, considers a Redis server to be unavailable.</p>
<p>ODOWN (objectively down): multiple sentinel instances all see the master as SDOWN, at which point the master enters ODOWN. Put simply, ODOWN means the cluster has collectively confirmed the master is "unavailable," and failover kicks off.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-18-597476c5a1db840af5dcda6d7bbf4db9.png" width="1107" height="910" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cluster-mode">Cluster Mode<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#cluster-mode" class="hash-link" aria-label="Direct link to Cluster Mode" title="Direct link to Cluster Mode" translate="no">​</a></h2>
<p>Official docs: <a href="https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/" target="_blank" rel="noopener noreferrer" class="">Scale with Redis Cluster</a></p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-22-e4af1f77ba6ebb2ec4dbd5b2942abc63.webp" width="1563" height="587" class="img_ev3q"></p>
<p>There are three common clustering solutions:</p>
<ol>
<li class="">Twemproxy (a Redis proxy open-sourced by Twitter)</li>
</ol>
<ul>
<li class="">No friendly monitoring/management dashboard, which makes operations and monitoring painful.</li>
<li class="">Its biggest pain point: no smooth scale-out/scale-in. Adding Redis instances for business growth means an enormous amount of operational work.</li>
</ul>
<ol start="2">
<li class="">Codis (built in-house at Wandoujia)</li>
</ol>
<ul>
<li class="">Has a management UI.</li>
<li class="">Handles dynamic node addition well.</li>
</ul>
<ol start="3">
<li class="">The official solution: Redis Cluster</li>
</ol>
<p><strong>Pros:</strong> Compared with Sentinel mode, Cluster mode drops the centralized design, implements load balancing automatically, and raises the storage ceiling as nodes are added. Nodes communicate over a lightweight protocol to reduce bandwidth usage, and dynamic node scaling is supported. The cluster maps keys to instances using hash slots: there are 16384 hash slots in total, distributed across all nodes by default, with each instance owning a contiguous range — essentially data partitioning. Each key is hashed with CRC16 and taken modulo 16384, i.e. <strong>CRC16(key) mod 16384</strong>, and the result determines which hash slot the key lives in.</p>
<p>In short, three wins: write capacity scales, storage capacity scales, and there's no central node.</p>
<p><strong>Cons:</strong> Data storage skew and data access skew.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-skew">Data Skew<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#data-skew" class="hash-link" aria-label="Direct link to Data Skew" title="Direct link to Data Skew" translate="no">​</a></h3>
<p>Data skew is when data is distributed unevenly across the instances of a sharded cluster — a large share of the data piles onto one or a few instances, storage pressure isn't spread evenly, and hot data keeps hitting the same fixed instances, driving up their load until they risk going down. There are three main causes.</p>
<ol>
<li class="">Bigkeys.</li>
</ol>
<p>A bigkey has either a very large value (String type) or a huge number of collection elements (collection types). It inflates that instance's data volume and memory consumption. Worse, operations on a bigkey generally block the instance's IO thread, so if the bigkey gets heavy traffic, every other request on that instance slows down.</p>
<p>The fundamental way to avoid bigkey-induced skew is to avoid packing too much data into a single key-value pair when generating data at the business layer. If the bigkey happens to be a collection type, another option is to split it into many small collections spread across different instances.</p>
<ol start="2">
<li class="">Uneven slot allocation.</li>
</ol>
<p>If the cluster operators don't distribute the slots evenly, a large amount of data lands in the same slot — and since a slot lives on exactly one instance, that instance ends up hoarding data, causing skew.</p>
<p>Operational discipline can prevent assigning too many slots to one instance in the first place. For a cluster with slots already allocated, first inspect the slot-to-instance mapping to see whether too many slots have piled onto one instance; if so, migrate some slots to other instances to eliminate the skew.</p>
<p>How you check slot allocation depends on the cluster: for Redis Cluster, use the CLUSTER SLOTS command; for Codis, check the codis dashboard UI.</p>
<ol start="3">
<li class="">Hash Tags.</li>
</ol>
<p>A Hash Tag is a pair of curly braces {} inside a key. The braces enclose part of the key, and when the client computes the key's CRC16 value, it only hashes the content inside the braces.</p>
<p>Say the key is user:profile:3231 and we make 3231 the Hash Tag, turning the key into user:profile:{3231}. The client then computes the CRC16 of just 3231; without the tag, it would hash the entire "user:profile:3231".</p>
<p>The benefit of Hash Tags: keys that share the same tag content map to the same slot, and therefore land on the same instance.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-8eec74dc40b94404b7903c0c126bb753-1-3af83afc758a9b796c22bc133b63ec9b.webp" width="1600" height="389" class="img_ev3q"></p>
<p>Where are Hash Tags typically used? Mainly in Redis Cluster and Codis, to support transactions and range queries. Neither Redis Cluster nor Codis supports cross-instance transactions or range queries natively, so when the application needs them, its only options are to pull the data up into the business layer for transaction handling, or to query each instance one by one and stitch together the range-query result.</p>
<p>With Hash Tags you can map the data involved in a transaction or range query onto the same instance, making both trivial to implement.</p>
<p>The catch is that Hash Tags can concentrate a lot of data onto one instance, causing skew and unbalanced load across the cluster. You have to weigh the need for range queries and transactions against the access pressure that skew brings.</p>
<p>My recommendation: if sharding by Hash Tag would create significant access pressure, prioritize avoiding data skew and skip Hash Tags altogether. Transactions and range queries can still be done client-side, whereas data skew destabilizes instances and can take the service down.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-access-skew-causes-and-remedies">Data Access Skew: Causes and Remedies<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#data-access-skew-causes-and-remedies" class="hash-link" aria-label="Direct link to Data Access Skew: Causes and Remedies" title="Direct link to Data Access Skew: Causes and Remedies" translate="no">​</a></h3>
<p><strong>The root cause of access skew is hot data on an instance</strong> (breaking-news content in a news app, hot items during an e-commerce promotion, and so on). <strong>Once hot data lands on an instance, that instance's request volume far exceeds the others'</strong>, putting it under enormous pressure.</p>
<p>Hot data is usually just one or a few keys, so reassigning slots doesn't solve the problem. Hot data is typically read-heavy, and in that case the remedy is to keep multiple replicas of it.</p>
<p>Concretely: make several copies of the hot data, and prepend a random prefix to each copy's key so the copies don't map to the same slot. Now the hot data has multiple copies that can serve requests concurrently, and because their keys differ, they map to different slots. When assigning those slots to instances, take care to put them on different instances — and the access pressure gets spread out.</p>
<p>Note: the multi-replica approach only works for read-only hot data. If the hot data is both read and written, multi-replica doesn't fit, because keeping the replicas consistent adds overhead. For read-write hot data, you have to scale up the instance itself — e.g. a machine with a beefier spec — to absorb the traffic.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cluster-usage-notes">Cluster Usage Notes<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#cluster-usage-notes" class="hash-link" aria-label="Direct link to Cluster Usage Notes" title="Direct link to Cluster Usage Notes" translate="no">​</a></h3>
<p>Redis Cluster nodes periodically exchange Gossip messages and run heartbeat checks. The official recommendation is to keep a Redis Cluster under 1000 nodes; beyond that, the bandwidth consumption becomes non-trivial.</p>
<ul>
<li class="">Message frequency: when a node finds its last contact with another node exceeded cluster-node-timeout/2, it sends a PING immediately.</li>
<li class="">Message size: the slots array (2kb) plus state data for 1/10 of the cluster (state for 10 nodes is about 1kb).</li>
<li class="">Machine footprint: the more machines the cluster spans, and the more evenly nodes are spread across them, the more aggregate bandwidth the cluster has available.</li>
</ul>
<p>Limitations of <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">Limited support for batch key operations: e.g. mget/mset keys must be in one 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">Limited support for key transactions and Lua: keys involved must be on one 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">The key is the smallest unit of partitioning: a bigkey cannot be split across partitions</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">No multiple databases: cluster mode has only 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">Replication is single-level only: no tree-shaped replication topology</span><br></div><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 delivers capacity and performance scalability that many workloads 'don't need'</span><br></div><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 performance usually 'degrades'</span><br></div><div class="token-line" style="color:hsl(220, 14%, 71%);text-shadow:0 1px rgba(0, 0, 0, 0.3)"><span class="token plain">Commands cannot cross nodes: mget, keys, scan, flush, sinter, etc.</span><br></div><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 and transactions cannot cross 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">Client maintenance is more complex: SDK and application overhead (e.g. more connection pools)</span><br></div></code></pre></div></div>
<p>For many scenarios, Redis Sentinel is already enough.</p>
<p>Codis vs. Redis Cluster:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-23-492599c8b3e333924535a3320378b0d1.webp" width="1230" height="600" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-cluster-summary">Redis Cluster Summary<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#redis-cluster-summary" class="hash-link" aria-label="Direct link to Redis Cluster Summary" title="Direct link to Redis Cluster Summary" 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 partitions data using virtual slots (16384 of them); each node owns a portion of the slots and their data, balancing both data and requests</span><br></div><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. Building a Redis Cluster has four steps: prepare the nodes, the meet operation, assign the slots, replicate the data</span><br></div><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 officially recommends the redis-trib.rb tool for quick setup (redis-trib before Redis 5.0; redis-cli from 5.0 on)</span><br></div><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 scaling works by moving slots and their data between nodes: on scale-out, slots migrate from source nodes to new nodes per a migration plan; on scale-in, any slots owned by a departing node migrate to other nodes first, then cluster forget makes every node in the cluster forget the removed 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">5. Use a smart client for maximum communication efficiency: the client internally computes and maintains the key-slot-node mapping to locate the target node quickly</span><br></div><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. Automatic failover splits into failure detection and node recovery. Node failure has subjective-down and objective-down stages; once more than half the nodes see a failed node as subjectively down, it is marked objectively down. A replica then triggers the failover process for the objectively-down master, preserving cluster availability</span><br></div><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. Common development and operations issues include: bandwidth consumption in very large clusters, pub/sub broadcast issues, cluster skew, and single-instance vs. cluster trade-offs</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-a-redis-cluster-with-redis-cli-version-704">Building a Redis Cluster with redis-cli (version 7.0.4)<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#building-a-redis-cluster-with-redis-cli-version-704" class="hash-link" aria-label="Direct link to Building a Redis Cluster with redis-cli (version 7.0.4)" title="Direct link to Building a Redis Cluster with redis-cli (version 7.0.4)" translate="no">​</a></h3>
<p>At least 6 nodes: (1 master + 1 replica) × 3 = 6 nodes. Before Redis 5.0, use the redis-trib tool; from 5.0 on, use the redis-cli command.</p>
<ol>
<li class="">Set up redis.conf. Use the same password on all nodes, then enable cluster mode (see the official docs for more configuration details):</li>
</ol>
<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%)"># Enable cluster mode</span><span class="token plain"></span><br></div><div class="token-line" style="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/en/assets/images/2022-08-image-24-f3c1ba172fed2ea618311e8117e78173.png" width="907" height="233" class="img_ev3q"></p>
<ol start="2">
<li class="">Run the cluster creation command:</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">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>Here redis-cli --cluster (or ./redis-trib.rb) is the cluster operations command; create means create a cluster; --replicas 1 (or --cluster-replicas 1) sets one replica per master, so total nodes ÷ (replicas + 1) gives the number of masters. The first n nodes in the list therefore become masters, and the rest become replicas, randomly assigned to different masters.</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>warning</div><div class="admonitionContent_BuS1"><p>After running the command you'll be prompted to confirm the cluster's slot allocation. You must type the full word yes, not just y — otherwise automatic slot allocation is skipped and the cluster is unusable. If slots go unallocated, run <code>redis-cli --cluster fix 127.0.0.1:6379 -a 【密码】</code> (against any node) to repair them.</p></div></div>
<p>The log output shows the cluster was created automatically.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-26-422b2f151de78e39201db4d10440f08f.webp" width="1473" height="430" class="img_ev3q"></p>
<ol start="3">
<li class="">Check the current cluster node info:</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">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>Node info below (slots not yet allocated at this point):</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-25-961e8f1293208ec3c1845e699dff0863.webp" width="1365" height="196" class="img_ev3q"></p>
<p>After slot allocation (you can see the slot ranges each master owns):</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-28-05b75092278370a56d2f2cc44a0858fe.webp" width="1600" height="379" class="img_ev3q"></p>
<p>Trying an insert:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-27-23c5475a76cba619e960379c596931d2.webp" width="932" height="289" class="img_ev3q"></p>
<p>Checking with a UI tool that the value was stored — connecting to any cluster node works:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-29-5703e1fbebb16ffc84c54bbb1e248717.webp" width="555" height="553" class="img_ev3q"></p>
<p>The Redis cluster is up and running.</p>
<p>Connecting to the cluster from spring-boot:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2022-08-image-30-a295ea9fd270c78888bafa3e889a27dd.webp" width="712" height="561" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/08/09/redis-zhu-cong-shao-bing-ji-qun-mo-shi#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Replication is the simplest to configure, but losing the master means losing writes — it mostly serves as the foundation for the other modes. Sentinel adds automatic failover on top of replication, and for many workloads that's already enough. Cluster mode brings horizontal scaling of both writes and storage, but introduces new problems like data skew and restricted cross-node commands. When choosing, size up your data volume and write pressure first — there's no need to jump straight to a full cluster.</p>]]></content:encoded>
            <category>Redis</category>
        </item>
        <item>
            <title><![CDATA[Thoughts on Serverless Architecture]]></title>
            <link>https://tanzhuo.xyz/en/2022/07/04/serverless-de-si-kao</link>
            <guid>https://tanzhuo.xyz/en/2022/07/04/serverless-de-si-kao</guid>
            <pubDate>Mon, 04 Jul 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Starting from the FaaS execution model, this post walks through Serverless's pay-per-use billing and auto-scaling advantages, examines vendor lock-in and cold starts, and offers a framework for judging when it fits.]]></description>
            <content:encoded><![CDATA[<p>While reorganizing our team's deployment setup recently, I took another hard look at Serverless. This post records my understanding of the architecture: what problem it solves, how it works under the hood, and which scenarios it fits — and which ones call for caution.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2022/07/04/serverless-de-si-kao#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>The trigger for this post was simple: we have a pile of low-frequency workloads — scheduled scripts, event callbacks, occasional file processing — each occupying a full or half server. The CPU sits idle most of the time, but the machine bills and the operations hours don't shrink one bit. This tension between "always-on resources, sparse workload" is exactly the problem Serverless sets out to solve.</p>
<p>Serverless has become a hot direction in cloud computing in recent years. Its core idea: <strong>developers don't need to worry about server deployment, operations, or scaling — they only focus on writing business logic.</strong></p>
<p>To be clear, "serverless" doesn't mean there are no servers. It means the servers are invisible to the developer — they still exist, but the cloud vendor schedules and maintains them, and the abstraction the developer faces shifts from "machines" to "functions."</p>
<p>In a traditional architecture, launching a system usually means provisioning servers, setting up the environment, configuring networking, monitoring resources, and handling scaling — all of which cost extra operational effort. In a Serverless architecture, the cloud vendor manages all of that infrastructure; the developer just writes function code, deploys it, and it runs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="serverless-in-the-cloud">Serverless in the Cloud<a href="https://tanzhuo.xyz/en/2022/07/04/serverless-de-si-kao#serverless-in-the-cloud" class="hash-link" aria-label="Direct link to Serverless in the Cloud" title="Direct link to Serverless in the Cloud" translate="no">​</a></h2>
<p>Serverless is usually delivered as <strong>cloud functions (Function as a Service, FaaS)</strong> — for example Alibaba Cloud Function Compute (FC), AWS Lambda, or Tencent Cloud SCF. You upload your code, and when a request comes in, the platform runs the function automatically and bills you based on actual invocation count and execution time.</p>
<p>The execution model boils down to a single pipeline: <strong>event source triggers → platform schedules an instance → function executes → instance is reclaimed.</strong> The event source can be an HTTP request, a message queue, a file change in object storage, or a timer. When the platform receives an event, it loads and runs your function code in an isolated environment (typically a container or lightweight sandbox); after execution, the environment is kept warm for a short while for reuse, and reclaimed if it stays idle too long.</p>
<p>The biggest change this model brings: <strong>applications run on demand instead of permanently occupying server resources.</strong></p>
<p>Billing changes accordingly. Traditional servers charge for "time held," traffic or not; FaaS charges for "actual execution," usually calculated from invocation count and execution time (multiplied by the allocated memory size). The sparser the workload, the bigger the cost advantage.</p>
<p>For example, in scenarios like:</p>
<ul>
<li class="">Scheduled task processing</li>
<li class="">Webhook event handling</li>
<li class="">Image processing</li>
<li class="">Data transformation</li>
</ul>
<p>None of these tasks need a continuously running server. With Serverless, a function spins up only when an event fires, saving substantial resource cost.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-elasticity-of-serverless">The Elasticity of Serverless<a href="https://tanzhuo.xyz/en/2022/07/04/serverless-de-si-kao#the-elasticity-of-serverless" class="hash-link" aria-label="Direct link to The Elasticity of Serverless" title="Direct link to The Elasticity of Serverless" translate="no">​</a></h2>
<p>A key feature of Serverless is <strong>automatic scaling</strong>. When request volume suddenly spikes, the platform automatically launches more function instances to handle it; when traffic drops, resources are released. The whole process needs no human intervention and comes with distribution and fault tolerance built in.</p>
<p>The mechanism behind this elasticity is that the platform treats "one invocation" as the basic scheduling unit: each function instance typically handles only a limited number of requests at a time, and more requests mean more instances spun up horizontally, rather than piling pressure on a single instance. Because instances are stateless and can be created and destroyed at will, scaling and failover become routine platform-level operations that the application team no longer has to design.</p>
<p>This model is very friendly to small projects and startup teams. In the traditional model, even a low-traffic service requires a permanently maintained server; with Serverless's per-invocation billing, upfront deployment and operations costs drop dramatically. Alibaba Cloud Function Compute (FC), for instance, currently offers roughly <strong>1 million free invocations per month</strong>, which is more than enough for many small applications.</p>
<p>That said, Serverless isn't perfect — there are trade-offs to weigh.</p>
<p>The most common one is <strong>vendor lock-in</strong>. In the Serverless model, functions tend to depend deeply on the cloud platform's services: OSS, RDS, message queues, logging systems, and so on. Once a system leans heavily on these capabilities, migrating to another cloud vendor usually means reworking the code to some degree. And the lock-in isn't just in code — trigger configuration, permission systems, and how logging and monitoring hook in all differ from vendor to vendor, so migration costs are more scattered than you'd expect.</p>
<p>Another classic issue is the <strong>cold start</strong>. Since Serverless functions usually run in containers or sandboxes, the platform reclaims resources when a function goes unused for a while. When a new request arrives, the runtime environment has to be spun up again, which introduces latency.</p>
<p>Cold start time is the sum of several stages: the platform scheduling and launching the runtime environment, loading the function code and its dependencies, and running the runtime and framework initialization. The heavier the runtime and the more dependencies, the slower this gets — which is why, for the same business logic, runtimes that need to boot a virtual machine like Java see noticeably worse cold starts than scripting languages.</p>
<p>In HTTP or event-triggered scenarios, the first invocation of a function can feel visibly slow. If your workload is highly latency-sensitive, you can buy <strong>reserved instances (pre-warmed resources)</strong> to reduce the cold-start impact — but that adds resource cost. Essentially you're trading "partial always-on" for latency, which puts you back on the traditional cost curve, so you need to run the numbers against your traffic pattern.</p>
<p>Overall, Serverless best fits:</p>
<ul>
<li class="">Event-driven applications</li>
<li class="">Short-running tasks</li>
<li class="">Services with erratic traffic</li>
<li class="">Early-stage, small-scale projects</li>
</ul>
<p>For systems that need to run continuously, demand high performance, or require heavily customized environments — large database services, long-running compute jobs — the traditional server architecture remains the better choice.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/07/04/serverless-de-si-kao#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<p>Building on the analysis above, if you're planning to move workloads to Serverless, think through a few things in advance:</p>
<ol>
<li class="">
<p><strong>Write functions to be stateless.</strong> Instances can be reclaimed at any time, so any state kept in local memory or on local disk is unreliable. Data that needs to persist should go into external storage or a cache service.</p>
</li>
<li class="">
<p><strong>Mind the execution time limit.</strong> FaaS platforms generally cap the duration of a single execution. Long-running tasks either need to be split into a chain of functions or moved to a different compute model.</p>
</li>
<li class="">
<p><strong>Assess how cold starts affect your call paths.</strong> Externally facing synchronous HTTP endpoints are latency-sensitive and feel cold starts the most; asynchronous and scheduled tasks barely notice them, so migrate those first.</p>
</li>
<li class="">
<p><strong>Control how deep your cloud-service dependencies go.</strong> Avoiding cloud services entirely is unrealistic, but you can funnel calls to services like OSS and message queues through a dedicated adapter layer in your code, so the blast radius of a future migration is much smaller.</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>tip</div><div class="admonitionContent_BuS1"><p>A pragmatic approach: pick one or two non-critical asynchronous tasks (say, scheduled cleanup or image compression) as a pilot, get the full billing/logging/alerting loop working end to end, and only then decide whether to expand usage.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/07/04/serverless-de-si-kao#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Serverless is not a full replacement for traditional architecture — it's a new compute model. Its core value: <strong>letting developers focus on the business itself rather than the infrastructure.</strong></p>
<p>It turns "on-demand execution and automatic scaling" into platform capabilities, at the price of accepting cold starts, execution time limits, and some degree of vendor lock-in. Whether to adopt it depends on your traffic shape and latency requirements, not on how new or old the architecture is.</p>
<p>As cloud computing evolves, more and more platforms offer Serverless capabilities, and future system architectures may well converge toward a combined model of:</p>
<p><strong>Serverless + microservices + containerization.</strong></p>]]></content:encoded>
            <category>Java</category>
            <category>K8s</category>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Comparing Linux Distributions]]></title>
            <link>https://tanzhuo.xyz/en/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi</link>
            <guid>https://tanzhuo.xyz/en/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[A comparison of Debian, CentOS, Ubuntu, and openSUSE — their positioning, package management, and stability trade-offs — plus why I picked what I did for production.]]></description>
            <content:encoded><![CDATA[<p>Linux comes in many distributions, each maintained by a different organization. These notes capture what I understand about the differences between some of them.</p>
<!-- -->
<p>Why is this worth a standalone post? Because the distribution you pick at install time largely determines your operations experience for years to come: which package manager you use, how fresh the packages in the repos are, whether the system can roll forward or only migrate across major versions, and how easy it is to find community documentation when something breaks. These differences are barely noticeable day to day, but they all come crashing in the moment you're troubleshooting production or upgrading the OS. Sorting out the main distributions' characteristics up front is far cheaper than switching systems after things go wrong.</p>
<p>Some background first: every Linux distribution is essentially "the Linux kernel + a package management system + a set of default software and configuration." The kernel comes from the same upstream for everyone; what really sets distributions apart is the packaging ecosystem (the Debian family uses apt/dpkg, the Red Hat family uses yum/dnf/rpm, the SUSE family uses zypper/rpm) and the release policy — whether they chase stability, freezing software versions for years, or chase new features with frequent updates. Once you understand those two axes, the trade-offs below become easy to follow.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="debian">Debian<a href="https://tanzhuo.xyz/en/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#debian" class="hash-link" aria-label="Direct link to Debian" title="Direct link to Debian" translate="no">​</a></h2>
<ol>
<li class="">Debian is my favorite distribution to use right now. Version 10 is rock solid and the system footprint is tiny. It's my current choice for production.</li>
</ol>
<p>Debian's stability comes from its release process: packages go through long periods of hardening in the unstable and testing stages before they reach the stable branch; once stable is released, software versions are essentially frozen and only receive security patches and critical fixes. The price is that repo versions skew old, but in exchange you almost never see an "update broke the system" incident on a server. On top of that, the default install is extremely minimal — no unnecessary preinstalled services — so memory and disk usage stay modest. That's the main reason I run it in production.</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/en/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#centos" class="hash-link" aria-label="Direct link to CentOS" title="Direct link to CentOS" translate="no">​</a></h2>
<ol start="2">
<li class="">CentOS was Red Hat's free spin of its commercial operating system. Its features and stability are quite good, though something has surely been trimmed somewhere — after all, they still sell a paid system. CentOS has since stopped being maintained, and its original creator, Gregory Kurtzer, has launched a new project: Rocky Linux.</li>
</ol>
<p>A bit more on that history: CentOS originally took the open-source code of RHEL (Red Hat Enterprise Linux), rebuilt it, stripped the trademarks, and released it for free — which is why it was highly compatible with RHEL and why many companies used it as free RHEL. Red Hat later changed course and turned CentOS into CentOS Stream, flipping it from a "stable downstream rebuild of RHEL" into an "upstream rolling preview of RHEL." For production users who valued stability, that changed the deal entirely. The community responded with successor projects like Rocky Linux and AlmaLinux, which continue the old downstream-of-RHEL compatibility route. If your existing workloads are deeply tied to the CentOS ecosystem, migrating to one of these successors is usually much cheaper than switching to the Debian family.</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/en/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#ubuntu" class="hash-link" aria-label="Direct link to Ubuntu" title="Direct link to Ubuntu" translate="no">​</a></h2>
<ol start="3">
<li class="">Ubuntu is built on top of Debian. It is richer and more powerful in features, but its stability and resource usage fall well short of Debian.</li>
</ol>
<p>Ubuntu is led by Canonical and makes more aggressive choices on the Debian base: newer software versions, broader driver support, a desktop experience that works out of the box, and LTS (long-term support) releases on a fixed cadence. Its sweet spot is desktops and development machines — new hardware gets supported quickly, the community is huge, and almost any problem you hit already has a ready-made answer online. But because it preinstalls more components and runs more default services, the same hardware will burn more resources than under Debian; for a server that only runs a handful of services, all that "richness" becomes a burden.</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/en/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#opensuse" class="hash-link" aria-label="Direct link to openSUSE" title="Direct link to openSUSE" translate="no">​</a></h2>
<ol start="4">
<li class="">openSUSE is said to be the most popular operating system in Germany, and its stability is reportedly excellent. I'll try it out against Debian when I get the chance.</li>
</ol>
<p>Behind openSUSE is SUSE, which follows a "commercial edition + community edition" model similar to Red Hat's. Two features are worth mentioning. First, YaST, a graphical system administration tool that gathers networking, partitioning, and service configuration in one place — very friendly if you'd rather not hand-edit config files. Second, it ships two tracks: Leap (fixed releases, aligned with the commercial SLE) and Tumbleweed (rolling release), so you can choose stability or freshness as needed. Relatively few people use it in China, and Chinese-language material is harder to find than for Debian/Ubuntu — something to factor into your decision.</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="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Never run an end-of-life system in production. A distribution without security updates leaves you exposed to every newly disclosed vulnerability — the CentOS saga is a fresh reminder. Check the support lifecycle of a release before you commit to it.</p>
</li>
<li class="">
<p>Migrating across distribution families is expensive. The differences between apt and yum/dnf go far beyond command names: package naming, how packages are split, and config file paths all differ, and your scripts and operational habits have to change accordingly. Migrating within a family (say, CentOS to Rocky) is much smoother.</p>
</li>
<li class="">
<p>"Fresh software" and "stable system" are at odds most of the time. If you need newer software on Debian stable, prefer the official backports repo or containers; avoid mixing in third-party repos to force upgrades, which easily wrecks the dependency graph.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/06/24/linux-xi-tong-bu-tong-fa-xing-ban-dui-bi#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>None of these distributions is absolutely better than another — it's all about matching the scenario. For servers where stability and a small footprint matter, I pick Debian. If you're heavily invested in the RHEL ecosystem, look at CentOS successors like Rocky Linux. For desktops and dev machines, Ubuntu's out-of-the-box experience saves the most hassle. openSUSE I'll leave for a hands-on comparison later. Picking one family and getting fluent with its package management and release rhythm is worth far more than hopping between systems.</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Bloodborne]]></title>
            <link>https://tanzhuo.xyz/en/2022/06/17/xie-yuan-zu-zhou</link>
            <guid>https://tanzhuo.xyz/en/2022/06/17/xie-yuan-zu-zhou</guid>
            <pubDate>Fri, 17 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Bloodborne is a dark action RPG by FromSoftware, its story built on a deep foundation of Cthulhu mythos, with a world steeped in mystery, madness, and the unknown.]]></description>
            <content:encoded><![CDATA[<p>Bloodborne is a dark action RPG by FromSoftware, its story built on a deep foundation of the <strong>Cthulhu mythos</strong>, with a world steeped in mystery, madness, and the unknown.</p>
<!-- -->
<p>The main stage of the game is an ancient city tucked away in the mountains of a distant land—<strong>Yharnam</strong>. Yharnam is an intensely closed-off, insular city, yet it became world-famous for a mysterious medical technique: <strong>Blood Healing</strong>. This treatment was said to cure almost any disease, drawing countless patients, travelers, and researchers from afar in hopes of ridding themselves of their ailments.</p>
<p>But as blood healing spread, a terrifying plague began to sweep through Yharnam—the <strong>Beast Scourge</strong>. The infected gradually lose their minds and eventually transform into frenzied, bloodthirsty beasts. The whole city descended into chaos and fear, and it is against this backdrop that the player's character, a Hunter, arrives in Yharnam.</p>
<p>As the story unfolds, the player slowly discovers: <strong>beasthood is no ordinary disease—it is tied to beings of a higher order.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-great-ones-and-the-truth-of-the-cosmos">The Great Ones and the Truth of the Cosmos<a href="https://tanzhuo.xyz/en/2022/06/17/xie-yuan-zu-zhou#the-great-ones-and-the-truth-of-the-cosmos" class="hash-link" aria-label="Direct link to The Great Ones and the Truth of the Cosmos" title="Direct link to The Great Ones and the Truth of the Cosmos" translate="no">​</a></h2>
<p>In the world of Bloodborne, there existed in ancient times a class of mysterious beings called the <strong>Great Ones</strong>. These are not gods in the traditional sense but higher-dimensional lifeforms from the depths of the cosmos. They possess wisdom and power far beyond human comprehension, and their forms are often indescribable.</p>
<p>In the Cthulhu mythos, similar beings are typically known as:</p>
<ul>
<li class="">Old Ones</li>
<li class="">Elder Gods</li>
<li class="">Outer Gods</li>
</ul>
<p>These Great Ones existed in this world since ancient times, but as the ages passed, their physical bodies withered away, leaving only their immense <strong>minds and consciousness</strong> still drifting through the cosmos.</p>
<p>The Great Ones yearn to <strong>produce offspring</strong>, but their forms and biology are so alien that they usually cannot reproduce on their own, so they attempt to use humans or other lifeforms as vessels to bear new "children of the gods." This became the crucial starting point of the entire story.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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="the-discovery-of-pthumeru">The Discovery of Pthumeru<a href="https://tanzhuo.xyz/en/2022/06/17/xie-yuan-zu-zhou#the-discovery-of-pthumeru" class="hash-link" aria-label="Direct link to The Discovery of Pthumeru" title="Direct link to The Discovery of Pthumeru" translate="no">​</a></h2>
<p>Early in human history, an ancient civilization called <strong>Pthumeru</strong> built a highly advanced city underground. The Pthumerians lived for generations in these subterranean ruins, conducting extensive research into ancient relics and mysterious powers. During one expedition, they discovered the <strong>remains of a Great One</strong> and extracted from it a peculiar substance:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-old-blood">The Old Blood<a href="https://tanzhuo.xyz/en/2022/06/17/xie-yuan-zu-zhou#the-old-blood" class="hash-link" aria-label="Direct link to The Old Blood" title="Direct link to The Old Blood" translate="no">​</a></h3>
<p>This blood possesses uncanny power:</p>
<ul>
<li class="">It can rapidly cure disease</li>
<li class="">It can strengthen the human body</li>
<li class="">It can even heighten one's spiritual perception</li>
</ul>
<p>But it comes with severe side effects. Humans exposed to the Old Blood for too long gradually lose their reason and ultimately succumb to <strong>beasthood</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-church-and-the-rise-of-blood-healing">The Church and the Rise of Blood Healing<a href="https://tanzhuo.xyz/en/2022/06/17/xie-yuan-zu-zhou#the-church-and-the-rise-of-blood-healing" class="hash-link" aria-label="Direct link to The Church and the Rise of Blood Healing" title="Direct link to The Church and the Rise of Blood Healing" translate="no">​</a></h2>
<p>After the Pthumerian ruins were rediscovered, a powerful organization was founded in Yharnam: the <strong>Healing Church</strong>. Using the Old Blood, the Church developed its famous <strong>blood healing</strong> techniques and quickly turned Yharnam into a world-renowned holy land of medicine. Patients flocked there for treatment, and the city prospered.</p>
<p>Yet as blood healing was used more and more widely, the beast plague began to erupt across the city. To deal with it, the Church secretly established a new organization: the <strong>Hunters</strong>. A Hunter's duty is a single one:</p>
<blockquote>
<p>Hunt down those who have turned into beasts, under the cover of night.</p>
</blockquote>
<p>This is what came to be known as <strong>The Hunt</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-dream-and-the-hunters-fate">The Dream and the Hunter's Fate<a href="https://tanzhuo.xyz/en/2022/06/17/xie-yuan-zu-zhou#the-dream-and-the-hunters-fate" class="hash-link" aria-label="Direct link to The Dream and the Hunter's Fate" title="Direct link to The Dream and the Hunter's Fate" translate="no">​</a></h2>
<p>After receiving a blood transfusion, the player's Hunter is drawn into a mysterious realm: the <strong>Hunter's Dream</strong>. It is a special domain created by the power of the Great Ones, and the place where Hunters endlessly return to life. Every time a Hunter dies, they awaken again in the Dream and re-enter Yharnam to continue the hunt.</p>
<p>But as the story progresses, the Hunter gradually discovers that Yharnam's calamity, the secret of blood healing, the existence of the Great Ones, and the Dream itself all conceal a deeper truth.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-bloodborne-is-really-about">What Bloodborne Is Really About<a href="https://tanzhuo.xyz/en/2022/06/17/xie-yuan-zu-zhou#what-bloodborne-is-really-about" class="hash-link" aria-label="Direct link to What Bloodborne Is Really About" title="Direct link to What Bloodborne Is Really About" translate="no">​</a></h2>
<p>On the surface, Bloodborne tells the story of Hunters battling beasts. But at a deeper level, what it truly explores is:</p>
<ul>
<li class="">Humanity's <strong>greed</strong> for powers beyond its grasp</li>
<li class="">The <strong>pursuit</strong> of gods and the truth of the cosmos</li>
<li class="">And the <strong>boundary between reason and madness</strong></li>
</ul>
<p>As players dig deeper into the world's secrets, humanity comes to realize a cruel fact: <strong>the cosmos is far vaster—and far more terrifying—than mankind ever imagined.</strong></p>]]></content:encoded>
            <category>Games</category>
        </item>
        <item>
            <title><![CDATA[Vue UI Libraries]]></title>
            <link>https://tanzhuo.xyz/en/2022/06/14/vue-chang-jian-uiku</link>
            <guid>https://tanzhuo.xyz/en/2022/06/14/vue-chang-jian-uiku</guid>
            <pubDate>Tue, 14 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A roundup of common UI component libraries in the Vue ecosystem: View Design, Element Plus, Vuetify, Ant Design Vue, and Vuesax, with each one's positioning and best-fit scenarios.]]></description>
            <content:encoded><![CDATA[<p>When starting a Vue project, choosing a UI component library is often the first decision before writing any code: pick the right one and you save a huge amount of repetitive work on buttons, forms, and modals; pick the wrong one and switching later gets expensive. Here's a roundup of the common Vue UI libraries I've come across, with notes on each one's positioning and my impressions, so I can come back to this when choosing next time.</p>
<!-- -->
<p>First, the things I usually look at when choosing: whether the component coverage is sufficient (how polished the high-frequency components like tables, forms, and date pickers are), whether the docs and community are active, whether the design style fits the product, and whether the project is still actively maintained. Let's go through them with that lens.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="view-design">View Design<a href="https://tanzhuo.xyz/en/2022/06/14/vue-chang-jian-uiku#view-design" class="hash-link" aria-label="Direct link to View Design" title="Direct link to View Design" translate="no">​</a></h2>
<p>An enterprise-grade UI component library and frontend solution built on Vue.js 3, serving tens of thousands of developers.</p>
<p>Its predecessor is iView, which built up a sizable user base back in the Vue 2 era, with a style leaning toward clean admin-dashboard aesthetics. Beyond the component library itself, the team also provides companion templates and tools, which makes it a good fit if you want a complete back-office solution rather than just scattered components.</p>
<p><a href="https://www.iviewui.com/" target="_blank" rel="noopener noreferrer" class="">iView / View Design — an enterprise-grade UI component library and frontend solution</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="element-ui">Element UI<a href="https://tanzhuo.xyz/en/2022/06/14/vue-chang-jian-uiku#element-ui" class="hash-link" aria-label="Direct link to Element UI" title="Direct link to Element UI" translate="no">​</a></h2>
<p>A Vue 3 based component library for designers and developers.</p>
<p>The Element family is probably the most frequently seen component library in Chinese Vue projects; the Vue 3 version is called Element Plus. Its strengths are thorough Chinese documentation, a complete component set, and a large community—almost any problem you hit has an existing answer somewhere. For admin and back-office systems, it's practically one of the default choices.</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/en/2022/06/14/vue-chang-jian-uiku#vuetify" class="hash-link" aria-label="Direct link to Vuetify" title="Direct link to Vuetify" translate="no">​</a></h2>
<p>Vuetify is a Vue UI library with beautifully handcrafted Material components. No design skills required—everything you need to create amazing applications is at your fingertips.</p>
<p>It strictly follows Google's Material Design spec, with a unified visual style that carries a distinctly Material feel. If your product is going for the Material look anyway, or targets an international audience, Vuetify is a better fit than the Chinese libraries; conversely, if you want to heavily customize it into your own visual system, the effort gets painful.</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/en/2022/06/14/vue-chang-jian-uiku#ant-design-vue" class="hash-link" aria-label="Direct link to Ant Design Vue" title="Direct link to Ant Design Vue" translate="no">​</a></h2>
<p>A Vue UI library that follows the Ant Design specification, containing a set of high-quality components and demos for building rich, interactive user interfaces.</p>
<p>Ant Design itself is a design system the Ant team created for the React ecosystem, and Ant Design Vue is its implementation on the Vue side. The upside is that the design spec has been battle-tested by a large number of admin products, so complex components like tables and forms are very feature-complete; and if your team runs both React and Vue projects, sharing one design language also lowers communication costs.</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/en/2022/06/14/vue-chang-jian-uiku#vuesax" class="hash-link" aria-label="Direct link to Vuesax" title="Direct link to Vuesax" translate="no">​</a></h2>
<p>Great styling and animations—unfortunately, it's no longer maintained.</p>
<p>Vuesax's components genuinely have livelier colors and interaction animations than the libraries above, making it a fit for projects going for a younger visual vibe. But being unmaintained means compatibility with new Vue versions and bug fixes are no longer guaranteed, so I wouldn't pick it for new projects—it's listed here mainly as a reference.</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="pitfalls-and-notes">Pitfalls and Notes<a href="https://tanzhuo.xyz/en/2022/06/14/vue-chang-jian-uiku#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and Notes" title="Direct link to Pitfalls and Notes" translate="no">​</a></h2>
<ol>
<li class="">
<p>Check maintenance status first. A library that's stopped being maintained, like Vuesax, is a landmine planted in your project's future; before choosing, look at the repo's commit history and how issues get answered.</p>
</li>
<li class="">
<p>Keep the Vue 2 and Vue 3 versions straight. Most of these libraries went through the Vue 2 to Vue 3 migration, and the package names and docs are often two separate sets (e.g. Element UI vs. Element Plus)—installing the wrong version simply won't run.</p>
</li>
<li class="">
<p>Import on demand. These UI libraries all have a sizable full-bundle footprint; for production projects, configure on-demand loading so only the components you use get bundled.</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>tip</div><div class="admonitionContent_BuS1"><p>If you're just writing a demo or an internal tool, pick whichever library's docs feel most comfortable and don't overthink it; save the cost of careful selection for production projects that will be maintained long-term.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://tanzhuo.xyz/en/2022/06/14/vue-chang-jian-uiku#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>For admin and back-office systems, Element Plus or Ant Design Vue are safe picks—both communities are big enough; for a Material look or an international audience, look at Vuetify; View Design suits teams that want a full back-office package; Vuesax is unmaintained and listed for reference only. There's no single right answer—whatever fits your team's habits and your product's style is the good choice.</p>]]></content:encoded>
            <category>Frontend</category>
        </item>
        <item>
            <title><![CDATA[Understanding the Linux Directory Structure]]></title>
            <link>https://tanzhuo.xyz/en/2022/06/13/linux-mu-lu-jie-gou-fen-xi</link>
            <guid>https://tanzhuo.xyz/en/2022/06/13/linux-mu-lu-jie-gou-fen-xi</guid>
            <pubDate>Mon, 13 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A tour of the directories under the Linux root, guided by the FHS standard: what goes in /bin, /etc, /var, /usr, /opt and why, plus how to tell apart a few commonly confused directories.]]></description>
            <content:encoded><![CDATA[<p>The first time you log into a Linux server and run <code>ls /</code>, the row of cryptic short directory names is bound to be a little bewildering. This note walks through the responsibilities of the common directories under the root, sorting out what belongs where.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-the-directories-look-like-this">Why the Directories Look Like This<a href="https://tanzhuo.xyz/en/2022/06/13/linux-mu-lu-jie-gou-fen-xi#why-the-directories-look-like-this" class="hash-link" aria-label="Direct link to Why the Directories Look Like This" title="Direct link to Why the Directories Look Like This" translate="no">​</a></h2>
<p>The Linux directory layout isn't arbitrary convention—it follows a specification called the FHS (Filesystem Hierarchy Standard). It defines which directory each category of files belongs in: executables, configuration, logs, and temporary files each have their place. The benefits are immediate—</p>
<ul>
<li class="">Package managers know where to install files, and can remove them cleanly on uninstall;</li>
<li class="">When operators troubleshoot, there's no guessing where configs and logs live—<code>/etc</code> and <code>/var/log</code> almost never disappoint;</li>
<li class="">Different distributions stay largely consistent, so experience transfers.</li>
</ul>
<p>To see the root layout on your own machine, one command is enough:</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%)"># List the top-level directories under root</span><span class="token plain"></span><br></div><div class="token-line" 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="the-core-directories-one-by-one">The Core Directories, One by One<a href="https://tanzhuo.xyz/en/2022/06/13/linux-mu-lu-jie-gou-fen-xi#the-core-directories-one-by-one" class="hash-link" aria-label="Direct link to The Core Directories, One by One" title="Direct link to The Core Directories, One by One" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-programs-and-commands">1) Programs and Commands<a href="https://tanzhuo.xyz/en/2022/06/13/linux-mu-lu-jie-gou-fen-xi#1-programs-and-commands" class="hash-link" aria-label="Direct link to 1) Programs and Commands" title="Direct link to 1) Programs and Commands" translate="no">​</a></h3>
<p>/bin: holds commonly used commands and binary executables, such as <code>ls</code>, <code>cp</code>, and <code>cat</code>—the basic commands every user relies on.</p>
<p>/sbin: commands and programs meant for administrators, typically tools for network configuration, disk partitioning, and system shutdown/reboot. It's usually not in a regular user's <code>PATH</code>.</p>
<p>/usr: the directory for user applications and files. The name is easily misread as "user," but it actually stands for Unix System Resources. The vast majority of software installed by the system lives here, with <code>/usr/bin</code>, <code>/usr/lib</code>, and <code>/usr/share</code> holding programs, libraries, and shared data respectively. Many modern distributions have done the usr merge, so <code>/bin</code> is actually a symlink to <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%)"># On distributions with the usr merge you can see the symlinks</span><span class="token plain"></span><br></div><div class="token-line" 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: short for "optional," it's where third-party software gets installed. Unlike <code>/usr</code>, which is managed by the package manager, <code>/opt</code> is better suited to commercial software that ships its own complete directory tree, or large software installed by manually unpacking an archive—one subdirectory per application, which also makes removal easy.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-configuration-and-devices">2) Configuration and Devices<a href="https://tanzhuo.xyz/en/2022/06/13/linux-mu-lu-jie-gou-fen-xi#2-configuration-and-devices" class="hash-link" aria-label="Direct link to 2) Configuration and Devices" title="Direct link to 2) Configuration and Devices" translate="no">​</a></h3>
<p>/etc: dedicated to system configuration files and their subdirectories. Changing system behavior almost always starts here—think <code>/etc/fstab</code>, <code>/etc/hosts</code>, <code>/etc/ssh/</code>. When backing up system configuration, this directory is the priority.</p>
<p>/dev: short for Device, containing the external devices currently connected to the Linux system. Linux follows the "everything is a file" design, so devices are accessed the same way as files—a disk is <code>/dev/sda</code>, a terminal is <code>/dev/tty</code>, and there are special devices like <code>/dev/null</code>.</p>
<p>/boot: holds the kernel files needed to boot Linux, including link files and images, and the bootloader (e.g. GRUB) configuration also lives here. Don't touch this directory lightly—deleting the wrong file can leave the system unable to boot.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-user-data">3) User Data<a href="https://tanzhuo.xyz/en/2022/06/13/linux-mu-lu-jie-gou-fen-xi#3-user-data" class="hash-link" aria-label="Direct link to 3) User Data" title="Direct link to 3) User Data" translate="no">​</a></h3>
<p>/home: the users' directory. Each system account gets its own folder—user alice's home directory is <code>/home/alice</code>.</p>
<p>/root: the administrator's home directory. Note that it is not under <code>/home</code>—that way, even if the partition mounted at <code>/home</code> breaks, the root user can still log in to troubleshoot.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-runtime-and-volatile-data">4) Runtime and Volatile Data<a href="https://tanzhuo.xyz/en/2022/06/13/linux-mu-lu-jie-gou-fen-xi#4-runtime-and-volatile-data" class="hash-link" aria-label="Direct link to 4) Runtime and Volatile Data" title="Direct link to 4) Runtime and Volatile Data" translate="no">​</a></h3>
<p>/var: holds files that change frequently, such as system logs. <code>/var/log</code> stores logs, <code>/var/lib</code> stores services' persistent data (database files often live here), and <code>/var/cache</code> stores caches. When a disk fills up, this is usually the first place to check:</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%)"># Find the subdirectories using the most space under /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: the temporary files directory, holding transient files produced by running programs; any user can write to it. Many distributions periodically clean out files that haven't been accessed, changed, or modified for a while (say, 10 days), so don't put anything you need to keep here.</p>
<p>/run: another temporary directory, storing transient information since the system booted, such as services' PID files and socket files. When the system reboots, files under this directory are cleared—it's typically mounted in memory and inherently volatile.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="commonly-confused-pairs">Commonly Confused Pairs<a href="https://tanzhuo.xyz/en/2022/06/13/linux-mu-lu-jie-gou-fen-xi#commonly-confused-pairs" class="hash-link" aria-label="Direct link to Commonly Confused Pairs" title="Direct link to Commonly Confused Pairs" translate="no">​</a></h2>
<ul>
<li class=""><strong>/tmp vs /run</strong>: both are temporary directories, but <code>/tmp</code> serves applications and users, while <code>/run</code> serves system services' runtime state; <code>/run</code> is always cleared on reboot, whereas <code>/tmp</code> depends on the distribution's policy.</li>
<li class=""><strong>/opt vs /usr/local</strong>: both hold software outside the package manager. Software compiled and installed by hand conventionally goes in <code>/usr/local</code> (with a structure parallel to <code>/usr</code>), while third-party software unpacked as a whole goes in <code>/opt</code>.</li>
<li class=""><strong>/root vs /</strong>: the former is the root user's home directory, the latter is the root of the entire filesystem. The names look alike but they're entirely different things.</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>tip</div><div class="admonitionContent_BuS1"><p>When investigating a "disk full" problem, check <code>/var/log</code> and <code>/tmp</code> first; when hunting for some software's configuration, look for a directory of the same name under <code>/etc</code> first. Remembering directory responsibilities is more useful than remembering specific paths.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://tanzhuo.xyz/en/2022/06/13/linux-mu-lu-jie-gou-fen-xi#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>The Linux directory structure is essentially classification by "the nature of the files": programs go in <code>/bin</code> and <code>/usr</code>, configuration in <code>/etc</code>, volatile data in <code>/var</code>, temporary files in <code>/tmp</code> and <code>/run</code>, and user data in <code>/home</code>. Once you internalize this classification logic, you can quickly find what you're looking for even on an unfamiliar machine.</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Installing Docker on CentOS]]></title>
            <link>https://tanzhuo.xyz/en/2022/06/08/centos-an-zhuang-docker</link>
            <guid>https://tanzhuo.xyz/en/2022/06/08/centos-an-zhuang-docker</guid>
            <pubDate>Wed, 08 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A complete walkthrough of installing Docker on CentOS via yum: configuring a China-based mirror, pinning a version, starting the service, plus changing the image storage path, setting up a registry mirror, and enabling start on boot.]]></description>
            <content:encoded><![CDATA[<p>With container technology as popular as it is today, knowing how to install Docker is an essential skill.</p>
<!-- -->
<p>The installation steps in Docker's official docs aren't complicated in themselves, but following them verbatim from mainland China usually gets you stuck on download speeds. On top of that, production environments typically need a pinned Docker version and a relocated image storage directory — details the official docs scatter across several pages. This post pulls together my full Docker installation process on CentOS, including the post-install configuration I always apply, so future reinstalls are just a matter of following along.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="installation-steps">Installation Steps<a href="https://tanzhuo.xyz/en/2022/06/08/centos-an-zhuang-docker#installation-steps" class="hash-link" aria-label="Direct link to Installation Steps" title="Direct link to Installation Steps" translate="no">​</a></h2>
<ol>
<li class="">Install the required yum packages</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%)"># yum-utils provides the yum-config-manager tool used to add repositories later</span><span class="token plain"></span><br></div><div class="token-line" 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 and lvm2 are dependencies of the devicemapper storage driver</span><span class="token plain"></span><br></div><div class="token-line" 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>These are all foundational tools: <code>yum-config-manager</code> is the command we need in the next step to add the repository, and the other two packages are storage-driver dependencies — installing them now avoids errors later.</p>
<ol start="2">
<li class="">Configure a China-based mirror to speed up Docker downloads</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%)"># Add Alibaba Cloud's docker-ce repository in place of the official one</span><span class="token plain"></span><br></div><div class="token-line" 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's official repository is very slow — or times out entirely — from mainland China. Switching to Alibaba Cloud's mirror brings package download speeds back to normal. Under the hood, this command writes a repo file into <code>/etc/yum.repos.d/</code>, after which yum can resolve Docker-related packages from that repository.</p>
<ol start="3">
<li class="">List the available package versions</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%)"># List every available docker-ce version in the repository, sorted newest first</span><span class="token plain"></span><br></div><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>The <code>--showduplicates</code> flag lists every historical version in the repository instead of only the latest. The second column of the output is the version string, which we'll use in the next step to install a specific version.</p>
<ol start="4">
<li class="">Install a specific Docker version</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%)"># Replace &lt;VERSION_STRING&gt; with the version string found in the previous step</span><span class="token plain"></span><br></div><div class="token-line" 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>Example: installing a particular Docker version.</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%)"># Install version 20.10.9, keeping docker-ce and docker-ce-cli in sync</span><span class="token plain"></span><br></div><div class="token-line" 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>This installs three packages, each with its own role: <code>docker-ce</code> is the daemon (dockerd), <code>docker-ce-cli</code> is the command-line client, and <code>containerd.io</code> is the underlying container runtime that actually handles container creation and lifecycle management. Running <code>yum install docker-ce</code> without a version installs whatever is newest in the repository; for production, pin the version so different machines don't drift apart.</p>
<ol start="5">
<li class="">Start the Docker service</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%)">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>Once started, run <code>docker info</code> or <code>docker version</code> to confirm the daemon is up. That completes the installation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="going-further">Going Further<a href="https://tanzhuo.xyz/en/2022/06/08/centos-an-zhuang-docker#going-further" class="hash-link" aria-label="Direct link to Going Further" title="Direct link to Going Further" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-changing-dockers-default-image-storage-path-and-configuring-a-registry-mirror">1) Changing Docker's default image storage path and configuring a registry mirror<a href="https://tanzhuo.xyz/en/2022/06/08/centos-an-zhuang-docker#1-changing-dockers-default-image-storage-path-and-configuring-a-registry-mirror" class="hash-link" aria-label="Direct link to 1) Changing Docker's default image storage path and configuring a registry mirror" title="Direct link to 1) Changing Docker's default image storage path and configuring a registry mirror" translate="no">​</a></h3>
<p>By default, Docker stores images and container data under <code>/var/lib/docker</code>. Many servers have limited space on the root partition, and a handful of large images can fill it up quickly, so it's common practice to move the storage directory to a larger data disk. Meanwhile, pulling from Docker Hub is slow in mainland China, and configuring a local registry mirror makes a noticeable difference. Both settings live in daemon.json.</p>
<ol>
<li class="">Edit /etc/docker/daemon.json with vim</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%)"># The file doesn't exist by default; saving it in vim creates it</span><span class="token plain"></span><br></div><div class="token-line" 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>
<ol start="2">
<li class="">Set <code>data-root</code> to change the image storage path, and set <code>registry-mirrors</code> to change the image download source — it's an array, so you can list multiple addresses separated by commas.</li>
</ol>
<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>The mirror here is the <a href="https://docker.mirrors.ustc.edu.cn/" target="_blank" rel="noopener noreferrer" class="">USTC mirror</a>. <code>registry-mirrors</code> is an array, so you can configure several addresses and Docker will try them in order when pulling images.</p>
<p>After editing daemon.json, restart 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>Then run <code>docker info</code> to verify the changes took effect. If they did, <code>Docker Root Dir</code> in the output will show the new storage path, and <code>Registry Mirrors</code> will list the configured mirror addresses.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-enabling-docker-to-start-on-boot">2) Enabling Docker to start on boot<a href="https://tanzhuo.xyz/en/2022/06/08/centos-an-zhuang-docker#2-enabling-docker-to-start-on-boot" class="hash-link" aria-label="Direct link to 2) Enabling Docker to start on boot" title="Direct link to 2) Enabling Docker to start on boot" 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%)"># Register as a system service so Docker starts automatically after a reboot</span><span class="token plain"></span><br></div><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>Servers get rebooted sooner or later. Forget this step and none of your containers come back after a reboot — you'd have to start the Docker service by hand. So enable it right after installation while you're at it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-notes">Pitfalls and Notes<a href="https://tanzhuo.xyz/en/2022/06/08/centos-an-zhuang-docker#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and Notes" title="Direct link to Pitfalls and Notes" translate="no">​</a></h2>
<ul>
<li class="">daemon.json is strict JSON — one extra comma or missing quote and Docker fails to start. If Docker won't restart after a config change, check this file's syntax first.</li>
<li class="">Changing <code>data-root</code> only switches to a new directory; the existing images and container data under <code>/var/lib/docker</code> are not migrated automatically. If you need them, copy them over yourself before restarting.</li>
<li class="">When pinning versions, keep the <code>docker-ce</code> and <code>docker-ce-cli</code> version strings identical — mixing them can produce client/daemon incompatibility warnings.</li>
<li class="">If the machine has an older Docker installed (e.g. the legacy <code>docker</code> or <code>docker-engine</code> packages), remove it cleanly before installing docker-ce to avoid conflicts.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/06/08/centos-an-zhuang-docker#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The whole process boils down to five steps: install the tooling, switch to a local mirror, list the versions, install a pinned version, and start the service. Don't rush to use it right away — first set up the image storage path, the registry mirror, and start-on-boot. Each is a one-time configuration that pays off for the life of the machine. Next time a fresh server needs provisioning, just work through this note from top to bottom.</p>]]></content:encoded>
            <category>Docker</category>
        </item>
        <item>
            <title><![CDATA[Docker Compose Basics]]></title>
            <link>https://tanzhuo.xyz/en/2022/06/08/docker-compose-ji-ben-shi-yong</link>
            <guid>https://tanzhuo.xyz/en/2022/06/08/docker-compose-ji-ben-shi-yong</guid>
            <pubDate>Wed, 08 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[docker-compose describes a multi-container application's services, networks, and volumes in a single YAML file and brings the whole stack up with one command. This post covers how to install it, how it works, and the commands you'll use day to day.]]></description>
            <content:encoded><![CDATA[<p>Once Docker is installed, we end up running lots of containerized services — and orchestrating them all becomes a problem: network grouping, startup order, volume mounts. That's why the Docker team built docker-compose, a component that makes container orchestration easy.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-orchestration">Why Orchestration<a href="https://tanzhuo.xyz/en/2022/06/08/docker-compose-ji-ben-shi-yong#why-orchestration" class="hash-link" aria-label="Direct link to Why Orchestration" title="Direct link to Why Orchestration" translate="no">​</a></h2>
<p>A single container is easy to start with <code>docker run</code>, but real applications rarely consist of just one: a web service usually comes with a database, a cache, and a message queue. These containers depend on each other, need to reach each other on the same network, and each mounts its own volumes. If everything relies on hand-typed <code>docker run</code> commands, nobody remembers the arguments once they get long — and redeploying on a new machine is torture.</p>
<p>docker-compose solves exactly this: it captures every container's startup parameters in a single YAML file that lives under version control, so anyone with the file can reproduce the whole environment with one command.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="installation">Installation<a href="https://tanzhuo.xyz/en/2022/06/08/docker-compose-ji-ben-shi-yong#installation" class="hash-link" aria-label="Direct link to Installation" title="Direct link to Installation" translate="no">​</a></h2>
<p>The open-source project lives at:</p>
<p><a href="https://github.com/docker/compose" target="_blank" rel="noopener noreferrer" class="">https://github.com/docker/compose</a></p>
<p>Download the binary matching your system architecture from the project's Releases page. Installation takes two steps.</p>
<ol>
<li class="">Download the file and upload it to /usr/local/bin on the server.</li>
</ol>
<p>We put it in /usr/local/bin because that directory is on the PATH by default, so once it's there you can run <code>docker-compose</code> from anywhere.</p>
<ol start="2">
<li class="">Make the file executable</li>
</ol>
<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%)"># Grant execute permission, otherwise the shell reports 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>After installing, run <code>docker-compose version</code> to verify — if it prints version info, you're good.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How It Works<a href="https://tanzhuo.xyz/en/2022/06/08/docker-compose-ji-ben-shi-yong#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works" translate="no">​</a></h2>
<p>docker-compose is just a binary that runs directly on Linux. It lets you configure every container your application needs in a YAML file, then create and start all the services from that configuration with a single command: docker-compose up -d.</p>
<p>It's not a separate container engine — under the hood it does exactly what manual <code>docker run</code> commands do: it parses the YAML and calls Docker's API to create networks, volumes, and containers. A few key mechanisms:</p>
<ol>
<li class="">
<p><strong>Project</strong>: compose defaults the project name to the directory containing the YAML file. Containers, networks, and volumes created within a project carry that prefix, keeping projects isolated from each other.</p>
</li>
<li class="">
<p><strong>Default network</strong>: <code>up</code> automatically creates a bridge network and puts every service defined in the file on it. Containers on the same network can reach each other using service names as hostnames — no need to care about container IPs.</p>
</li>
<li class="">
<p><strong>Declarative management</strong>: on subsequent <code>up</code> runs, compose diffs the YAML against the current container state and only recreates services that changed, leaving the rest untouched.</p>
</li>
</ol>
<p>A typical docker-compose.yml looks roughly like this:</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%)"># image to use</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># host port:container port</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># volume mount</span><span class="token plain"></span><br></div><div class="token-line" style="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%)"># startup order: app first, then 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%)"># can also build on the spot from a 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%)"># restart automatically if the container exits abnormally</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-commands">Common Commands<a href="https://tanzhuo.xyz/en/2022/06/08/docker-compose-ji-ben-shi-yong#common-commands" class="hash-link" aria-label="Direct link to Common Commands" title="Direct link to Common Commands" translate="no">​</a></h2>
<p>All the commands below must be run from the directory containing docker-compose.yml, or with <code>-f</code> pointing to the file.</p>
<p>Common commands:</p>
<p>docker-compose up [start containers; add -d to run in the background]</p>
<p>docker-compose stop [stop containers]</p>
<p>docker-compose ps [list all orchestrated services]</p>
<p>docker-compose logs -f --tail=500 &lt;container name&gt; [follow a container's logs live]</p>
<p>docker-compose restart [restart containers]</p>
<p>docker-compose rm  [remove containers]</p>
<p>docker-compose build  [build images]</p>
<p>A few extra notes:</p>
<ul>
<li class=""><code>up</code> without <code>-d</code> runs in the foreground and streams every container's logs to your terminal — handy for debugging, and Ctrl+C stops everything. For day-to-day deployment it's almost always <code>up -d</code>.</li>
<li class="">The <code>--tail=500</code> on <code>logs</code> starts following from the last 500 lines only; without it the full log history floods out, and with a large log volume the terminal hangs for a long while.</li>
<li class=""><code>stop</code> only stops containers without removing them; <code>rm</code> removes stopped containers. To stop and remove containers and networks in one go, use <code>docker-compose down</code>.</li>
<li class="">After changing a Dockerfile, <code>up -d</code> alone won't rebuild the image — either <code>build</code> first and then <code>up</code>, or just run <code>up -d --build</code>.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-notes">Pitfalls and Notes<a href="https://tanzhuo.xyz/en/2022/06/08/docker-compose-ji-ben-shi-yong#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and Notes" title="Direct link to Pitfalls and Notes" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>Re-run up after editing the YAML</strong>: <code>restart</code> alone doesn't apply config changes — it just restarts the old containers. To land the new configuration you must run <code>up -d</code> again so compose recreates the containers that changed.</p>
</li>
<li class="">
<p><strong>depends_on only controls startup order</strong>: it guarantees containers start in order, but not that the dependencies are actually ready. For example, the database container may be up while the process isn't yet accepting connections — the app will error out if it connects at that moment, so the app side needs retry logic.</p>
</li>
<li class="">
<p><strong>Watch your working directory with relative volume paths</strong>: relative paths in the YAML are resolved relative to the YAML file's location. When running from a different directory, use <code>-f</code> to point at the file explicitly and avoid mounting the wrong path.</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>warning</div><div class="admonitionContent_BuS1"><p><code>docker-compose down</code> with the <code>-v</code> flag deletes the volumes along with everything else. Before running it against stateful services (databases, etc.), make absolutely sure the data in those volumes is disposable.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/06/08/docker-compose-ji-ben-shi-yong#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>docker-compose is essentially a pile of <code>docker run</code> arguments frozen into a version-controllable YAML file: one <code>up -d</code> brings up the whole stack, and <code>logs</code>, <code>ps</code>, and <code>restart</code> cover everyday operations. On a single machine it's more than comfortable enough — once your services grow to the point of needing multi-machine scheduling, that's the time to look at orchestration systems like Kubernetes.</p>]]></content:encoded>
            <category>Docker</category>
        </item>
        <item>
            <title><![CDATA[Everyday Linux Commands]]></title>
            <link>https://tanzhuo.xyz/en/2022/06/08/linux-chang-yong-ming-ling</link>
            <guid>https://tanzhuo.xyz/en/2022/06/08/linux-chang-yong-ming-ling</guid>
            <pubDate>Wed, 08 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A quick reference for day-to-day Linux ops commands: CPU, memory, file operations, and system info queries, plus a list of frequently used commands for easy lookup.]]></description>
            <content:encoded><![CDATA[<p>I use these Linux commands over and over in daily work, so rather than searching for them every time, I've organized them into a quick-reference note. They're grouped by CPU, memory, files, and system, with a list of frequently used commands at the end.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cpu-commands">CPU Commands<a href="https://tanzhuo.xyz/en/2022/06/08/linux-chang-yong-ming-ling#cpu-commands" class="hash-link" aria-label="Direct link to CPU Commands" title="Direct link to CPU Commands" 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%)"># Number of physical CPUs</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Number of CPU cores</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Number of CPU threads</span><span class="token plain"></span><br></div><div class="token-line" 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 model</span><span class="token plain"></span><br></div><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%)"># Detailed CPU information</span><span class="token plain"></span><br></div><div class="token-line" 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="memory-commands">Memory Commands<a href="https://tanzhuo.xyz/en/2022/06/08/linux-chang-yong-ming-ling#memory-commands" class="hash-link" aria-label="Direct link to Memory Commands" title="Direct link to Memory Commands" 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%)"># Current memory usage</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Top 10 processes by memory usage</span><span class="token plain"></span><br></div><div class="token-line" 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="file-commands">File Commands<a href="https://tanzhuo.xyz/en/2022/06/08/linux-chang-yong-ming-ling#file-commands" class="hash-link" aria-label="Direct link to File Commands" title="Direct link to File Commands" 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%)"># show files or directories</span><span class="token plain"></span><br></div><div class="token-line" 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 files with details (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%)"># list all files and directories in the current directory, including hidden ones (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%)"># create a directory</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># create a directory, creating parent directories as needed (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%)"># change directory</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># create an empty file</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># create a file with content</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># view file contents</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># copy</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># move or rename</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># delete a file</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># delete recursively, including subdirectories and files</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># force delete</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># search the filesystem for a file</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># count lines, words, and characters in text</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># search for a string in a text file</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># delete an empty directory</span><span class="token plain"></span><br></div><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%)"># show a directory as a tree; requires the tree package</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># show the current directory</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="system-commands">System Commands<a href="https://tanzhuo.xyz/en/2022/06/08/linux-chang-yong-ming-ling#system-commands" class="hash-link" aria-label="Direct link to System Commands" title="Direct link to System Commands" 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%)"># Show system information</span><span class="token plain"></span><br></div><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%)"># Query the kernel 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 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="frequently-used-commands">Frequently Used Commands<a href="https://tanzhuo.xyz/en/2022/06/08/linux-chang-yong-ming-ling#frequently-used-commands" class="hash-link" aria-label="Direct link to Frequently Used Commands" title="Direct link to Frequently Used Commands" translate="no">​</a></h2>
<ol>
<li class="">cd: change the current working directory</li>
<li class="">ls: list files and subdirectories in the current working directory</li>
<li class="">mkdir: create a new directory</li>
<li class="">rm: delete files or directories</li>
<li class="">cp: copy files or directories</li>
<li class="">mv: move files or directories</li>
<li class="">touch: create a new file or update an existing file's access and modification times</li>
<li class="">cat: view file contents</li>
<li class="">grep: search for a specific string in files</li>
<li class="">find: locate files in the filesystem</li>
<li class="">chmod: change file or directory permissions</li>
<li class="">chown: change file or directory ownership</li>
<li class="">top: show the processes currently running on the system</li>
<li class="">ps: list currently running processes</li>
<li class="">ssh: connect to a remote host over a secure shell</li>
<li class="">scp: copy files between hosts over a secure shell</li>
<li class="">tar: create or extract tar archives</li>
<li class="">gzip: compress files</li>
<li class="">unzip: decompress files</li>
<li class="">ping: test network connectivity to another host</li>
<li class="">ifconfig: show and manage network interfaces</li>
<li class="">route: show and manage the network routing table</li>
<li class="">netstat: show network status information</li>
<li class="">iptables: manage firewall rules</li>
<li class="">systemctl: manage system services</li>
<li class="">service: manage system services</li>
<li class="">du: show disk usage of directories and files</li>
<li class="">df: show filesystem disk usage</li>
<li class="">uname: show system information</li>
<li class="">date: show or change the system date and time</li>
<li class="">history: show the history of previously executed commands</li>
<li class="">man: view a command's manual page</li>
<li class="">which: find the path of a given command</li>
<li class="">whereis: find the path and manual page of a given command</li>
<li class="">whoami: show the current user's username</li>
<li class="">su: switch to another user or the superuser</li>
<li class="">sudo: run a command with superuser privileges</li>
<li class="">ssh-keygen: generate an SSH key pair</li>
<li class="">ssh-copy-id: copy an SSH public key to a remote host</li>
<li class="">scp: securely copy files over SSH</li>
<li class="">curl: send requests to a server and fetch the response</li>
<li class="">wget: download files from the web</li>
<li class="">vim: a powerful text editor</li>
<li class="">nano: an easy-to-use text editor</li>
<li class="">tar: create or extract tar archives</li>
<li class="">gzip: compress files</li>
<li class="">bzip2: compress files</li>
<li class="">unzip: decompress files</li>
<li class="">zip: create or extract zip archives</li>
<li class="">sshfs: mount a remote filesystem over SSH</li>
<li class="">rsync: sync files and directories remotely</li>
<li class="">awk: process and analyze text</li>
<li class="">sed: process and transform text</li>
<li class="">sort: sort lines of text</li>
<li class="">uniq: remove duplicate lines from text</li>
<li class="">cut: extract columns from text</li>
<li class="">paste: merge text columns into a single file</li>
<li class="">tee: copy standard output to a file and the terminal</li>
<li class="">diff: compare the differences between two files</li>
<li class="">patch: apply a patch from a diff file</li>
<li class="">head: show the first lines of a file</li>
<li class="">tail: show the last lines of a file</li>
<li class="">tar: create or extract tar archives</li>
<li class="">gzip: compress files</li>
<li class="">bzip2: compress files</li>
<li class="">unzip: decompress files</li>
<li class="">zip: create or extract zip archives</li>
<li class="">rpm: install, remove, and query RPM packages</li>
<li class="">dpkg: install, remove, and query Debian packages</li>
<li class="">ps: list running processes</li>
<li class="">kill: stop a running process</li>
<li class="">top: show the processes currently running on the system</li>
<li class="">uptime: show system uptime and load</li>
<li class="">free: show system memory usage</li>
<li class="">df: show filesystem disk usage</li>
<li class="">du: show disk usage of directories and files</li>
<li class="">mount: mount a filesystem</li>
<li class="">umount: unmount a filesystem</li>
<li class="">chroot: change the root directory</li>
<li class="">ln: create symbolic or hard links</li>
<li class="">echo: print text or variables</li>
<li class="">export: set environment variables</li>
<li class="">source: execute a script and import it into the current shell</li>
<li class="">alias: create an alias</li>
<li class="">unalias: remove an alias</li>
<li class="">jobs: list currently running jobs</li>
<li class="">bg: send a job to run in the background</li>
<li class="">fg: bring a job back to the foreground</li>
<li class="">nohup: run a job in the background so it survives closing the terminal</li>
<li class="">crontab: set up scheduled tasks</li>
<li class="">at: run a command at a specified time</li>
<li class="">screen: create multiple terminal sessions</li>
<li class="">tmux: create multiple terminal sessions</li>
<li class="">curlftpfs: mount a remote filesystem over FTP</li>
<li class="">dig: query DNS servers</li>
<li class="">nslookup: query DNS servers</li>
<li class="">ping: test network connectivity to another host</li>
<li class="">traceroute: show the path packets take through the network</li>
<li class="">tcpdump: capture network packets</li>
<li class="">iftop: show network traffic information</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/06/08/linux-chang-yong-ming-ling#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Nobody remembers every command — what matters is knowing where to look them up. This note collects the commands I use most, grouped by CPU, memory, files, and system, and together with the man pages it covers most troubleshooting scenarios. As new commands worth recording come up, I'll keep adding them here.</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Mounting a New Disk on Linux]]></title>
            <link>https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan</link>
            <guid>https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan</guid>
            <pubDate>Wed, 08 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A complete walkthrough of mounting a new disk on a Linux server: partitioning with fdisk, formatting with mkfs, mounting, and editing /etc/fstab for auto-mount at boot, plus common pitfalls.]]></description>
            <content:encoded><![CDATA[<p>You add a new hard drive to a server, plug it in, and the system shows no extra usable space—a scenario almost everyone runs into the first time they expand storage. Between "connected to the motherboard" and "able to store files" there are four steps: detection, partitioning, formatting, and mounting. This note walks through the whole process and explains what each step actually does.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background-why-a-freshly-plugged-in-disk-isnt-usable">Background: Why a Freshly Plugged-in Disk Isn't Usable<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#background-why-a-freshly-plugged-in-disk-isnt-usable" class="hash-link" aria-label="Direct link to Background: Why a Freshly Plugged-in Disk Isn't Usable" title="Direct link to Background: Why a Freshly Plugged-in Disk Isn't Usable" translate="no">​</a></h2>
<p>In Linux, everything is a file, and disks are no exception. When a new disk is attached, the kernel recognizes it as a block device such as <code>/dev/sdb</code> or <code>/dev/vdb</code>, but at that point it's just a "raw" disk:</p>
<ul>
<li class="">No partition table, so the system doesn't know how the space is divided;</li>
<li class="">No filesystem, so the kernel doesn't know how data is organized;</li>
<li class="">No mount point, so userspace programs can't access it through a path.</li>
</ul>
<p>The full workflow is therefore: detect → partition → format → mount, and finally configure auto-mount at boot. Skipping any step breaks the chain.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-by-step">Step by Step<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#step-by-step" class="hash-link" aria-label="Direct link to Step by Step" title="Direct link to Step by Step" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-connect-the-disk-to-the-motherboard-this-matters">1) Connect the disk to the motherboard (this matters)<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#1-connect-the-disk-to-the-motherboard-this-matters" class="hash-link" aria-label="Direct link to 1) Connect the disk to the motherboard (this matters)" title="Direct link to 1) Connect the disk to the motherboard (this matters)" translate="no">​</a></h3>
<p>On a physical machine, that means hooking up the SATA and power cables; on a cloud server, it means attaching the cloud disk to the instance in the console. This sounds obvious, but when troubleshooting "the system can't see the disk," a loose hardware connection really is the most common cause.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-reboot-the-system">2) Reboot the system<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#2-reboot-the-system" class="hash-link" aria-label="Direct link to 2) Reboot the system" title="Direct link to 2) Reboot the system" 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>On older systems, or when hot-plugging doesn't take effect, a reboot is the easiest way to make the kernel rescan the hardware. Environments that support hot-plugging can detect the disk without rebooting, but a reboot is the most reliable fallback.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-check-whether-the-disk-has-been-detected">3) Check whether the disk has been detected<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#3-check-whether-the-disk-has-been-detected" class="hash-link" aria-label="Direct link to 3) Check whether the disk has been detected" title="Direct link to 3) Check whether the disk has been detected" 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> lists all block devices and their partitions. A new disk typically shows capacity but no partitions (no entries like <code>/dev/sdb1</code> underneath it). Note down this device name—you'll need it in every subsequent step. You can also cross-check with <code>lsblk</code>, which displays disks and partitions as a tree and is easier to read.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-enter-the-disk-and-start-partitioning">4) Enter the disk and start partitioning<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#4-enter-the-disk-and-start-partitioning" class="hash-link" aria-label="Direct link to 4) Enter the disk and start partitioning" title="Direct link to 4) Enter the disk and start partitioning" 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">disk name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><br></div></code></pre></div></div>
<p>Replace <code>{disk name}</code> with the device name from the previous step, e.g. <code>sdb</code>. <code>fdisk</code> is an interactive tool; the commands you'll use most often inside it are:</p>
<ul>
<li class=""><code>n</code>: create a new partition</li>
<li class=""><code>p</code>: print the current partition table</li>
<li class=""><code>d</code>: delete a partition</li>
<li class=""><code>w</code>: write changes to disk and exit</li>
<li class=""><code>q</code>: exit without saving</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-set-up-the-partitionsa-single-partition-is-fine">5) Set up the partitions—a single partition is fine<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#5-set-up-the-partitionsa-single-partition-is-fine" class="hash-link" aria-label="Direct link to 5) Set up the partitions—a single partition is fine" title="Direct link to 5) Set up the partitions—a single partition is fine" translate="no">​</a></h3>
<p>If the whole disk is just for storing data, press <code>n</code> to create one primary partition, hit Enter to accept the default start and end sectors, and turn the entire disk into a single partition, then <code>w</code> to save and exit. Splitting a disk into multiple partitions rarely pays off these days and only adds management overhead.</p>
<p>One reminder: the traditional MBR partition table only supports up to 2TB. Disks larger than 2TB need a GPT partition table, in which case you should use <code>parted</code> or <code>gdisk</code> instead.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-format-the-partition">6) Format the partition<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#6-format-the-partition" class="hash-link" aria-label="Direct link to 6) Format the partition" title="Direct link to 6) Format the partition" 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">partition name</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><br></div></code></pre></div></div>
<p>Note that the target here is the <strong>partition</strong> (e.g. <code>/dev/sdb1</code>), not the whole disk. <code>mkfs.ext4</code> creates an ext4 filesystem on the partition, writing metadata structures like the superblock and inode tables. ext4 is currently the most universal choice across Linux distributions; if your distribution defaults to XFS (like the CentOS family), <code>mkfs.xfs</code> works just as well.</p>
<p>Formatting wipes all data on the partition, so double-check the device name before running it.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-mount-the-partition">7) Mount the partition<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#7-mount-the-partition" class="hash-link" aria-label="Direct link to 7) Mount the partition" title="Direct link to 7) Mount the partition" 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">partition 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">some directory</span><span class="token punctuation" style="color:hsl(220, 14%, 71%)">}</span><br></div></code></pre></div></div>
<p>The mount point is just a regular directory—if it doesn't exist, create it first with <code>mkdir</code>, e.g. <code>mkdir /data</code>. Once mounted, files written to <code>/data</code> actually land on the new disk. You can verify with <code>df -h</code>, which shows the mapping between partitions, capacity, and mount points.</p>
<p>If the mount point directory already contained files, they'll be "hidden" after mounting and reappear once you <code>umount</code>—no data is lost, but it's confusing, so use an empty directory as the mount point.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-set-up-auto-mount-at-boot">8) Set up auto-mount at boot<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#8-set-up-auto-mount-at-boot" class="hash-link" aria-label="Direct link to 8) Set up auto-mount at boot" title="Direct link to 8) Set up auto-mount at boot" translate="no">​</a></h3>
<p>The <code>mount</code> command only lasts for the current boot; after a restart the mount is gone. To make it persistent, edit <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>Append a line at the end of the file:</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/(disk partition) /(mount directory) ext4 (filesystem type) defaults 0 0</span><br></div></code></pre></div></div>
<p>This line has six fields, in order:</p>
<ol>
<li class=""><strong>Device</strong>: the partition to mount, e.g. <code>/dev/sdb1</code>;</li>
<li class=""><strong>Mount point</strong>: the directory path, e.g. <code>/data</code>;</li>
<li class=""><strong>Filesystem type</strong>: must match what you formatted with, here <code>ext4</code>;</li>
<li class=""><strong>Mount options</strong>: <code>defaults</code> means a standard set of options like read-write and allow-execute;</li>
<li class=""><strong>Dump backup flag</strong>: <code>0</code> means don't back up with the dump utility;</li>
<li class=""><strong>fsck check order</strong>: <code>0</code> means no filesystem check at boot; the root partition is usually <code>1</code>, and data disks can be <code>2</code> or <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>warning</div><div class="admonitionContent_BuS1"><p>A mistake in <code>/etc/fstab</code> can make the system fail to boot and drop into emergency mode. After saving, run <code>mount -a</code> first to verify—it mounts every not-yet-mounted entry in fstab. Only reboot once it completes without errors.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-notes">Pitfalls and Notes<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and Notes" title="Direct link to Pitfalls and Notes" translate="no">​</a></h2>
<ul>
<li class=""><strong>Use UUIDs instead of device names for reliability.</strong> Names like <code>/dev/sdb</code> depend on the kernel's detection order and can change after adding or removing disks, causing fstab to mount the wrong one. Look up the partition's UUID with <code>blkid</code> and write the first fstab column as <code>UUID=xxxx</code>—that way device-name drift can't affect it.</li>
<li class=""><strong>Confirm the device name before formatting.</strong> Running <code>mkfs</code> on the wrong target is irreversible. Double-check with <code>lsblk</code> or <code>fdisk -l</code> beforehand and make sure the capacity matches what you expect.</li>
<li class=""><strong>Distinguish disks from partitions.</strong> Partitioning, formatting, and mounting each operate on different objects: <code>fdisk</code> works on the whole disk (<code>/dev/sdb</code>), while <code>mkfs</code> and <code>mount</code> work on partitions (<code>/dev/sdb1</code>). Mixing them up is the most common source of errors for beginners.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://tanzhuo.xyz/en/2022/06/08/linux-gua-zai-ci-pan#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>The main line of mounting a new disk is four steps: <code>fdisk -l</code> to confirm detection, <code>fdisk</code> to partition, <code>mkfs.ext4</code> to format, <code>mount</code> to mount, and finally writing <code>/etc/fstab</code> so the configuration survives reboots. Once you understand the chain of "raw disk → partition → filesystem → mount point," any future storage expansion or disk replacement can be troubleshot layer by layer along that same chain.</p>]]></content:encoded>
            <category>Linux</category>
        </item>
        <item>
            <title><![CDATA[Setting Up a K3S Cluster]]></title>
            <link>https://tanzhuo.xyz/en/2022/06/02/k3s-ji-qun-da-jian</link>
            <guid>https://tanzhuo.xyz/en/2022/06/02/k3s-ji-qun-da-jian</guid>
            <pubDate>Thu, 02 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Building an HA K3S cluster with embedded etcd across six Alibaba Cloud servers using k3sup: environment prep, master/worker deployment commands, and a rundown of common k3sup flags.]]></description>
            <content:encoded><![CDATA[<p>I've been learning how to deploy K8S clusters recently and tried quite a few approaches along the way: the official <a href="https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/" target="_blank" rel="noopener noreferrer" class=""><strong>kubeadm</strong></a> tool, QingCloud's KK one-click installer, and various open-source (free and paid) one-click deployment tools on GitHub. After trying them all, I settled on <a href="https://github.com/alexellis/k3sup" target="_blank" rel="noopener noreferrer" class="">K3S-up</a> for deploying my cluster.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-k3s">Why K3S<a href="https://tanzhuo.xyz/en/2022/06/02/k3s-ji-qun-da-jian#why-k3s" class="hash-link" aria-label="Direct link to Why K3S" title="Direct link to Why K3S" translate="no">​</a></h2>
<p>Compared to full K8S, K3S runs far fewer system containers and is much more lightweight. It uses containerd as the container runtime by default, ships traefik (written in Go) as its built-in ingress, and bundles SQLite as a replacement for etcd. That said, with multiple master nodes you should use etcd to guarantee data consistency and achieve HA.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="environment-planning">Environment Planning<a href="https://tanzhuo.xyz/en/2022/06/02/k3s-ji-qun-da-jian#environment-planning" class="hash-link" aria-label="Direct link to Environment Planning" title="Direct link to Environment Planning" translate="no">​</a></h2>
<p>For this setup I used six shared-type servers on Alibaba Cloud to build an HA k3s cluster backed by embedded etcd:</p>
<ul>
<li class="">3 masters (2 cores, 4 GB RAM)</li>
<li class="">3 workers (2 cores, 4 GB RAM)</li>
</ul>
<p>All running Debian 10.10.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerequisites">Prerequisites<a href="https://tanzhuo.xyz/en/2022/06/02/k3s-ji-qun-da-jian#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites" translate="no">​</a></h2>
<ol>
<li class="">Disable the firewall and update the package index:</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="">Install cgroupfs-mount, then reboot the machine:</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>Download the K3s binaries. Since <a href="https://github.com/k3s-io/k3s" target="_blank" rel="noopener noreferrer" class="">k3s</a> is hosted on GitHub, downloads from servers in mainland China can be painfully slow. It's better to grab the k3s binary from GitHub first and copy it onto the servers yourself, saving the download time during installation.</p>
</li>
<li class="">
<p>Verify the environment:</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%)"># show basic commands</span><span class="token plain"></span><br></div><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%)"># check whether the environment meets the requirements</span><br></div></code></pre></div></div>
<p>Repeat steps 1-4 on every server. If that sounds tedious, set up one server first, snapshot its system disk as an image, and select that image when creating the remaining cloud servers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploying-the-cluster">Deploying the Cluster<a href="https://tanzhuo.xyz/en/2022/06/02/k3s-ji-qun-da-jian#deploying-the-cluster" class="hash-link" aria-label="Direct link to Deploying the Cluster" title="Direct link to Deploying the Cluster" translate="no">​</a></h2>
<ol>
<li class="">Once the environment checks pass, use k3sup to initialize the first control-plane master node:</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 starts the server in cluster mode with embedded 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="">Join the other master nodes to the cluster:</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 means join as a master; --server-ip points to the first 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="">Join the worker nodes:</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="">Check node status:</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="common-k3sup-flags">Common k3sup Flags<a href="https://tanzhuo.xyz/en/2022/06/02/k3s-ji-qun-da-jian#common-k3sup-flags" class="hash-link" aria-label="Direct link to Common k3sup Flags" title="Direct link to Common k3sup Flags" translate="no">​</a></h2>
<ul>
<li class=""><code>--cluster</code> — start this server in cluster mode using embedded etcd (embedded HA)</li>
<li class=""><code>--skip-install</code> — if k3s is already installed, run this to just fetch the kubeconfig</li>
<li class=""><code>--ssh-key</code> — specify the SSH key path for remote login</li>
<li class=""><code>--local-path</code> — set where the kubeconfig is saved; defaults to <code>./kubeconfig</code>, which gets overwritten by default</li>
<li class=""><code>--merge</code> — merge the config into an existing file instead of overwriting it, e.g. add it to the default kubectl config: <code>--local-path ~/.kube/config --merge</code></li>
<li class=""><code>--context</code> — set the kubeconfig context name; defaults to <code>default</code></li>
<li class=""><code>--ssh-port</code> — specify the SSH port; defaults to 22</li>
<li class=""><code>--k3s-extra-args</code> — optional extra arguments passed to the k3s installer, wrapped in quotes, e.g. <code>--k3s-extra-args '--no-deploy traefik'</code> or <code>--k3s-extra-args '--docker'</code>; combine multiple arguments inside a single pair of quotes: <code>--k3s-extra-args '--no-deploy traefik --docker'</code></li>
<li class=""><code>--k3s-version</code> — pin a specific k3s version, e.g. <code>v1.21.1</code></li>
<li class=""><code>--k3s-channel</code> — set the k3s version by channel, e.g. <code>stable</code></li>
<li class=""><code>--ipsec</code> — force k3s to use <code>--flannel-backend ipsec</code></li>
<li class=""><code>--print-command</code> — print the command being sent to the remote machine over SSH</li>
<li class=""><code>--datastore</code> — pass a SQL connection string to k3s's <code>--datastore-endpoint</code>; the format must <a href="https://rancher.com/docs/k3s/latest/en/installation/ha/" target="_blank" rel="noopener noreferrer" class="">follow what k3s requires per the Rancher docs</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cluster-dashboard">Cluster Dashboard<a href="https://tanzhuo.xyz/en/2022/06/02/k3s-ji-qun-da-jian#cluster-dashboard" class="hash-link" aria-label="Direct link to Cluster Dashboard" title="Direct link to Cluster Dashboard" translate="no">​</a></h2>
<p>k3s pairs well with kuboard. Kuboard dashboard docs: <a href="https://kuboard.cn/overview/share-coder.html" target="_blank" rel="noopener noreferrer" class="">Kuboard Overview | Kuboard</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2022/06/02/k3s-ji-qun-da-jian#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>With that, we have a working k3s cluster that can scale out as the workload grows. The whole process runs remotely over SSH via k3sup, which is a lot less hassle than running kubeadm by hand, and it makes repeated rebuilds practical even under mainland China's network conditions. K3S itself is lightweight, and with embedded etcd it delivers master-node HA, making it a solid choice for small clusters. Next up: wiring in monitoring and a service mesh stack.</p>]]></content:encoded>
            <category>K8s</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Divinity: Original Sin 2]]></title>
            <link>https://tanzhuo.xyz/en/2022/01/13/shen-jie-yuan-zui-2</link>
            <guid>https://tanzhuo.xyz/en/2022/01/13/shen-jie-yuan-zui-2</guid>
            <pubDate>Thu, 13 Jan 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Nearly every book about the birth of Rivellon tells of the Seven Gods and the world's origins. This post traces the birth of the Seven, and the rise of the Divine and the Godwoken.]]></description>
            <content:encoded><![CDATA[<p>On the continent of Rivellon, nearly every book about the world's creation tells of the Seven Gods and the origins of Rivellon. No one can vouch for how true these stories are, but they have been passed down through the ages among every race.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-birth-of-the-seven-gods">The Birth of the Seven Gods<a href="https://tanzhuo.xyz/en/2022/01/13/shen-jie-yuan-zui-2#the-birth-of-the-seven-gods" class="hash-link" aria-label="Direct link to The Birth of the Seven Gods" title="Direct link to The Birth of the Seven Gods" translate="no">​</a></h2>
<p>Legend has it that before the planes, the worlds, and everything within them came to be, there was nothing but pure, untapped energy—the raw material of creation and life, known as Source. Opposing it was the Void—destruction and nothingness. These two opposites existed in balance with one another. Where there is balance and friction, life takes root and universes are born. Then, just as a plant reaches for sunlight, Source found its way to the planes it could create upon. Likewise, the Void drifted toward the planes of the universe and began to exert its enormous influence. On many planes, Source and Void each fought for the upper hand. Where Source prevailed, life flourished. Rivellon is one such world.</p>
<p>Its potent concentration of Source spontaneously conceived primal beings of power—elemental gods, the earliest known instances of divinity. Over time these gods either grew stronger or withered away, their fortunes shifting according to the deeds of their creations. Gods of the earliest primitive faiths soon emerged—a god of war, a god of love—growing ever more powerful as mortals came to know war and love.</p>
<p>As the gods grew, six rising races formalized this concentration of Source through what would later be called worship. Thus arose the Seven Lords—each corresponding to one of the races (one of them to the wizards). The Seven drew Source from the worship of their followers and used it to gain power—and as their gods grew mighty, so too did the races. The gods who could not compete, or whose power was lesser, faded into oblivion along the way.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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="the-rise-of-the-divine-and-the-godwoken">The Rise of the Divine and the Godwoken<a href="https://tanzhuo.xyz/en/2022/01/13/shen-jie-yuan-zui-2#the-rise-of-the-divine-and-the-godwoken" class="hash-link" aria-label="Direct link to The Rise of the Divine and the Godwoken" title="Direct link to The Rise of the Divine and the Godwoken" translate="no">​</a></h2>
<p>As the power of the Seven kept growing, the friction between them grew fiercer. None would yield, and their contests were fought out through their respective races. War raged, and for a long age nothing could unite them again... until the Void came.</p>
<p>Though the veil of the real world holds firm, it is not perfect. The Void could periodically find a crack through which to invade, threatening the races and the gods themselves. The Seven knew they had to unite to stop it, and so they created the Divine—a lone mortal champion. They granted him their divine power, making him a hero who transcended death to become an avatar of the gods. This Divine would beat back the Void, driving it out beyond the veil of reality and back into its own realm. And so it happened many times over—a Divine would arise and repel the Void. The most recent Divine to save the world was Lucian, who fought the Void again and again and prevailed. But Lucian is now dead, and a new Divine has yet to appear.</p>
<p>Descended from the mortal heroes who helped repel the Void in the first Chaos War, a lineage of people known as the Godwoken kept emerging. Their bloodline lets the Godwoken perceive and manipulate Source with a deftness rare among mortals. With these abilities, the Godwoken became the only mortals capable of receiving the gods' gift—the full power of the Seven. The divine awakening appears across all races and manifests differently in each individual. All of them possess an innate ability to perceive Source and, with proper training, to absorb it and wield it for feats of incredible creation and destruction.</p>]]></content:encoded>
            <category>Games</category>
        </item>
        <item>
            <title><![CDATA[Automated Releases with Jenkins]]></title>
            <link>https://tanzhuo.xyz/en/2021/11/19/jenkins</link>
            <guid>https://tanzhuo.xyz/en/2021/11/19/jenkins</guid>
            <pubDate>Fri, 19 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Notes on building an automated release pipeline with Jenkins: pull code, build with maven, package images via docker-compose and push to a private registry, then trigger remote deployment—plus pitfalls and cleanup tips.]]></description>
            <content:encoded><![CDATA[<p>After doing enough manual releases, the routine of packaging, uploading, and restarting for every deploy gets both time-consuming and error-prone. These are my notes on wiring the whole release process into a Jenkins pipeline.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2021/11/19/jenkins#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>Before the pipeline, a release went roughly like this: build the package locally, scp it to the server, log in, stop the service, swap the jar, start it again. The steps aren't complicated, but they all depend on a human doing them right—miss one environment config change or upload the package to the wrong machine, and you have a classic recipe for a production incident. Handing these steps to Jenkins saves effort, but more importantly it makes releases repeatable and traceable: every build is recorded, and when something goes wrong you can roll back to a specific image version.</p>
<p>Here are some of the steps from my recent Jenkins release setup.</p>
<p>The overall flow: use Jenkins to release to production, packaging docker images and pushing them to a private docker registry, with docker login configured so production can push/pull images and run them.</p>
<p>Broken down, the pipeline does four things: pull code from git and swap in environment config, build the jar with maven, build images via docker-compose and push them to the private registry, and finally notify the target server remotely to pull the new image and run it. The rest of this post follows that order.</p>
<p>Installing docker and pulling the Jenkins image is well covered elsewhere, so I'll skip it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="plugin-integration">Plugin Integration<a href="https://tanzhuo.xyz/en/2021/11/19/jenkins#plugin-integration" class="hash-link" aria-label="Direct link to Plugin Integration" title="Direct link to Plugin Integration" translate="no">​</a></h2>
<p>Jenkins itself is really just a scheduler—its build capabilities mostly come from global tool configuration and plugins. So step one is setting up the build components under "Global Tool Configuration." Jenkins can download and install them automatically, or you can point it at existing installations on the host.</p>
<p>Configure and install the basic components: maven, jdk, git, nodeJs, docker, and so on.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2021-11-image-1-e2d082c744c33296646996dab9f2ca3a.webp" width="1587" height="886" class="img_ev3q"></p>
<p>With the tools in place, head to the plugin center to install plugins that connect to external systems. The rule here is install-as-needed: if your code lives on gitlab, install the gitlab plugin. It can receive gitlab webhook pushes, so a commit automatically triggers a build.</p>
<p>Install the various supporting plugins, such as gitlab.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2021-11-image-15888d64c81502e58d0621da66a56419.webp" width="1600" height="165" class="img_ev3q"></p>
<p>The nodejs plugin is used for frontend releases.</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>Frontend and backend can share the same Jenkins; only the build tool differs—maven for the backend, while frontend jobs just specify a nodeJs version and run npm build.</p>
<p>Finally, you need a plugin that can connect to a remote server and execute commands. The Jenkins machine and the production machines usually aren't the same box. Once the build artifact is pushed to the registry, something has to tell the target server to pull it and restart—that's this plugin's job, running scripts on the remote host over SSH.</p>
<p>Plugin for connecting to remote servers and executing commands:</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="creating-the-pipeline-job">Creating the Pipeline Job<a href="https://tanzhuo.xyz/en/2021/11/19/jenkins#creating-the-pipeline-job" class="hash-link" aria-label="Direct link to Creating the Pipeline Job" title="Direct link to Creating the Pipeline Job" translate="no">​</a></h2>
<p>With the plugins ready, it's time to create the job. A job is essentially a sequence of build steps: each step specifies which component to use and what command to run, and the output of one step is the input to the next.</p>
<p>Create the job, then configure the command and component for each stage.</p>
<p>First, configure the git repository address and project code to pull.</p>
<p>Then replace environment values in the source—for example, swap the dev nacos connection address and namespace for the pro ones.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2021-11-image-4-d082c74127085b930fc6786f03f16aff.png" width="1269" height="136" class="img_ev3q"></p>
<p>The environment-value replacement step deserves a bit more explanation. The config center address and namespace usually differ between dev and production. If the repository defaults to dev config, you have to replace it before building the production package—otherwise the service will connect to the test environment's nacos once it goes live.</p>
<p>I use the simplest approach, the sed -i command, which matches a regular expression and replaces the text.</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 edits the file in place; s/old/new/g is a regex replace, g means replace all matches</span><span class="token plain"></span><br></div><div class="token-line" 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>The upside of sed is zero dependencies—one line in a script does the job. The downside is that the replacement rules end up scattered across Jenkins job configs, which becomes hard to maintain as they multiply. Once the project grows, consider maven profiles or your config center's multi-environment support to consolidate environment differences in one place.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="packaging-and-pushing-artifacts">Packaging and Pushing Artifacts<a href="https://tanzhuo.xyz/en/2021/11/19/jenkins#packaging-and-pushing-artifacts" class="hash-link" aria-label="Direct link to Packaging and Pushing Artifacts" title="Direct link to Packaging and Pushing Artifacts" translate="no">​</a></h2>
<p>Use maven with the jdk to build the project's jar.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2021-11-image-5-7fe705d054ce0e93b99727213b30f198.webp" width="1475" height="788" class="img_ev3q"></p>
<p>After the build, use a shell command to cp the jar from the target directory into the corresponding docker-compose directory. The docker-compose file and each service's Dockerfile need to be written ahead of time.</p>
<p>Why write those two files in advance: the Dockerfile describes how a single service becomes an image (base image, copy the jar, start command), while docker-compose gathers the build declarations of multiple services in one place—so a single command produces all the images without a separate docker build per service.</p>
<p>Run docker-compose to batch-build the images.</p>
<p>After the build, run docker tag to version-tag the images.</p>
<p>Tagging gives every release a unique version number instead of everything being called latest. Using Jenkins's built-in build number as the tag is an easy option, and rolling back is just a matter of specifying a historical version number.</p>
<p>Once tagging is done, docker push the images to the registry.</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%)"># Tag the image with the build number, then push to the private registry</span><span class="token plain"></span><br></div><div class="token-line" 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>Before pushing, remember that both the Jenkins machine and the target server must docker login to the private registry first, or push/pull will fail on authentication.</p>
<p>Connect remotely to the target server or the k8s master node, run the job command, and pass it the image download address.</p>
<p>At this point the build machine's work is done. The remote command only needs to tell the target server the full address of the new image; the server pulls the image and restarts the container itself. Build and runtime are fully separated—the target server needs no build tools at all.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cleaning-up-old-resources">Cleaning Up Old Resources<a href="https://tanzhuo.xyz/en/2021/11/19/jenkins#cleaning-up-old-resources" class="hash-link" aria-label="Direct link to Cleaning Up Old Resources" title="Direct link to Cleaning Up Old Resources" translate="no">​</a></h2>
<p>After packaging and pushing to the private registry, don't forget to clear out unused resources in the local workspace, along with docker images and the like, to avoid excessive disk usage after repeated releases. That closes the loop and lets you release version after version.</p>
<p>Cleanup can simply be the last step of the pipeline: delete the build artifacts in the workspace, then remove the local images produced by this build. Docker images are stored in layers, and old versions pile up if never cleaned. A few dozen releases later the disk fills up and builds start failing for no obvious reason.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-notes">Pitfalls and Notes<a href="https://tanzhuo.xyz/en/2021/11/19/jenkins#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and Notes" title="Direct link to Pitfalls and Notes" translate="no">​</a></h2>
<ol>
<li class="">
<p>When Jenkins runs inside a container, executing docker commands in a job requires mounting the host's docker.sock into the container; otherwise the container can't find the docker daemon.</p>
</li>
<li class="">
<p>If the private registry uses http instead of https, docker refuses to connect by default. Add the registry address to insecure-registries in the docker configuration on both the pushing and pulling sides.</p>
</li>
<li class="">
<p>Watch out for special character escaping in sed replacements. When the replacement text contains <code>/</code>, switch to a different delimiter like <code>#</code> to avoid clashing with sed's own delimiter.</p>
</li>
<li class="">
<p>Store credentials for remote execution (SSH private keys, registry usernames and passwords) in Jenkins's credentials manager—never in plain text inside the job's shell scripts.</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>tip</div><div class="admonitionContent_BuS1"><p>Tie image tags to git commits or build numbers whenever possible—when debugging a production issue, you can quickly map an image back to the exact code change.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2021/11/19/jenkins#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Once the pipeline is running end to end, a release becomes a single click of the build button (or an automatic trigger on commit): pull code, replace config, maven build, docker-compose images, tag and push, remotely start the new version, clean up—all with zero manual intervention. Compared with manual releases, the win isn't just speed. Every step is codified in the job configuration, so release outcomes are stable and predictable, and a rollback is nothing more than switching an image tag.</p>]]></content:encoded>
            <category>Tech</category>
            <category>CI/CD</category>
        </item>
        <item>
            <title><![CDATA[The Witcher 3: Wild Hunt]]></title>
            <link>https://tanzhuo.xyz/en/2021/08/10/wu-shi-3</link>
            <guid>https://tanzhuo.xyz/en/2021/08/10/wu-shi-3</guid>
            <pubDate>Tue, 10 Aug 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[The Witcher 3: Wild Hunt inherits its world from The Witcher novels. A quick look at the Conjunction of the Spheres, where witchers came from, and how the game's main story begins.]]></description>
            <content:encoded><![CDATA[<p>The game inherits its world from <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="">The Witcher</a> series of novels. Fifteen hundred years before the events of the books, a cataclysm struck: worlds from different dimensions collided, becoming directly connected and bleeding into one another.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-conjunction-of-the-spheres">The Conjunction of the Spheres<a href="https://tanzhuo.xyz/en/2021/08/10/wu-shi-3#the-conjunction-of-the-spheres" class="hash-link" aria-label="Direct link to The Conjunction of the Spheres" title="Direct link to The Conjunction of the Spheres" translate="no">​</a></h2>
<p>That cataclysm is known as the Conjunction of the Spheres. It left all manner of creatures from other worlds stranded in this one—ghouls, grave hags, and higher vampires are among the remnants it left behind. These creatures carry their own distinct ecologies, relics of a bygone age. According to elven legend, humans came to this world after their own was destroyed. Their ancestors learned to harness the forces of chaos, giving rise to the first human mages, who fought against the creatures from other worlds. But with monsters scattered everywhere and hard to kill, and ordinary people unable to stand against them, witchers came into being—mutated humans hired by common folk to hunt monsters for a living, protecting people from the creatures that threatened them.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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="the-games-story">The Game's Story<a href="https://tanzhuo.xyz/en/2021/08/10/wu-shi-3#the-games-story" class="hash-link" aria-label="Direct link to The Game's Story" title="Direct link to The Game's Story" translate="no">​</a></h2>
<p>After recovering his memory, Geralt sets out to track down Yennefer. Eventually, Yennefer reaches out to Geralt after years apart, asking him to meet her in a village in Willoughby. But when Geralt arrives at the meeting place, the entire village has been razed by an army, and Yennefer is nowhere to be found. Along the way Geralt runs into Vesemir, who helps him follow Yennefer's trail. On the road, Geralt dreams of events at Kaer Morhen—a vision warning him that his adopted daughter Ciri is in some kind of trouble. Geralt and Vesemir stop at White Orchard, where Yennefer appears at the local inn escorted by Nilfgaardian soldiers. Before the old lovers can even catch up, Yennefer insists that Geralt come with her at once to Vizima for an audience with the Emperor of Nilfgaard. Their party is ambushed by the Wild Hunt on the way, but they make it to their destination. In the meeting with the emperor, Geralt learns that Ciri has returned from a distant land, that her life is in danger, and that the Wild Hunt is pursuing her. And so the main story of the game truly begins.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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>Games</category>
        </item>
        <item>
            <title><![CDATA[An Idea for a Java Logic Engine]]></title>
            <link>https://tanzhuo.xyz/en/2021/06/16/javaluo-ji-yin-qing-de-gou-si</link>
            <guid>https://tanzhuo.xyz/en/2021/06/16/javaluo-ji-yin-qing-de-gou-si</guid>
            <pubDate>Wed, 16 Jun 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[An idea for a visual logic engine that bakes the factory, proxy, strategy, and decorator patterns plus SPI into one tool: compose business logic by dragging components, Kettle-style, so backend developers write less boilerplate.]]></description>
            <content:encoded><![CDATA[<p>After years on the job, I have come to appreciate just how much design patterns matter. Used well, they make complex business logic flexible, polymorphic, extensible, and maintainable. So why not take it a step further—build a logic engine with design patterns baked in? This note is a record of that idea.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-i-want-to-solve">The Problem I Want to Solve<a href="https://tanzhuo.xyz/en/2021/06/16/javaluo-ji-yin-qing-de-gou-si#the-problem-i-want-to-solve" class="hash-link" aria-label="Direct link to The Problem I Want to Solve" title="Direct link to The Problem I Want to Solve" translate="no">​</a></h2>
<p>Backend business development has an awkward reality: a huge share of the code is the same skeleton repeated over and over—accept parameters, query data, branch on conditions, assemble a result, persist it. Every time, this flow gets written by hand. Developers who are good with design patterns abstract it nicely; those who aren't produce a giant tangle of if-else.</p>
<p>The problem isn't that developers don't understand design patterns. It's that every project has to re-implement factory, strategy, and friends from scratch, and the quality of those implementations varies wildly. If that layer of abstraction could be distilled into an engine, business developers would only need to care about the actual business logic.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-core-idea">The Core Idea<a href="https://tanzhuo.xyz/en/2021/06/16/javaluo-ji-yin-qing-de-gou-si#the-core-idea" class="hash-link" aria-label="Direct link to The Core Idea" title="Direct link to The Core Idea" translate="no">​</a></h2>
<p>My vision: a UI-driven logic engine built specifically for backend Java developers, with design patterns baked into the engine itself. Developers just compose logic components, double-click a component to write SQL or Java code, pass component member variables between components, and finally persist to the database.</p>
<p>The principle is similar to today's Kettle tool—Kettle does visual orchestration of data extraction and transformation; this engine would do visual orchestration of business logic.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2021-06-image-4c7820a68dee74f2dae3cf60411a78e5.webp" width="892" height="517" class="img_ev3q"></p>
<p>A component is the smallest logic unit, and each one does exactly one thing: a query, a validation, a computation, or a write. Complex business logic is just a flowchart of these units composed sequentially or conditionally. With a logic engine like this, Java development could shed a massive amount of repetitive work.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-design-patterns-support-it">How Design Patterns Support It<a href="https://tanzhuo.xyz/en/2021/06/16/javaluo-ji-yin-qing-de-gou-si#how-design-patterns-support-it" class="hash-link" aria-label="Direct link to How Design Patterns Support It" title="Direct link to How Design Patterns Support It" translate="no">​</a></h2>
<p>The inspiration comes from a combination of patterns I actually use at work: factory, proxy, strategy, and decorator, plus the SPI concept. Each has its role in the engine:</p>
<ol>
<li class="">
<p>Factory pattern: creates concrete logic component instances based on configuration. Every node dragged onto the UI is instantiated by the factory at runtime.</p>
</li>
<li class="">
<p>Strategy pattern: each logic component is a strategy implementation, so a given flow node can swap in different implementations without affecting the overall orchestration.</p>
</li>
<li class="">
<p>Proxy pattern: execution funnels through a proxy strategy class that handles cross-cutting concerns like parameter passing and exception handling, then forwards to the actual component.</p>
</li>
<li class="">
<p>Decorator pattern: layers capabilities onto a component without modifying it—wrapping a component with logging or caching, for example.</p>
</li>
<li class="">
<p>SPI concept: components expose themselves through an agreed interface. A new component just implements the interface and registers itself to be discovered by the engine—no changes to the engine's own code.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-runs">How It Runs<a href="https://tanzhuo.xyz/en/2021/06/16/javaluo-ji-yin-qing-de-gou-si#how-it-runs" class="hash-link" aria-label="Direct link to How It Runs" title="Direct link to How It Runs" translate="no">​</a></h2>
<p>The whole flow is configuration-driven: the UI components generate a configuration file, and the configuration file tells the proxy strategy class which component logic unit's .class files to invoke. All logic strategies are loaded at system startup, making sure these Spring Beans are properly loaded into memory—the cost of class loading and bean initialization is paid at startup, so runtime is just table lookups and forwarding.</p>
<p>All subsequent execution goes through the proxy strategy class, which assembles each individual bean to complete a complex piece of business logic.</p>
<p>This design also honors the single responsibility principle and the open-closed principle: each component has one job, and new business features are added by creating new components and changing configuration, not by modifying existing code.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="relation-to-low-code-platforms">Relation to Low-Code Platforms<a href="https://tanzhuo.xyz/en/2021/06/16/javaluo-ji-yin-qing-de-gou-si#relation-to-low-code-platforms" class="hash-link" aria-label="Direct link to Relation to Low-Code Platforms" title="Direct link to Relation to Low-Code Platforms" translate="no">​</a></h2>
<p>In today's market, the closest products to this idea are probably low-code or no-code platforms. But there's a difference in positioning: low-code platforms mostly target business users or full-stack scenarios and try to generate the pages too, while this idea targets only backend Java developers. It doesn't shy away from writing SQL and Java code—it just hands the orchestration and design-pattern skeleton over to the engine.</p>
<p>That's also why I think it's feasible: it doesn't chase "writing no code," only "writing no repetitive structural code."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2021/06/16/javaluo-ji-yin-qing-de-gou-si#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>This note is essentially an archive of an idea: combine factory, proxy, strategy, decorator, and SPI into a configuration-driven logic engine, replacing hand-written flow skeletons with visual orchestration. It doesn't need to become a sprawling platform. Even getting the "component + configuration + proxy strategy" mechanism working in a single project would be enough to validate the direction.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[The Vuetify UI Library]]></title>
            <link>https://tanzhuo.xyz/en/2021/02/22/vuetify</link>
            <guid>https://tanzhuo.xyz/en/2021/02/22/vuetify</guid>
            <pubDate>Mon, 22 Feb 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Vuetify is a Material Design component framework built on Vue.js. A look at its main features, documentation quality, and use cases, plus what to keep in mind when choosing it.]]></description>
            <content:encoded><![CDATA[<p>Vuetify is a Material Design component framework built on Vue.js, offering a rich set of UI components and styles that help developers build attractive, responsive web applications faster. Vuetify follows the Material Design spec and provides a large set of standardized components — buttons, cards, tables, forms, menus, navigation, dialogs, and more — letting you quickly assemble consistent, good-looking user interfaces.</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2023-05-image-5-c0296426bf2bb007f952c88106fb31f3.webp" width="1353" height="319" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-pay-attention-to-ui-component-libraries">Why pay attention to UI component libraries<a href="https://tanzhuo.xyz/en/2021/02/22/vuetify#why-pay-attention-to-ui-component-libraries" class="hash-link" aria-label="Direct link to Why pay attention to UI component libraries" title="Direct link to Why pay attention to UI component libraries" translate="no">​</a></h2>
<p>When working on front-end projects, a lot of the interface work is actually repetitive: buttons, form validation, popups, table pagination — every project goes through it again. Writing it all from scratch not only takes time but also makes visual and interaction consistency hard to guarantee. A mature component library distills these common parts so you can put your energy into business logic — which is why I keep an eye on the various UI libraries out there.</p>
<p>The Vue ecosystem has plenty of component libraries to choose from. Element and Ant Design Vue lean toward an admin-dashboard design language, while Vuetify takes a different road: a full implementation of Google's Material Design spec. If your project wants to stay close to the Material style, or needs to keep visual parity with an Android app, Vuetify is a natural choice.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="main-features">Main features<a href="https://tanzhuo.xyz/en/2021/02/22/vuetify#main-features" class="hash-link" aria-label="Direct link to Main features" title="Direct link to Main features" translate="no">​</a></h2>
<p>Vuetify's main features include:</p>
<ol>
<li class="">Built on Vue.js: Vuetify is a component framework built on Vue.js, integrating cleanly with Vue applications and providing a rich set of components and styles. Components are configured via props and extended via events and slots, no different from writing ordinary Vue components — the learning cost is mostly in getting familiar with each component's properties.</li>
<li class="">Material Design style: Vuetify follows the Material Design spec, providing a large set of standardized components and styles that help developers build consistent, attractive interfaces. The spec isn't just about colors and rounded corners — it also covers shadow elevation, motion curves, and spacing systems. Follow the spec and your pages are less likely to end up looking messy.</li>
<li class="">Responsive design: Vuetify offers responsive design, automatically adjusting layout and styles based on screen size and device type. It has a built-in grid system and breakpoints (xs to xl), and the layout components switch automatically based on viewport width, so mobile adaptation basically needs no extra media queries.</li>
<li class="">Plugin-based design: Vuetify uses a plugin-based design, making it easy to extend and customize components for different business needs. Theme colors, fonts, and component default behaviors can all be configured centrally at initialization, and it also supports on-demand component imports to control bundle size.</li>
<li class="">Rich component library: Vuetify provides a rich set of UI components and styles — buttons, cards, tables, forms, menus, navigation, dialogs, and more — for quickly building attractive, responsive web applications. Beyond the basics, it also covers the more tedious-to-implement components like date pickers and data tables.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="documentation-and-ecosystem">Documentation and ecosystem<a href="https://tanzhuo.xyz/en/2021/02/22/vuetify#documentation-and-ecosystem" class="hash-link" aria-label="Direct link to Documentation and ecosystem" title="Direct link to Documentation and ecosystem" translate="no">​</a></h2>
<p>Vuetify also provides solid Chinese documentation, and I think it's fairly well made — every property and state of each component is described quite clearly. Each component page comes with interactive examples, with properties, events, and slots listed in separate sections, making it easy to look things up. Documentation quality is often underrated during selection — by the later stages of using a component library, most of your time is actually spent reading the docs.</p>
<p>I've seen it used in some of Alibaba's open-source projects.</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="pitfalls-and-things-to-watch">Pitfalls and things to watch<a href="https://tanzhuo.xyz/en/2021/02/22/vuetify#pitfalls-and-things-to-watch" class="hash-link" aria-label="Direct link to Pitfalls and things to watch" title="Direct link to Pitfalls and things to watch" translate="no">​</a></h2>
<p>A few things worth thinking through before choosing it:</p>
<ol>
<li class="">
<p>The design style is fairly locked in. Material Design's visual signature is very distinct, and if your design goes for a different look, forcing Vuetify to customize may cost more than it's worth — in that case a more neutral-styled library is a better fit.</p>
</li>
<li class="">
<p>Mind the bundle size. Importing all components and styles wholesale makes for a hefty bundle; for production projects, import on demand and bundle only the components you use.</p>
</li>
<li class="">
<p>The version pairing with Vue. Vuetify's major versions are tied to Vue's major versions, so before upgrading, confirm the target version's support for your current Vue version to avoid getting halfway through and finding the component APIs have changed.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://tanzhuo.xyz/en/2021/02/22/vuetify#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>Vuetify's positioning is clear: a full realization of Material Design within Vue. Broad component coverage, high-quality docs, and responsive out of the box — it suits projects that buy into the Material style and want to stand up an interface quickly. Conversely, if you have strong custom visual requirements, weigh the cost of reworking the theme.</p>
<p>I'll try using it in my own front-end projects going forward.</p>]]></content:encoded>
            <category>Frontend</category>
        </item>
        <item>
            <title><![CDATA[Working with Kettle]]></title>
            <link>https://tanzhuo.xyz/en/2021/02/04/kettlegong-ju-shi-yong</link>
            <guid>https://tanzhuo.xyz/en/2021/02/04/kettlegong-ju-shi-yong</guid>
            <pubDate>Thu, 04 Feb 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Notes on using Kettle for multi-source data sync, migration, and transformation: the core concepts, what the common components are for, and the scenarios and limits of query-based CDC.]]></description>
            <content:encoded><![CDATA[<p>Lately I've been using the ETL tool Kettle, getting to know and put to work its features for syncing, migrating, transforming, and correcting data across multiple data sources.</p>
<!-- -->
<p>Building business systems, you run into this kind of requirement all the time: the old system's data needs to move to a new system, and the two have different table structures; or data from several databases needs to be rolled up into one reporting database, with mismatched field names, status codes, and data types. Handling this entirely by hand-written scripts — piles of SQL plus import/export programs — means rewriting code every time the requirements change, which gets tiring to maintain. That's exactly what ETL tools solve: they turn the three steps of Extract, Transform, and Load into a visual, workflow-driven process. Kettle is a fairly representative open-source implementation of this kind of tool.</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 — a "kettle," true to its name — treats the table data in various sources as flowing water, and gathers, splits, and parses those streams. It's an open-source data integration tool offering rich data-processing features, including extract, transform, and load (ETL). At its core is a graphical design tool where users build data-processing flows through simple drag-and-drop and connection operations. Kettle also ships with a powerful processing engine that supports multithreading and distributed processing, letting it handle large-scale data efficiently. It supports a wide range of sources and targets — relational databases, files, web services, and more — making it easy to integrate with all kinds of data sources. Kettle also provides a rich plugin mechanism, so users can develop custom plugins to extend its capabilities. In short, Kettle is a powerful, easy-to-use, and highly extensible data integration tool, widely used in data warehousing, business intelligence, data analysis, and similar fields.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How it works<a href="https://tanzhuo.xyz/en/2021/02/04/kettlegong-ju-shi-yong#how-it-works" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h2>
<p>Kettle has two basic concepts: Transformations and Jobs. A Transformation handles the actual data flow, built from individual Steps linked by Hops, with data flowing between steps one row at a time. A Job is the higher-level scheduling unit, organizing multiple Transformations to run in sequence or by condition. The flow you draw in the graphical designer, once saved, is an XML description file — you can run it directly in the designer, or hand it off to a command-line tool for scheduled execution on a server.</p>
<p>At runtime each step is an independent thread: as soon as an upstream step produces a row, it pushes that row downstream, so the whole flow is pipelined and doesn't wait for the previous step to finish everything. This is one reason it can handle sizable data volumes — the data isn't loaded into memory all at once.</p>
<p>No programming needed; you just drag components and configure them by hand to accomplish complex data-processing tasks. As for CDC, Kettle reads and transforms data using a query-based approach, which suits one-off data migration and transformation. It's not for scenarios with high real-time requirements.</p>
<p>A bit more on that: CDC (Change Data Capture) generally comes in two flavors. One is log-based — for example, parsing MySQL's binlog — where every database change can be captured in near real time. The other is query-based, running SQL on a schedule and comparing timestamps or auto-increment keys to find the changed rows. Kettle belongs to the latter camp: what it sees is a snapshot at the moment of the query, blind to any intermediate states between two queries, and deletions are hard to detect. So it fits one-off migrations and scheduled batch syncs; for real-time sync you need to switch to a log-based approach.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-small-migration-example">A small migration example<a href="https://tanzhuo.xyz/en/2021/02/04/kettlegong-ju-shi-yong#a-small-migration-example" class="hash-link" aria-label="Direct link to A small migration example" title="Direct link to A small migration example" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2021-02-image-fb2bb7146b43fcc0dff8c1e94bf83070.webp" width="1600" height="879" class="img_ev3q"></p>
<p>Kettle offers quite a lot of components to cover different scenarios for moving, importing, exporting, and value-mapping data, and it can export data to Excel files. The image above is a typical migration flow: query data out of the source database, run it through a few cleanup and transformation steps in the middle, and finally write it into the target database. Below, I'll go through these common components in the order they appear in the flow.</p>
<p>Table input: runs SQL against a database to query out the data to import. This is the flow's starting point — write a SELECT statement, and each row of the result becomes a row in the data stream passed downstream. You can parameterize the SQL with variables, making it easy to reuse the same transformation across different environments.</p>
<p>Table output: takes the final result set produced by Kettle and writes it into a table. This is the flow's endpoint, INSERTing each incoming row into the target table. You can configure the batch commit size; batch writes are far faster than committing row by row.</p>
<p>Field renaming: lets you select data columns, set column aliases, and so on. Source and target table field names often don't match — say the old database calls it user_name and the new one username — so you unify the names in this step, and later steps no longer have to care about the source's naming. You can also drop unneeded columns right here, reducing the load on downstream steps.</p>
<p>Sort: sorts by data fields. Not much use on its own, but it usually serves the next step — Kettle's merge-type components generally require both inputs to be sorted by the join field, so you typically sort each side before merging.</p>
<p>Merge join: merges data from two different sources, similar to a MySQL join. Two table-input streams are joined together on the specified fields, so even tables across databases can be "joined" — something pure SQL can't do. The prerequisite, as noted, is that both streams be sorted by the join field first.</p>
<p>Value mapper: many databases use status codes like 1, 2, 3 that might become 4, 5, 6 in the new database — value mapping handles the substitution. It's essentially a lookup table configured inside the component, mapping source values to target values one-to-one, with an optional default to catch anything that doesn't match.</p>
<p>Field correction: fixes the source's field names and data types to ease migration into the new source. A classic case is the old database storing dates as strings while the new one uses a datetime type, or numeric precision that needs adjusting — convert it all in this step to avoid type errors when writing to the target database.</p>
<p>Insert / Update: performs inserts against the target source, and updates instead if a matching id already exists. This is the common upsert: look up the target table by the specified key fields, insert if not found, update if found. For incremental sync, use it in place of table output so the transformation can run repeatedly without producing duplicate data.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-things-to-watch">Pitfalls and things to watch<a href="https://tanzhuo.xyz/en/2021/02/04/kettlegong-ju-shi-yong#pitfalls-and-things-to-watch" class="hash-link" aria-label="Direct link to Pitfalls and things to watch" title="Direct link to Pitfalls and things to watch" translate="no">​</a></h2>
<ol>
<li class="">
<p>Garbled Chinese characters: the database connection's charset must match the database itself. It's best to explicitly specify the encoding in the MySQL connection parameters — otherwise you'll finish the migration only to find every Chinese character turned into a question mark, and have to redo it.</p>
</li>
<li class="">
<p>Forgetting to sort before merging: merge-type components depend on sorted input. Skipping the sort step won't necessarily throw an error, but the join results will be wrong, and that's harder to catch than an error — always double-check the row counts.</p>
</li>
<li class="">
<p>Batch commit and transactions: you can raise the table output's default commit size to speed things up, but think through what happens on failure — committed data won't roll back if something fails midway. Before rerunning, either clear the target table or switch to the Insert / Update component to guarantee idempotency.</p>
</li>
<li class="">
<p>Migrating large tables: an unconditioned SELECT pulling the whole table puts real pressure on the source database. Run it during off-peak hours where possible, or batch it by primary key or time.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://tanzhuo.xyz/en/2021/02/04/kettlegong-ju-shi-yong#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>Used well, Kettle can simplify managing database data — during a major project version change, when reconciling the database structure and old-versus-new data, it's one of the good tools to reach for. Its positioning is clear: batch-style data moving and cleaning, with a graphical flow that lowers maintenance cost, sparing you from writing piles of throwaway scripts for these one-off or periodic data tasks. As for real-time sync, leave that to a log-based CDC approach — let each tool do what it's good at.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Big Data</category>
        </item>
        <item>
            <title><![CDATA[Sekiro: Shadows Die Twice]]></title>
            <link>https://tanzhuo.xyz/en/2020/11/12/zhi-lang</link>
            <guid>https://tanzhuo.xyz/en/2020/11/12/zhi-lang</guid>
            <pubDate>Thu, 12 Nov 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Sekiro: Shadows Die Twice is a third-person action-adventure game by From Software. This post traces the two threads of its backstory: Isshin Ashina seizing the land, and the Divine Dragon and the power of immortality.]]></description>
            <content:encoded><![CDATA[<p>Sekiro: Shadows Die Twice is a third-person action-adventure sandbox game made by From Software. You play a shinobi setting out to rescue his master — a mighty young lord of divine Japanese heritage — and to take revenge on his sworn enemy.</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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>The backstory runs along two threads: one is Isshin Ashina seizing the land, the other is the Divine Dragon and the power of immortality. Start with Isshin. The land of Ashina was originally home to the Ashina clan, until the Interior Ministry's forces annexed it. Near the end of the Warring States period, the sword saint Isshin Ashina led the Ashina and their branch house — the Hirata clan, same lineage but a different surname — to defeat the Ministry's soldiers and reclaim the occupied territory. From your first conversation with Genichiro Ashina, Isshin's grandson, you learn that the head of the Hirata clan is Genichiro's uncle.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="isshin-ashina-seizes-the-land">Isshin Ashina seizes the land<a href="https://tanzhuo.xyz/en/2020/11/12/zhi-lang#isshin-ashina-seizes-the-land" class="hash-link" aria-label="Direct link to Isshin Ashina seizes the land" title="Direct link to Isshin Ashina seizes the land" translate="no">​</a></h2>
<p>The Hirata clan's retainer shinobi is the giant ninja Owl, who adopted the protagonist Wolf on the battlefield; his defining feature is the mane of fur draped over his shoulders — the traditional Chinese localization renders his name as "Owl." At the same time, another masterless shinobi, the Monkey — later the sculptor at the Dilapidated Temple — gave his rice ball on the battlefield to Emma, who was a little girl back then. But the Monkey didn't raise Emma himself; he entrusted her to his friend, the physician Dogen, reasoning, after a few drinks, that following a physician was better than following a shinobi. Emma grew into a skilled healer and became Isshin Ashina's personal physician, learning his swordsmanship along the way. This contrast shows how differently the two shinobi turned out: the Monkey was the more good-natured of the two, while Owl, to reach his own ends, cared more about cultivating a right-hand man.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-divine-dragon-and-the-power-of-immortality">The Divine Dragon and the power of immortality<a href="https://tanzhuo.xyz/en/2020/11/12/zhi-lang#the-divine-dragon-and-the-power-of-immortality" class="hash-link" aria-label="Direct link to The Divine Dragon and the power of immortality" title="Direct link to The Divine Dragon and the power of immortality" translate="no">​</a></h2>
<p>The other thread is the Divine Dragon. Depicted with the lower body of a cherry tree and the upper body of a one-armed dragon, it hails from a Western dragon homeland and drifted to Ashina. All the immortality in the game traces directly or indirectly back to the Divine Dragon — the Dragon's Heritage, the centipede hosts, and the Rejuvenating Waters. Of these three powers, the Dragon's Heritage is the most orthodox and the closest to the Divine Dragon itself. The Dragon's immortality needs a source of power: from the description of the "Divine Devouring" item, you can tell the Dragon's power must draw on the small local gods living among Ashina's grasses and trees, and the ancient land of Ashina nurtured a great many of these drainable small gods — which is why the Dragon chose to stay in Ashina. The same goes for the Dragon's Heritage: it has no effect on the bearer, but it drains the life force of those around them — the Dragon's Blood illness.</p>
<p>The Dragon's power also affected the water, and the Okami clan, who lived nearest the Divine Dragon atop the mountain, drank the water infused with immortality, came to worship the Dragon, and built the Fountainhead Palace, styling it a fairyland.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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>The first bearer of the Dragon's Heritage came from the Okami clan — Lord Takeru. Takeru and his attendant Lady Tomoe were most likely both women; the Okami's main fighting force was female, relying on dance-plus-attack (that mask you can assemble from three pieces to spend skill points on extra attack power) and lightning summoning as their primary means of attack, while the men were all serpent-bodied deviants. The Okami clan and the Fountainhead Palace deserve a fuller telling later. Takeru and Tomoe chose to leave home in search of a way to sever the Dragon's Heritage, befriended Isshin Ashina and Genichiro Ashina, and taught them the art of lightning summoning. They also left behind many notes that would later become crucial clues for Kuro and his shinobi Wolf in their quest to end immortality.</p>]]></content:encoded>
            <category>Games</category>
        </item>
        <item>
            <title><![CDATA[Cracking JRebel in IntelliJ IDEA 2020]]></title>
            <link>https://tanzhuo.xyz/en/2020/11/03/idea-2020-jrebelpo-jie</link>
            <guid>https://tanzhuo.xyz/en/2020/11/03/idea-2020-jrebelpo-jie</guid>
            <pubDate>Tue, 03 Nov 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[A walkthrough of activating JRebel inside IDEA, plus a look at where JVM hot deployment hits its limits and a few compliant alternatives.]]></description>
            <content:encoded><![CDATA[<p>With the latest JRebel installed in IDEA, the only thing left is activation. I'll jot down the steps while I'm at it, and also sort out how hot deployment actually works and where its boundaries are.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-bother-with-it">Why bother with it<a href="https://tanzhuo.xyz/en/2020/11/03/idea-2020-jrebelpo-jie#why-bother-with-it" class="hash-link" aria-label="Direct link to Why bother with it" title="Direct link to Why bother with it" translate="no">​</a></h2>
<p>One of the most patience-draining parts of Java development is having to restart the whole service just to change a single line of code. A Spring application of any real size has to run through class scanning, bean instantiation, context refresh, and connection pool init on startup, and the bigger the project, the slower it gets. Tweaking a string, adjusting one <code>if</code> branch, fixing a null pointer — each one costs you a full startup.</p>
<p>What JRebel does is cut out that wait: change a class file and it takes effect immediately, no process restart, with the in-memory sessions, caches, and connections all still intact.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-hot-deployment-draws-the-line">Where hot deployment draws the line<a href="https://tanzhuo.xyz/en/2020/11/03/idea-2020-jrebelpo-jie#where-hot-deployment-draws-the-line" class="hash-link" aria-label="Direct link to Where hot deployment draws the line" title="Direct link to Where hot deployment draws the line" translate="no">​</a></h2>
<p>The JVM itself can do hot swapping. A debugger uses JPDA to call JVMTI's <code>RedefineClasses</code>, which can swap out the bytecode of an already-loaded class — but only the method bodies.</p>
<p>The moment you touch the structure of a class, it fails:</p>
<ul>
<li class="">Adding or removing methods</li>
<li class="">Adding or removing fields</li>
<li class="">Changing the inheritance hierarchy or implemented interfaces</li>
<li class="">Changing annotations or method signatures</li>
</ul>
<p>Any of these throws <code>schema change not implemented</code> outright, and a restart is your only option.</p>
<p>JRebel doesn't go through standard HotSwap. Instead it hooks into the class-loading phase, rewrites the bytecode, and maintains its own version mapping, so structural changes take effect too. It also adapts to frameworks like Spring, Hibernate, and MyBatis — add a <code>@Service</code>, edit an XML mapping, add a Controller method, and the framework-side metadata refreshes along with it. That adaptation work is substantial, and it's why the tool costs money.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="activation-steps">Activation steps<a href="https://tanzhuo.xyz/en/2020/11/03/idea-2020-jrebelpo-jie#activation-steps" class="hash-link" aria-label="Direct link to Activation steps" title="Direct link to Activation steps" translate="no">​</a></h2>
<p>Click the activation page.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2020-11-image-3c1090b040f1384f1eb9111c5a5c4f67.webp" width="1600" height="991" class="img_ev3q"></p>
<p>Choose online server license activation.</p>
<p>Enter the server license URL.</p>
<p>The URL is: <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>The GUID part needs to be generated (generator):</p>
<p><a href="https://www.guidgen.com/" target="_blank" rel="noopener noreferrer" class="">Generate GUIDs online</a></p>
<p>Go to the site, generate a GUID, and paste it into the URL.</p>
<p>Check the box to agree to the terms and click activate.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2020-11-image-1-092fcb5e72c89913544c4338f2d3f8f2.webp" width="1600" height="991" class="img_ev3q"></p>
<p>That's it — JRebel is cracked.</p>
<p>The URL here is a full string of "server address + a GUID," not just the domain. The GUID acts as a client identifier; just generate a new one of your own. The email field afterward can be anything — the license server doesn't validate it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-things-to-watch">Pitfalls and things to watch<a href="https://tanzhuo.xyz/en/2020/11/03/idea-2020-jrebelpo-jie#pitfalls-and-things-to-watch" class="hash-link" aria-label="Direct link to Pitfalls and things to watch" title="Direct link to Pitfalls and things to watch" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>Successful activation doesn't mean it's working.</strong> JRebel needs its own Run / Debug buttons to launch the app; using the regular Run won't mount the agent, and changed code still requires a restart.</p>
</li>
<li class="">
<p><strong>rebel.xml decides where it looks for classes.</strong> JRebel relies on this file to map the runtime classpath back to the project's compiled output directory. For Maven projects, generate it with <code>jrebel-maven-plugin</code>:</p>
</li>
</ol>
<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;!-- Add this to build/plugins in pom.xml, then run mvn jrebel:generate to produce 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>The IDEA plugin usually handles this automatically; for multi-module projects, confirm every module gets one generated.</p>
<ol start="3">
<li class="">
<p><strong>It depends on external network access.</strong> This kind of online licensing has to reach the license server on every startup, so an intranet dev machine or an offline environment will hang right at the validation step.</p>
</li>
<li class="">
<p><strong>Upgrades break it easily.</strong> After a major IDEA version bump the plugin may not be compatible, and the license server itself could be shut down at any time.</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>warning</div><div class="admonitionContent_BuS1"><p>Using a third-party license server is essentially bypassing JRebel's commercial licensing. Fine for personal tinkering, but doing this in a commercial project carries legal risk — for team environments, go with a proper license.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="less-hassle-the-alternatives">Less hassle: the alternatives<a href="https://tanzhuo.xyz/en/2020/11/03/idea-2020-jrebelpo-jie#less-hassle-the-alternatives" class="hash-link" aria-label="Direct link to Less hassle: the alternatives" title="Direct link to Less hassle: the alternatives" translate="no">​</a></h2>
<p>If all you want is to restart less often, JRebel isn't your only option:</p>
<ul>
<li class=""><strong>DCEVM + HotSwapAgent</strong>: an open-source route. DCEVM is a modified JVM that loosens HotSwap's restrictions to support adding and removing methods and fields; HotSwapAgent builds on top of it to fill in the framework adaptations. Its capabilities come close to JRebel's, at the cost of swapping out your JVM.</li>
<li class=""><strong>Spring Boot DevTools</strong>: uses two ClassLoaders to separate third-party dependencies from your business code, so only the business layer restarts on a change. It's not true hot deployment, but the restart is far faster than a cold start — zero cost, zero risk.</li>
<li class=""><strong>IDEA's native HotSwap</strong>: in Debug mode, just recompile a single class and it takes effect. Only method bodies can change, but that's plenty for day-to-day tweaking of parameters or logging.</li>
<li class="">JRebel also offers a free trial officially; during an evaluation, using the official one is simply less trouble.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://tanzhuo.xyz/en/2020/11/03/idea-2020-jrebelpo-jie#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>Hot deployment tools solve exactly one problem: the feedback loop being too long. Once you understand the hard boundary that the JVM only allows replacing method bodies, you see where JRebel's price comes from and why DevTools is only a compromise. Which one to pick depends on your project's size and the cost you're willing to accept.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[MySQL Transaction Deadlocks Explained]]></title>
            <link>https://tanzhuo.xyz/en/2020/10/15/mysqlshi-wu-si-suo</link>
            <guid>https://tanzhuo.xyz/en/2020/10/15/mysqlshi-wu-si-suo</guid>
            <pubDate>Thu, 15 Oct 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[How concurrent transactions walk step by step into a mutual-waiting standoff: deriving deadlock causes from lock ordering, explaining InnoDB's undo-log-based rollback choice and the wait-for graph detection mechanism, and closing with practical ways to reduce deadlock probability.]]></description>
            <content:encoded><![CDATA[<p>As business features and modules pile up in a MySQL-backed system, the number of database transactions grows with them. In day-to-day feature work we frequently open transactions to operate on data — or open distributed global transactions — and once there are enough transactions running concurrently, there's a real chance of transaction deadlocks.</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="why-this-topic">Why This Topic<a href="https://tanzhuo.xyz/en/2020/10/15/mysqlshi-wu-si-suo#why-this-topic" class="hash-link" aria-label="Direct link to Why This Topic" title="Direct link to Why This Topic" translate="no">​</a></h2>
<p>What makes deadlocks nasty is that they aren't stably reproducible bugs: run the test environment single-threaded a hundred times and nothing happens, then a concurrency spike in production makes them pop up, leaving nothing in the logs but <code>Deadlock found when trying to get lock</code>. Without understanding the cause, it's easy to dismiss them as flukes — until one day they erupt en masse right at business peak. This post walks through deadlocks end to end: how they arise, how they're detected, and how to avoid them.</p>
<p>First, some background. InnoDB row locks are taken on index records. When a transaction updates a row, it acquires an exclusive lock (X lock) on that row and holds it until the transaction commits or rolls back — that's what the two-phase locking protocol requires. Lock hold time equals transaction lifetime, and this is the foundation for everything that follows: the longer the transaction, the bigger the window in which someone else collides with your locks.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-a-deadlock-forms">How a Deadlock Forms<a href="https://tanzhuo.xyz/en/2020/10/15/mysqlshi-wu-si-suo#how-a-deadlock-forms" class="hash-link" aria-label="Direct link to How a Deadlock Forms" title="Direct link to How a Deadlock Forms" translate="no">​</a></h2>
<p>Let's break down how a transaction deadlock comes about.</p>
<p>Two sessions are connected: one session holds transaction T1, the other holds transaction T2.</p>
<ol>
<li class="">
<p>T1 modifies rows1</p>
</li>
<li class="">
<p>T2 modifies rows2</p>
</li>
<li class="">
<p>T2 modifies rows1</p>
</li>
<li class="">
<p>T2 waits for T1 to release the X lock on rows1</p>
</li>
<li class="">
<p>T1 modifies rows2</p>
</li>
<li class="">
<p>T1 waits for T2 to release the X lock on rows2</p>
</li>
<li class="">
<p>Mutual waiting — deadlock</p>
</li>
</ol>
<p>The crux is in steps 3 and 5: the two transactions touch each other's already-locked rows in opposite orders. T1 clutches rows1 while waiting for rows2; T2 clutches rows2 while waiting for rows1. Neither will let go first — because letting go means rolling back. The wait relationships form a cycle, and that is the essence of deadlock: <strong>circular waiting on resources</strong>.</p>
<p>Without outside intervention, the program cannot resolve this on its own. It takes human action or a separate watchdog thread to break the deadlock.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-mysql-picks-the-victim">How MySQL Picks the Victim<a href="https://tanzhuo.xyz/en/2020/10/15/mysqlshi-wu-si-suo#how-mysql-picks-the-victim" class="hash-link" aria-label="Direct link to How MySQL Picks the Victim" title="Direct link to How MySQL Picks the Victim" translate="no">​</a></h2>
<p>The way out of a deadlock is simple: release one of the transactions by rolling it back.</p>
<p>But rollback raises a question — which transaction is the more reasonable one to roll back? MySQL decides by <strong>undo log</strong>: whichever transaction has more undo log entries carries more weight, and the lower-weight transaction is the one sacrificed and rolled back. This resolves the deadlock while keeping the amount of rolled-back data — and thus the performance cost — as small as possible.</p>
<p>The logic is easy to follow: undo log records the modifications a transaction has already made, so more entries means the transaction has done more work, and rolling it back costs more. InnoDB therefore prefers to roll back the transaction that has "done less," minimizing the cost of redoing work. The chosen transaction receives a deadlock error, and the application layer, upon catching it, can retry the whole transaction — which is why transactions should be designed to be retryable in the first place.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="estimating-deadlock-probability">Estimating Deadlock Probability<a href="https://tanzhuo.xyz/en/2020/10/15/mysqlshi-wu-si-suo#estimating-deadlock-probability" class="hash-link" aria-label="Direct link to Estimating Deadlock Probability" title="Direct link to Estimating Deadlock Probability" translate="no">​</a></h2>
<p>The probability of any given transaction in the system hitting a deadlock is roughly n2r4/4R2.</p>
<p>n: the number of transactions — the more transactions there are, the higher the deadlock probability (transactions nested within transactions).</p>
<p>r: the number of operations per transaction — the more rows each transaction touches, the higher the deadlock probability.</p>
<p>R: the set of data being operated on — the smaller it is, the higher the deadlock probability (different transactions hitting the same slice of data; the more scattered the modified data and the larger the data set, the smaller the chance of X locks landing on the same row).</p>
<p>The formula points straight at the optimization levers: r has an exponent of 4, so its impact is the most violent — <strong>splitting big transactions into small ones and reducing the rows each transaction touches is the highest-yield way to cut deadlock probability</strong>. And R sits in the denominator, meaning hot data is a breeding ground for deadlocks: when every transaction crowds in to update the same handful of rows (a counter row, an inventory row), deadlock probability gets amplified drastically.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="active-detection-the-wait-for-graph">Active Detection: the Wait-for Graph<a href="https://tanzhuo.xyz/en/2020/10/15/mysqlshi-wu-si-suo#active-detection-the-wait-for-graph" class="hash-link" aria-label="Direct link to Active Detection: the Wait-for Graph" title="Direct link to Active Detection: the Wait-for Graph" translate="no">​</a></h2>
<p>MySQL's active transaction detection: the wait-for graph.</p>
<p>Before each session's transaction proceeds, an algorithm can determine ahead of time whether a transaction cycle exists (two transactions each blocking on data the other holds X locks on, forming a loop), and preemptively release the transaction with the smaller undo log weight.</p>
<p>The wait-for graph models the waiting relationships as a directed graph: each transaction is a node, and "T2 is waiting on T1's lock" draws an edge from T2 to T1. Whenever a transaction blocks because it can't acquire a lock, InnoDB adds the edge to the graph and checks for a cycle — a cycle means deadlock, and the lower-weight transaction is picked out and rolled back immediately, with no idle waiting.</p>
<p>Beyond active detection, InnoDB has a safety net: lock wait timeout. Once a row-lock wait exceeds the time set by <code>innodb_lock_wait_timeout</code>, the waiting statement errors out. It doesn't distinguish a true deadlock from plain lock contention — it just guarantees that a transaction won't hang forever.</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%)">-- View details of the most recent deadlock (the LATEST DETECTED DEADLOCK section)</span><span class="token plain"></span><br></div><div class="token-line" 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%)">-- Shows the locks each transaction held, the locks it waited on, and which side was rolled back</span><span class="token plain"></span><br></div><div class="token-line" 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%)">-- View the lock wait timeout (in seconds)</span><span class="token plain"></span><br></div><div class="token-line" 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>That said, in complex business scenarios it's not uncommon for a deadlock to already have happened by the time MySQL's wait-for graph detection would have kicked in.</p>
<p>So we need to sort out our business flows and the range of data each one affects, and shrink the odds of deadlock — or risk ending up resolving deadlocks by hand.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-notes">Pitfalls and Notes<a href="https://tanzhuo.xyz/en/2020/10/15/mysqlshi-wu-si-suo#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and Notes" title="Direct link to Pitfalls and Notes" translate="no">​</a></h2>
<p>Building on the mechanics above, a few lessons are worth keeping in mind for real-world development:</p>
<ol>
<li class="">
<p><strong>Standardize lock ordering.</strong> Look back at how the deadlock formed: the root cause was two transactions accessing the same rows in opposite orders. If all business code agrees to update data in a single consistent order (say, ascending primary key), the circular-wait loop can never close.</p>
</li>
<li class="">
<p><strong>Keep transactions short.</strong> Don't make RPC calls, publish messages, or wait for user input inside a transaction. Stretching the lock hold time is holding the door open for deadlocks.</p>
</li>
<li class="">
<p><strong>Watch out for gap locks.</strong> Under repeatable-read isolation, range-based updates and deletes take gap locks. Two transactions locking adjacent gaps and then inserting into each other's gaps will also deadlock — and this kind is often invisible if you only look at row data; you need the lock information in <code>SHOW ENGINE INNODB STATUS</code>.</p>
</li>
<li class="">
<p><strong>Retry at the application layer.</strong> Once a deadlock is detected, one side always gets rolled back. Business code must catch the deadlock error and retry the entire transaction — not just the last statement.</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>warning</div><div class="admonitionContent_BuS1"><p>Deadlock detection itself has a cost: when large numbers of transactions queue on a hot row, every new waiter has to traverse the wait graph, and the detection overhead grows with concurrency. For hot-row update scenarios, split the hotspot at the business level rather than expecting the database to muscle through.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2020/10/15/mysqlshi-wu-si-suo#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The essence of a deadlock is circular waiting created by interleaved lock ordering. InnoDB uses the wait-for graph to actively spot cycles, then rolls back the cheaper transaction by undo log weight as the fallback. But detection and rollback are only remedies — the real work happens beforehand: split transactions small, unify lock ordering, and spread out hot data. Only then does deadlock probability drop to a level you can ignore.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Database</category>
        </item>
        <item>
            <title><![CDATA[Notes from Building an IntelliJ IDEA Plugin]]></title>
            <link>https://tanzhuo.xyz/en/2020/08/25/kai-fa-ideacha-jian-ji-lu</link>
            <guid>https://tanzhuo.xyz/en/2020/08/25/kai-fa-ideacha-jian-ji-lu</guid>
            <pubDate>Tue, 25 Aug 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Notes from building my own MyBatis SQL log parsing plugin: why I built it, how parameter filling works under the hood, the learning curve of IDEA plugin development, and some takeaways from publishing to the plugin marketplace.]]></description>
            <content:encoded><![CDATA[<p>I had been using a plugin called MyBatis Log Plugin, and the SQL log parameter-filling feature was the part I really liked. Older versions were apparently free, but newer versions became paid. So I decided to build a plugin with similar functionality myself, named mybatis log analysis. For version 1.0.0, the plan is to ship the basics first: SQL log parsing with automatic parameter filling, plus SQL beautification.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-build-this-plugin">Why Build This Plugin<a href="https://tanzhuo.xyz/en/2020/08/25/kai-fa-ideacha-jian-ji-lu#why-build-this-plugin" class="hash-link" aria-label="Direct link to Why Build This Plugin" title="Direct link to Why Build This Plugin" translate="no">​</a></h2>
<p>Anyone who writes business code with MyBatis knows the feeling: the SQL printed to the console is a prepared statement full of <code>?</code> placeholders, with the parameters printed on a separate line. When you're debugging and want to paste that SQL into a database client to run it directly, you have to fill the parameters back in by hand — and with more than a few parameters, that gets tedious and error-prone fast. Parameter-filling plugins solve exactly this small but high-frequency chore. So when the plugin I relied on went paid, my first instinct wasn't to hunt for an alternative — the feature itself is no mystery, so why not write my own, and use it as an excuse to learn the whole IDEA plugin development path along the way.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="feature-plan">Feature Plan<a href="https://tanzhuo.xyz/en/2020/08/25/kai-fa-ideacha-jian-ji-lu#feature-plan" class="hash-link" aria-label="Direct link to Feature Plan" title="Direct link to Feature Plan" translate="no">​</a></h2>
<p>Version 1.0.0 covers the core, most-used parts first:</p>
<ol>
<li class="">
<p>SQL log parsing with automatic parameter filling;</p>
</li>
<li class="">
<p>SQL beautification (formatted output, making long SQL easier to read).</p>
</li>
</ol>
<p>Later, when time allows, I'll build out the other features: xml/java navigation links, one-click generation of controller, service, entity, mapper, and xml files, and so on.</p>
<p>Version 1.0.0 is already finished and submitted to the IDEA plugin repository.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-parameter-filling-works">How Parameter Filling Works<a href="https://tanzhuo.xyz/en/2020/08/25/kai-fa-ideacha-jian-ji-lu#how-parameter-filling-works" class="hash-link" aria-label="Direct link to How Parameter Filling Works" title="Direct link to How Parameter Filling Works" translate="no">​</a></h2>
<p>The mechanism isn't complicated. With the log level set to DEBUG, MyBatis splits one query into two lines of output:</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>The <code>Preparing</code> line is the prepared SQL with placeholders; the <code>Parameters</code> line lists the parameter values in order, with each value's Java type in parentheses. The plugin's job is to pair the two lines up:</p>
<ol>
<li class="">
<p>Locate adjacent <code>Preparing</code> and <code>Parameters</code> lines in the log text;</p>
</li>
<li class="">
<p>Split the parameter list on commas, and use the type in parentheses to decide how to splice each value in — strings and dates need quotes, numbers are substituted as-is;</p>
</li>
<li class="">
<p>Fill the processed parameters back into the <code>?</code> placeholders in order, producing a complete statement that can be executed directly;</p>
</li>
<li class="">
<p>Finally run it through SQL formatting and output the beautified result.</p>
</li>
</ol>
<p>The core logic is just text parsing plus string substitution. What actually eats the time is the IDE integration: how to register a tool window, how to listen to console output, how to hook actions into menus — all of which means dealing with the IntelliJ Platform APIs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-development-process-felt-like">What the Development Process Felt Like<a href="https://tanzhuo.xyz/en/2020/08/25/kai-fa-ideacha-jian-ji-lu#what-the-development-process-felt-like" class="hash-link" aria-label="Direct link to What the Development Process Felt Like" title="Direct link to What the Development Process Felt Like" translate="no">​</a></h2>
<p>While building the plugin, simple features were simple to implement, but anything more sophisticated demands a fairly deep understanding of the APIs IDEA exposes — and I couldn't find comprehensive API documentation on the official plugin docs site, only some official demo code. So there was no choice but to spend time reading and digesting the demos. It makes for a rather unfriendly learning curve.</p>
<p>This is very different from everyday business development. With business frameworks the docs usually work like "look up the API, check the example, use it" — whereas on the IntelliJ Platform you mostly feel your way forward through three channels: browsing the official demo repositories, reading the platform source directly, and studying how other open source plugins implement similar features. When you don't know which extension point to use, finding an open source plugin with a similar feature and comparing its <code>plugin.xml</code> and entry classes is often faster than digging through the docs.</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>Here's the link to the published plugin: <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="pitfalls-and-notes">Pitfalls and Notes<a href="https://tanzhuo.xyz/en/2020/08/25/kai-fa-ideacha-jian-ji-lu#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and Notes" title="Direct link to Pitfalls and Notes" translate="no">​</a></h2>
<ul>
<li class="">Parameter substitution can't be a naive global string replace. Parameter values may themselves contain commas or question marks; splitting the <code>Parameters</code> line on commas and replacing the <code>?</code> placeholders one by one, in order, is far more robust than a one-shot regex replace.</li>
<li class="">Use the type information. The type in parentheses on the <code>Parameters</code> line determines whether a value needs quoting; ignore it and the resulting SQL will most likely fail to run in the database.</li>
<li class="">Submitting to the JetBrains Marketplace involves manual review. The plugin description, icon, and compatible IDE version range all have to be spelled out in the configuration — preparing these in advance saves several rounds of back-and-forth.</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>tip</div><div class="admonitionContent_BuS1"><p>When the plugin development docs fall short, the <a href="https://plugins.jetbrains.com/docs/intellij/welcome.html" target="_blank" rel="noopener noreferrer" class="">IntelliJ Platform SDK documentation</a> read side by side with the source code of open source plugins is by far the best learning path.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2020/08/25/kai-fa-ideacha-jian-ji-lu#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The plugin's logic itself is tiny; its value is in automating away a frequent bit of manual work. For me the bigger gain was walking the full path of an IDEA plugin from development to marketplace release: the feature implementation was only a small part of it. Understanding the platform's extension mechanisms and getting used to learning from source code and demos — that is the real barrier on this road. Future versions will add the xml/java navigation and one-click code generation features.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Reflections on My First Few Years in Tech]]></title>
            <link>https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de</link>
            <guid>https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de</guid>
            <pubDate>Tue, 26 May 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Before I knew it, I had spent several years in the internet industry. Back when I first started learning to code, I was writing demo projects every day — from the simplest Hello World to little games, crawlers, and admin systems — and every new skill brought a real sense of achievement.]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="foreword">Foreword<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#foreword" class="hash-link" aria-label="Direct link to Foreword" title="Direct link to Foreword" translate="no">​</a></h2>
<p>Before I knew it, I had spent several years working in the internet industry.</p>
<!-- -->
<p>Back when I first started learning to code, I was writing all kinds of demo projects every day. From the simplest <strong>Hello World</strong> to little games, web crawlers, and admin dashboards — every new technology I picked up brought a real sense of achievement. I learned fast in those days, and I was full of enthusiasm.</p>
<p>As time went on, I gradually noticed something distinctive about this industry:</p>
<p><strong>The barrier to entry isn't especially high, but the ceiling is extremely high.</strong></p>
<p>The body of knowledge in the tech world is enormous. The more you learn, the more you realize how little you actually know. Much of the time we are simply standing on the shoulders of giants, looking up at even taller giants.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-early-stage-of-growing-as-an-engineer">The Early Stage of Growing as an Engineer<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#the-early-stage-of-growing-as-an-engineer" class="hash-link" aria-label="Direct link to The Early Stage of Growing as an Engineer" title="Direct link to The Early Stage of Growing as an Engineer" translate="no">​</a></h2>
<p>In the early stage, most people level up in a few common ways: learning new frameworks, imitating excellent projects, reading technical articles, and building demo project after demo project.</p>
<p>The open source community in particular is full of great design ideas, architectural patterns, and high-quality code. Reading those projects is a fast way to absorb a lot of engineering practice.</p>
<p>But if you want to genuinely raise your ability, there is one process that matters more than anything:</p>
<p><strong>Building a complete system entirely on your own.</strong></p>
<p>From system design and implementation to deployment and ongoing maintenance — going through that whole cycle gives you a much deeper understanding of software development. The first time you design a system architecture yourself and watch it run stably in production, the sense of achievement is intense.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hitting-the-plateau">Hitting the Plateau<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#hitting-the-plateau" class="hash-link" aria-label="Direct link to Hitting the Plateau" title="Direct link to Hitting the Plateau" translate="no">​</a></h2>
<p>As experience accumulates, most engineers eventually reach a stage where <strong>technical growth slows down noticeably</strong>.</p>
<p>At this stage, the work is no longer simple feature development. You have to think about far more complex concerns, such as:</p>
<ul>
<li class="">System security</li>
<li class="">Reliability</li>
<li class="">High-availability architecture</li>
<li class="">Performance optimization</li>
<li class="">Monitoring and operations</li>
<li class="">Scalability</li>
<li class="">Deployment and resource management</li>
</ul>
<p>As systems grow, the architecture becomes more and more complex, and the range of technologies you need keeps expanding. Many people get anxious at this stage — they feel their learning has slowed, and some even worry about being replaced by younger engineers.</p>
<p>In reality, breaking through this plateau rarely depends on raw learning speed anymore. What it takes is <strong>long-term accumulation of experience and a large volume of real project practice</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-depth-matters">Why Depth Matters<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#why-depth-matters" class="hash-link" aria-label="Direct link to Why Depth Matters" title="Direct link to Why Depth Matters" translate="no">​</a></h2>
<p>Once your skills reach a certain level, what truly matters is no longer "how many technologies you know," but whether you have developed <strong>a core technical strength of your own</strong>.</p>
<p>Plenty of young engineers write excellent code, yet still lack experience in <strong>whole-system design, architectural thinking, and analyzing complex problems</strong>. That gap comes, fundamentally, from years of technical accumulation.</p>
<p>To keep improving past a certain point, you have to keep pushing the boundary of your knowledge outward rather than staying in a single domain.</p>
<p>Take databases as an example — a complete body of knowledge there includes:</p>
<ul>
<li class="">Relational databases</li>
<li class="">Non-relational databases</li>
<li class="">Graph databases</li>
<li class="">Data storage structures</li>
<li class="">Sharding strategies</li>
<li class="">Storage engine internals</li>
</ul>
<p>The database is often one of the most critical parts of a system, so understanding its internals matters enormously for architecture design.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-complexity-of-software-architecture">The Complexity of Software Architecture<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#the-complexity-of-software-architecture" class="hash-link" aria-label="Direct link to The Complexity of Software Architecture" title="Direct link to The Complexity of Software Architecture" translate="no">​</a></h2>
<p>As a system scales up, its architecture touches many different technical domains, for example:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="deployment-and-resource-management">Deployment and Resource Management<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#deployment-and-resource-management" class="hash-link" aria-label="Direct link to Deployment and Resource Management" title="Direct link to Deployment and Resource Management" translate="no">​</a></h3>
<ul>
<li class="">Docker containers</li>
<li class="">CPU / memory / bandwidth limits</li>
<li class="">Storage performance</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="system-architecture-design">System Architecture Design<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#system-architecture-design" class="hash-link" aria-label="Direct link to System Architecture Design" title="Direct link to System Architecture Design" translate="no">​</a></h3>
<ul>
<li class="">Microservice architecture</li>
<li class="">API design</li>
<li class="">Scalability</li>
<li class="">Performance optimization</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="distributed-systems">Distributed Systems<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#distributed-systems" class="hash-link" aria-label="Direct link to Distributed Systems" title="Direct link to Distributed Systems" translate="no">​</a></h3>
<ul>
<li class="">Sharding across databases and tables</li>
<li class="">Multi-region active-active</li>
<li class="">Disaster recovery</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="security-and-data">Security and Data<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#security-and-data" class="hash-link" aria-label="Direct link to Security and Data" title="Direct link to Security and Data" translate="no">​</a></h3>
<ul>
<li class="">Encrypted communication</li>
<li class="">Security policy</li>
<li class="">Data validation</li>
</ul>
<p>Even things that look like minor details — a database column's length, how many bytes a value occupies, the design of API parameters — can affect how far the system can scale in the future.</p>
<p>That is why a great architect needs more than strong technical skill: they need <strong>a broad base of knowledge and deep practical experience</strong>. Architecture, to a large degree, determines how much room a system has to grow.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-different-roles-in-software">The Different Roles in Software<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#the-different-roles-in-software" class="hash-link" aria-label="Direct link to The Different Roles in Software" title="Direct link to The Different Roles in Software" translate="no">​</a></h2>
<p>Different roles in the software industry carry different responsibilities.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="product-manager">Product Manager<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#product-manager" class="hash-link" aria-label="Direct link to Product Manager" title="Direct link to Product Manager" translate="no">​</a></h3>
<p>Decides what problem the software should solve and where the product is headed. A good product manager needs to understand not only market demand but also the cost and constraints of technical implementation — otherwise it's easy to design features that can never realistically ship.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="project-manager">Project Manager<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#project-manager" class="hash-link" aria-label="Direct link to Project Manager" title="Direct link to Project Manager" translate="no">​</a></h3>
<p>Leans toward project management: schedule control, task allocation, and team coordination, with a clear grip on the overall state of the project.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="engineer--architect">Engineer / Architect<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#engineer--architect" class="hash-link" aria-label="Direct link to Engineer / Architect" title="Direct link to Engineer / Architect" translate="no">​</a></h3>
<p>Responsible for technical design and system implementation — finding the right solution within a complex technology landscape and keeping the system stable over the long run.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-shift-in-how-you-grow">The Shift in How You Grow<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#the-shift-in-how-you-grow" class="hash-link" aria-label="Direct link to The Shift in How You Grow" title="Direct link to The Shift in How You Grow" translate="no">​</a></h2>
<p>Past a certain point, many engineers gradually realize they are no longer just users of technology — they start trying to understand the deeper questions.</p>
<p>For example:</p>
<ul>
<li class="">Why is the system designed this way?</li>
<li class="">Why does a particular architecture fit this business better?</li>
<li class="">How do you break through the bottlenecks in a complex system?</li>
</ul>
<p>From using open source software at the start, to contributing to open source projects, to eventually designing your own frameworks or middleware.</p>
<p>This is one of the most important transitions in an engineer's growth:</p>
<p><strong>From a user of technology to a creator of technology.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="final-thoughts">Final Thoughts<a href="https://tanzhuo.xyz/en/2020/05/26/jin-ji-nian-de-gong-zuo-xin-de#final-thoughts" class="hash-link" aria-label="Direct link to Final Thoughts" title="Direct link to Final Thoughts" translate="no">​</a></h2>
<p>Looking back on a career, perhaps everyone eventually asks themselves one question:</p>
<p>What has kept me going in this industry?</p>
<p>A hunger for technical knowledge?
The fire of youth?
Or has technology simply become part of life?</p>
<p>Maybe the answer doesn't matter much.</p>
<p>What matters is that in this ever-changing world of technology, you hold on to:</p>
<p><strong>Curiosity, the ability to learn, and the passion to keep exploring.</strong></p>]]></content:encoded>
            <category>Life</category>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Distributed Locks and Spring Transaction Ordering: A Subtle Concurrency Bug]]></title>
            <link>https://tanzhuo.xyz/en/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti</link>
            <guid>https://tanzhuo.xyz/en/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[A reader used a ZooKeeper distributed lock for ticket-sale deductions, yet the counts still went wrong under high concurrency. The root cause: the lock was released before the transaction committed, letting the next thread read stale data. Here is the reasoning and the fix.]]></description>
            <content:encoded><![CDATA[<p>This came from a question someone posted online. I walked through the reasoning with him and solved his problem, so I'm writing it down here.</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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="background">Background<a href="https://tanzhuo.xyz/en/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>"I added a distributed lock, but the data is still wrong" is a classic class of concurrency problem. The lock itself has no bug, and the transaction commits and rolls back just fine — but when you combine the two, getting the order of lock release and transaction commit slightly wrong renders the serialization guarantee useless. These problems rarely surface before load testing, and during troubleshooting people tend to fixate on the lock implementation while overlooking the transaction boundary. That was exactly what this reader ran into.</p>
<p>He was using a ZooKeeper distributed lock (ephemeral sequential nodes) to guard updates to values in MySQL. The feature was essentially a ticket-selling mechanism: under high concurrency, the ticket count must stay exactly right — never one more, never one less.</p>
<p>With the ZooKeeper lock wrapped around the service layer, the lock was released once the service method finished executing — but at that point the declarative Spring-managed transaction annotated on the method had not yet committed. Under concurrency, this ordering leads to data corruption.</p>
<p>Naturally, his database data went wrong. With 100 threads each taking exactly one ticket from a pool of 100, the remaining count should be 0. His result was not 0 — it never hit the expected value.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="root-cause">Root Cause<a href="https://tanzhuo.xyz/en/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#root-cause" class="hash-link" aria-label="Direct link to Root Cause" title="Direct link to Root Cause" translate="no">​</a></h2>
<p>Here is what happens: after the first thread finishes the update method, the transaction has not yet committed, but the distributed lock has already been released. When the next thread enters the method and runs its select, it still reads the stale pre-update data — a dirty read from its perspective. That is because Spring's declarative transaction, <code>@Transactional(rollbackFor = Exception.class)</code>, is AOP-based: the commit happens in around-advice after the method returns. By the time that advice commits the transaction, the ZooKeeper lock is already gone. This ordering gap lets the next thread read stale data, and the computation goes wrong.</p>
<p>Let's spell out the execution order. Declarative transactions work by having Spring generate a proxy for the target class; the transaction begins and commits at the proxy layer, wrapped around the business method. His lock/unlock code, however, lived inside the business method. So the actual timeline of one call looks like this:</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%)">// Call sequence through the proxy (illustrative)</span><span class="token plain"></span><br></div><div class="token-line" 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. Transaction interceptor begins the transaction</span><span class="token plain"></span><br></div><div class="token-line" 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. Enter the business method, acquire the ZooKeeper lock</span><span class="token plain"></span><br></div><div class="token-line" 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 remaining tickets, update to deduct</span><span class="token plain"></span><br></div><div class="token-line" 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. Release the lock inside the business method   &lt;-- lock is gone here</span><span class="token plain"></span><br></div><div class="token-line" 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. Business method returns, interceptor commits  &lt;-- but the commit happens here</span><br></div></code></pre></div></div>
<p>Between steps 4 and 5 there is a window: the lock is released, the transaction is not yet committed. The next thread waiting on the lock acquires it inside this window and runs its select. Since the previous transaction hasn't committed, under the default read-committed or repeatable-read isolation level it sees the old pre-deduction value — effectively two threads each deducting once from the same remaining count. The lock's mutual exclusion was never broken; what broke was the implicit assumption that "a read inside the lock always sees the previous lock holder's write."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-fix">The Fix<a href="https://tanzhuo.xyz/en/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#the-fix" class="hash-link" aria-label="Direct link to The Fix" title="Direct link to The Fix" translate="no">​</a></h2>
<p>So we need to change the ordering between the Spring transaction and the release of the distributed lock. Commit the transaction first, then release the distributed lock — or simply move the lock up to the controller layer — and the problem goes away.</p>
<p>Both approaches are the same at heart: make the lock's hold span fully cover the transaction's lifetime.</p>
<ol>
<li class="">
<p>Acquire and release the lock outside the service layer (in the controller, or in an outer method with no transaction), with the transactional method as the inner call wrapped by the lock. When that inner method returns, the transaction has committed — only then is the lock released.</p>
</li>
<li class="">
<p>Drop the declarative transaction and use a programmatic one (<code>TransactionTemplate</code> or a manual commit), explicitly committing first and unlocking second in code, so the ordering is entirely in your hands.</p>
</li>
</ol>
<p>One caveat: if you move the locking logic into another method of the same class and reach the transactional method via a <code>this</code> self-invocation, the transaction annotation will not take effect — self-invocation bypasses the proxy. Either split the two methods into separate classes, or fetch the class's own proxy from the container and call through it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-distributed-lock-is-not-data-integrity">A Distributed Lock Is Not Data Integrity<a href="https://tanzhuo.xyz/en/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#a-distributed-lock-is-not-data-integrity" class="hash-link" aria-label="Direct link to A Distributed Lock Is Not Data Integrity" title="Direct link to A Distributed Lock Is Not Data Integrity" translate="no">​</a></h2>
<p>One more thing worth adding: a distributed lock cannot guarantee data integrity. All it guarantees is exclusive invocation of that particular service-layer method.</p>
<p>If some other service method has a mapper touching the same row, the data will still go wrong.</p>
<p>So you should add an optimistic-lock column to the table to protect data integrity. Optimistic locking preserves integrity well, and its concurrency performance beats the service-level locking he was using.</p>
<p>The optimistic-locking approach is to add a <code>version</code> column and make the update conditional on the version you read:</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%)">-- Read the version column together with the 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 comment" style="color:hsl(220, 10%, 40%)">-- Update conditioned on the old version, incrementing it at the same time</span><span class="token plain"></span><br></div><div class="token-line" 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%)">-- Zero affected rows means someone else changed the data first: retry or fail</span><br></div></code></pre></div></div>
<p>This line of defense sits on the data itself: no matter how many entry points modify the row, final consistency is enforced by the database. A distributed lock solves "only one worker at a time"; optimistic locking solves "even if several workers act, the data stays correct." They operate at different layers and do not substitute for each other.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-notes">Pitfalls and Notes<a href="https://tanzhuo.xyz/en/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and Notes" title="Direct link to Pitfalls and Notes" translate="no">​</a></h2>
<ol>
<li class="">
<p>Before adding a distributed lock, be clear about which boundary wraps which: the lock must fully cover the transaction. "Lock inside the method + <code>@Transactional</code> on the method" is the easiest combination to write and the easiest to get wrong.</p>
</li>
<li class="">
<p>Unit tests and low-concurrency scenarios almost never catch this class of bug; it only reproduces when concurrency pressure hits the window between lock release and transaction commit. Concurrency-consistency verification of deduction-style endpoints before going live is a must.</p>
</li>
<li class="">
<p>When investigating inconsistent data, don't stare only at whether the lock implementation is correct. Draw the transaction's begin and commit points into the timeline as well — the problem often lives right at the seam between the two.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2020/03/31/fen-bu-shi-suo-yu-springbootshi-wu-guan-li-shun-xu-wen-ti#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>In this case the lock and the transaction each worked correctly on their own; what went wrong was their ordering — the lock was released before the commit, opening a window for the next thread to read stale data. The fix is to make the lock fully enclose the transaction, or to switch to a programmatic transaction and control the commit timing explicitly. Going further, the correctness of deduction-style data should not rest solely on mutual exclusion at the entry point: adding an optimistic-lock column to the table and placing the last line of defense at the database layer is the more robust approach.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[How I Think About Java Backend Architecture]]></title>
            <link>https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou</link>
            <guid>https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou</guid>
            <pubDate>Fri, 20 Mar 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Organizing my understanding of Java backend architecture: from coding standards, module decomposition, middleware, and database governance to security, team collaboration, and the evolution toward microservices.]]></description>
            <content:encoded><![CDATA[<p>I've built out a few system architectures recently, so I took the time to organize my understanding of and thinking about backend architecture.</p>
<!-- -->
<p>In day-to-day development, we often ask ourselves: <strong>what kind of project structure counts as a sound architecture?</strong></p>
<p>Some would say a good architecture should:</p>
<ul>
<li class="">Reduce repetitive work for developers</li>
<li class="">Have a clear, easy-to-understand structure</li>
<li class="">Be reliably stable</li>
<li class="">Be easy to extend and maintain</li>
</ul>
<p>But in reality, that's only <strong>architecture at the project level</strong>.</p>
<p>As the business grows, systems tend to evolve from <strong>a single-machine project into a clustered architecture, and eventually into a distributed system</strong>. Along the way, all kinds of components get introduced, such as:</p>
<ul>
<li class="">Service registry</li>
<li class="">Configuration center</li>
<li class="">API gateway</li>
<li class="">Circuit breaking and degradation</li>
<li class="">Database and table sharding</li>
<li class="">Disaster recovery</li>
<li class="">Assorted middleware</li>
</ul>
<p>Meanwhile, deployment evolves toward a <strong>containerized Docker + K8S stack</strong>, and the server count may grow from one or two machines to dozens or even hundreds. At that point the system is no longer a simple project — it's a full <strong>architectural system</strong>.</p>
<p>Architecture design spans both macro-level system design and micro-level code design.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-at-the-code-level">Architecture at the code level<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#architecture-at-the-code-level" class="hash-link" aria-label="Direct link to Architecture at the code level" title="Direct link to Architecture at the code level" translate="no">​</a></h2>
<p>At the code level, architecture means unifying development conventions and improving development efficiency. For example:</p>
<ul>
<li class="">Automatic DTO parameter validation (AOP)</li>
<li class="">A unified Result response object</li>
<li class="">Code generation</li>
<li class="">Standardized Service / Mapper / Controller structure</li>
<li class="">Auto-generated online API documentation</li>
</ul>
<p>It also means standardizing code organization: strictly controlled package structure, consistent class naming conventions, and sensible module boundaries.</p>
<p>Well-written code isn't just about efficiency — it's about <strong>other people being able to quickly understand what the code means</strong>. A good code architecture dramatically lowers maintenance costs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="business-module-decomposition-and-system-decoupling">Business module decomposition and system decoupling<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#business-module-decomposition-and-system-decoupling" class="hash-link" aria-label="Direct link to Business module decomposition and system decoupling" title="Direct link to Business module decomposition and system decoupling" translate="no">​</a></h2>
<p>During system design, you need to fully understand the relationships between business domains, and use those relationships to drive <strong>module decomposition and decoupling</strong>, balancing module independence, code reuse, and system complexity.</p>
<p>A sound architecture should achieve:</p>
<ul>
<li class="">Business modules that don't interfere with one another</li>
<li class="">Feature modules that can be maintained independently</li>
<li class="">Code coupling kept as low as possible</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="middleware-and-system-level-capability">Middleware and system-level capability<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#middleware-and-system-level-capability" class="hash-link" aria-label="Direct link to Middleware and system-level capability" title="Direct link to Middleware and system-level capability" translate="no">​</a></h2>
<p>As the system scales, it typically brings in various infrastructure components, such as:</p>
<ul>
<li class="">MQ (message queues)</li>
<li class="">Redis</li>
<li class="">NoSQL</li>
<li class="">ELK logging stack</li>
<li class="">Distributed locks</li>
<li class="">Distributed transactions</li>
</ul>
<p>Architecture design requires knowing the use cases for each piece of middleware — which components to introduce under which circumstances — and allocating server resources sensibly. You also need clarity on each middleware's responsibilities, each module's boundaries, and the call relationships between services.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="database-design-and-sql-governance">Database design and SQL governance<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#database-design-and-sql-governance" class="hash-link" aria-label="Direct link to Database design and SQL governance" title="Direct link to Database design and SQL governance" translate="no">​</a></h2>
<p>Database design occupies a critical place in architecture. A few areas deserve particular attention.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="database-design">Database design<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#database-design" class="hash-link" aria-label="Direct link to Database design" title="Direct link to Database design" translate="no">​</a></h3>
<ul>
<li class="">Choose column types sensibly</li>
<li class="">Standardize column naming conventions</li>
<li class="">Design indexes properly</li>
<li class="">Add denormalized columns where appropriate</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="sql-quality-control">SQL quality control<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#sql-quality-control" class="hash-link" aria-label="Direct link to SQL quality control" title="Direct link to SQL quality control" translate="no">​</a></h3>
<p>Every SQL statement in the system deserves strict review:</p>
<ul>
<li class="">Does it use indexes correctly?</li>
<li class="">Are there full table scans?</li>
<li class="">Are there complex, hard-to-maintain queries?</li>
</ul>
<p>Complex SQL should be broken up where possible, and the number of rows affected per statement — along with the odds of deadlocks — should be minimized.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="system-performance-and-load-testing">System performance and load testing<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#system-performance-and-load-testing" class="hash-link" aria-label="Direct link to System performance and load testing" title="Direct link to System performance and load testing" translate="no">​</a></h2>
<p>Before a system goes live, it needs <strong>load testing and performance testing</strong>: simulating high-concurrency traffic, probing the servers' limits, and measuring system TPS.</p>
<p>At the same time, tune:</p>
<ul>
<li class="">Server configuration</li>
<li class="">Container configuration</li>
<li class="">JVM parameters</li>
</ul>
<p>Load testing gives you a much clearer picture of where the system's bottlenecks are.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="code-quality-management">Code quality management<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#code-quality-management" class="hash-link" aria-label="Direct link to Code quality management" title="Direct link to Code quality management" translate="no">​</a></h2>
<p>Low-quality code severely undermines system stability — abnormal CPU spikes, excessive memory usage, even outright crashes.</p>
<p>That calls for code quality tooling, such as:</p>
<ul>
<li class="">FindBugs</li>
<li class="">SonarQube</li>
</ul>
<p>Static analysis tools surface potential problems before they bite.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="server-security-design">Server security design<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#server-security-design" class="hash-link" aria-label="Direct link to Server security design" title="Direct link to Server security design" translate="no">​</a></h2>
<p>System security is an equally important part of architecture. Common measures include:</p>
<ul>
<li class="">Changing default ports</li>
<li class="">Internal/external access whitelists</li>
<li class="">Regular vulnerability scans</li>
<li class="">Encrypting API parameters</li>
</ul>
<p>At the API level, use HTTPS transport encryption, symmetric/asymmetric encryption, and parameter signing. Also minimize open ports, enforce strong passwords, and configure brute-force protection.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="project-management-and-team-collaboration">Project management and team collaboration<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#project-management-and-team-collaboration" class="hash-link" aria-label="Direct link to Project management and team collaboration" title="Direct link to Project management and team collaboration" translate="no">​</a></h2>
<p>Architecture isn't only a technical problem — it also covers <strong>team management and development process</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="git-branch-management">Git branch management<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#git-branch-management" class="hash-link" aria-label="Direct link to Git branch management" title="Direct link to Git branch management" translate="no">​</a></h3>
<p>Keep Git branches under control:</p>
<ul>
<li class="">New feature work on feature branches</li>
<li class="">Urgent bug fixes on hotfix branches</li>
<li class="">The test environment on a test branch</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="task-management">Task management<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#task-management" class="hash-link" aria-label="Direct link to Task management" title="Direct link to Task management" translate="no">​</a></h3>
<p>Distribute development tasks sensibly so workloads don't vary wildly between developers. Tools like ZenTao and Teambition help with team collaboration and task management.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="automated-testing">Automated testing<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#automated-testing" class="hash-link" aria-label="Direct link to Automated testing" title="Direct link to Automated testing" translate="no">​</a></h2>
<p>As the system grows, relying on manual testing alone becomes hopelessly inefficient, so an <strong>automated testing system</strong> needs to be built up incrementally.</p>
<p>Automated testing can:</p>
<ul>
<li class="">Verify functionality quickly</li>
<li class="">Catch problems early</li>
<li class="">Improve system stability</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontendbackend-separation">Frontend/backend separation<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#frontendbackend-separation" class="hash-link" aria-label="Direct link to Frontend/backend separation" title="Direct link to Frontend/backend separation" translate="no">​</a></h2>
<p>Frontend/backend separation is now the mainstream architecture. Frontend frameworks (Vue, for instance) have mature ecosystems covering page rendering, parameter validation, interaction logic, and animation.</p>
<p>With the split, the backend focuses on API development and the frontend on page interaction, while also relieving servers of static asset traffic.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="static-assets-and-object-storage">Static assets and object storage<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#static-assets-and-object-storage" class="hash-link" aria-label="Direct link to Static assets and object storage" title="Direct link to Static assets and object storage" translate="no">​</a></h2>
<p>Large volumes of static assets — images, videos, files — can be stored centrally in object storage such as <strong>OSS</strong>.</p>
<p>The upload flow can shift to the frontend uploading directly to OSS, with the backend only storing asset URLs and handling access control. This takes significant load off the backend servers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="microservices-and-future-architecture">Microservices and future architecture<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#microservices-and-future-architecture" class="hash-link" aria-label="Direct link to Microservices and future architecture" title="Direct link to Microservices and future architecture" translate="no">​</a></h2>
<p>A system should consider <strong>microservice architecture</strong> from the very start — service registry, configuration center, service governance — and be deployable to <strong>Kubernetes</strong> for horizontal scaling.</p>
<p>The architecture may evolve further from there — Service Mesh, cloud-native architecture. Many large companies have already landed these, but for small and mid-sized companies, <strong>traditional microservice architecture</strong> remains the norm for now.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-an-architect-needs-to-think">How an architect needs to think<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#how-an-architect-needs-to-think" class="hash-link" aria-label="Direct link to How an architect needs to think" title="Direct link to How an architect needs to think" translate="no">​</a></h2>
<p>A mature architect is not just a technical implementer but a long-term planner for the system. When designing an architecture, a few angles usually deserve thought:</p>
<ul>
<li class="">Can it support where the business is heading?</li>
<li class="">Is the system easy to maintain?</li>
<li class="">Is the technical approach stable and reliable?</li>
<li class="">Are the costs under control?</li>
<li class="">Can the team sustain it long term?</li>
</ul>
<p>Good architecture is rarely designed in one pass — it takes shape gradually through <strong>business growth, technical evolution, and continuous refinement</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2020/03/20/javahou-tai-jia-gou#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>Architecture design is a complete system spanning code to systems, technology to teams. Coding standards, module decomposition, middleware selection, and database governance solve the system's own problems; security design, team collaboration, and automated testing ensure the system can evolve healthily over the long run. There's no one-size-fits-all architecture — only architecture that matches your current business scale and team capability. Rather than chasing a perfect design in one shot, leave room to evolve and let the architecture grow with the business.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Building a Remote Desktop in Java]]></title>
            <link>https://tanzhuo.xyz/en/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian</link>
            <guid>https://tanzhuo.xyz/en/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian</guid>
            <pubDate>Tue, 28 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[An experiment in implementing remote desktop with Java: notes on Robot screen-capture performance, the bandwidth cost of shipping images, and the trade-offs between h.264 video streaming and run-length-encoded delta transmission.]]></description>
            <content:encoded><![CDATA[<p>On a whim I wondered: could you build remote desktop sharing in Java?</p>
<!-- -->
<p>We use remote desktop tools all the time — Sunlogin, TeamViewer — but few people ever think about how they actually work under the hood. Break it down and it's really just three things: capture the screen, shrink the data, and ship it over the network to the other end for playback. Every one of those steps has performance problems, and Java is at an inherent disadvantage on the very first one. This post records the pitfalls I hit at each of the three stages while tinkering with a Java remote desktop, and what I ended up choosing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="screen-capture-is-the-first-hurdle">Screen capture is the first hurdle<a href="https://tanzhuo.xyz/en/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#screen-capture-is-the-first-hurdle" class="hash-link" aria-label="Direct link to Screen capture is the first hurdle" title="Direct link to Screen capture is the first hurdle" translate="no">​</a></h2>
<p>Of course it's possible — it's all just code. But Java is comparatively slow here. Why? Not because Java code executes slowly, but because Java's built-in screen capture is inefficient. Java provides the Robot class for desktop capture; a 2K-screen JPG comes out around 200 KB, and on my RX 2700X CPU a single capture took a full 30 milliseconds. That's well short of 30 fps, never mind 60. And even if you could hit 60 fps, how would you efficiently push that from Java to every remote client?</p>
<p>A bit more on the mechanics. <code>java.awt.Robot</code>'s <code>createScreenCapture</code> goes through AWT's native calls: under the hood it copies the entire screen's pixels from video memory into RAM, then wraps them in a <code>BufferedImage</code>. That process is a synchronous full copy — the higher the resolution, the more data gets copied. One frame of raw RGB on a 2K screen is over ten megabytes, and that memory shuffle alone guarantees it can't be fast. Robot was designed for GUI test automation taking an occasional screenshot, not sustained high-frequency capture.</p>
<p>Some suggest multi-threaded capture to raise the frame rate. I don't think that approach holds up — why? Because it burns far too much CPU. The right move is to find a method that's both efficient and light on CPU.</p>
<p>Multi-threading has another hidden problem: the capture bottleneck is in the system call and the memory copy. Multiple threads fighting over that same channel won't necessarily scale throughput linearly, but CPU usage genuinely doubles. A remote desktop is a long-running background tool — maxing out the host's CPU is a non-starter by itself.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="shipping-raw-images-doesnt-work">Shipping raw images doesn't work<a href="https://tanzhuo.xyz/en/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#shipping-raw-images-doesnt-work" class="hash-link" aria-label="Direct link to Shipping raw images doesn't work" title="Direct link to Shipping raw images doesn't work" translate="no">​</a></h2>
<p>And transmitting images is a dead end, because each image is too big: 200 KB, and 30 per second is 6,000 KB — a serious drain on bandwidth.</p>
<p>Do the math and it's obvious this road goes nowhere: 6,000 KB per second is close to 50 Mbps of bandwidth. Residential upload can't handle that, let alone one host streaming to multiple remote clients at once. And JPEG compresses each frame independently — even when 99% of the pixels haven't changed between two frames, the whole image gets encoded and sent again, wasting everything on redundant information.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="video-streaming-is-the-right-path">Video streaming is the right path<a href="https://tanzhuo.xyz/en/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#video-streaming-is-the-right-path" class="hash-link" aria-label="Direct link to Video streaming is the right path" title="Direct link to Video streaming is the right path" translate="no">​</a></h2>
<p>The best approach is a video stream, using the open h.264 format. There's the even better h.265, but I couldn't find an open-source jar for it — h.265 is still in its licensing-fee era. h.264 records data based on which pixels changed in each frame, rather than storing most of the pixel information of the whole image. So an h.264 stream is far smaller than the sum of transmitted images, and much friendlier to the network.</p>
<p>This is exactly the idea behind inter-frame compression in video codecs: the encoder sends a complete keyframe every so often, and the frames in between record only the delta relative to the previous frame. Desktop content happens to be the ideal case for this — most of the time only the mouse and a small window region are moving while the background sits perfectly still, so inter-frame redundancy is enormous and the compression ratio follows.</p>
<p>Recording the desktop and streaming it to clients sidesteps both the bandwidth cost of images and the capture-rate problem. But with video streaming, real-time transcoding and traffic are no small challenges of their own.</p>
<p>The hard part of real-time transcoding is balancing latency against compute: crank the compression up and encoding time climbs — and nothing kills remote control like the picture lagging behind your hand; turn it down and the bandwidth can't cope. Mature remote desktop software generally resolves this tension with hardware encoding. Catching up in pure Java at the software level is a tall order.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-compromise-i-landed-on">The compromise I landed on<a href="https://tanzhuo.xyz/en/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#the-compromise-i-landed-on" class="hash-link" aria-label="Direct link to The compromise I landed on" title="Direct link to The compromise I landed on" translate="no">​</a></h2>
<p>What I ultimately used was run-length encoding, transmitting only the changed data in the RGB bitmap captured by Robot. But when screen pixels change too drastically, RLE's data volume balloons. My conclusion: if you really want to transmit the desktop as images, you have to break through both the efficiency of Java's screen pixel acquisition and minimize the transmitted data size — only then can you maximize the remote desktop's frame rate.</p>
<p>To expand on the approach: compare the current frame's RGB array against the previous frame's pixel by pixel, encode only the changed pixels along with their positions, and send those. The receiving end applies the delta onto its locally cached image. Run-length encoding itself is simple: consecutive identical values are stored as "value + repeat count." On a static screen the delta is nearly zero and it works great. But its weakness is just as clear — the moment the whole screen scrolls or a video plays, almost every pixel changes, the delta degrades into a full frame, and with the encoding overhead stacked on top, the data can end up larger than just sending the raw image.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and caveats<a href="https://tanzhuo.xyz/en/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and caveats" title="Direct link to Pitfalls and caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Robot capture time scales directly with resolution. Test at a small resolution and the frame rate looks fine; switch to a 2K screen and reality hits immediately. Benchmark at your target resolution.</p>
</li>
<li class="">
<p>Delta transmission must handle the first frame and packet loss: a delta is meaningless when the receiver has no baseline image, so a full frame must be sent first. Lose one delta frame mid-stream and everything after it stays corrupted — either use reliable transport or periodically force a full-frame refresh as a safety net.</p>
</li>
<li class="">
<p>Don't overlook the receiver's reconstruction cost. Merging deltas back into a bitmap and painting it to the screen eats CPU too — a weak client will stutter just the same.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2020/01/28/javayuan-cheng-zhuo-mian-shi-xian#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>The conclusion from this experiment is fairly clear: for remote desktop in Java, the bottleneck isn't the language — it's the efficiency of the Robot capture path and the cost of pure-software compression. Raw image transmission blows up bandwidth; RLE delta transmission suits static screens but collapses under heavy change; video streaming is the engineering answer, except the pure-Java ecosystem lacks a convenient open-source h.264 implementation. If you just want a toy to prove the concept, delta transmission is enough. To build a real product, you'll need native encoding libraries or hardware encoding.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Exporting Hundreds of Millions of MySQL Rows to Excel]]></title>
            <link>https://tanzhuo.xyz/en/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu</link>
            <guid>https://tanzhuo.xyz/en/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu</guid>
            <pubDate>Thu, 16 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[A production design for exporting massive list data from a SaaS system to Excel: MQ-based load leveling with async consumers, read replicas to absorb query pressure, id-cursor pagination with EasyExcel spilling to disk and uploading to OSS — 75.5 million rows exported in about 20 minutes.]]></description>
            <content:encoded><![CDATA[<p>Our company's SaaS system needed to let users export list data to Excel. But some customers' data — financial transaction records, for instance — had already grown into the tens of millions and beyond. Exports at that scale need a dedicated solution.</p>
<!-- -->
<p>Otherwise, come month-end, hundreds of companies exporting Excel at the same time would send MySQL QPS through the roof, push server CPU and RAM past alert thresholds in an instant, and large-scale exports could render servers unavailable or even bring them down. (Note: for sensitive data like financials, add file encryption or temporary authorized-download links.)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-design">The design<a href="https://tanzhuo.xyz/en/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu#the-design" class="hash-link" aria-label="Direct link to The design" title="Direct link to The design" translate="no">​</a></h2>
<p>The starting principle: bulk exports must not affect normal business operations.</p>
<p>Deploy a dedicated database read replica to absorb the query load, and deploy separate groups of servers running a Spring Boot project as an MQ consumer cluster. All export requests are published to an MQ queue; Spring Boot uses pull mode to actively fetch export tasks from the queue and execute them, making full use of MQ's load-leveling and async capabilities, with thread pool resources strictly allocated inside the Spring Boot project.</p>
<p>Each task consumer queries MySQL list data in batches using the large-scale pagination scheme, uses Alibaba's EasyExcel to write the data to local disk, uploads the finished file to OSS (this can also go over the OSS internal network, streaming straight into the OSS file system), and then deletes the local file.</p>
<p>This design decouples exports completely from the main business — export becomes an independent module deployed on its own server nodes. Even if the export service goes down under a sudden flood of export requests (unlikely in practice, since MQ absorbs exactly this kind of spike), the main business keeps running untouched.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="resource-limits-and-paginated-queries">Resource limits and paginated queries<a href="https://tanzhuo.xyz/en/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu#resource-limits-and-paginated-queries" class="hash-link" aria-label="Direct link to Resource limits and paginated queries" title="Direct link to Resource limits and paginated queries" translate="no">​</a></h2>
<p>Combined with thread pool limits and a cap on how many files a single customer can export concurrently, we strictly control how many tasks a JVM pulls at once, keeping the service robust.</p>
<p>The database queries use the large-scale pagination scheme: first select the target ids by condition, drop the large-offset limit pattern in favor of an id cursor, keeping every SQL query in the millisecond range; and choose the selected columns carefully (an index is mandatory) so each batch flowing out of MySQL stays within a bounded size (100–500 KB).</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%)">-- Use an id cursor instead of a large-offset limit; start each batch from the previous batch's max 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%)">table</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(286, 60%, 67%)">where</span><span class="token plain"> conditions </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"> previous_batch_max_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>On top of this we built an abstract factory and interfaces for developers to implement exports for all kinds of list features. Developers don't need to care how the data is paginated and batched, how the Excel file is generated, or how it gets uploaded — they just write the export SQL query and focus on the data.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="real-world-results">Real-world results<a href="https://tanzhuo.xyz/en/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu#real-world-results" class="hash-link" aria-label="Direct link to Real-world results" title="Direct link to Real-world results" translate="no">​</a></h2>
<p>In testing, exporting 75.5 million rows took a bit over 20 minutes (it could be faster with well-placed indexes and multi-threaded task decomposition for the queries and export), which comfortably covers our current business needs.</p>
<p>Estimating each SQL round-trip plus execution at 100 ms, the rough formula: (75,500,000 / 5,000 × 100 ms) / 1,000 / 60 ≈ 25 min</p>
<p>Memory growth for a single customer's export stays within about a 20 MB swing, because dataList only holds 5,000 rows at a time — once the data is flushed to disk, the list objects are garbage collected, so memory barely grows, with disk IO at 3–4 MB per batch. With multiple customers, thread resource limits and a cap on total export tasks are needed to keep the JVM from OOMing.</p>
<p><strong>A single Excel worksheet holds only around 1 million rows; beyond that you need to split across workbooks, and how you split depends on available memory. For a full export of 100 million records, a single file would be far too large — the Excel output then needs to be split, stored, and compressed, otherwise the file is too big to open on the client side without exhausting memory.</strong></p>
<p>Each database round-trip also must not burn too much performance, so the 75.5 million rows are exported over many batches. Once the Excel files on disk are complete, they're zip-compressed and uploaded straight to OSS, and finally the OSS resource URL is handed to the frontend app or web browser for the client to download.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="takeaways">Takeaways<a href="https://tanzhuo.xyz/en/2020/01/16/mysqlbai-mo-ji-shu-ju-dao-chu#takeaways" class="hash-link" aria-label="Direct link to Takeaways" title="Direct link to Takeaways" translate="no">​</a></h2>
<p>Even for something as mundane as a data export feature, pushing performance to the limit touches a surprising number of concerns: MQ-based async load leveling, read replicas absorbing queries, cursor pagination, memory control and file splitting, deployment isolation, and planning horizontal scaling for future business growth. Every piece has to be designed properly.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Database</category>
        </item>
        <item>
            <title><![CDATA[Paginating Hundreds of Millions of Rows in MySQL]]></title>
            <link>https://tanzhuo.xyz/en/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo</link>
            <guid>https://tanzhuo.xyz/en/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[Once a table grows to millions of rows, plain limit-based pagination gets slower with every page. Notes from optimizing pagination on a billing table: locating primary keys with a subquery, deferred joins, and verifying with explain to bring deep pagination down to acceptable latency.]]></description>
            <content:encoded><![CDATA[<p>As the company's business grew, so did our data volume. MySQL is a free, community-driven open-source database, and if you want to paginate through millions of rows with it, relying on limit alone just doesn't cut it. That's not a knock on MySQL — with its pluggable storage engine architecture, it covers the vast majority of application needs, and managing 100 TB with MySQL is entirely feasible. How you use it, however, is another matter.</p>
<!-- -->
<p>Pagination is table stakes for pretty much every admin list page. When the data is small, any implementation works; the problems only surface once the table grows to a few million rows: the first few pages return in milliseconds, later pages get slower and slower, and the last few pages may simply time out. This post documents the deep pagination problem I ran into on our company's billing table, and how I optimized it step by step.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-limit-gets-slow">Why limit gets slow<a href="https://tanzhuo.xyz/en/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#why-limit-gets-slow" class="hash-link" aria-label="Direct link to Why limit gets slow" title="Direct link to Why limit gets slow" translate="no">​</a></h2>
<p>limit accepts one or two numeric arguments, which must be integer constants. With two arguments, the first specifies the offset of the first returned row and the second specifies the maximum number of rows to return.</p>
<p>The key is how the offset is implemented: MySQL cannot "jump" to row 100,000. It has to read all of the preceding offset rows and throw them away, returning only the final few. In other words, <code>limit 100000, 20</code> actually reads 100,020 rows. If the selected columns aren't in the index, every one of those rows also requires a lookup back into the clustered index via the primary key, and the more rows you read, the more that cost gets amplified.</p>
<p>With offsets under 100k, limit performance is just about tolerable, but as the offset grows, performance falls off a cliff.</p>
<p>Our billing table had reached 2.3 million rows in a single table. Paginating to the last page with limit could easily take 20 seconds or more. The exact timing of course depends on indexes, column count, data contents, and query conditions.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="locate-primary-keys-with-a-subquery-first">Locate primary keys with a subquery first<a href="https://tanzhuo.xyz/en/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#locate-primary-keys-with-a-subquery-first" class="hash-link" aria-label="Direct link to Locate primary keys with a subquery first" title="Direct link to Locate primary keys with a subquery first" translate="no">​</a></h2>
<p>To fix the pagination performance, my approach was: use a subquery to fetch only the primary key ids first, then use those ids to fetch the full rows.</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%)">-- The subquery fetches only the primary key id, with the where conditions</span><span class="token plain"></span><br></div><div class="token-line" 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%)">-- It can be satisfied entirely from the index, no row lookups needed</span><span class="token plain"></span><br></div><div class="token-line" 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>(Include the where conditions in the subquery.)</p>
<p>Since the primary key id is already the primary index, the limit runs very fast. Adding the filter columns to a composite index is what makes the improvement really substantial. The reason this step is fast: when selecting only id, the whole query can be completed within the index; each index record is tiny, so for the same offset scan the IO cost is far lower than scanning full rows.</p>
<p>But add sorting and performance drops sharply again — from what I observed, sorting 300k rows by id or timestamp takes 1–2 seconds.</p>
<p>The best approach is to abandon the limit offset entirely and use <code>where id &gt; xxx</code> to locate the target starting position much faster, then sort and take 20 rows with limit. This comfortably handles millions of rows. This pattern is commonly called cursor pagination (or keyset pagination): each time you turn a page, you carry over the id of the last row on the previous page, and <code>where id &gt; xxx</code> locates the starting point directly in the index. No matter which page you're on, you only ever scan about 20 rows. The trade-off is that you can only page sequentially — no jumping to arbitrary pages — which makes it a good fit for feeds and exports.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fetch-full-rows-by-id-list">Fetch full rows by id list<a href="https://tanzhuo.xyz/en/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#fetch-full-rows-by-id-list" class="hash-link" aria-label="Direct link to Fetch full rows by id list" title="Direct link to Fetch full rows by id list" translate="no">​</a></h2>
<p>First query out the ids you need, then run an unconditioned <code>in idList</code> query:</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%)">-- Step two: fetch the full columns using the id list from step one</span><span class="token plain"></span><br></div><div class="token-line" 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%)">-- No where conditions; keep the ordering as-is</span><span class="token plain"></span><br></div><div class="token-line" 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%)">columns</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>(No conditions; keep the ordering if there is one — sorting doesn't matter here because the data set is already tiny, so even an external sort is fast.)</p>
<p>When fetching the list, all you need is <code>where id in (...)</code>. All the other conditions were already applied during the select-id step, so the row fetch needs no conditions at all — but do keep the ordering.</p>
<p>This makes paginated queries extremely fast, and if the list has few columns, you can build a covering index and go even faster. In practice, list pages often have 10 or 20+ columns, and indexing all of them is a bad idea: the index would be huge, and inserts and updates would slow down maintaining it, potentially hurting the table overall. So query design means balancing column count, sort order, number of conditions, index type, and which columns to index.</p>
<p>Joins are basically off the table at this data scale — prefer denormalized columns instead. On large tables, a join means every row of the driving table performs a lookup against the driven table, and the amplification becomes severe as data grows. Duplicating commonly joined columns into the main table trades one extra write for one fewer join on every read — usually a good deal for read-heavy list scenarios.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="verify-the-execution-plan-with-explain">Verify the execution plan with explain<a href="https://tanzhuo.xyz/en/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#verify-the-execution-plan-with-explain" class="hash-link" aria-label="Direct link to Verify the execution plan with explain" title="Direct link to Verify the execution plan with explain" translate="no">​</a></h2>
<p>Optimization isn't a matter of gut feeling — after changing a SQL statement, check the execution plan to confirm.</p>
<p>Run the query through explain to see how it executes: whether it uses an index, which index type, whether row lookups happen, the estimated scanned rows, and other key information. Master these and your database performance improves in a meaningful way.</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%)">-- Prefix the query with explain to see the execution plan</span><span class="token plain"></span><br></div><div class="token-line" 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%)">-- Focus on type (index access type), key (index actually used),</span><span class="token plain"></span><br></div><div class="token-line" 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 (estimated rows scanned), and Extra (Using index = covering 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"> conditions </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>In testing, this pagination scheme handles 1 million rows at 20 per page well within 1 second — around 600 ms per request.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="beyond-a-single-table">Beyond a single table<a href="https://tanzhuo.xyz/en/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#beyond-a-single-table" class="hash-link" aria-label="Direct link to Beyond a single table" title="Direct link to Beyond a single table" translate="no">​</a></h2>
<p>Pagination optimization solves single-table query performance, but the table keeps growing, so the architecture needs an escape hatch prepared in advance.</p>
<p>If a single table exceeds 5 million rows, it's time to consider horizontal sharding.</p>
<p>At the business logic level, to increase single-database throughput, consider read/write splitting: writes to the primary, reads from replicas, possibly multiple replicas.</p>
<p>Split databases vertically along business lines, and separate hot data from cold data when allocating server resources.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and caveats<a href="https://tanzhuo.xyz/en/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and caveats" title="Direct link to Pitfalls and caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>The subquery approach assumes the where-clause columns have a suitable composite index. Otherwise the select-id step itself degrades into a full table scan and the optimization is wasted.</p>
</li>
<li class="">
<p>Cursor pagination with <code>where id &gt; xxx</code> requires the sort key to be monotonic and unique. If you sort by timestamp and timestamps have duplicates, paging can skip or repeat rows — the usual fix is a composite sort on (timestamp, id).</p>
</li>
<li class="">
<p>The <code>in (idList)</code> length equals the page size — 20 is fine. Don't generalize this pattern to passing thousands of ids at once.</p>
</li>
<li class="">
<p>The rows value from explain is an estimate and can be off when table statistics are stale. When in doubt, cross-check with the slow query log and actual execution times.</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>tip</div><div class="admonitionContent_BuS1"><p>Before changing pagination SQL in production, verify the execution plan against realistic data volumes on a replica or a test database. Deep pagination problems simply don't reproduce on small data sets.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://tanzhuo.xyz/en/2020/01/15/mysqlbai-mo-shu-ju-fen-ye-sao-cao-zuo#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>The root cause of slow deep pagination is that limit offsets must scan and discard rows one by one, and the cost grows with the data. The fix is to make the scan happen over the smallest possible data set: locate primary keys within the index first, then fetch full rows; and drop the offset entirely in favor of cursor pagination where you can. After the change, use explain to verify the indexes are actually being used. When a single table can no longer keep up, move on to architectural measures: sharding, read/write splitting, and hot/cold data separation.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Database</category>
        </item>
        <item>
            <title><![CDATA[Some Thoughts on Logo Design]]></title>
            <link>https://tanzhuo.xyz/en/2019/12/31/qian-tan-logoshe-ji</link>
            <guid>https://tanzhuo.xyz/en/2019/12/31/qian-tan-logoshe-ji</guid>
            <pubDate>Tue, 31 Dec 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[From brand positioning and color choices to shape, form, and trademark registration — what it takes to carry a logo from design to launch. A logo isn't just a pretty graphic; it's the vessel of brand memory.]]></description>
            <content:encoded><![CDATA[<p>When a company has no reputation yet, a logo gives people something recognizable to latch onto. Once the company becomes well known, that graphic maps neatly into people's memory and reinforces the brand — the icing on the cake.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-a-logo-exists">Why a Logo Exists<a href="https://tanzhuo.xyz/en/2019/12/31/qian-tan-logoshe-ji#why-a-logo-exists" class="hash-link" aria-label="Direct link to Why a Logo Exists" title="Direct link to Why a Logo Exists" translate="no">​</a></h2>
<p>The core value of a logo is lowering the cost of remembering. People remember images far more firmly than words: mention a brand, and what surfaces in the mind first is usually not its full name but its graphic mark. Letting people remember a company's brand identity through visual memory — that's the first job a logo has to do.</p>
<p>Conversely, a logo that's hard to make out or looks nearly identical to its competitors hands that memory cost right back to the user. So the first criterion for judging a logo isn't "does it look good" but "can you remember it at a glance, and recognize it again days later."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="start-from-the-companys-positioning">Start from the Company's Positioning<a href="https://tanzhuo.xyz/en/2019/12/31/qian-tan-logoshe-ji#start-from-the-companys-positioning" class="hash-link" aria-label="Direct link to Start from the Company's Positioning" title="Direct link to Start from the Company's Positioning" translate="no">​</a></h2>
<p>Logo design should start from the company's culture and the services it provides. To put it plainly: what does the company do? Should its character feel classic and steady, or young and full of energy?</p>
<p>This step gets skipped all the time. Many people take a brief and start drawing immediately, and the revisions grind on until both sides are exhausted. A safer approach is to write down a few questions first:</p>
<ol>
<li class="">
<p>What is the company's core business, and who are its target users?</p>
</li>
<li class="">
<p>What keywords should users associate with the logo — "professional," "approachable," "high-tech"?</p>
</li>
<li class="">
<p>What do the main competitors' logos look like, and how do we differentiate?</p>
</li>
</ol>
<p>Answer these clearly before picking up the pen, and the direction will drift far less. And precisely because this upfront work is substantial, this is where a professional visual designer earns their keep — for many well-known brands, the logo design fee alone runs into hundreds of thousands. What's being bought isn't a few strokes of line work but the complete positioning rationale behind them.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="choosing-colors">Choosing Colors<a href="https://tanzhuo.xyz/en/2019/12/31/qian-tan-logoshe-ji#choosing-colors" class="hash-link" aria-label="Direct link to Choosing Colors" title="Direct link to Choosing Colors" translate="no">​</a></h2>
<p>For a tech or internet company, a young, energetic character is almost mandatory. The palette can be somewhat varied, but the total number of colors must stay small — too many and it turns gaudy. Keep the color count in check.</p>
<p>On saturation, I lean toward restraint: colors should run slightly muted, and saturation shouldn't go too high. Large blocks of highly saturated color are tiring to look at — the longer you stare, the more agitated you feel. If you want the eye to find the mark comfortable, approachable, and relaxing, low saturation and few hues are usually the safer choice.</p>
<p>There's another point that's easy to overlook: the logo will end up in all kinds of contexts — websites, business cards, print, dark-background app interfaces. During design, it's best to also verify that it stays crisp in black-and-white monochrome. Many schemes that lean on gradients and multi-color pairings turn to mush the moment they're converted to a single color.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="shape-and-type">Shape and Type<a href="https://tanzhuo.xyz/en/2019/12/31/qian-tan-logoshe-ji#shape-and-type" class="hash-link" aria-label="Direct link to Shape and Type" title="Direct link to Shape and Type" translate="no">​</a></h2>
<p>The graphic form should follow the company's line of business, and it also depends on whether the company wants a wordmark or a more abstract symbol.</p>
<p>A wordmark treats the company name itself as the visual — the upside is minimal cognitive load: see it, read it. An abstract symbol relies more on long-term exposure to build the association, but once established, it wins on recognizability and versatility. Internet companies often work tech elements into the graphic, though the specific imagery still has to resonate with what the company actually is.</p>
<p>One mindset worth keeping straight: the logo may not be the best-looking one, but the logo that best fits the company's image is enough. Piling on detail to show off craftsmanship tends to sacrifice legibility at small sizes — a favicon is only a dozen-odd pixels, and a complex graphic is essentially unreadable at that scale.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dont-forget-trademark-registration">Don't Forget Trademark Registration<a href="https://tanzhuo.xyz/en/2019/12/31/qian-tan-logoshe-ji#dont-forget-trademark-registration" class="hash-link" aria-label="Direct link to Don't Forget Trademark Registration" title="Direct link to Don't Forget Trademark Registration" translate="no">​</a></h2>
<p>Finishing the design is only half the job; legal protection matters just as much. Once the logo is finalized, register the trademark and file the copyright — otherwise infringement can cut both ways: someone else might register your mark first, or your design might unknowingly collide with an already registered trademark. Registering a trademark is, at its core, about protecting the company's brand identity.</p>
<p>In practice, run a trademark search before the design is even finalized to confirm there are no similar marks in your target classes — so a scheme you've polished long and hard doesn't have to be scrapped because it can't be registered.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2019/12/31/qian-tan-logoshe-ji#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Taking a logo from nothing to launch roughly means positioning analysis, color and shape design, multi-context verification, and trademark registration. It doesn't need to dazzle everyone — it only needs to convey precisely who the company is and be memorable at a glance. Put "fits the company's image" ahead of "looks good," and most of the trade-offs answer themselves.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Notes on Adopting ELK]]></title>
            <link>https://tanzhuo.xyz/en/2019/12/19/guan-yu-shi-yong-elk</link>
            <guid>https://tanzhuo.xyz/en/2019/12/19/guan-yu-shi-yong-elk</guid>
            <pubDate>Thu, 19 Dec 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Our stats workload pushed the MySQL cluster to its limit, and index tuning couldn't save it. So we brought in ELK: Logstash for incremental sync, Elasticsearch for aggregations, Kibana for visualization. Here's the reasoning and the incremental sync design.]]></description>
            <content:encoded><![CDATA[<p>Our company's statistics workload had been putting heavy pressure on the system. The MySQL cluster hit a query bottleneck, and index tuning could no longer deliver stats results fast enough — so we adopted the three-piece open source combo: Elasticsearch, Logstash, and Kibana.</p>
<!-- -->
<p>Some background first. Statistical queries are a different beast from ordinary business queries. Business queries mostly fetch a handful of rows by primary key or index — exactly what MySQL is good at. Statistical queries, on the other hand, tend to scan large amounts of data for grouping and aggregation, where B+ tree indexes offer limited help. Once the data volume grows, a single stats SQL can take seconds or even tens of seconds, dragging down business requests on the same database. The common answer is to peel the statistics workload off the OLTP database and hand it to an engine built for aggregate analysis. Under the hood, Elasticsearch combines inverted indexes with columnar doc values, which makes it a natural fit for filtering and aggregation — and the ELK stack is one of the most approachable options in the community, so it seemed worth writing down.</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="choosing-the-tools-and-dividing-the-work">Choosing the Tools and Dividing the Work<a href="https://tanzhuo.xyz/en/2019/12/19/guan-yu-shi-yong-elk#choosing-the-tools-and-dividing-the-work" class="hash-link" aria-label="Direct link to Choosing the Tools and Dividing the Work" title="Direct link to Choosing the Tools and Dividing the Work" translate="no">​</a></h2>
<p>As an alternative to Logstash, Alibaba's DataX would also work — its feature set actually goes a step further than Logstash's. I stuck with Logstash, simply because I only learned about DataX later on and saw no reason to swap it out.</p>
<p>The two are positioned somewhat differently. Logstash is the Elastic family's data pipeline: its jdbc input plugin pulls data on a schedule, a config file is all it takes to get running, and the Elasticsearch output integration comes ready-made. DataX is Alibaba's open source tool for syncing across heterogeneous data sources, with broader plugin coverage and stronger batch throughput. For a single pipeline like "scheduled incremental sync from MySQL to ES," Logstash is plenty — no point in switching for switching's sake.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-12-image-1-60438a663c67bcd8701b19a0ae751a56.webp" width="1461" height="1034" class="img_ev3q"></p>
<p>The division of labor across the three components is clean:</p>
<p>Logstash extracts and filters the statistics data from MySQL and incrementally syncs it into Elasticsearch.</p>
<p>The application then queries Elasticsearch through the Java API.</p>
<p>Kibana provides a web UI for visualizing the cluster and inspecting index details.</p>
<p>In other words: Logstash handles data in, the Java application handles data out, and Kibana makes the health of the cluster and indexes visible. The three stay out of each other's way.</p>
<p>Elasticsearch queries themselves are fairly simple, but the official docs feel light on examples — many of the trickier aggregations you have to figure out yourself. For things like multi-level nested aggregations or sorting on aggregation results, the documentation usually only gives the most basic sample, and in real business scenarios you end up dialing them in bit by bit through Kibana's Dev Tools.</p>
<p>Overall the learning curve isn't steep. It's quick to pick up.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-java-integration">The Java Integration<a href="https://tanzhuo.xyz/en/2019/12/19/guan-yu-shi-yong-elk#the-java-integration" class="hash-link" aria-label="Direct link to The Java Integration" title="Direct link to The Java Integration" translate="no">​</a></h2>
<p>For the Java API, we used the elasticsearch-rest-high-level-client jar.</p>
<p>On top of it, I built a factory tailored to our business scenarios and abstracted a few layers of code for the other developers. The goal of the wrapper is simple: I didn't want every developer hand-assembling <code>SearchSourceBuilder</code> and parsing <code>SearchResponse</code> directly. Instead, the common patterns — condition filtering, pagination, aggregation — are collapsed into a handful of methods, so business code only cares about passing parameters and getting results.</p>
<p>The API's query and result-handling methods can feel a bit clunky to work with, but as long as you write the code against the query statement itself, it's actually easy to follow. The high level client's builder structure maps almost one-to-one onto the Query DSL's JSON structure: get the DSL working in Kibana first, then translate it into Java, and you'll rarely go wrong.</p>
<p>The overall experience was quite friendly. All the software works right after installation, though you do need to tweak the configuration — I won't go into detail here: IPs and ports, language settings, passwords, and so on.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sync-only-the-fields-the-stats-need">Sync Only the Fields the Stats Need<a href="https://tanzhuo.xyz/en/2019/12/19/guan-yu-shi-yong-elk#sync-only-the-fields-the-stats-need" class="hash-link" aria-label="Direct link to Sync Only the Fields the Stats Need" title="Direct link to Sync Only the Fields the Stats Need" translate="no">​</a></h2>
<p>Our company's work is mainly statistics, so we had Logstash extract only the key statistical data — for millions of rows, the extracted stats fields came to less than 100 MB. This dramatically speeds up ES aggregations, so I'd advise against pulling in entire tables; take only the fields that matter most for statistics.</p>
<p>This point deserves elaboration. Elasticsearch runs its aggregations over memory and doc values: the leaner the index, the smaller the segment files, the higher the proportion that can be cached — and the faster the aggregations. Besides, ES is not a database and doesn't need to shoulder the job of storing full detail records. The source of truth for detail data is always MySQL; ES holds only the statistical dimensions and metric fields. If anything breaks, the index can be rebuilt at any time, which takes a lot of weight off your mind.</p>
<p>The full pipeline looks like this:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2020-01-----8c8c2bbb2ecaab9ad0f92291cf1cef25.webp" width="1600" height="740" class="img_ev3q"></p>
<p>This is the flow diagram I drew for our company's team.</p>
<p>If the extraction volume were extremely large, you could put Kafka in the middle as a buffer for a second round of filtering. Since our data volume isn't anywhere near that level, there was no need to burn extra server resources on it. The value of a message queue in the middle is peak shaving and decoupling: when upstream extraction and downstream writes run at different rates, the queue absorbs traffic bursts, and it also gives you a convenient place to hang extra cleansing logic. But every additional component is additional operational cost — if the volume doesn't call for it, don't add it.</p>
<p>Kibana is quite mature by now. You can write the official Query DSL, or plain SQL — though the official recommendation is still to use the Query DSL for aggregation and querying.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2020-01-image-0c13f4afeeb7506820c66bd5ea02aec4.webp" width="1600" height="808" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementing-incremental-sync">Implementing Incremental Sync<a href="https://tanzhuo.xyz/en/2019/12/19/guan-yu-shi-yong-elk#implementing-incremental-sync" class="hash-link" aria-label="Direct link to Implementing Incremental Sync" title="Direct link to Implementing Incremental Sync" translate="no">​</a></h2>
<p>For incremental sync and syncing updated rows, my approach was to add a data_version field to the source tables (the optimistic locking idea — a data version number). Whenever a row is modified, or a new row is inserted, data_version gets set. I used a timestamp (for business reasons at our company), and I recommend making this field a bigint — the timestamp type only works up to 2028.</p>
<p>This gives every row a version. During incremental and update sync, only rows whose data_version has changed get picked up, greatly reducing the load on the MySQL servers, Logstash, and Elasticsearch.</p>
<p>Logstash can record the data_version value of the last row from the previous run. On the next run, that previous data_version value goes into the where clause to filter out just the rows that matter. The SQL must sort by data_version, because Logstash only remembers the data_version of the final row of the previous run.</p>
<p>Behind this is the tracking column mechanism of the Logstash jdbc plugin: the plugin persists the tracking column value of the last record from the previous run to a local file, and on the next execution passes it into the where clause as a parameter. That's why the sort is mandatory — if the result set isn't ordered by data_version ascending, the recorded "last row" isn't the maximum value, and the next sync round will miss data.</p>
<p>Otherwise, would you do a full-table sync every single time? You can imagine the load. Surely nobody would go that route — a full sync only happens on the very first run. Everything after that is incremental and update sync.</p>
<p>The reason updated rows also make it across is that each row is written to ES with a fixed document id (usually the MySQL primary key). When a row's version changes, it gets picked up again, and writing it into ES is simply an overwrite — no duplicate documents.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2019/12/19/guan-yu-shi-yong-elk#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Think carefully about the tracking column's type. As mentioned above, when using a timestamp as the version number, make the field a bigint to avoid the upper-bound issue of the timestamp type. Also, if the timestamp's precision is too coarse, multiple modifications within the same second can slip through at the boundary — either the business can tolerate that, or switch to a monotonically increasing sequence.</p>
</li>
<li class="">
<p>Physical deletes don't sync. The data_version scheme only detects inserts and updates; rows deleted directly in MySQL simply stop appearing in the result set, leaving stale documents behind in ES. Either use soft-delete flags in the business layer so a delete becomes just another "update" that syncs across, or rebuild the index periodically as a safety net.</p>
</li>
<li class="">
<p>Don't treat ES as your only store. The stats index in ES should always be replayable from MySQL. That way, if the mapping design turns out wrong or a field needs adding, you just drop the index and rerun Logstash — no complicated online migration.</p>
</li>
<li class="">
<p>Always verify the incremental SQL's ordering and boundary conditions. Greater-than versus greater-than-or-equal in the where clause, whether the sort actually takes effect — run the SQL by hand first, then compare the recorded values across two Logstash runs to confirm nothing is missed or fetched twice.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2019/12/19/guan-yu-shi-yong-elk#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The core win from adopting ELK was peeling statistical aggregation off the MySQL cluster: Logstash does incremental sync based on data_version, pulling only the fields the stats need; Elasticsearch handles the aggregate queries, wrapped on the Java side with the high level client for business use; Kibana covers both DSL debugging and cluster monitoring. The whole setup introduces no unnecessary components — the trade-offs follow the actual scale of the data. Enough is enough.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Getting Hooked on Swimming]]></title>
            <link>https://tanzhuo.xyz/en/2019/11/25/zhu-jian-shang-yin-de-you-yong</link>
            <guid>https://tanzhuo.xyz/en/2019/11/25/zhu-jian-shang-yin-de-you-yong</guid>
            <pubDate>Mon, 25 Nov 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[My gym has a pool, and it felt like a waste to have a membership and never use it. So I went — after three lessons with a coach, I've basically got the breaststroke down, though my form still needs work.]]></description>
            <content:encoded><![CDATA[<p>My gym has a pool, and it felt like a waste to have a membership and never swim a few laps.</p>
<!-- -->
<p>So off I went. After three lessons with a coach, I've basically got the breaststroke down — though my form is still far from textbook.</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>But that's not the point. The point is I can now swim across the pool and back. It's so damn fun. Hahaha.</p>
<p>These days, every time I get in the water my whole body just relaxes, and my mind goes completely blank. All I have to do is swim, or just float.</p>
<p>Now that I've learned the basics, next up is working on my stroke, my form, and my speed. I'm going at least once a week at this point.</p>
<p>The best part is the shower right after swimming — that's the real bliss. But that split second when you climb out of the pool and gravity hits you again...</p>]]></content:encoded>
            <category>Life</category>
        </item>
        <item>
            <title><![CDATA[Dark Souls 1]]></title>
            <link>https://tanzhuo.xyz/en/2019/11/18/hei-hun-1</link>
            <guid>https://tanzhuo.xyz/en/2019/11/18/hei-hun-1</guid>
            <pubDate>Mon, 18 Nov 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Dark Souls 1. In the Age of Ancients, the world was unformed, shrouded by fog — a land of gray crags, archtrees, and everlasting dragons. Then one day, a first flame kindled beneath a tree, and with it came disparity.]]></description>
            <content:encoded><![CDATA[<p>Dark Souls 1 is an action RPG developed by the Japanese studio FromSoftware. First released in 2011, it went on to earn enormous success and acclaim worldwide. As the first entry in the series, its depth and difficulty have cemented it as one of the classics of gaming history.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="about-the-game">About the Game<a href="https://tanzhuo.xyz/en/2019/11/18/hei-hun-1#about-the-game" class="hash-link" aria-label="Direct link to About the Game" title="Direct link to About the Game" translate="no">​</a></h2>
<p>You play a character known as an "Undead," whose task is to journey through the kingdom of Lordran and confront the crisis brought on by dark magic and monstrous creatures. Along the way, you explore region after region, fight all manner of enemies, and work through puzzles and riddles.</p>
<p>One of the game's defining traits is its brutal difficulty. You face powerful enemies of every kind, from towering monsters to other players invading your world. Combat demands mastery of attacking, blocking, and dodging, along with smart use of your weapons and equipment. Death here is not the end — it is how you learn and grow. Only through repeated attempts and exploration can you reach the game's final victory.</p>
<p>Beyond the difficulty, the game's other hallmark is its depth and complexity. It has an intricate world with a rich cast of characters and interwoven storylines. You piece together the lore by exploring and solving puzzles, gradually coming to understand the world's background and history.</p>
<p>All in all, Dark Souls 1 is a classic RPG whose depth and difficulty have earned it a permanent place in gaming history. If you enjoy challenging games with real depth, this one will keep you hooked.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-story">The Story<a href="https://tanzhuo.xyz/en/2019/11/18/hei-hun-1#the-story" class="hash-link" aria-label="Direct link to The Story" title="Direct link to The Story" translate="no">​</a></h2>
<p>In the Age of Ancients, the world was unformed. It was a land of gray crags, towering archtrees, and everlasting dragons. The world was like chaos — no light, no dark, only the ancient dragons and the archtrees, immortal and eternal. But one day, beneath one of those trees, the First Flame was kindled — and with fire came disparity.</p>
<p>Light and dark, heat and cold, life and death.</p>
<p>From the dark, several humanoid beings emerged, drawn to the flame, and within it they found the Souls of Lords: Nito, the First of the Dead; the Witch of Izalith; Gwyn, the Lord of Sunlight, with his Silver Knights; and the Furtive Pygmy, so easily forgotten. Armed with the power of Lords, they challenged the dragons.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2020-01-29e7e21cea9e2777e86ed69d33d1410a_r-72cbf3a7ba302233f749b96eee217ae7.webp" width="1280" height="720" class="img_ev3q"></p>
<p>But the dragons were everlasting, protected by scales of immortality that made them nearly impossible to defeat. Yet among them was an aberration — Seath the Scaleless, a white dragon born without the immortal scales. Unlike his kin, Seath was not everlasting. He betrayed the dragons and revealed the secret of their immortality to Lord Gwyn.</p>
<p>Gwyn led his Silver Knights and hurled bolts of lightning that pierced the dragons' scales.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2020-05-image-67c219e1dee3e1154c7afe8b84c84df9.webp" width="1076" height="518" class="img_ev3q"></p>
<p>The Witch of Izalith and her daughters wove great firestorms that burned down the archtrees coiling into the sky. Nito unleashed a miasma of death and disease upon the dragons, plague wracking their flesh.</p>
<p>The dragons fell. And so began the Age of Fire.</p>
<p>The realm of the gods, Anor Londo:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-11-timg--2--e0e9a8b322c31089e7372f23097f2e5a.webp" width="1600" height="935" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="linking-the-fire-and-the-undead">Linking the Fire, and the Undead<a href="https://tanzhuo.xyz/en/2019/11/18/hei-hun-1#linking-the-fire-and-the-undead" class="hash-link" aria-label="Direct link to Linking the Fire, and the Undead" title="Direct link to Linking the Fire, and the Undead" translate="no">​</a></h2>
<p>But fire, in time, must fade — and when it does, only darkness will remain. Countless ages later, in the present day, the flame is guttering out. Light no longer reaches the world of men, and night stretches on without end. And among humans, the Undead — those branded with the Darksign — have begun to appear.</p>
<p>The Undead cannot die, but with each death, their soul and humanity slip away. Slowly they become Hollow — mindless husks that hunt souls on instinct alone. Yet as long as an Undead holds fast to their purpose, they will keep rising again from the flame.</p>
<p>The appearance of the Undead threw some kingdoms into turmoil. To deal with them, the Undead were rounded up and confined in the Northern Undead Asylum — since they could not be killed, they could only be locked away, quarantined from the world. The Asylum was their prison.</p>
<p>Legend told of a chosen Undead who would arise in the Northern Undead Asylum. This Undead would leave the Asylum and make a pilgrimage to the land of Lordran — the very land where Anor Londo, realm of the gods, stands.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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/en/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/en/assets/images/2019-11-timg--3--5ff38817aa68232926a9d6be653d0456.webp" width="1446" height="906" class="img_ev3q"></p>
<p>To prolong the First Flame, Lord Gwyn chose to burn his own soul as kindling, extending the Age of Fire in the world of souls. This act came to be known as Linking the Fire. After linking the flame, Gwyn was burned down to a mere husk — his humanity and soul all but consumed. He could no longer even wield his signature lightning.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-11-timg-2-779d5acc3a2fe4f1317924155d5b9796.jpg" width="1259" height="786" class="img_ev3q"></p>
<p>The Grand Archives of Anor Londo, where the white dragon researched the immortal crystals:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-11-20191118192243_1-99351699dec759d9bc5a042b5495418e.webp" width="1600" height="900" class="img_ev3q"></p>
<p>Seath the Scaleless:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-11-20191123132954_1-dfc9d5f4353f82e44fbe94c23aac2e48.webp" width="1600" height="900" class="img_ev3q"></p>
<p>Born without the scales of immortality, Seath became obsessed with finding a substitute — the immortal crystal. He succeeded in creating it, but was consumed by his own research: blinded in both eyes, his lower body fused into crystal.</p>
<p>The Kiln of the First Flame:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-11-20191123171440_1-c5d10f28aec4b3e67129843ae8dee376.webp" width="1600" height="900" class="img_ev3q"></p>
<p>The Witch of Izalith never considered linking the fire. Instead, she sought to recreate the First Flame herself and extend the Age of Fire on her own terms. But she forgot to fear the flame. Her reckless attempt spiraled out of control and birthed the Flame of Chaos. She, her children, her subjects — her entire kingdom — became a breeding ground for the creatures of chaos, transformed into the demon city of Lost Izalith.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-11-20191123163221_1-01caa2a2108e13e383eb8b721b0e241d.webp" width="1600" height="900" class="img_ev3q"></p>]]></content:encoded>
            <category>Games</category>
        </item>
        <item>
            <title><![CDATA[Migrating to Alibaba Cloud]]></title>
            <link>https://tanzhuo.xyz/en/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi</link>
            <guid>https://tanzhuo.xyz/en/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[Taking advantage of the Singles' Day sale to move my blog from a Hong Kong server to Alibaba Cloud: a full Docker migration, domain ICP filing, and automated Let's Encrypt renewal with certbot—the process and the pitfalls.]]></description>
            <content:encoded><![CDATA[<p>I used to run everything on a Hong Kong server—4 cores, 4G RAM, 90G disk, 5M bandwidth—at 150 yuan a month, which felt a bit steep. With the Singles' Day sale on at Alibaba Cloud, I moved the whole blog to a mainland server and automated SSL certificate renewal while I was at it.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>My blog used to run on a Hong Kong server with 4 cores, 4G RAM, a 90G disk, and 5M bandwidth, for 150 yuan a month. For a personal blog that's hardly a good deal—traffic is low, the 4 cores sit mostly idle, and the money was really buying the convenience of "no ICP filing required."</p>
<p>The Singles' Day sale came at just the right time: I picked up a mainland cloud server with 2 cores, 8G RAM, a 40G disk, and 5M bandwidth for 1,399 yuan over 3 years—about 38 yuan a month. Fewer cores, but double the memory, which suits my "blog plus a handful of homegrown services" usage much better. 5M bandwidth is plenty for static pages and small applications; plenty of small business sites run on no more than that.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-migration">The Migration<a href="https://tanzhuo.xyz/en/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#the-migration" class="hash-link" aria-label="Direct link to The Migration" title="Direct link to The Migration" translate="no">​</a></h2>
<p>The blog was already deployed with Docker, which made this migration much easier. One direct benefit of containerized deployment: no environment to reinstall—move the images and the data over, start the containers, done.</p>
<p>The overall plan had three steps:</p>
<ol>
<li class="">
<p>Install Docker on the new server and pull (or import) the original images;</p>
</li>
<li class="">
<p>Archive the data volume directories on the old server, transfer them to the matching paths on the new machine, and keep the mount points identical;</p>
</li>
<li class="">
<p>Once the containers are confirmed running on the new machine, switch the DNS records to the new IP.</p>
</li>
</ol>
<p>Moving the data volumes can be done crudely but effectively with tar and 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%)"># Old server: archive the data volume directory</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># Transfer to the new 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 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>As long as the directory structure and mount configuration stay the same, the containers come up on the new machine exactly as they were. This is why I insist on Docker for deploying my personal services: migration cost is close to zero.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="domain-icp-filing">Domain ICP Filing<a href="https://tanzhuo.xyz/en/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#domain-icp-filing" class="hash-link" aria-label="Direct link to Domain ICP Filing" title="Direct link to Domain ICP Filing" translate="no">​</a></h2>
<p>Here lies the biggest difference between mainland and Hong Kong servers: pointing a domain at a mainland IP and serving web traffic requires completing ICP filing first—otherwise requests on ports 80/443 get blocked.</p>
<p>Filing is submitted through the Alibaba Cloud console: fill in the entity and website details, upload your documents, and wait for the regulator's review. Mine took about 6 days. During the review the domain can't point at the new server, but the server itself is fully usable—you can finish setting up and debugging the environment over IP or SSH, then flip the DNS as soon as the filing clears, with almost no downtime in between.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="automated-ssl-renewal">Automated SSL Renewal<a href="https://tanzhuo.xyz/en/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#automated-ssl-renewal" class="hash-link" aria-label="Direct link to Automated SSL Renewal" title="Direct link to Automated SSL Renewal" translate="no">​</a></h2>
<p>This migration was also a chance to sort out SSL certificates once and for all. I'm still using free Let's Encrypt certificates, which are valid for only 90 days each—that's deliberate on their part, to push you toward automated renewal instead of swapping certificates by hand.</p>
<p>I use the Docker version of certbot for this. The certbot container handles issuance and renewal, with the certificate and challenge directories mounted out as data volumes; nginx mounts the same certificate files. The renewal command looks roughly like this:</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%)"># Check and renew any certificates nearing expiry</span><span class="token plain"></span><br></div><div class="token-line" 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>The <code>renew</code> command only renews certificates that are close to expiring, so running it daily from crontab has no side effects—just have nginx reload its configuration after a successful renewal. With this in place, SSL is effectively "free forever" and can be left alone.</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="whats-next">What's Next<a href="https://tanzhuo.xyz/en/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#whats-next" class="hash-link" aria-label="Direct link to What's Next" title="Direct link to What's Next" translate="no">​</a></h2>
<p>With 8G of RAM, running only a blog on this machine would be a waste. I'll gradually deploy more of my own services onto it to provide backends for my demo apps. And with HTTPS and ICP filing both squared away—WeChat Mini Program backends happen to require a filed domain served over HTTPS—both prerequisites are met, so building some personal mini programs is now on the table.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<ol>
<li class="">
<p>Deploying Docker on the Hong Kong server produced all sorts of strange behavior. The most memorable: the host didn't support Docker's newer storage driver format, and I had to reformat the disk and change the filesystem's storage format to get it working. Docker's storage drivers have requirements on kernel versions and filesystem parameters, and old or unusual host environments trip over this easily. None of these problems ever appeared on Alibaba Cloud—smooth sailing throughout.</p>
</li>
<li class="">
<p>The domain can't point at the new server while the filing is under review, so finish all the migration and debugging work in advance to compress the cutover window to a minimum.</p>
</li>
<li class="">
<p>Alibaba Cloud isn't perfect either—the network sometimes feels noticeably jittery. It doesn't matter for a personal blog, but keep an eye on it for latency-sensitive services.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2019/11/10/bo-ke-qian-yi-dao-a-li-yun-fu-wu-qi#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Most of the time this migration took was spent waiting on the ICP filing; the technical part cost almost nothing thanks to Docker. With certificate renewal automated on top, this server has basically entered "set it and forget it" mode. For an individual developer, a cheap mainland server plus a filed domain actually opens up more possibilities, not fewer.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Building Some Little Demos]]></title>
            <link>https://tanzhuo.xyz/en/2019/10/24/hao-jiu-mei-xie-bo-ke-liao</link>
            <guid>https://tanzhuo.xyz/en/2019/10/24/hao-jiu-mei-xie-bo-ke-liao</guid>
            <pubDate>Thu, 24 Oct 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Picked up another server from a Tencent Cloud sale: 3 years for 600 yuan, 1 core, 2G RAM, 2M bandwidth, 40G disk. Still felt a bit pricey for such a low spec, but I bought it anyway. Then it just sat there doing nothing, so I started building little demos for fun. The first one is done!]]></description>
            <content:encoded><![CDATA[<p>Why haven't I written anything in so long? It's not that I'm lazy—I forgot my blog admin password. Forgetting the password to your own blog is quite an achievement.</p>
<!-- -->
<p>So I had to SSH into the server and reset it.</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>Good thing I hadn't forgotten the server login password too. Though even then it wouldn't matter—I could just reset it through the cloud provider. And if I forgot the provider account password as well, no problem, SMS recovery on my phone. Looking at it that way, I really don't need to worry about passwords at all.</p>
<p>Recently I spotted another Tencent Cloud promotion and bought a server: 3 years for 600 yuan, 1 core, 2G RAM, 2M bandwidth, 40G disk. Still felt a bit pricey for such a low spec, but I bought it anyway. And then it just sat there doing nothing, so I decided to build a few little demos for fun. The first one is done!</p>
<p>All the demos are open-sourced on Gitee:</p>
<p>Multi-user web chat demo: <a href="http://118.25.113.204/gga_parent/" target="_blank" rel="noopener noreferrer" class="">http://118.25.113.204/gga_parent/</a> (the server has long since been taken offline)</p>
<p>Development time: about 4 hours</p>
<p>More fun demos to come whenever new ideas strike.</p>]]></content:encoded>
            <category>Life</category>
        </item>
        <item>
            <title><![CDATA[First Impressions of Flutter]]></title>
            <link>https://tanzhuo.xyz/en/2019/09/18/flutter</link>
            <guid>https://tanzhuo.xyz/en/2019/09/18/flutter</guid>
            <pubDate>Wed, 18 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Notes from my first encounter with Flutter: the Skia rendering engine, the widget model, the Dart language and hot reload, plus cross-platform delivery from a single codebase.]]></description>
            <content:encoded><![CDATA[<p>Flutter is an open-source mobile app development framework built and maintained by Google. Written in Dart, it ships with a high-performance rendering engine, a rich widget library, and a fast development cycle, making it a solid choice for building high-quality, cross-platform mobile applications.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-cross-platform-matters">Why Cross-Platform Matters<a href="https://tanzhuo.xyz/en/2019/09/18/flutter#why-cross-platform-matters" class="hash-link" aria-label="Direct link to Why Cross-Platform Matters" title="Direct link to Why Cross-Platform Matters" translate="no">​</a></h2>
<p>Mobile development comes with an unavoidable reality: covering both Android and iOS for the same product usually means two codebases, two tech stacks, and double the maintenance cost. The earlier generation of hybrid solutions either wrapped everything in a WebView, with obvious performance and UX penalties, or—like React Native—mapped JS components onto native platform controls, which never quite achieved true cross-platform consistency.</p>
<p>Flutter takes a different road: it doesn't rely on native platform controls at all and draws the entire UI itself. That makes it far more thorough about "one codebase, identical behavior everywhere" than its predecessors—and it's why I decided to spend time learning it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-rendering-engine">The Rendering Engine<a href="https://tanzhuo.xyz/en/2019/09/18/flutter#the-rendering-engine" class="hash-link" aria-label="Direct link to The Rendering Engine" title="Direct link to The Rendering Engine" translate="no">​</a></h2>
<p>Flutter's rendering engine is built on the Skia graphics library, delivering high-quality, high-performance drawing and animation. Its widget library covers a wide range of UI components, so you can build apps in all kinds of styles with ease, and custom widgets are fully supported. Flutter also provides a rich set of APIs and tooling for a fast development cycle and efficient debugging.</p>
<p>The self-rendering part deserves some elaboration: Flutter doesn't translate your interface into each platform's native controls. Instead, it paints every pixel directly onto a canvas via Skia. The platform layer only needs to supply a Surface to draw on and forward input events; layout, painting, and animation are all handled by the Flutter engine itself. The payoff is that the same UI code renders almost identically across operating systems and OS versions—no surprises caused by a platform control behaving differently on one system.</p>
<p><a href="https://flutter.cn/" target="_blank" rel="noopener noreferrer" class="">Flutter: Build apps for any screen</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cross-platform-reach">Cross-Platform Reach<a href="https://tanzhuo.xyz/en/2019/09/18/flutter#cross-platform-reach" class="hash-link" aria-label="Direct link to Cross-Platform Reach" title="Direct link to Cross-Platform Reach" translate="no">​</a></h2>
<p>Cross-platform support is one of Flutter's biggest strengths: you can build for Android and iOS at the same time, and the resulting apps deliver the same user experience on both. Flutter also supports web and desktop targets, covering a variety of scenarios.</p>
<p>In other words, a single codebase can in theory cover Android, iOS, the web, and desktop platforms like Windows and macOS. Of course, maturity varies across targets—mobile is where Flutter was polished first—but for utility and content-display apps, the cost savings from multi-platform reuse are very real.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-dart-language">The Dart Language<a href="https://tanzhuo.xyz/en/2019/09/18/flutter#the-dart-language" class="hash-link" aria-label="Direct link to The Dart Language" title="Direct link to The Dart Language" translate="no">​</a></h2>
<p>Flutter uses Dart as its development language. Dart is an object-oriented, statically typed language with strong type inference and first-class async support, which boosts both productivity and code quality. Flutter's Dart compiler compiles Dart to native code, achieving high performance and fast rendering across platforms.</p>
<p>Dart is not just a "convenient default" for Flutter. It supports both JIT and AOT compilation: during development, JIT lets code changes take effect instantly, which is what powers hot reload; for release builds, AOT compiles down to machine code, so there's no interpreter at runtime and performance is close to native. A single language that satisfies both development speed and runtime performance is rare among cross-platform frameworks.</p>
<p>Anyone comfortable with Java or JavaScript will pick up Dart almost immediately—its syntax sits somewhere between the two, and the async/await style of asynchronous code is very intuitive.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-widget-model">The Widget Model<a href="https://tanzhuo.xyz/en/2019/09/18/flutter#the-widget-model" class="hash-link" aria-label="Direct link to The Widget Model" title="Direct link to The Widget Model" translate="no">​</a></h2>
<p>Flutter's UI framework is built around a component model called "widgets." Widgets are the most basic building blocks in Flutter, encapsulating both UI elements and logic. Everything in a Flutter UI is a widget—text, images, buttons, and more. Widgets form a hierarchy: they can be nested and composed, giving you the freedom to build arbitrarily complex interfaces.</p>
<p>"Everything is a widget" is Flutter's core design philosophy. Not only are buttons and text widgets—so are behaviors like padding (Padding), centering (Center), and gesture handling (GestureDetector). Complex interfaces aren't built by inheriting from some giant base class; they emerge from composing small widgets layer by layer. Composition over inheritance is on full display here.</p>
<p>Widgets split into two kinds based on whether they hold mutable state: a StatelessWidget describes a piece of UI that never changes, while a StatefulWidget pairs with a State object to manage the parts that do—when the state changes, the framework automatically rebuilds the corresponding widget subtree. Once that clicks, you've essentially grasped Flutter's declarative UI model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hot-reload">Hot Reload<a href="https://tanzhuo.xyz/en/2019/09/18/flutter#hot-reload" class="hash-link" aria-label="Direct link to Hot Reload" title="Direct link to Hot Reload" translate="no">​</a></h2>
<p>Flutter's hot reload lets you preview UI changes in real time during development, with no need to recompile and redeploy the app—a huge boost to iteration speed. Flutter also ships with a rich set of debugging and visualization tools to help you track down problems quickly.</p>
<p>Under the hood, hot reload injects your modified source code incrementally into the running Dart VM and then rebuilds the widget tree—preserving the app's current state. That means when you're tweaking the styling of some deeply nested page, you don't have to click your way back in from the home screen every time; save the file and the UI refreshes in place. Compared to native development, the UI-tuning experience is a qualitative leap.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2019/09/18/flutter#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<p>A few things worth knowing before you start:</p>
<ol>
<li class="">
<p>Behind restrictive networks in China, SDK downloads and pub dependency fetches can be painfully slow or fail outright—you'll need to configure a local mirror (set the <code>PUB_HOSTED_URL</code> and <code>FLUTTER_STORAGE_BASE_URL</code> environment variables).</p>
</li>
<li class="">
<p>Deeply nested widget code becomes hard to read fast. Get in the habit of splitting out small widgets early rather than piling hundreds of lines into a single build method.</p>
</li>
<li class="">
<p>For platform-native capabilities (Bluetooth, push notifications, etc.), you'll need Platform Channels to communicate with native code—which still requires some Android/iOS knowledge.</p>
</li>
<li class="">
<p>Hot reload isn't magic: changes touching global state initialization, the main function, or enums require a hot restart or even a full rebuild.</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>tip</div><div class="admonitionContent_BuS1"><p>The first time you run <code>flutter doctor</code>, fix every missing item it flags. It will save you from a pile of mysterious build errors down the road.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2019/09/18/flutter#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Flutter genuinely impressed me. The widgets are excellent, and paired with Dart the learning curve is very gentle. After a few quick demos I felt ready for real app development—one codebase running on Android, iOS, Windows desktop, and Mac. Self-rendering guarantees consistency across platforms, widget composition reads clearly, and hot reload takes the pain out of UI tweaking. For anyone curious about cross-platform development, Flutter is an option worth investing in seriously.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Frontend</category>
        </item>
        <item>
            <title><![CDATA[Learning Frontend Development]]></title>
            <link>https://tanzhuo.xyz/en/2019/09/18/xue-xi-liao-vue</link>
            <guid>https://tanzhuo.xyz/en/2019/09/18/xue-xi-liao-vue</guid>
            <pubDate>Wed, 18 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Notes from learning Vue.js: two-way data binding, componentization, Virtual DOM, and lifecycle hooks—plus some thoughts on what TypeScript brings to frontend development.]]></description>
            <content:encoded><![CDATA[<p>Vue.js is a progressive JavaScript framework for building user interfaces. Its core library focuses solely on the view layer, making it easy to integrate with other libraries or existing projects. Vue.js follows the MVVM pattern, keeping the view and the data in sync through two-way data binding, so developers can manage data and views far more conveniently.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-learn-a-frontend-framework">Why Learn a Frontend Framework<a href="https://tanzhuo.xyz/en/2019/09/18/xue-xi-liao-vue#why-learn-a-frontend-framework" class="hash-link" aria-label="Direct link to Why Learn a Frontend Framework" title="Direct link to Why Learn a Frontend Framework" translate="no">​</a></h2>
<p>The reason for these notes is simple: as a mostly backend developer, there's no escaping the fact that sooner or later I'll have to build pages myself. In the traditional jQuery style, data is scattered all over the DOM; changing one piece of state means manually locating the corresponding node and updating it, and once the page gets complex the code turns into unmaintainable spaghetti. Vue.js solves exactly this problem—it hands the synchronization of data and view over to the framework, leaving the developer to care only about the data itself. On top of that, its "progressive" positioning means you don't have to rewrite an entire project: you can try it out in a single page or a single module first, which is very friendly to teams with existing codebases.</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="what-makes-vuejs-stand-out">What Makes Vue.js Stand Out<a href="https://tanzhuo.xyz/en/2019/09/18/xue-xi-liao-vue#what-makes-vuejs-stand-out" class="hash-link" aria-label="Direct link to What Makes Vue.js Stand Out" title="Direct link to What Makes Vue.js Stand Out" translate="no">​</a></h2>
<p>Key characteristics of Vue.js:</p>
<ol>
<li class="">
<p>Two-way data binding: Vue.js keeps data and views automatically in sync through two-way data binding, making both much easier to manage. The mechanism behind it is data interception plus publish-subscribe: the framework intercepts reads and writes on data properties, collecting dependencies on read and notifying the corresponding views on write. You mutate a plain JavaScript object, and the page follows along—no more manual DOM manipulation.</p>
</li>
<li class="">
<p>Component-based development: Vue.js supports breaking a page into independent components, each with its own data and view, easy to reuse and maintain. Components pass data down through props and notify upward through events, with clear boundaries. This mirrors how backend code is split into modules and classes by responsibility, so it's not hard for backend folks to grasp.</p>
</li>
<li class="">
<p>Lightweight and approachable: The Vue.js API is simple and clear, the learning curve is gentle, and it plays well alongside other frameworks and libraries (like React and Angular). The official documentation is available in Chinese with plenty of examples, and you can start writing with a single script tag—no build tooling required, which matters a lot to beginners.</p>
</li>
<li class="">
<p>Template-oriented programming: Vue.js separates HTML from JavaScript, keeping the code clean and readable. Templates are essentially still HTML, just with interpolation and directives added—what you write for the page structure is what you get, while the logic stays contained in the component's JavaScript.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vuejs-under-the-hood">Vue.js Under the Hood<a href="https://tanzhuo.xyz/en/2019/09/18/xue-xi-liao-vue#vuejs-under-the-hood" class="hash-link" aria-label="Direct link to Vue.js Under the Hood" title="Direct link to Vue.js Under the Hood" translate="no">​</a></h2>
<p>Technical details worth knowing:</p>
<ol>
<li class="">
<p>Virtual DOM: Vue.js uses a Virtual DOM to make DOM operations efficient, improving performance and user experience. The idea is to describe the DOM tree as JavaScript objects in memory first; when data changes, a new virtual tree is generated and diffed against the old one, and only the parts that actually changed are applied to the real DOM. Touching the real DOM directly is expensive—this intermediate layer keeps the "what changed" computation in memory, where it's far cheaper.</p>
</li>
<li class="">
<p>Lifecycle: Vue.js components have their own lifecycle—creation, mounting, updating, destruction—and you can hook different logic into each stage. API requests typically go after creation completes; logic that needs the real DOM must wait until mounting is done; and resources like timers and event listeners should be cleaned up in the destruction phase. Understanding the lifecycle is the key to understanding when your code actually runs.</p>
</li>
<li class="">
<p>Directives and events: Vue.js provides directives and event handling such as v-if, v-for, and v-on, making it easy to manipulate DOM elements and respond to user actions. v-if controls whether an element renders, v-for handles list iteration, and v-on binds event callbacks. Directives declare common DOM operations right in the template, so reading the template tells you how the page behaves—far more transparent than sprinkling addEventListener calls throughout your JavaScript.</p>
</li>
<li class="">
<p>Plugins and component libraries: The Vue.js ecosystem is rich, with a wealth of plugins and component libraries for building high-quality applications quickly. Routing and state management have official solutions, and mature UI component libraries exist too—most admin-style pages can be assembled from parts rather than built wheel by wheel.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-typescript-changes">What TypeScript Changes<a href="https://tanzhuo.xyz/en/2019/09/18/xue-xi-liao-vue#what-typescript-changes" class="hash-link" aria-label="Direct link to What TypeScript Changes" title="Direct link to What TypeScript Changes" translate="no">​</a></h2>
<p>Frontend technology has evolved enormously, and with TypeScript in the mix I personally think the outlook is excellent. JavaScript has always been weakly typed; with TypeScript it gains fixed syntax, data types, and the concept of classes, making frontend logic far clearer. Combined with Vue's componentization, frontend development can now roughly follow an MVC-style structure. You can write components and complex business-logic layers with TypeScript and Vue together—it's genuinely much cleaner than how things used to be written.</p>
<p>For someone used to strongly typed languages, the significance is this: the shape of API data can be described with type definitions, the editor provides completion and checking, and many trivial mistakes surface at compile time instead of as undefined errors in the browser. The larger the frontend project, the more that layer of constraint pays off.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-caveats">Pitfalls and Caveats<a href="https://tanzhuo.xyz/en/2019/09/18/xue-xi-liao-vue#pitfalls-and-caveats" class="hash-link" aria-label="Direct link to Pitfalls and Caveats" title="Direct link to Pitfalls and Caveats" translate="no">​</a></h2>
<p>A few reminders from the learning process:</p>
<ol>
<li class="">
<p>Two-way binding isn't omnipotent. Data interception has its limits—certain operations on objects and arrays go unnoticed by the framework. When you hit "I changed the data but the page didn't update," check the reactivity caveats first rather than assuming the framework is broken.</p>
</li>
<li class="">
<p>Don't manipulate the DOM directly inside components. Since you've handed view management to the framework, drive the page through data. Mixing in manual DOM operations fights the framework's update mechanism and produces bugs that are very hard to trace.</p>
</li>
<li class="">
<p>Split components in moderation. Split too finely and props drilling through layers becomes painful; don't split and a single component bloats. "Independently reusable, single responsibility" is a good rule of thumb for granularity.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2019/09/18/xue-xi-liao-vue#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Vue.js frees frontend development from manual DOM manipulation through two-way data binding and componentization, while the Virtual DOM and the lifecycle are the two keys to understanding how it runs. For a backend developer, once TypeScript is in the picture, frontend code gains a sense of order—types and layers—and the learning cost turns out lower than expected. These notes capture the conceptual layer; the pitfalls I hit in real projects will get written up separately.</p>]]></content:encoded>
            <category>Tech</category>
            <category>Frontend</category>
        </item>
        <item>
            <title><![CDATA[A Field Guide to Java Web Frameworks and Tooling]]></title>
            <link>https://tanzhuo.xyz/en/2019/07/22/zi-ji-da-jian-javawebxiang-mu-kuang-jia</link>
            <guid>https://tanzhuo.xyz/en/2019/07/22/zi-ji-da-jian-javawebxiang-mu-kuang-jia</guid>
            <pubDate>Mon, 22 Jul 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[A catalog of the Java Web stack I've touched at work and in my own studies: from the Spring family, ORMs, and message queues to container orchestration, distributed tracing, and monitoring — each with a one-line summary of what it does, kept as a technology-selection reference.]]></description>
            <content:encoded><![CDATA[<p>The following are technologies I've learned about and tried out at work and in my own studies, recorded here for reference.</p>
<!-- -->
<p>The reason for writing this list is simple: the Java Web ecosystem is huge. From a monolith at the start, through microservices, containerization, monitoring and alerting, every layer has multiple viable options. Encountering them piecemeal, after a while I could only remember the names, not what each one was for. So I'm gathering them in one place, with each entry stating clearly "what it is and what problem it solves", so that next time I'm making a technology decision I can just flip to this page instead of searching from scratch. The list roughly follows the order "development frameworks → data layer → microservice governance → infrastructure → big data and tooling".</p>
<p>SpringBoot, Spring-cloud, Spring-cloud-alibaba — the whole Spring family. SpringBoot uses auto-configuration and starter dependencies to shrink configuration to a minimum, and is the default starting point for Java projects today; Spring Cloud layers microservice capabilities on top — service registration, configuration, gateways; Spring Cloud Alibaba plugs domestic-ecosystem components like Nacos, Sentinel, and Seata into the same set of abstractions.</p>
<p>MyBatis-Plus (optimistic locking, automatic pagination, code generator for service, mapper, entity, controller, custom generation templates). It fills in generic CRUD on top of MyBatis, so single-table operations rarely need hand-written SQL; the code generator with custom templates lets you scaffold full CRUD for a new table in minutes.</p>
<p>JOOQ: a Java ORM framework. Its philosophy is the opposite of MyBatis — build SQL with a type-safe Java DSL, so misspelled columns are caught at compile time. A good fit when SQL logic is complex and you want strong typing guarantees.</p>
<p>Uid-generator: Baidu's UID generator (distributed snowflake algorithm, globally unique Long-typed UIDs). Once you shard databases and tables, auto-increment primary keys stop working. The snowflake algorithm assembles a roughly monotonic Long ID from "timestamp + machine bits + sequence number" — globally unique and index-friendly.</p>
<p>Xxl-job: a distributed task scheduling center. It solves the two classic problems of single-machine cron jobs: tasks vanish when the machine dies, and multi-instance deployments run them twice. It separates the scheduler from the executors, with a built-in admin UI, failure retries, and sharded execution.</p>
<p>Apache-Shiro, Spring-Security: login/security frameworks. Both cover authentication and authorization. Shiro is lightweight and quick to pick up; Spring Security is more feature-complete and more deeply integrated with the Spring ecosystem, but its configuration curve is steeper.</p>
<p>Druid (Alibaba), HikariCP: common database connection pools. The point of a pool is reusing database connections and avoiding constant handshakes. Druid ships with SQL monitoring and anti-injection statistics pages; HikariCP is known for raw performance and is SpringBoot's default choice.</p>
<p>Bcrypt: a password hashing scheme for user passwords in the database. It's a slow hash, and salting produces a different result every time — even if the database is exfiltrated, rainbow-table attacks at scale are impractical. Far more trustworthy than storing raw MD5/SHA hashes.</p>
<p>JWT: a spec/strategy for generating temporary tokens. User information is signed and embedded in the token itself, so the server keeps no session state — a natural fit for horizontal scaling across instances. The cost: once issued, a token can't be actively revoked; you fall back on short lifetimes or a blacklist.</p>
<p>Undertow, Tomcat, Jboss, Weblogic: Servlet containers. Tomcat is the most universal; Undertow is NIO-based with a small memory footprint, often swapped in as SpringBoot's embedded container; JBoss and Weblogic are heavier commercial application servers.</p>
<p>Eolinker, Swagger, Knife4j, Yapi: online API documentation and automated testing tools. Their core value is generating docs automatically from code annotations so they stay in sync with the code — avoiding the classic frontend/backend finger-pointing over "the docs don't match the API". Knife4j is a Chinese-made enhanced skin for Swagger.</p>
<p>MySQL 8.0, PostgreSQL: relational databases (RDS). The primary store for business data. MySQL has the richest ecosystem and documentation; PostgreSQL is more feature-complete and performs better in scenarios like JSON and GIS.</p>
<p>Sqlite: an embedded database. The entire database is a single file with no separate process needed — good for client-side local storage and small tools.</p>
<p>Redis, MongoDB: non-relational (NoSQL) databases. Redis is in-memory, commonly used for caching, distributed locks, and counters; MongoDB is a document database with a flexible schema, suited to data whose structure keeps changing.</p>
<p>Dobbo, Feign: remote method invocation. Both make cross-service calls read like local method calls. Dubbo uses a custom protocol over long-lived connections and performs well; Feign is HTTP-based with declarative interfaces, and pairs more naturally with the Spring Cloud stack.</p>
<p>Sentinel, Hystrix: rate limiting and degradation. These are a microservice's self-preservation tools: when upstream traffic exceeds capacity, rate-limit first; when a downstream dependency dies, fail fast and fall back to degraded logic, so threads don't get dragged down and the failure doesn't cascade along the call chain — the so-called avalanche. Hystrix has stopped new feature development; new projects generally pick Sentinel.</p>
<p>Skywalking: distributed tracing (developed at Huawei, donated to Apache). Through Java Agent bytecode instrumentation, with zero intrusion into business code, it can draw the complete call chain of a request across multiple services — extremely intuitive when hunting down "which hop is slow".</p>
<p>Zookeeper, Eureka, Consul, Nacos, Etcd: registry and configuration centers. A service registry solves "service instance addresses keep changing — how do callers find them"; a config center solves "changing config requires a restart". Nacos combines both and is the default choice in the Spring Cloud Alibaba stack; Etcd is the underlying store for Kubernetes.</p>
<p>Seata: Alibaba's distributed transaction solution. When one business operation spans multiple databases or services, local transactions can't guarantee overall consistency. Seata's AT mode records rollback logs automatically through a proxied data source, with minimal intrusion into business code.</p>
<p>ELK: distributed log collection, data aggregation, distributed search engine. That is, the Elasticsearch + Logstash + Kibana combo: collect logs from every node into central storage, then query them with full-text search and a visualization UI. Once you have more than a few services, troubleshooting without centralized logs is basically impossible.</p>
<p>Micrometer, Prometheus, Grafana: service health monitoring, data monitoring, IO monitoring, and so on. Micrometer exposes metrics on the application side, Prometheus scrapes them on a schedule and stores them as time series, and Grafana handles charts and alerting dashboards — currently the most common monitoring trio.</p>
<p>Kafka, RabbitMQ, RocketMQ: common message queues — peak shaving, decoupling, async processing. Producers just publish and consumers process at their own pace, with traffic spikes landing in the queue first to be digested gradually. Kafka has the highest throughput and leans toward log and stream-processing scenarios; RabbitMQ has flexible routing; RocketMQ is more complete on transactional and delayed messages.</p>
<p>Nginx, apache-http-service, Traefik: HTTP servers. Beyond static asset hosting, they more often serve as reverse proxies and layer-7 load balancers; Traefik can auto-discover containers and update routing rules, making it a better fit for container environments.</p>
<p>Ali-OSS, HUAWEI-OBS: third-party static asset storage. Putting images and attachments in object storage instead of the application server's disk is both lower-maintenance and easier to pair with CDN acceleration.</p>
<p>Ali-NAS: intranet shared file storage mounts — a shared disk on the internal network. When multiple machines need to read and write the same files, mounting NAS beats running your own NFS by a fair margin of ops work.</p>
<p>Ali-SLB: layer-4/layer-7 load balancer. The first layer at the traffic entrance: layer 4 forwards TCP, layer 7 can route by domain and path; Nginx and the application usually come after it.</p>
<p>Findbugs, Sonarqube: static code analysis. They can flag null-pointer risks, unclosed resources, and similar issues without running the code; SonarQube also hooks into CI, using quality gates to block substandard commits.</p>
<p>PDMan: a database visualization tool built by Chinese developers. Used to draw ER diagrams, version table schemas, and export DDL — far more reliable than verbal agreements about table structure when collaborating in a team.</p>
<p>Docker, Containerd, CRI-O: common container runtimes. Containers package an application and its dependencies into one image, largely eliminating "works on my machine". Containerd and CRI-O are lower-level runtimes that Kubernetes talks to directly via the CRI interface.</p>
<p>Docker-Compose: a container orchestration tool. One YAML file describes multiple containers' images, ports, and dependencies; one command brings up the whole local environment — very handy for development and integration testing.</p>
<p>Kubernetes: container orchestration, operations, resource management, resource scheduling — one-stop management. Its core idea is declarative: you describe the desired state, and it handles scheduling, scaling, and self-healing, continuously converging actual state toward desired state. It's the de facto standard for production container orchestration.</p>
<p>Istio: a popular service mesh framework, enabling polyglot backend development with traffic control, degradation, and circuit breaking. A service mesh pushes governance logic — rate limiting, circuit breaking, canary releases — out of business code and down into sidecar proxies, so services written in different languages share the same governance capabilities.</p>
<p>Helm: application/resource management middleware for K8S. Effectively Kubernetes's package manager: it templates a pile of YAML into a Chart, and one command handles install, upgrade, and rollback.</p>
<p>Ali-EsayExcel: Alibaba's open-source high-efficiency Excel jar. Built to address POI's tendency to run out of memory on large files, with streaming read/write optimizations — importing and exporting hundreds of thousands of rows stays stable.</p>
<p>Netty: the go-to Java NIO networking library. It wraps NIO's fiddly Selector and Buffer details into an event-driven Pipeline model, and serves as the network-layer foundation of many RPC frameworks and middleware.</p>
<p>Disruptor: a high-performance in-memory queue (developed by LMAX, a UK forex trading firm). It preallocates memory with a ring buffer and avoids lock contention and false sharing, pushing single-machine inter-thread message latency extremely low.</p>
<p>Caffeine: a high-performance in-process JVM cache, built on Disruptor underneath. As an in-process cache, access never touches the network; it's often paired with Redis in a two-tier setup — local cache absorbs hotspots, Redis guarantees shared state.</p>
<p>Jenkins: CI/CD middleware — automated releases and pipeline production. It chains pulling code, compiling, testing, building images, and deploying into a pipeline that runs automatically on every commit, shrinking the room for error in manual releases.</p>
<p>Apahce-Hdoop: distributed big-data storage and processing. Hadoop's HDFS splits large files into blocks stored redundantly across machines, while MapReduce computes near where the data lives — the old bedrock of the big-data stack.</p>
<p>Apahce-Flink: a stream-processing framework, suited to near-real-time computation in big data. It pushes computation onto the data stream itself — events are processed as they arrive — with state management and exactly-once semantics. Commonly used for real-time reporting and real-time risk control.</p>
<p>MyCat2, Apache-Shardingsphere: sharding middleware. When a single table grows too large for queries to keep up, you shard horizontally; the middleware parses SQL, routes it to the right shard, and merges results, keeping the application as oblivious as possible.</p>
<p>TiDB, KunlunBase: NewSQL distributed relational databases. A different angle on the same problem: make the database itself distributed, stay MySQL-protocol compatible, and scale out without the application ever touching sharding rules.</p>
<p>Arthas: Alibaba's open-source JVM diagnostic tool — flame graph generation, deadlock diagnosis, and more. Without restarting or changing code, you can attach to a live JVM to inspect method timings, decompile classes, and trace calls — a lifesaver for hard production issues.</p>
<p>Kettle, DataX, Canal: open-source ETL tools for data migration and cleansing. The first two do batch extract-and-transform; Canal is the odd one out — it masquerades as a MySQL replica and parses the binlog to capture real-time incremental changes, commonly used for cache synchronization and heterogeneous data sync.</p>
<p>Jmeter: the standard tool for API testing and load testing. Run a load test before going live to learn an API's throughput ceiling and response-time distribution — so your rate-limit thresholds are grounded in data instead of guesswork.</p>
<p>Ansible: a multi-server operations tool. SSH-based with no agent needed on target machines; describe the desired state in YAML and execute in bulk — changing one config across dozens of machines without logging into each one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pitfalls-and-notes">Pitfalls and Notes<a href="https://tanzhuo.xyz/en/2019/07/22/zi-ji-da-jian-javawebxiang-mu-kuang-jia#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and Notes" title="Direct link to Pitfalls and Notes" translate="no">​</a></h2>
<ul>
<li class="">A list is not a selection. For components at the same layer (say, Sentinel vs Hystrix, Eureka vs Nacos), you only need one. Piling everything from this list into a project just increases maintenance cost.</li>
<li class="">Prefer following an ecosystem. If you're on Spring Cloud Alibaba, then registry, rate limiting, and distributed transactions naturally go to Nacos, Sentinel, and Seata; mix components from different ecosystems and you own the compatibility problems yourself.</li>
<li class="">Watch a component's maintenance status. The ecosystem turns over quickly — for projects in maintenance mode like Hystrix, new systems should steer clear and pick alternatives with actively iterating communities.</li>
<li class="">Every distributed component carries operational cost. Kafka, Elasticsearch, and Kubernetes each deserve a dedicated person to study them; small teams should figure out who will maintain a component before adopting it.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2019/07/22/zi-ji-da-jian-javawebxiang-mu-kuang-jia#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>This list covers the rough landscape of a Java Web project, from development frameworks and data storage through microservice governance to containerization and big data. The point of recording it isn't to use everything, but to know — when a concrete problem shows up — what off-the-shelf options exist and where each one fits. The ecosystem keeps evolving, and this page will keep being amended and corrected along with real-world use.</p>]]></content:encoded>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Dark Souls III]]></title>
            <link>https://tanzhuo.xyz/en/2019/06/17/hei-hun-3</link>
            <guid>https://tanzhuo.xyz/en/2019/06/17/hei-hun-3</guid>
            <pubDate>Mon, 17 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Dark Souls III takes place in the kingdom of Lothric. The player must avert the apocalypse born of the conflict between the Age of Fire and the previous bearers of the Darksign, by confronting the Lords of Cinder — the heroes who once linked the fire. After countless heroes have linked the flame, today the fire has grown extremely faint.]]></description>
            <content:encoded><![CDATA[<p>Dark Souls III takes place in the kingdom of Lothric. The player must avert the apocalypse born of the conflict between the Age of Fire and the previous bearers of the Darksign. To do so, the player has to confront the Lords of Cinder — the heroes who once linked the fire.</p>
<!-- -->
<p>After countless heroes have linked the flame, the fire today has grown extremely faint. The appointed Lord of Cinder knows all too well that linking the fire is a road of no return, and refuses to do it. With no suitable candidate to be found, the only option left is to rouse the former Lords of Cinder from their graves and send them to fulfill the duty once more. But three of these Lords also refuse to carry out their charge.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-11-timg--5--f730f26f57da9cda2e75d95b98193de6.webp" width="1600" height="900" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-lords-who-refuse-to-link-the-fire">The Lords Who Refuse to Link the Fire<a href="https://tanzhuo.xyz/en/2019/06/17/hei-hun-3#the-lords-who-refuse-to-link-the-fire" class="hash-link" aria-label="Direct link to The Lords Who Refuse to Link the Fire" title="Direct link to The Lords Who Refuse to Link the Fire" translate="no">​</a></h2>
<p>The Abyss Watchers, corrupted by long exposure to the power of the Abyss, have descended into madness. They endlessly slaughter one another, with no thought to spare for the fire. Aldrich, having foreseen the age of the deep sea that would follow the flame's fading, abandoned the linking in despair and devoured countless lives — including one of the gods of the first age — becoming a mass of rot coiled inside the old royal city, awaiting the coming of the deep. Yhorm the Giant, having failed to protect his own people from the flame's harm and watched his capital fall to ruin, sank into guilt and remorse, and so refuses to act at all.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-burden-of-linking-the-fire">The Burden of Linking the Fire<a href="https://tanzhuo.xyz/en/2019/06/17/hei-hun-3#the-burden-of-linking-the-fire" class="hash-link" aria-label="Direct link to The Burden of Linking the Fire" title="Direct link to The Burden of Linking the Fire" translate="no">​</a></h2>
<p>In the end, the task of linking the fire falls to the Ashen One — the player.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-11-timg--8--7e3b2ec746b4ec84b0251d3e4e510493.webp" width="1600" height="900" class="img_ev3q"></p>
<p>Lords mired in despair aren't going to return to their thrones just because you ask nicely. To claim their power, the player must fight through wave after wave of guardians, kill them, and retrieve what remains of their cinders. Finally, when the cinders of all five Lords have been returned to their thrones at Firelink Shrine, the Ashen One absorbs their combined power.</p>
<p>Then the Ashen One journeys to the Kiln of the First Flame — the place where the First Flame burns — and after defeating the Soul of Cinder standing guard there, gains power enough to link the fire. Depending on your choices, the story branches into four endings: linking the fire, letting it fade, usurping it, or the secret ending.</p>
<p>Perhaps the Age of Fire is finally coming to an end.</p>
<p>The original creator of this video on Bilibili has granted permission.</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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/en/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/en/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/en/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/en/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/en/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/en/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/en/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/en/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/en/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/en/assets/images/2019-11-timg--3--1-fdff78fdffe6f95b0a340454eb3dd7dc.webp" width="1600" height="1024" class="img_ev3q"></p>]]></content:encoded>
            <category>Games</category>
        </item>
        <item>
            <title><![CDATA[Adding Plugins to the Ghost Blog Platform]]></title>
            <link>https://tanzhuo.xyz/en/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian</link>
            <guid>https://tanzhuo.xyz/en/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[My adventure adding comments to a Ghost blog: I wired up Disqus, got it working, then discovered it's blocked in China so domestic visitors couldn't see it at all. After weighing a proxy workaround, I switched to Valine, which works fine in China.]]></description>
            <content:encoded><![CDATA[<p>I originally wanted to integrate Disqus, the popular international commenting service, into Ghost so my blog could have a comment section. Being lazy, I decided to just use something other people had already built.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://tanzhuo.xyz/en/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>Ghost is a very clean blog platform — so clean that it doesn't even ship with comments. The official stance is clear: comments belong to the realm of third-party services, and a blogging platform should focus on content. I actually agree with this design. A comment system involves user identity, spam prevention, and data storage; maintaining one yourself is no small cost, and handing it off to a dedicated service saves a lot of headaches.</p>
<p>So the problem became: which commenting service to pick, and how to hook it into Ghost.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-ghost-integrates-third-party-comments">How Ghost Integrates Third-Party Comments<a href="https://tanzhuo.xyz/en/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#how-ghost-integrates-third-party-comments" class="hash-link" aria-label="Direct link to How Ghost Integrates Third-Party Comments" title="Direct link to How Ghost Integrates Third-Party Comments" translate="no">​</a></h2>
<p>Ghost has no plugin marketplace, but it leaves two doors open:</p>
<ol>
<li class="">
<p><strong>Code Injection</strong>. In the admin settings you can insert a snippet of HTML/JS directly into the site-wide <code>&lt;head&gt;</code> or the end of <code>&lt;body&gt;</code>, without touching theme files. This is great for quickly trying out a commenting service.</p>
</li>
<li class="">
<p><strong>Editing the theme template</strong>. Put the commenting service's embed code into the theme's <code>post.hbs</code> post template, so it only loads on article pages. This is the more proper approach, and it gives you more control over where the comment section appears.</p>
</li>
</ol>
<p>Third-party commenting services all integrate basically the same way: you place a placeholder <code>div</code> on the page and load a JS snippet; the script fetches comment data from the vendor's servers and renders it into the placeholder. Comment data lives on the vendor's side, fully decoupled from the blog itself.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wiring-up-disqus">Wiring Up Disqus<a href="https://tanzhuo.xyz/en/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#wiring-up-disqus" class="hash-link" aria-label="Direct link to Wiring Up Disqus" title="Direct link to Wiring Up Disqus" translate="no">​</a></h2>
<p>Disqus is the most widely used commenting service outside China, and it's the example used in Ghost's official docs. I read through the documentation, and setup was dead simple: register an account, create a site to get your shortname, paste the embed code into the post template, and you're done.</p>
<p>Once everything was configured, it turned out things were not so simple after all.</p>
<p>The configuration did take effect — opening the blog on a network with unrestricted internet access, the comment section rendered fine. But on an ordinary network inside China, the spot where the comments should be was just blank. The reason was straightforward: Disqus's embed script and API domains are blocked in China, so the script never loads and the comment section never renders.</p>
<p>I was speechless.</p>
<p>For a Chinese-language blog whose readers are mostly in China, this is equivalent to having no comment feature at all. Worse, the failing script can also slow the page down: the browser sits there waiting until the request times out before giving up.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="weighing-the-proxy-option">Weighing the Proxy Option<a href="https://tanzhuo.xyz/en/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#weighing-the-proxy-option" class="hash-link" aria-label="Direct link to Weighing the Proxy Option" title="Direct link to Weighing the Proxy Option" translate="no">​</a></h2>
<p>A blocked service isn't necessarily hopeless. The popular approach in the community is to run your own reverse proxy: use your own server (or a CDN, or a serverless function) to relay requests to Disqus's API, and patch the embed script on the frontend to point at your proxy. That way, requests from users in China hit your proxy first, and the proxy talks to Disqus.</p>
<p>So my plan was to go find someone's ready-made proxy code on GitHub when I had time. Writing it myself would take a while; grabbing something off the shelf sounded pretty good.</p>
<p>But thinking it over calmly, this approach has a few unavoidable problems:</p>
<ul>
<li class="">The proxy itself needs deployment and maintenance — you're effectively running an extra service just for a comment box;</li>
<li class="">The comment login/authorization flow also goes through Disqus, so the proxy has to handle more than one or two endpoints, and it's easy to miss some;</li>
<li class="">The free tier of Disqus comes with ads anyway.</li>
</ul>
<p>Taking on all that burden just for comments isn't a great trade.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="switching-to-valine">Switching to Valine<a href="https://tanzhuo.xyz/en/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#switching-to-valine" class="hash-link" aria-label="Direct link to Switching to Valine" title="Direct link to Switching to Valine" translate="no">​</a></h2>
<p>-Updated 2019-12-19</p>
<p>In the end I went with <a href="https://valine.js.org/" target="_blank" rel="noopener noreferrer" class="">Valine</a>, a commenting system that works directly in China — ad-free and free to use.</p>
<p>Valine takes a different approach than Disqus: it has no backend service of its own. Comment data is stored on a BaaS platform like LeanCloud, and on the frontend you just include one JS file and fill in the App ID and App Key you registered. There's no forced login — visitors can leave a comment with just a nickname, which is plenty lightweight for a personal blog.</p>
<p>Integration works the same way as described above: drop the initialization code into your article pages via Code Injection or the theme template. There's no access issue from within China, and page loads no longer stall.</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>tip</div><div class="admonitionContent_BuS1"><p>Before choosing a commenting system, actually open the page once from your target readers' network environment. Plenty of services work perfectly on the developer's own network and behave completely differently elsewhere.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2019/06/13/guan-yu-gei-ghostbo-ke-xi-tong-jia-dian-cha-jian#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The lesson from all this tinkering is simple: when picking a third-party service, put availability before features. However full-featured Disqus is, it's worth zero if your readers can't load it; Valine is bare-bones, but for a Chinese-language blog serving domestic readers it's the more pragmatic choice. Ghost's Code Injection mechanism keeps the switching cost low — a full round of trial and error only takes half a day.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Setting Up HTTPS for Dockerized Nginx with certbot]]></title>
            <link>https://tanzhuo.xyz/en/2019/06/13/li-yong-docker</link>
            <guid>https://tanzhuo.xyz/en/2019/06/13/li-yong-docker</guid>
            <pubDate>Thu, 13 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Let's Encrypt certificates issued by certbot are symlinks, and they become unreadable once mounted into an nginx container. Notes on this docker volumes + symlink gotcha and the correct way to mount them.]]></description>
            <content:encoded><![CDATA[<p>Environment: Docker-1.13.1, nginx-1.15.12, certbot</p>
<!-- -->
<p>Enabling HTTPS for a site isn't hard when nginx runs directly on the host — tutorials are everywhere. But when nginx runs inside a docker container, the certificate files have to be mounted in via volumes, which adds a layer of filesystem indirection. And as it happens, the certificate files certbot generates are symlinks. When these two things collide, you get a pitfall that's genuinely hard to reason about without checking the docs. This post records how I debugged it at the time.</p>
<p>Since sites without HTTPS now get flagged as insecure, I decided to set it up and apply for a free <strong>Let's Encrypt</strong> certificate — it's not much hassle after all. What I never expected was that configuring an SSL certificate inside a docker nginx container hides a trap. I was starting from scratch and had never read the docker documentation, so I got bitten here. When I get the chance, I really should work through the official docs properly.</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="where-the-certificates-come-from">Where the Certificates Come From<a href="https://tanzhuo.xyz/en/2019/06/13/li-yong-docker#where-the-certificates-come-from" class="hash-link" aria-label="Direct link to Where the Certificates Come From" title="Direct link to Where the Certificates Come From" translate="no">​</a></h2>
<p>First, some context on my situation. After using certbot to get a free SSL certificate for my domain, the resulting</p>
<p>fullchain.pem and privkey.pem files were symlinks.</p>
<p>This is no accident — it's certbot's deliberate design. The real certificate files live under <code>/etc/letsencrypt/archive/&lt;domain&gt;/</code>, and each renewal generates a new set of files there. The fullchain.pem and privkey.pem under <code>/etc/letsencrypt/live/&lt;domain&gt;/</code> are just symlinks pointing at the latest version in archive. That way your nginx config can reference the fixed live path and never needs changing after a renewal. On a physical host this mechanism is very convenient — but inside a container it falls apart.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="mounting-into-the-container">Mounting into the Container<a href="https://tanzhuo.xyz/en/2019/06/13/li-yong-docker#mounting-into-the-container" class="hash-link" aria-label="Direct link to Mounting into the Container" title="Direct link to Mounting into the Container" translate="no">​</a></h2>
<p>Next, I used docker volumes to mount the SSL config folder into the nginx container, and configured nginx.conf along the way. (Note that the nginx container's nginx.conf works a little differently: the main nginx.conf includes the *.conf files under /etc/nginx/conf.d/ — a small detail worth paying attention to. And don't forget to map the container's port 443.)</p>
<p>The site config looks roughly like this:</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%)"># Placed under /etc/nginx/conf.d/, pulled in by the main config's 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%)"># These are paths inside the container — they must match</span><span class="token plain"></span><br></div><div class="token-line" 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 the files actually end up after mounting</span><span class="token plain"></span><br></div><div class="token-line" 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="where-the-trap-is">Where the Trap Is<a href="https://tanzhuo.xyz/en/2019/06/13/li-yong-docker#where-the-trap-is" class="hash-link" aria-label="Direct link to Where the Trap Is" title="Direct link to Where the Trap Is" translate="no">​</a></h2>
<p>Here's the catch. Because the certificate files are symlinks, they become unusable once mounted into the container.</p>
<p>nginx throws an error saying it can't find the certificate file. But if you enter the container, you'll see the certificate file is indeed there — except it can't be read. This has nothing to do with permissions. It's entirely because docker volumes only map real file data; a symlink can't be meaningfully mapped, and what's left in the container is just an empty shell of a link.</p>
<p>There's a simple way to diagnose this kind of problem: get into the container and run <code>ls -l</code>. If the certificate file shows up as a link pointing at some path, try <code>cat</code>-ing it. If it can't be read, the link's target almost certainly doesn't exist inside the container.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-it-works-this-way">Why It Works This Way<a href="https://tanzhuo.xyz/en/2019/06/13/li-yong-docker#why-it-works-this-way" class="hash-link" aria-label="Direct link to Why It Works This Way" title="Direct link to Why It Works This Way" translate="no">​</a></h2>
<p>It's actually not hard to understand. A symlink is just a shortcut. If a shortcut inside a volume could reach through to arbitrary files on the host, that would be the real problem. The symlink might live on the /home volume while the actual file it points to lives on the /usr volume. But my volumes config only mapped /home, which cannot access data on /usr. You have to map things so the link target is accurately covered — mounting just the directory containing the symlink isn't enough, and neither is mounting the symlink file directly.</p>
<p>Put another way: a symlink stores nothing but a target path. Docker copies the link into the container as-is, and the container then resolves that path against <strong>its own</strong> filesystem. If the target path wasn't mounted in as well, the link is dead. This is also exactly what container isolation is supposed to mean — if mounting a shortcut let you follow it to read any file on the host, isolation would be meaningless.</p>
<p><strong>The mount must cover the full path that the certificate symlinks resolve to. Only then can nginx inside docker follow the links to the actual certificate files at startup.</strong></p>
<p>In other words, the key is making sure the symlink's target path genuinely exists inside the container. For certbot, that means both the live and archive directories have to go into the container together so the links can resolve:</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%)"># Mount the entire letsencrypt directory at container start —</span><span class="token plain"></span><br></div><div class="token-line" 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%)"># with both the symlinks under live and the real files under 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 comment" style="color:hsl(220, 10%, 40%)"># present, the links resolve correctly</span><span class="token plain"></span><br></div><div class="token-line" 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="pitfalls-and-notes">Pitfalls and Notes<a href="https://tanzhuo.xyz/en/2019/06/13/li-yong-docker#pitfalls-and-notes" class="hash-link" aria-label="Direct link to Pitfalls and Notes" title="Direct link to Pitfalls and Notes" translate="no">​</a></h2>
<ol>
<li class="">
<p>The official nginx image's main config only handles global settings; site configs come in via include <code>/etc/nginx/conf.d/*.conf</code>. Your server block has to go in the right place, or your changes silently do nothing.</p>
</li>
<li class="">
<p>Port 443 is easy to forget. If nginx listens on 443 inside the container but the host doesn't map it, browsers still can't connect — and the nginx logs show nothing wrong.</p>
</li>
<li class="">
<p>Let's Encrypt certificates are only valid for 90 days. After renewal, a new set of files appears under archive and the symlinks under live are updated accordingly. If you took the shortcut of hardcoding the resolved real file paths in your config, they'll be wrong after the next renewal — which is exactly why mounting the whole directory and referencing the live paths is the recommended approach. And remember to have nginx inside the container reload its config after a renewal.</p>
</li>
<li class="">
<p>Seeing that a file "exists" in the container doesn't mean it's readable. <code>ls</code> on a symlink won't reveal the problem; <code>cat</code> it, or just read nginx's error message — that's more direct.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2019/06/13/li-yong-docker#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>The essence of this pitfall is two individually reasonable designs colliding: certbot uses symlinks to decouple "a fixed path" from "certificate files that change", while docker volumes faithfully transport the link itself without following its target. Once you understand that a symlink stores nothing but a path, the mystery disappears — mount the link and its target into the container together so the path resolves fully inside it, and the certificates become readable. Consider it a lesson in docker documentation, paid for with one pitfall.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[The Inner Monologue of Going to the Gym]]></title>
            <link>https://tanzhuo.xyz/en/2019/06/09/jian-shen-zhen-tmlei</link>
            <guid>https://tanzhuo.xyz/en/2019/06/09/jian-shen-zhen-tmlei</guid>
            <pubDate>Sun, 09 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[The day before gym day: Ah, nice, I get to work out again! Awesome. A few hours before the gym: Kinda don't feel like going! Home is so comfy. Fine, I said I'd go, so let's go. During the workout: I'm an idiot. After the workout: I'm an idiot.]]></description>
            <content:encoded><![CDATA[<p>The day before gym day:</p>
<!-- -->
<p>Ah, nice, I get to work out again! Awesome 👍</p>
<p>A few hours before the gym:</p>
<p>Kinda don't feel like going! Hanging out at home is so much comfier. Fine, I said I'd go, so let's go.</p>
<p>During the workout:</p>
<p>I'm an idiot.</p>
<p>After the workout:</p>
<p>I'm an idiot.</p>]]></content:encoded>
            <category>Life</category>
        </item>
        <item>
            <title><![CDATA[Factorio]]></title>
            <link>https://tanzhuo.xyz/en/2019/06/07/factorio-yi-xing-gong-han</link>
            <guid>https://tanzhuo.xyz/en/2019/06/07/factorio-yi-xing-gong-han</guid>
            <pubDate>Fri, 07 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Factorio is a game about building industrial production lines and keeping them running efficiently. You mine ore, research technologies, build factories, set up automated production lines, and trade blows with the alien bugs. You start from nothing, the hard way.]]></description>
            <content:encoded><![CDATA[<p><strong>Factorio</strong> is a game about building industrial production lines and keeping them running efficiently. You mine ore, research technologies, build factories, set up automated production lines — all while trading blows with the alien bugs.</p>
<!-- -->
<p>You start from nothing, the hard way. Chopping trees with an axe, swinging a pickaxe at ore, handcrafting inserters and conveyor belts — but grinding along like that gets you absolutely nowhere.</p>
<p>So you'll need efficient large-scale power plants, a sprawling petrochemical industry, a magnificent fully automated production chain, and an army of robots running errands for you, until you become the true master of a resource-rich industrial empire. But there's always a mob out to get you. The native bug swarms on this planet are not happy about you messing around in their backyard, and one day they'll band together to come after you. So build weapons, set up defenses, and prepare to put down the uprising — show them who's really in charge.</p>
<p>In <strong>multiplayer</strong> you can join different factions and, led by the true masters of the craft, divide up the work with your friends to build a magnificent industrial park together. Factorio's <strong>mod support</strong> has drawn designers from all over the world to polish and reinvent the game — from tweaks and quality-of-life helpers to complete overhauls, an ever-evolving stream of mods keeps the fun coming.</p>
<p>When I first started playing:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-06-20190101145512_1-8279c66667d49aac7576aeac78719403.webp" width="1600" height="900" class="img_ev3q"></p>
<p>A few hours in:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-06-20190101161133_1-0ce1893596a944b10f607a51512f0227.webp" width="1600" height="900" class="img_ev3q"></p>
<p>After starting a fresh map:</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/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/en/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/en/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/en/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/en/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/en/assets/images/2019-06-20190404232753_1-d42628f86e01577d527736e1193b517b.webp" width="1600" height="900" class="img_ev3q"></p>
<p>Final verdict: what a damn grind.</p>]]></content:encoded>
            <category>Games</category>
        </item>
        <item>
            <title><![CDATA[Changing the Default Docker Image Storage Path on CentOS]]></title>
            <link>https://tanzhuo.xyz/en/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing</link>
            <guid>https://tanzhuo.xyz/en/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[A war story about changing Docker's default image storage path: from the --graph flag to storage drivers and d_type support, finally solved by mounting a data disk to stop images from vanishing after reboots.]]></description>
            <content:encoded><![CDATA[<p>Environment: CentOS 7, Docker 1.13.1. This is the full story of every pitfall I hit while changing Docker's default image storage path.</p>
<!-- -->
<p>(Mind your own OS and Docker versions, and please read the whole article before doing anything — otherwise you're on your own.)</p>
<p>Docker stores images under /var/lib/docker by default. You generally don't want to keep that default location: image files tend to be large, and the more services you run, the more pressure you put on the host's system disk — plus each image comes with its own mounted volumes and so on, which is beyond the scope of this article. The point is, data shouldn't live on the system disk; it should go on a dedicated data disk.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="changing-the-storage-path">Changing the Storage Path<a href="https://tanzhuo.xyz/en/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing#changing-the-storage-path" class="hash-link" aria-label="Direct link to Changing the Storage Path" title="Direct link to Changing the Storage Path" translate="no">​</a></h2>
<p>Down to business: edit Docker's config file at /usr/lib/systemd/system/docker.service</p>
<p><img decoding="async" loading="lazy" src="https://tanzhuo.xyz/en/assets/images/2019-06-image-2-c8d543b950a8f529a3452dca4033e4b6.png" width="561" height="662" class="img_ev3q"></p>
<p>Add the graph option <code>--graph="your path" \</code>, save the change, then reload the config and restart the Docker service:</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%)"># Reload the systemd configuration</span><span class="token plain"></span><br></div><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%)"># Restart the Docker 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">systemctl restart docker.service</span><br></div></code></pre></div></div>
<p>Run <code>docker info</code> to check Docker's current state:</p>
<p><img decoding="async" loading="lazy" src="data:image/png;base64,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" width="267" height="33" class="img_ev3q"></p>
<p>Docker Root Dir now shows "your path", so the configuration took effect. I'd suggest pulling an image and checking the target folder to confirm it's really working, then rebooting the server to see whether the images are still there afterwards.</p>
<p><strong>If you run docker images after the reboot and your downloaded images have vanished —</strong></p>
<p>Congratulations, you're in the same boat I was, and you may start questioning your life choices. Run docker info again and the Docker Root Dir path looks perfectly fine. What I did here was delete the original /var/lib/docker folder. If you still want the data inside, copy it somewhere first, or just move the folder and rename it — the name doesn't really matter, what matters is that /var/lib/docker no longer exists. Try again after that and it should work — at least it did for me.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="postscript-the-images-vanished-again-after-a-reboot-2019-06-16">Postscript: The Images Vanished Again After a Reboot (2019-06-16)<a href="https://tanzhuo.xyz/en/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing#postscript-the-images-vanished-again-after-a-reboot-2019-06-16" class="hash-link" aria-label="Direct link to Postscript: The Images Vanished Again After a Reboot (2019-06-16)" title="Direct link to Postscript: The Images Vanished Again After a Reboot (2019-06-16)" translate="no">​</a></h2>
<p>I thought everything was configured properly. Of course it wasn't that simple — I clearly still didn't know Docker well enough.</p>
<p>This time my hosting provider suddenly did hardware maintenance, force-shutting down most of their servers (lesson: don't cheap out on no-name cloud providers). My server went down too, and after the maintenance and reboot, my blog was still unreachable. I had Docker set to start on boot with containers set to restart automatically, but apparently none of that kicked in, so it was time to dig in. (I genuinely thought I had this configured last time. Apparently not.)</p>
<p>I logged into the server, ran docker images, and the images were gone again, just like before. So my earlier fix hadn't actually solved anything — or more precisely, the configuration did take effect, but broke as soon as the host rebooted.</p>
<p>Even though docker info showed the correct Docker Root Dir, things were already broken: both docker images and docker ps -a returned nothing. Worse, if you run <code>service docker stop</code> at this point, you'll never get Docker to start again. <code>service docker start</code> fails with:</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>Following the hint and running systemctl status docker.service and journalctl -xe tells you basically nothing useful. To see the actual error, run the Docker daemon in the foreground so the startup logs print directly to your terminal:</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%)"># Run the Docker daemon in the foreground to see startup logs directly</span><span class="token plain"></span><br></div><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="the-storage-driver-rabbit-hole">The Storage Driver Rabbit Hole<a href="https://tanzhuo.xyz/en/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing#the-storage-driver-rabbit-hole" class="hash-link" aria-label="Direct link to The Storage Driver Rabbit Hole" title="Direct link to The Storage Driver Rabbit Hole" translate="no">​</a></h2>
<p>I learned a lot while chasing this error — for instance, Docker's five storage driver modes:</p>
<p>Jianshu: <a href="https://www.jianshu.com/p/00ffd8df6010" target="_blank" rel="noopener noreferrer" class="">https://www.jianshu.com/p/00ffd8df6010</a></p>
<p>Someone else's blog: <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>I eventually went with Overlay2, the upgraded version of OverlayFS. My original driver was Device mapper — docker info printed a notice at the bottom saying Device mapper was only auto-selected for test environments and that production setups should switch to something else. That sealed my decision to move to Overlay2, but I didn't rush the switch. First I kept investigating the mystery: why couldn't Docker read the image files under /home/docker_data after a host reboot?</p>
<p>I searched Google, Baidu, GitHub, Zhihu, forums, Jianshu, and the official docs and found nothing — until I stumbled on a thread in the official Docker community where some guy abroad had hit almost exactly the same issue. The discussion there pointed to a version problem, so I figured, fine, time to install something newer.</p>
<p>So I completely removed Docker and reinstalled it (note: since 1.13, Docker switched to date-based version numbers, split into Community Edition CE and Enterprise Edition 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%)"># Completely remove the old Docker 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">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>I installed docker-ce-17.12.0.ce (reportedly a fairly stable release) and configured /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>On startup, Docker complained that my Linux kernel didn't support the overlay2 storage driver, so I upgraded the kernel — you need 4.0 or newer for overlay2.</p>
<p>Then docker info warned me that my disk didn't support d_type. So I reformatted the mounted disk as xfs with ftype=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 comment" style="color:hsl(220, 10%, 40%)"># Check the filesystem info; ftype=1 means d_type is supported</span><span class="token plain"></span><br></div><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/en/assets/images/2019-06-image-5-58cf24accf2b008eebe5ea5ec87f3add.png" width="818" height="180" class="img_ev3q"></p>
<p>I pointed Docker's default image storage location at this d_type-capable data disk and restarted 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>Now docker info showed no warnings at all — d_type enabled, storage driver switched to 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>This felt perfect: docker info was completely clean. I immediately pulled a Tomcat image and confirmed it landed in the designated folder. Great! I ran reboot right away to restart the host.</p>
<p><strong>After the reboot, docker images was empty AGAIN!!!</strong></p>
<p><strong>Who am I? What am I doing? What has all of this even been for?</strong></p>
<p>I was completely done. I had no idea which step I'd gotten wrong.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-final-fix">The Final Fix<a href="https://tanzhuo.xyz/en/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing#the-final-fix" class="hash-link" aria-label="Direct link to The Final Fix" title="Direct link to The Final Fix" translate="no">​</a></h2>
<p>Here's how I finally solved it: I mounted the data disk directly at /var/lib/docker, set it to auto-mount on boot, and removed the data-root setting from /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>After the next host reboot, docker images showed my images, and the containers came back up automatically. Now even a power outage or shutdown is no problem — Docker restarts the containers after the host comes back.</p>
<p>Later I moved to an Alibaba Cloud server and everything just worked with zero issues, so it may well have been a hardware problem all along.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping Up<a href="https://tanzhuo.xyz/en/2019/06/05/centos-docker-xiu-gai-mo-ren-jing-xiang-cun-chu-lu-jing#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping Up" title="Direct link to Wrapping Up" translate="no">​</a></h2>
<p>Changing the image storage path looks like a one-flag job — just add --graph — but it dragged in a whole chain of issues: storage drivers, kernel versions, filesystem d_type support. After going around in circles, my final approach was the opposite of where I started: don't touch Docker's path configuration at all, mount the data disk directly at /var/lib/docker, and set it to auto-mount on boot. Images and containers then survive reboots just fine. If your images are disappearing after reboots too, look first at the storage driver and the disk mount ordering — and don't rule out flaky hardware on a budget server. When picking a cloud provider, cheap isn't always a bargain.</p>]]></content:encoded>
            <category>Tech</category>
        </item>
        <item>
            <title><![CDATA[Hello, Ghost]]></title>
            <link>https://tanzhuo.xyz/en/2019/06/04/first_post</link>
            <guid>https://tanzhuo.xyz/en/2019/06/04/first_post</guid>
            <pubDate>Tue, 04 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[After deploying who knows how many container services with Docker, I went ahead and set up an open-source Ghost blog too — a place to jot down tech notes and bits of everyday life.]]></description>
            <content:encoded><![CDATA[<p>After deploying who knows how many container services with Docker, I went ahead and set up an open-source Ghost blog too — a place to jot down tech notes and bits of everyday life.</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>Life</category>
        </item>
    </channel>
</rss>