← 2026-03-18 📂 All Days 2026-03-20 →
🏗️
🏗️ System Design
🏗️

🏗️ 系统设计 Day 6 / System Design Day 6

CDN (Content Delivery Network) — 内容分发网络


想象你在设计... / Imagine You're Building...

你在上海开了一家咖啡豆网店,客户遍布全球。每次有人从纽约访问你的网站,请求要飞越太平洋到上海服务器取图片、CSS、JS,再飞回去。往返 200ms+,用户等得花都谢了 🌸。

解决方案?在纽约、伦敦、东京都放一份你网站的静态资源副本。用户访问时,就近取货。

这就是 CDN(内容分发网络)

You run a coffee bean shop in Shanghai with global customers. Every request from NYC flies across the Pacific and back — 200ms+ round trip. Solution? Cache copies of your static assets in NYC, London, Tokyo. Users get served from the nearest copy. That's a CDN.


架构图 / Architecture Diagram

用户请求 User Request │ ┌──────┴──────┐ │ DNS 解析 │ │ (返回最近的 │ │ CDN 节点) │ └──────┬──────┘ │ ┌───────────────────┼───────────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ CDN Edge │ │ CDN Edge │ │ CDN Edge │ │ 纽约 │ │ 伦敦 │ │ 东京 │ │ ████████ │ │ ████ │ │ ██████ │ │ (cached) │ │ (cached) │ │ (cached) │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ └───────────────────┼───────────────────┘ │ cache miss 时回源 ▼ ┌──────────────┐ │ Origin Server│ │ 源站 (上海) │ └──────────────┘

核心概念 / Key Concepts

1. Cache Hit vs Cache Miss

  • Hit(命中): CDN 节点有缓存 → 直接返回,超快(< 50ms)
  • Miss(未命中): CDN 没有 → 回源站取,存一份,下次就 Hit 了

2. TTL (Time To Live)

  • 缓存过期时间。太短 → 频繁回源;太长 → 用户看到旧内容
  • 静态资源(图片/CSS/JS):TTL 长(1天-1年),文件名带 hash(app.a3f2b1.js
  • API 响应:TTL 短(几秒-几分钟)或不缓存

3. Cache Invalidation(缓存失效)

  • 发布新版本时需要清除旧缓存
  • 方法 1:Purge API(主动清除指定 URL)
  • 方法 2:文件名 hash(新版本 = 新文件名 = 自动绕过旧缓存)✅ 推荐

4. Push vs Pull CDN

  • Pull: CDN 节点在第一次请求时从源站拉取(大多数 CDN 默认行为)
  • Push: 你主动上传内容到 CDN(适合大文件、已知内容)

别踩这个坑 / Don't Fall Into This Trap

❌ 把带用户个人信息的 API 响应(如 /api/me)也放 CDN 缓存

→ 用户 A 看到用户 B 的数据!

✅ 只缓存公开、不含个人信息的内容。私有内容设 Cache-Control: private, no-store

⚠️ CDN 缓存了错误的 response(比如 500 错误页面)

→ 设置:只缓存 2xx 响应,或设置很短的负缓存 TTL

⚠️ 源站宕机时 CDN 还能服务(这是优点!)但如果 TTL 过了且源站还没恢复 → "stale-while-revalidate" 策略可以继续用旧缓存


面试要点 / Interview Key Points

  1. CDN 降低延迟(地理距离)+ 减轻源站压力
  2. 适合静态资源;动态内容需要谨慎(考虑 Edge Computing)
  3. 常见 CDN:CloudFront (AWS), Cloudflare, Akamai, Fastly
  4. CDN + Load Balancer + Cache = 高性能系统的三驾马车

Day 6 / 150 — 系统设计基础系列

💻
💻 Algorithms
💻

💻 算法 Day 6 / Algorithms Day 6 — #271 Encode and Decode Strings (Medium) — Arrays & Hashing

🔗 https://leetcode.com/problems/encode-and-decode-strings/


现实类比 / Real-World Analogy

你要把一堆购物清单放进一个信封里寄出去。问题是:收件人怎么知道哪里是一个清单的结束、另一个的开始?

笨办法:用逗号分隔 → 但如果清单内容本身有逗号呢?

聪明办法:每个清单前面写上它的长度 → `"5#Hello3#Bye"` → 无歧义!

题目 / Problem Statement

设计一个算法,将字符串列表编码为单个字符串,再解码回原始列表。

Design an algorithm to encode a list of strings into a single string, and decode it back.

Input:  ["Hello", "World"]
Encode: "5#Hello5#World"
Decode: ["Hello", "World"]

编码后的字符串可以包含任何字符(包括 #、空字符串等),必须无歧义。


追踪过程 / Trace Through

编码 ["Hi", "", "a#b"]

"Hi" → len=2 → "2#Hi" "" → len=0 → "0#" "a#b" → len=3 → "3#a#b" encoded = "2#Hi0#3#a#b"

解码 "2#Hi0#3#a#b"

i=0: find '#' at index 1 → length=2 → read "Hi" → i=4 i=4: find '#' at index 5 → length=0 → read "" → i=6 i=6: find '#' at index 7 → length=3 → read "a#b" → i=11 Done! → ["Hi", "", "a#b"] ✅

注意 "a#b" 内部的 # 不会造成混淆,因为我们是按长度读取的,不是按分隔符!


Python 解法 / Python Solution

class Codec:
    def encode(self, strs: list[str]) -> str:
        # Format: "length#string" for each string
        result = []
        for s in strs:
            result.append(f"{len(s)}#{s}")
        return "".join(result)

    def decode(self, s: str) -> list[str]:
        result = []
        i = 0
        while i < len(s):
            # Find the '#' delimiter
            j = s.index('#', i)
            # Characters before '#' are the length
            length = int(s[i:j])
            # Read exactly 'length' characters after '#'
            result.append(s[j + 1 : j + 1 + length])
            # Move pointer past the string
            i = j + 1 + length
        return result

复杂度 / Complexity

  • 时间 Time: O(n) — n 是所有字符串总长度
  • 空间 Space: O(1) — 不算输出空间(编码和解码都是线性扫描)

边界情况 / Edge Cases

codec = Codec()

# Empty list
codec.decode(codec.encode([])) == []  # ✅

# List with empty string
codec.decode(codec.encode([""])) == [""]  # ✅ "0#" → [""]

# Strings containing '#'
codec.decode(codec.encode(["a#b", "#"])) == ["a#b", "#"]  # ✅

# Very long string
codec.decode(codec.encode(["a" * 10000])) == ["a" * 10000]  # ✅

举一反三 / Pattern Recognition

模式:长度前缀编码(Length-Prefixed Encoding)

这和网络协议(TCP、HTTP/2)、序列化格式(Protocol Buffers)用的是同一个思路:先告诉你"接下来有多少字节",然后精确读取。

为什么不用特殊分隔符(如 |\0)?

→ 字符串可能包含任何字符!长度前缀永远不会歧义。

相关题目:

  • #443 String Compression(类似的编码思维)
  • 序列化/反序列化(#297 Serialize and Deserialize Binary Tree)

Day 6 / 150 — Arrays & Hashing 系列

昨天 Day 4:Group Anagrams | 明天 Day 7:Product of Array Except Self

🗣️
🗣️ Soft Skills
🗣️

🗣️ 软技能 Day 6 / Soft Skills Day 6

Leadership — 领导力(不靠头衔)

问题 / Question:

"Describe how you've mentored or grown other engineers."
"描述你是如何指导或培养其他工程师的。"

为什么这很重要 / Why This Matters

Senior/Staff 工程师的核心职责之一不是写更多代码,而是让团队里每个人都变得更强。面试官问这题是想看:

  1. 你有没有"利他"意识(不只关心自己的 output)
  2. 你的指导方式是否有结构、有结果
  3. 你能不能识别他人的成长需求

At senior+ levels, your impact is measured by the engineers you've grown, not just the code you've shipped.


❌ 糟糕的回答 / Bad Approach

"我经常帮初级工程师 review 代码,给他们指出问题,告诉他们怎么改。"

问题:

  • 这只是日常工作,不算 mentoring
  • "告诉他们怎么改" = 给答案,不是教思考
  • 没有具体故事、没有成长轨迹

✅ 好的回答框架 / Good Approach (STAR)

Situation: "团队来了一位从 bootcamp 毕业的新工程师 Alex。他代码能 work,但 PR 里经常缺乏边界处理和测试,review 来回很多轮。"

Task: "作为他的 buddy engineer,我的目标不只是帮他过 PR,而是让他在 3 个月内能独立负责一个模块。"

Action:

  • "我没有直接告诉他'加个 null check',而是在 PR comment 里问引导性问题:'如果这个参数是 undefined 会发生什么?'让他自己发现问题"
  • "每周 1:1 花 30 分钟,前 15 分钟他讲本周遇到的困难,后 15 分钟我分享一个设计决策的背景(为什么我们用 Redis 而不是 Memcached)"
  • "第 6 周开始让他 lead 一个小 feature 的设计 doc,我只 review 不动手"

Result: "3 个月后,他的 PR 首次 review 通过率从 20% 提升到 75%。第 4 个月他独立 ship 了用户通知系统。半年后他成了团队里 on-call 最可靠的人之一。"


Senior/Staff 级别技巧 / Senior/Staff Tips

🎯 从"给答案"到"问问题"

  • 初级:直接告诉他怎么做(他需要 unblock)
  • 中级:给方向,让他自己找路
  • 高级:只问问题,让他自己发现问题和答案

🎯 Mentoring ≠ 只有 1:1

  • 写好的设计文档 = 一次写,教会所有人
  • 做技术分享 / lunch & learn = 批量 mentoring
  • 建立团队 wiki / onboarding guide = 可扩展的知识传播

🎯 跟踪成长,不只是感觉

  • 用具体指标说话(PR 通过率、独立完成的 feature 数、on-call 表现)
  • "他成长了"太模糊,"他从需要 3 轮 review 到 1 轮"才有说服力

关键要点 / Key Takeaways

  1. Mentoring 的核心是授人以渔 — 教思考方式,不只是给答案
  2. 好的 mentor 会有意识地退后 — 让 mentee 犯可控的错误并从中学习
  3. 具体的成长指标证明你的 mentoring 有效果
  4. 最高级的 leadership:建立系统和文化,让 mentoring 自然发生(不依赖你个人)

Day 6 / 150 — 软技能系列

🎨
🎨 Frontend
🎨

🎨 前端 Day 6 / Frontend Day 6

CSS Variables & Modern CSS Features — CSS 自定义属性


猜猜这段代码输出什么?/ What does this layout look like?

:root {
  --primary: #3b82f6;
  --spacing: 16px;
  --radius: 8px;
}

.card{
  --primary: #ef4444;  /* 局部覆盖 */
  padding: var(--spacing);
  border: 2px solid var(--primary);
  border-radius: var(--radius);
}

.card .badge{
  background: var(--primary);
  color: white;
  padding: calc(var(--spacing) / 4) calc(var(--spacing) / 2);
  border-radius: var(--radius);
}

🤔 .badgebackground 是蓝色 #3b82f6 还是红色 #ef4444

答案:红色 #ef4444

CSS Variables 遵循继承规则.card 里重新定义了 --primary,它的所有后代元素(包括 .badge)都会继承这个新值。这和 Sass/Less 变量完全不同(它们是编译时替换,不支持继承)。


CSS Variables 核心 / Key Concepts

1. 定义与使用

/* 定义:用 -- 前缀 */
:root {
  --color-text: #1a1a1a;
  --font-size-base: 16px;
}

/* 使用:用 var() 函数 */
body{
  color: var(--color-text);
  font-size: var(--font-size-base);
}

/* 带 fallback 值 */
p{
  color: var(--color-accent, #666);  /* 如果 --color-accent 未定义,用 #666 */
}

2. 运行时 vs 编译时

Sass: $primary: blue → 编译后变成 → color: blue(写死了) CSS: --primary: blue → 运行时读取 → 可以随时改!

这意味着你可以:

  • 用 JS 动态改变 → document.documentElement.style.setProperty('--primary', 'red')
  • 根据 media query 改变 → 深色模式只需重新定义变量
  • 组件内局部覆盖 → 不影响全局

3. Dark Mode 只需几行

:root {
  --bg: #ffffff;
  --text: #1a1a1a;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1a1a;
    --text: #f0f0f0;
  }
}

body{
  background: var(--bg);
  color: var(--text);
}

你可能不知道 / You Might Not Know

CSS Variables 可以用在 calc() 里做数学运算:

:root {
  --base: 8px;
}

.component{
  padding: calc(var(--base) * 2);      /* 16px */
  margin: calc(var(--base) * 3);       /* 24px */
  border-radius: calc(var(--base) / 2); /* 4px */
}

这就是 Design Token 的基础 — 用一个基数推导出整个间距系统(4px, 8px, 12px, 16px, 24px, 32px...),改一个值就改全部。Tailwind CSS 内部就是这个原理。


⚠️ 常见陷阱 / Gotcha

.box{
  --size: 100;
  width: var(--size)px;  /* ❌ 不行!不会变成 100px */
  width: calc(var(--size) * 1px);  /* ✅ 这样才行 */
}

CSS Variables 存的是字符串var(--size)px 会变成 100 px(中间有空格),无效。必须用 calc() 做单位转换。


Mini Challenge 🧩

不看上面的内容,回答:

:root { --gap: 10px; }
.parent { --gap: 20px; }
.parent .child { margin: var(--gap); }

.childmargin 是多少?

答案:20px.parent 覆盖了 --gap.child 作为后代继承 .parent 的值。


Day 6 / 150 — CSS 基础系列

昨天 Day 4:Responsive Design & Media Queries | 明天 Day 7:JavaScript DOM 基础

🤖
🤖 AI
🤖

🤖 AI Day 6 — 新闻速递 / News Roundup

2026年3月 | March 2026

⚠️ 以下为基于公开报道的摘要,具体数字以原始来源为准。
Based on public reporting; verify specifics at original sources.

📰 1. Claude 4 发布 — Anthropic 的新旗舰

Anthropic 发布了 Claude 4 系列模型,包含 Claude 4 Opus(旗舰推理模型)和 Claude 4 Sonnet(平衡性能/速度)。据报道在编码、数学推理和长文档分析上有显著提升。

为什么你应该关心:

Claude 4 是 GPT-5 之后的又一个"代际跳跃"。如果你在做 AI 产品,现在有两个顶级推理模型可选。对开发者来说,更强的编码能力意味着 AI pair programming 又升了一个台阶。


📰 2. 开源模型追赶闭源 — Llama 4 / Qwen 3

Meta 的 Llama 4 和阿里的 Qwen 3 系列在基准测试上接近 GPT-4 级别。Llama 4 Scout(17B 参数)在 MMLU 上接近 GPT-4o 水平。

为什么你应该关心:

开源模型的质量已经到了"够用"的临界点。对创业公司意味着:不再被 OpenAI/Anthropic 的定价绑定。对大公司意味着:可以在私有基础设施上部署,数据不出内网。如果你在选模型,先评估开源方案——可能省 90% 的 API 成本。


📰 3. AI Agent 框架爆发

2026年初,AI Agent(智能体)从概念变成了产品。OpenAI 的 Operator、Anthropic 的 Computer Use、以及开源的 browser-use 等项目让 AI 能直接操作浏览器、写代码、管理文件。

为什么你应该关心:

"AI 能帮你查东西"和"AI 能帮你做事"是两个完全不同的级别。Agent 意味着 AI 从"回答问题"升级到"执行任务"。工程师需要思考:你的产品/API 是否对 Agent 友好?是否有好的错误提示让 Agent 能自动恢复?


📰 4. 欧盟 AI Act 正式执行

欧盟的 AI 法案(AI Act)开始分阶段执行。高风险 AI 系统(医疗诊断、招聘筛选、信用评分)需要通过合规审查,包括数据来源透明度、偏见测试、人工审核机制。

为什么你应该关心:

如果你的产品面向欧洲用户,这不是"以后再说"——它现在就是法律了。即使你在美国,欧盟用户的 GDPR 合规 + AI Act 合规 = 你需要知道你的模型用了什么数据、做了什么决策。文档化不是可选的。


📰 5. Vibe Coding 成为主流开发方式

"Vibe Coding"——用自然语言描述需求、让 AI 生成代码、开发者做审查和架构决策——在 2026 年成为被广泛接受的开发方式。Cursor、Copilot、Claude Code 等工具的日活用户据报道已超百万。

为什么你应该关心:

不是"AI 会不会取代工程师",而是"用 AI 的工程师会不会取代不用的"。核心变化:写代码的成本降低了,但设计正确系统的能力反而更值钱了。算法理解、系统设计、代码审查能力变得更重要,因为你要能审查 AI 写的代码。


Day 6 / 150 — AI 新闻系列