← 2026-04-09 📂 All Days 2026-04-11 →

🏗️ 系统设计 / System Design — Design a Key-Value Store

Day 21 · Phase: Growth · Est. 3 min

想象你在设计... / Real-World Scenario

想象你在设计 Redis——世界上使用最广泛的键值存储之一。每天有数十亿次请求打到它:缓存数据、存储 session、实现排行榜。你需要设计一个既高性能又高可用的键值存储系统。

Imagine you're designing Redis — one of the world's most widely used key-value stores. Billions of requests hit it daily: caching data, storing sessions, powering leaderboards. You need to design a high-performance, highly available key-value store.


架构图 / Architecture Diagram

Client Requests │ ▼ ┌─────────────────────────────────────────────┐ │ API / Client Layer │ │ GET key / SET key value / DEL key / TTL │ └───────────────────────┬─────────────────────┘ │ ┌─────────────▼──────────────┐ │ Request Router │ │ (Consistent Hashing) │ └──────┬──────────┬───────────┘ │ │ ┌───────────▼──┐ ┌───▼────────────┐ │ Node A │ │ Node B │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ │MemTable │ │ │ │MemTable │ │ │ │(in-memory)│ │ │ │(in-memory)│ │ │ └─────┬─────┘ │ │ └─────┬─────┘ │ │ │ flush │ │ │ flush │ │ ┌─────▼─────┐ │ │ ┌─────▼─────┐ │ │ │ SSTable │ │ │ │ SSTable │ │ │ │ (on disk) │ │ │ │ (on disk) │ │ │ └───────────┘ │ │ └───────────┘ │ └───────────────┘ └────────────────┘ │ │ └────────────────┘ │ ┌─────────▼──────────┐ │ Replication Log │ │ (Write-Ahead Log) │ └────────────────────┘

核心数据结构 / Core Data Structures:

  • MemTable: 内存中的有序跳表,写操作先落这里 / In-memory sorted skip-list, writes land here first
  • SSTable: 磁盘上的不可变有序文件 / Immutable sorted files on disk
  • Write-Ahead Log (WAL): 崩溃恢复用 / For crash recovery
  • Bloom Filter: 快速判断 key 是否可能存在,避免无效磁盘读 / Quickly check if key might exist to avoid unnecessary disk reads

关键权衡 / Key Tradeoffs

为什么这样设计?/ Why design it this way?

1. LSM Tree vs B-Tree

维度LSM TreeB-Tree
写性能✅ 顺序写,极快🔄 随机写,较慢
读性能🔄 需合并多层✅ 直接查找
空间放大🔄 Compaction 前有冗余✅ 较少冗余
适用场景写多读少,如日志系统读多写少,如数据库索引
LSM 是 RocksDB、LevelDB 的基础;B-Tree 是 PostgreSQL、MySQL 的基础

2. 一致性 vs 性能

  • 强一致性 (同步复制): 写操作等所有副本确认 → 延迟高但不丢数据
  • 最终一致性 (异步复制): 写操作只等主节点 → 延迟低但可能短暂不一致

3. 内存 vs 持久化

  • 纯内存方案 (Memcached): 极快但重启丢数据
  • 混合方案 (Redis with AOF/RDB): 定期持久化,兼顾性能与持久性

别踩这个坑 / Common Mistakes

❌ 忘记处理热点 Key (Hot Keys)

某些 key 被频繁访问(如明星发微博时 user:123 被千万次读取),单节点会成为瓶颈。

解决:本地缓存 + 请求合并 (request coalescing)

❌ 没有 TTL 机制导致内存泄漏

不过期的 key 会撑爆内存。Redis 用懒删除 + 主动扫描双策略。

❌ 忽视 Compaction 调优

LSM 的 Compaction 会突发 I/O 竞争,影响正常读写。生产环境要限速 (rate limiting)。

❌ Consistent Hashing 中节点数量设置错误

虚拟节点 (virtual nodes) 数量太少会导致负载不均衡,建议 100-200 个虚拟节点/物理节点。


📚 References


🧒 ELI5 (给五岁小孩解释)

键值存储就像一个超级快的字典。你说"帮我记住 user:123 的密码是 abc",它就记住了。下次你问"user:123 的密码是什么",它立刻告诉你。重要的是:这本字典要很快、要记很多东西、还要不怕服务器崩溃。LSM Tree 就像先在便利贴(内存)上写,然后定期整理到笔记本(磁盘)上——写得很快,整理在后台做。

A key-value store is like a super-fast dictionary. You say "remember that user:123's password is abc" and it does. Later you ask "what's user:123's password?" and it tells you instantly. The trick is making it fast, able to hold tons of data, and crash-proof. LSM Tree is like jotting notes on sticky notes (memory) first, then organizing them into a notebook (disk) later — writes are super fast, cleanup happens in the background.

💻 算法 / Algorithms — #76 Minimum Window Substring (Hard)

Day 21 · Phase: Growth · Est. 4 min
🧩 滑动窗口模式 (5/6) — building on the template from Day 17

🧩 滑动窗口 (5/6) — 模式回顾 / Pattern Recap

这是滑动窗口第 5 题(共 6 题)。今天我们看最难的变体:最小窗口子串。

This is problem 5/6 in our Sliding Window block. Today: the hardest variant — Minimum Window Substring.

本块所有题 / Block problems:

  1. #121 Best Time to Buy and Sell Stock (Easy) ✅
  2. #3 Longest Substring Without Repeating Characters (Medium) ✅
  3. #424 Longest Repeating Character Replacement (Medium) ✅
  4. #567 Permutation in String (Medium) ✅
  5. #76 Minimum Window Substring (Hard) ← 今天 / Today
  6. #239 Sliding Window Maximum (Hard) → 明天 / Tomorrow

模版回顾 / Template Recap:

left = 0
for right in range(len(arr)):
    window.add(arr[right])  # expand right
    while CONDITION_VIOLATED:
        window.remove(arr[left])  # shrink left
        left += 1
    result = update(result, right - left + 1)

核心洞察 / Key Insight: 右指针扩张探索,左指针收缩维护约束。每个元素最多进出窗口各一次 → O(n)


题目 / Problem

🔗 LeetCode #76 — Minimum Window Substring 🔴 Hard

📹 NeetCode 讲解

题意: 给定字符串 s 和 t,找 s 中包含 t 所有字符的最短子串。

Find the minimum window in s that contains all characters of t.

Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC"
Explanation: "BANC" is the smallest window containing A, B, C

现实类比 / Real-World Analogy

想象你在新闻文章中找一段话,必须包含"人工智能"、"融资"、"OpenAI"这三个词。你希望找到包含这三个词的最短段落——不是全文,而是最紧凑的片段。这就是最小窗口子串问题。

Imagine you're searching a news article for a passage that must contain "artificial intelligence", "funding", and "OpenAI". You want the shortest paragraph that includes all three — not the whole article, the most compact snippet. That's this problem.


与模版的映射 / Mapping to Template

模版概念本题实现
window.add()右指针字符加入窗口计数 window[c] += 1,若满足 t 的需求则 formed += 1
CONDITION_VIOLATED → shrink when?formed == required(已包含所有字符)时收缩(找更短的)
window.remove()左指针字符移出,若不再满足需求 formed -= 1
result记录 (window_size, left, right) 最小值

⚠️ 关键区别 vs 前几题: 之前是窗口违反约束时收缩;本题是窗口满足条件时收缩(贪心地找更短)。

Key difference vs. previous problems: we shrink when the window IS valid (greedy — find shorter), not when it's invalid.


Python 解法 + 追踪 / Solution + Trace

from collections import Counter

def minWindow(s: str, t: str) -> str:
    if not t or not s:
        return ""
    
    # Count chars needed from t
    need = Counter(t)          # {'A':1, 'B':1, 'C':1}
    required = len(need)       # 3 distinct chars needed
    
    window = {}
    formed = 0                 # how many chars satisfy their count in t
    
    # (window_length, left, right)
    best = float("inf"), 0, 0
    
    left = 0
    for right in range(len(s)):
        c = s[right]
        window[c] = window.get(c, 0) + 1
        
        # Check if this char now satisfies t's requirement
        if c in need and window[c] == need[c]:
            formed += 1
        
        # Shrink while window is valid (all chars present)
        while formed == required:
            # Update best
            win_len = right - left + 1
            if win_len < best[0]:
                best = (win_len, left, right)
            
            # Remove left char
            left_c = s[left]
            window[left_c] -= 1
            if left_c in need and window[left_c] < need[left_c]:
                formed -= 1
            left += 1
    
    return "" if best[0] == float("inf") else s[best[1]:best[2]+1]

追踪 / Trace (s = "ADOBECODEBANC", t = "ABC"):

right=0(A): window={A:1}, formed=1 right=1(D): window={A:1,D:1}, formed=1 right=2(O): formed=1 right=3(B): window={..B:1}, formed=2 right=4(E): formed=2 right=5(C): window={..C:1}, formed=3 ← 满足! shrink: best=(6,0,5)="ADOBEC", remove A → formed=2 right=...继续扩张... right=9(B): formed=3 again shrink: best=min(6, right-left+1)... 最终: best="BANC" (length 4) ✅

复杂度 / Complexity:

  • ⏱ Time: O(|s| + |t|) — 每个字符最多进出窗口一次
  • 🗂 Space: O(|s| + |t|) — 窗口计数哈希表

举一反三 / Connect to the Block

题目约束类型收缩时机
#567 Permutation in String窗口 = 固定大小固定长度收缩
#3 Longest Substring无重复字符有重复时收缩
#76 Min Window包含所有目标字符满足条件时贪心收缩
#239 Sliding Window Max最大值固定大小 (明天!)

📝 Quiz

{"question":"In #76 Minimum Window Substring, when do we shrink the left pointer?","options":["When the window is invalid (missing chars from t)","When the window is valid (contains all chars of t)","When the window size exceeds len(t)","When we find a repeated character"],"correct_index":1}

📚 References


🧒 ELI5

想象你有一盒彩色积木,我给你一张清单说"我需要红、蓝、绿各一块"。你从左到右扫描这盒积木,用一个滑动框框住它们。当框里有清单上所有颜色时,你试着从左边缩小这个框(看能不能更短)。一旦缺了某个颜色,就从右边继续扩大。最后记录你找到的最小的框。

Imagine a box of colored blocks, and I give you a checklist: "I need one red, one blue, one green." You scan the blocks left to right with a sliding frame. When the frame contains all the colors, you try to shrink it from the left (can it be shorter?). Once you're missing a color, expand from the right. Record the smallest valid frame you found.

🗣️ 软技能 / Soft Skills — Saying No to a Popular Idea

Day 21 · Phase: Growth · Est. 2 min
Category: Decision Making · Level: Senior/Staff

为什么这很重要 / Why This Matters

"说不"是高级工程师最难学的技能之一。初级工程师往往迎合团队氛围,资深工程师懂得拒绝那些听起来好但实际有害的想法——同时不损害关系。面试官想看你是否有独立判断力,还是随大流。

Saying "no" is one of the hardest skills for senior engineers. Junior engineers often go along to get along; senior engineers know how to reject ideas that sound good but are actually harmful — without damaging relationships. Interviewers want to see if you have independent judgment, or if you just follow the crowd.


STAR 结构 / STAR Breakdown

情境 (Situation):

团队在讨论将某个功能提前发布以赶上季度 OKR,大多数人支持这个想法。但你发现这个功能的某个关键边缘情况还没测试。

Your team is discussing shipping a feature early to hit quarterly OKRs. Most people are on board. But you've noticed a critical edge case that hasn't been tested.

任务 (Task):

作为技术 lead,你需要在不成为"阻碍者"的情况下,阻止一个有风险的决策。

As tech lead, you need to block a risky decision without becoming "the blocker."

行动 (Action):

  1. 先理解再反对 — 先问"我理解大家想赶上 Q3,能帮我理解为什么这个时间点很关键吗?"
  2. 用数据说话 — 展示具体风险:"如果这个 edge case 触发,会影响 X% 的付费用户的数据完整性"
  3. 提供替代方案 — "我们可以在两周内修复这个问题,或者先 flag-gate 这个功能给 5% 用户"
  4. 让决策者做决定 — "我已经展示了风险。你们来决定。"

结果 (Result):

团队选择了 flag-gate 方案。两周后,edge case 被触发了——因为有限发布,只有少量用户受影响,很快修复。

The team chose the flag-gate approach. Two weeks later, the edge case was triggered — but with limited rollout, only a small percentage of users were affected and it was fixed quickly.


❌ 差答案 vs ✅ 好答案

❌ 差答案:

"我直接告诉大家这个 idea 不行,我们不能这么做。"

这显得武断,没有解释原因,也没有提供出路。

❌ 另一种差答案:

"我虽然有顾虑,但还是跟着大家走了。"

这表明你没有独立判断力,或者你害怕冲突。

✅ 好答案的关键要素:

  • 具体的技术风险(不是感觉,是数据)
  • 展示了你理解业务压力(不是纯技术思维)
  • 提供了替代方案而不只是否定
  • 最终让决策者决定(你不是独断者)

Senior/Staff 进阶技巧 / Senior/Staff Tips

1. "我有顾虑" vs "这不行"

前者邀请对话;后者关闭对话。措辞很重要。

"I have concerns" vs "this won't work" — the first invites dialogue, the second closes it.

2. 建立"技术债追踪"习惯

在 Jira/Linear 上记录你的反对意见和理由,即使你被否决了。这样当问题真的发生时,有记录可循。

Document your objections in Jira/Linear even when overruled. When the problem surfaces, you have a paper trail.

3. 区分"我反对"和"我不支持"

有时候你可以说:"我不支持这个方向,但如果团队决定做,我会全力帮助执行。" — 展示了判断力,同时不破坏团队凝聚力。

Sometimes you can say "I don't support this direction, but if the team decides to proceed, I'll give my full effort." — Shows judgment without breaking team cohesion.


关键要点 / Key Takeaways

  • 🎯 说不不是阻碍,是负责任 — Saying no is leadership, not obstruction
  • 📊 用数据而非感觉 — Use data, not gut feelings
  • 🔄 永远提供替代方案 — Always offer alternatives
  • 🤝 理解业务压力 — Acknowledge business pressure before pushing back

📚 References


🧒 ELI5

假设班里大多数人都想在下雨天去户外玩,你知道会淋湿生病。好的做法是说:"我知道大家很想出去玩(理解诉求),但是今天下雨会让很多人生病(具体风险)。我们可以改在室内玩这个游戏,同样很好玩(替代方案)。你们来决定。" 而不是说"不行,我不去"然后坐那儿。

Suppose most of your class wants to play outside in the rain. A good approach is: "I know everyone wants to go out (acknowledge the desire), but we'll get sick in the rain (specific risk). We could play this game inside instead — just as fun (alternative). Your call." Not just "no, I'm not going" while sitting in the corner.

🎨 前端 / Frontend — React Forms: Controlled vs Uncontrolled

Day 21 · Phase: Growth · Week 4 · Est. 2 min
Category: React Patterns

真实场景 / Real Scenario

你在做一个 dashboard,需要一个搜索框:用户输入时实时过滤表格数据,同时在用户提交表单时阻止默认行为。这时候你面临一个选择:用受控组件还是非受控组件?

You're building a dashboard with a search input that filters a table in real time as the user types, while also preventing default form submission. You face a choice: controlled or uncontrolled component?


核心概念 / Core Concepts

受控组件 / Controlled Component

React 的 state 是表单值的唯一数据源。

React state is the single source of truth for the input value.

function SearchForm() {
  const [query, setQuery] = useState(""); // React controls the value

  return (
    <form onSubmit={(e) => e.preventDefault()}>
      <input
        type="text"
        value={query}           // ← Controlled by React state
        onChange={(e) => setQuery(e.target.value)}
        // hint text: "Search..."
      />
      <p>Searching for: {query}</p>  {/* Real-time sync */}
    </form>
  );
}

非受控组件 / Uncontrolled Component

DOM 自己管理状态,你用 ref 在需要时读取值。

The DOM manages its own state; you read it with a ref when needed.

function LoginForm() {
  const emailRef = useRef<HTMLInputElement>(null); // DOM manages value
  const passwordRef = useRef<HTMLInputElement>(null);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    // Read values only on submit, not on every keystroke
    const email = emailRef.current?.value;
    const password = passwordRef.current?.value;
    console.log({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" ref={emailRef} defaultValue="" />
      <input type="password" ref={passwordRef} />
      <button type="submit">Login</button>
    </form>
  );
}

猜猜输出 / Quiz — What happens?

function Mystery() {
  const [val, setVal] = useState("hello");
  
  return (
    <input
      value={val}
      // No onChange handler!
    />
  );
}

这段代码会怎样?/ What does this do?

A) 正常渲染,用户可以输入 / Renders normally, user can type

B) 报错,组件崩溃 / Throws an error, component crashes

C) 输入框被锁定,用户无法修改 / Input is read-only, user can't change it

D) 忽略 value prop,用 defaultValue 代替 / Ignores value prop, uses defaultValue

<details><summary>显示答案 / Show Answer</summary>

✅ C — React 会发出警告,输入框变成只读。这是受控组件的常见陷阱:设置了 value 但没有 onChange 就等于只读。

React warns and the input becomes read-only. This is a common trap with controlled components: setting value without onChange makes it effectively read-only.

</details>


❌ 常见错误 vs ✅ 正确做法

❌ 混用 value 和 defaultValue:

// This is a bug! Switches from uncontrolled to controlled
<input defaultValue="initial" value={someState} />

✅ 选一种,坚持到底:

// Controlled: use value + onChange
<input value={state} onChange={(e) => setState(e.target.value)} />

// Uncontrolled: use defaultValue + ref
<input defaultValue="initial" ref={myRef} />

❌ 对每个表单字段都用 useState:

// Verbose for large forms
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");

✅ 对象化 state 或用 useReducer:

const [form, setForm] = useState({ name: "", email: "", phone: "" });
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) =>
  setForm(prev => ({ ...prev, [e.target.name]: e.target.value }));

何时用哪种 / When to Use Which

场景选择原因
实时验证 / 搜索过滤受控需要实时访问值
简单登录表单非受控只需要提交时的值
文件上传 <input type="file">只能非受控浏览器不允许程序化设置文件
大型复杂表单React Hook Form性能优化,减少重渲染
需要格式化输入(电话号码等)受控需要拦截并格式化用户输入

经验法则 / Rule of thumb:

需要在输入时对值做什么 → 受控

只需要提交时的最终值 → 非受控(或 React Hook Form)

*If you need to do something with the value as the user types → **Controlled**. If you only need the final value on submit → **Uncontrolled** (or React Hook Form).*

📝 Quiz

{"question":"Which input type MUST use an uncontrolled component in React?","options":["type='text'","type='file'","type='checkbox'","type='radio'"],"correct_index":1}

📚 References


🧒 ELI5

想象一个白板上写字。受控组件是:你每写一个字,就立刻复印一份到 React 的笔记本里——React 始终知道白板上写了什么。非受控组件是:你随便写,React 不管——只有你喊"好了,给我看看"的时候,React 才去白板上看。大多数情况下两种都行,但如果你需要在用户写字的时候就做事(比如实时搜索),就用受控的;如果只关心最终结果,就用非受控的。

Imagine writing on a whiteboard. Controlled: every letter you write gets instantly copied into React's notebook — React always knows what's on the board. Uncontrolled: you write freely, React ignores it — only when you say "show me now" does React check the board. Both work in most cases, but if you need to react as the user types (like live search), use controlled; if you only care about the final result, use uncontrolled.

🤖 AI — Prompt Engineering: Beyond Basic Prompts

Day 21 · Phase: Growth · Est. 2 min
Category: Practical · Mode: CONCEPT (web search unavailable)

直觉解释 / Intuitive Explanation

大多数人用 LLM 的方式就像对着搜索引擎说话:"给我写一首诗"。但 prompt engineering 是一门真正的技术——理解语言模型如何"思考",然后设计输入让它给出更好的输出。

这不是在"哄"AI,而是在工程化地引导概率分布。

Most people use LLMs like a search engine: "write me a poem." But prompt engineering is a real engineering discipline — understanding how language models "think," then designing inputs that steer toward better outputs. It's not about "sweet-talking" AI; it's about engineering the probability distribution.


核心技术 / How It Works

1. Zero-Shot vs Few-Shot

# Zero-shot: 直接问
prompt = "Classify the sentiment: 'I love this product!'"
# → "Positive"

# Few-shot: 提供示例,大幅提升准确率
prompt = """
Classify sentiment:
"I love this!" → Positive
"It's broken." → Negative
"Works as expected." → Neutral
"I hate waiting so long!" → ?
"""
# → Much more consistent and accurate

为什么 few-shot 有效? 示例在 token 序列中建立了模式,模型通过 in-context learning 学会了格式和判断标准,无需重新训练。

Why does few-shot work? Examples establish a pattern in the token sequence; the model learns format and criteria through in-context learning without retraining.


2. Chain-of-Thought (CoT) 思维链

# ❌ 直接问(容易出错)
prompt = "Roger has 5 tennis balls. He buys 2 more cans of 3 balls each. How many?"
# → "11" (sometimes wrong)

# ✅ 让模型"思考"
prompt = """
Roger has 5 tennis balls. He buys 2 more cans of 3 balls each. How many does he have now?
Let's think step by step:
"""
# → "Roger starts with 5 balls.
#    He buys 2 cans × 3 balls = 6 balls.
#    Total: 5 + 6 = 11 balls."
# Much more reliable!

关键洞察: 让模型输出中间步骤,相当于给它更多"计算空间"(token 序列)。数学推理准确率可提升 20-50%。

Key insight: Making the model output intermediate steps gives it more "computation space." Math reasoning accuracy can improve 20-50%.


3. 结构化 Prompting / Structured Prompting

# System + User + Output format
SYSTEM = """
You are a senior code reviewer. 
When reviewing code:
1. Check for security vulnerabilities FIRST
2. Then performance issues
3. Then readability
Format: JSON with keys: security[], performance[], readability[]
"""

USER = "Review this: def get_user(id): return db.query(f'SELECT * FROM users WHERE id={id}')"

# → {"security": ["SQL injection vulnerability: use parameterized queries"],
#    "performance": ["SELECT * fetches unnecessary columns"],
#    "readability": ["Missing type hints, no docstring"]}

4. 高级技术 / Advanced Techniques

ReAct (Reason + Act): 让模型交替推理和行动

Thought: I need to find the current weather in SF
Action: search("weather San Francisco today")
Observation: 62°F, partly cloudy
Thought: Now I can answer
Answer: It's 62°F and partly cloudy in SF.

Self-Consistency: 同一问题运行 5 次,取最多数答案 → 准确率+15%

Constitutional AI: 让模型用一套原则自我审查输出


可运行代码 / Runnable Python Snippet

# pip install openai
import openai

client = openai.OpenAI()  # uses OPENAI_API_KEY env var

def chain_of_thought(question: str) -> str:
    """Add CoT to any question automatically."""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "Think step by step before answering."},
            {"role": "user", "content": f"{question}\n\nLet's think step by step:"}
        ],
        temperature=0  # deterministic for reasoning tasks
    )
    return response.choices[0].message.content

# Example
result = chain_of_thought("If a train travels 120km in 2 hours, how far in 5 hours?")
print(result)
# → "The train travels 120km / 2 hours = 60 km/h.
#    In 5 hours: 60 × 5 = 300 km."

应用场景 / Applications

技术最适合
Few-shot分类、格式转换、风格模仿
Chain-of-Thought数学、逻辑推理、多步骤任务
Structured outputJSON 提取、表单填写、数据处理
ReActAgent 系统、需要工具调用的任务

📚 References


🧒 ELI5

想象你让一个新同事做事情。如果你说"帮我分析这份报告",他可能不知道你要什么格式、关注什么重点。但如果你说"请按照这个格式(示例),分步骤分析,重点看安全和性能问题"——你会得到好得多的结果。Prompt engineering 就是学会怎么给 AI 同事布置任务,让它理解你真正想要什么。

Imagine asking a new coworker to do something. "Analyze this report" gets you who-knows-what. But "please follow this format (example), analyze step by step, focus on security and performance" — you get something much better. Prompt engineering is learning how to assign tasks to your AI coworker so it understands what you actually want.