← 2026-05-05 📂 All Days 2026-05-07 →

🏗️ 系统设计 / System Design — Day 30

Design an E-Commerce Platform (Advanced · Mastery Phase)


场景 / Scenario

想象你在设计一个像 Amazon 一样的电商平台。每天有数百万用户浏览商品、下订单、支付、等待发货。你的系统需要处理秒杀活动(100万人同时抢同一件商品)、保证库存准确性、同时支持搜索、推荐、履约(fulfillment)全流程。

Imagine you're designing an e-commerce platform like Amazon. Millions of users daily browse products, place orders, pay, and wait for delivery. Your system must handle flash sales (1M concurrent users on one product), guarantee inventory accuracy, and support search, recommendations, and fulfillment end-to-end.


架构图 / Architecture Diagram

┌─────────────────────────────┐ Users ──────▶ CDN ──▶ │ API Gateway / BFF │ └──────────┬──────────────────┘ │ ┌─────────────────────────┼───────────────────────────┐ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────────┐ ┌────────────────────┐ │ Product │ │ Order Service │ │ Inventory Service │ │ Service │ │ │ │ (Redis + DB) │ │ (Elastic │ │ ┌───────────┐ │ │ │ │ Search + │ │ │ Payment │ │ │ Pessimistic Lock │ │ PostgreSQL)│ │ │ Service │ │ │ for Flash Sales │ └─────────────┘ │ └───────────┘ │ └────────────────────┘ │ ┌───────────┐ │ ┌─────────────┐ │ │ Fulfillmt │ │ ┌────────────────────┐ │ Search & │ │ │ Service │ │ │ Notification │ │ Recommend │ │ └───────────┘ │ │ Service (Kafka) │ │ (Kafka │ └────────┬────────┘ └────────────────────┘ │ + ML) │ │ └─────────────┘ ┌───────▼────────┐ │ Message Queue │ │ (Kafka / SQS) │ └────────────────┘

关键设计决策 / Key Design Decisions

#### 1. 库存扣减 (Inventory Deduction)

这是电商最难的问题。两种策略:

悲观锁(秒杀场景): SELECT ... FOR UPDATE + Redis atomic decrement

乐观锁(普通场景): UPDATE SET stock=stock-1 WHERE stock > 0 AND version=N

Inventory deduction is the hardest e-commerce problem. Use pessimistic locking (SELECT FOR UPDATE + Redis) for flash sales, optimistic locking (version-based) for normal traffic.

#### 2. 订单状态机 (Order State Machine)

PENDING → PAID → PROCESSING → SHIPPED → DELIVERED ↘ CANCELLED ↗

使用 Kafka 驱动状态转换,保证最终一致性,避免分布式事务。

Use Kafka to drive state transitions. Avoid distributed transactions — embrace eventual consistency.

#### 3. 搜索 vs 查询 (Search vs Query)

  • 产品查询 → PostgreSQL (精确,ACID)
  • 商品搜索 → Elasticsearch (全文,相关性)
  • 推荐 → Feature store + ML model

为什么这样设计?/ Tradeoffs

选择优点缺点
Redis 库存原子操作,毫秒级需要异步同步到DB
Kafka 异步解耦峰值削平,容错最终一致性,更复杂
Elasticsearch全文搜索快数据同步延迟,运维复杂
微服务拆分独立扩展,隔离故障分布式一致性难,延迟高

⚠️ 别踩这个坑 / Common Mistakes

  1. 超卖(oversell):不加锁直接 stock -= 1 → 用 Redis DECR + Lua 脚本做原子操作
  2. 支付超时:用户支付中,库存一直占用 → 订单设 TTL(如15分钟),超时自动释放库存
  3. 价格竞态:促销时价格变化,但已在购物车里的旧价格 → 下单时重新验证价格
  4. 履约幂等:重复发货通知 → 每个消息带幂等键,消费者做去重

Oversell prevention, payment timeout TTL (15min hold), price re-validation at checkout, and idempotent fulfillment are the four most commonly missed details.


📚 References

🧒 ELI5

电商就像一个超级大超市。搜索是导购员(Elasticsearch),库存是收银机(Redis原子操作),支付是结账台,发货是后仓。秒杀就像限量版球鞋发售,要排队领号,不然大家都抢同一双。

An e-commerce system is like a giant supermarket. Search is the directory board, inventory is the checkout counter, payment is the cashier, and fulfillment is the warehouse. A flash sale is like limited sneaker drops — you need a queue system or everyone fights for the same pair.

💻 算法 / Algorithms — Day 31

#153 Find Minimum in Rotated Sorted Array 🟡 Medium

🧩 二分搜索模式 (4/7) — 旋转数组变体

🧩 二分搜索 (4/7) — building on the template from Day 28-30

这道题是二分搜索模式的旋转变体——数组不再完全有序,但仍保留关键单调性。

This is the rotation variant of binary search — the array isn't fully sorted, but key monotonicity is preserved.


链接 / Links


现实类比 / Real-World Analogy

你有一副扑克牌,原本按 1-52 排好序,但有人从中间切了一刀——比如从第30张切开,变成了 [31,32,...,52,1,2,...,30]。你要找最小值(也就是"切口"在哪里),但你不能逐张翻——只能用"二分猜"。

You have a deck of cards originally sorted 1-52, but someone cut the deck — e.g., [31,32,...,52,1,2,...,30]. Find the "cut point" (minimum) using binary search, not linear scan.


与模板的映射 / Mapping to Template

回顾模板:

# Standard template: find target in sorted array
left, right = 0, len(arr) - 1
while left <= right:
    mid = (left + right) // 2
    if arr[mid] == target: return mid
    elif arr[mid] < target: left = mid + 1
    else: right = mid - 1

旋转数组的变化: 我们没有 target,只有一个性质:最小值右边的所有元素 < arr[right],左边的所有元素 > arr[right](如果被旋转了)。

用 arr[mid] vs arr[right] 来决定收缩哪一半:

  • arr[mid] > arr[right]:mid 在左段(较大的一段),最小值在右边 → left = mid + 1
  • arr[mid] < arr[right]:mid 在右段(较小的一段),最小值在左边(含mid) → right = mid

The twist: instead of comparing to a target, compare arr[mid] to arr[right] to determine which half the minimum lives in.


Python 解法 + 追踪 / Solution with Trace

def findMin(nums: list[int]) -> int:
    left, right = 0, len(nums) - 1
    
    while left < right:  # Note: < not <=
        mid = (left + right) // 2
        
        if nums[mid] > nums[right]:
            # mid is in the larger left portion
            # minimum must be to the right of mid
            left = mid + 1
        else:
            # mid is in the smaller right portion
            # minimum could be mid itself
            right = mid
    
    return nums[left]  # left == right == answer

# Trace: [3,4,5,1,2]
# left=0, right=4, mid=2 → nums[2]=5 > nums[4]=2 → left=3
# left=3, right=4, mid=3 → nums[3]=1 <= nums[4]=2 → right=3
# left=3, right=3 → return nums[3] = 1 ✓

为什么用 left < right 而不是 left <= right?

因为 right = mid(不是 mid - 1),所以收敛时 left==right,不用额外检查。

Why left < right? Because we use right = mid (not mid - 1), convergence happens at left == right — no off-by-one.

时间复杂度: O(log n) 空间复杂度: O(1)


举一反三 / Connect to the Block

这道题在 Binary Search 模式块的位置:

题号题目关键变化
#704Binary Search纯模板
#74Search a 2D Matrix2D → 1D 映射
#875Koko Eating Bananas搜索答案空间
#153Find Min in Rotated旋转数组 → 比 right
#33Search in Rotated找 target(下一题!)
#981Time Based KV搜索时间戳
#4Median of Two ArraysHard,合并两段

规律: #153 和 #33 是姐妹题——先找最小值,再在两段有序数组中找 target。

#153 and #33 are sibling problems — find the pivot first, then binary search in the correct half.


📚 References

🧒 ELI5

想象把一条直线从中间折了一下。折点是最低点。每次猜中间,如果中间比右边高,说明折点在右边;如果中间比右边低,说明折点在左边(或者就是中间自己)。每次都砍掉一半,很快就找到了!

Imagine folding a line in the middle — the fold is the lowest point. Each guess, if the middle is higher than the right end, the fold is to the right; otherwise it's to the left. Halve the search space each time!


Quiz

🗣️ 软技能 / Soft Skills — Day 30

Technical Vision — Staff Level

Q: Describe how you've set technical direction for a team or organization


为什么这个问题重要 / Why This Matters

Staff+ 工程师区别于 Senior 工程师的核心:不只是解决眼前的技术问题,而是定义哪些问题值得解决、如何构建能让整个团队更快的系统。这道题考察的是 technical leadership,而不只是技术能力。

The Staff+ differentiator: not just solving technical problems, but defining which problems matter and building systems that multiply team velocity. This question probes technical leadership, not just technical ability.


STAR 框架 / STAR Breakdown

Situation(背景): 描述当时的技术现状和痛点——团队在哪里卡住了?为什么现有方向不够?

Describe the technical status quo and pain point — where was the team stuck, and why was the current direction insufficient?

Task(任务): 你的角色是什么?是被指派的还是主动发现的问题?

What was your role? Did you identify the gap proactively, or were you assigned to it?

Action(行动): 这是重点。要包含:

  • 你如何诊断问题(数据、用户访谈、系统深入分析)
  • 你如何构建共识(1:1 对话、RFC 文档、原型 demo)
  • 你如何处理反对意见(谁不同意,为什么,如何化解)
  • 你如何分解执行(roadmap、里程碑、accountability)

The action is the crux. Include: diagnosis (data/interviews/deep dives), building consensus (1:1s, RFC docs, prototypes), handling dissent, and breaking down execution into milestones.

Result(结果): 量化影响。工程效率提升多少?系统可靠性如何改变?团队文化有什么变化?

Quantify the impact: engineering efficiency gains, reliability improvements, cultural shifts.


❌ 弱回答 vs ✅ 强回答 / Bad vs Good

❌ 弱回答:

"我提出了迁移到微服务的想法,大家都同意了,然后我们执行了。"

这个答案缺乏:诊断过程、如何说服别人、遇到了什么阻力、结果的量化。

✅ 强回答:

"2022年我们的 monolith 部署时间从45分钟增长到90分钟,每次有人合并代码都要等2小时才能验证。我通过分析 CI 日志发现瓶颈在3个重度耦合的模块。我写了一个 RFC 提议提取这3个服务,但有2个 senior engineer 反对(担心分布式复杂性增加)。我做了一个小型 PoC 用 2 周时间证明通信开销可控,并邀请反对者一起 review 结果。最终我们用6个月完成迁移,部署时间降到8分钟,团队可以独立发布,季度 feature velocity 提升了40%。"

Weak: "I proposed microservices and everyone agreed." Strong: Specific pain point data, RFC with dissent, PoC to build trust, measurable outcome.


📈 Senior vs Staff 级别差异 / Senior vs Staff Differentiation

维度Senior EngineerStaff Engineer
范围单个系统/服务跨团队/多系统
时间季度内1-2年愿景
共识说服自己团队影响多个团队
指标系统指标(latency)组织指标(velocity, reliability SLA)

Staff 答案要展示 跨组织的影响力 和 multi-quarter 的时间视角。

Staff answers must show cross-organizational impact and multi-quarter time horizons.


关键要点 / Key Takeaways

  1. 数据先行:技术方向必须基于实测数据,不是直觉 / Lead with data, not intuition
  2. 异见是礼物:最强的反对意见往往暴露了你没想到的风险 / Dissent reveals blind spots — embrace it
  3. 写下来:RFC/ADR 文档是建立共识的最有效工具 / Written proposals build consensus at scale
  4. 度量变化:没有量化结果,就没有证明 / No measurement = no proof

📚 References

🧒 ELI5

设技术方向就像给登山队选路线。Senior 工程师会爬好自己的那段路。Staff 工程师需要看完整的山,决定哪条路对整个队伍最安全、最快,还要说服所有人这条路才是对的——即使有人喜欢走另一条。

Setting technical direction is like choosing a route for a climbing team. Senior engineers climb their own section well. Staff engineers survey the whole mountain, pick the best route for the entire team, and convince everyone it's right — even when some prefer a different path.

🎨 前端 / Frontend — Day 30

React.memo, useMemo, useCallback — The Memoization Triangle


真实场景 / Real Scenario

你在做一个数据 dashboard,有一个 <DataTable> 组件显示 1000 行数据,还有一个搜索框。每次用户输入一个字,父组件 re-render,DataTable 也跟着重新渲染——即使数据根本没变,页面开始卡顿。

You're building a data dashboard. A <DataTable> shows 1000 rows. A search input causes the parent to re-render on every keystroke, dragging DataTable along for the ride — even though its data didn't change. The UI starts stuttering.


三者关系 / The Triangle

React.memo ──── wraps ──────▶ Component │ receives │ ┌─────────────────────┴──────────────────────┐ ▼ ▼ useMemo useCallback (memoize a value) (memoize a function) 问题: React.memo 只对 props shallow-equal 检查。 如果 prop 是 object/function,每次 render 都是新引用 → memo 失效! useMemo/useCallback 解决的就是这个"引用稳定性"问题。

代码示例 / Code Snippet

// ❌ 问题:每次父组件 render,handleSort 和 config 都是新引用
// DataTable 的 React.memo 完全没用
function Dashboard() {
  const [query, setQuery] = useState('');
  const [data] = useState(largeDataset);

  const handleSort = (key: string) => {   // new ref every render!
    console.log('sort by', key);
  };
  
  const config = { pageSize: 50 };         // new ref every render!
  
  return (
    <>
      <input onChange={e => setQuery(e.target.value)} />
      <DataTable data={data} onSort={handleSort} config={config} />
    </>
  );
}

// ✅ 修复:稳定引用,让 React.memo 真正生效
function Dashboard() {
  const [query, setQuery] = useState('');
  const [data] = useState(largeDataset);

  const handleSort = useCallback((key: string) => {
    console.log('sort by', key);
  }, []);                                  // stable ref ✓
  
  const config = useMemo(() => ({
    pageSize: 50
  }), []);                                 // stable ref ✓
  
  return (
    <>
      <input onChange={e => setQuery(e.target.value)} />
      <DataTable data={data} onSort={handleSort} config={config} />
    </>
  );
}

const DataTable = React.memo(({ data, onSort, config }) => {
  // Now only re-renders when data/onSort/config actually change
  return <table>...</table>;
});

猜猜输出?/ Quiz

const fn1 = useCallback(() => console.log('hello'), []);
const fn2 = useCallback(() => console.log('hello'), []);

console.log(fn1 === fn2);

A) true

B) false

C) Depends on render count

D) TypeError

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

B) false

useCallback 返回的是 同一个组件实例中跨 render 稳定的引用,但 fn1 和 fn2 是两个独立的 useCallback 调用,是两个不同的函数对象。同一个 useCallback(依赖不变时)跨 render 返回相同引用,所以 fn1 === fn1(跨两次 render)会是 true,但 fn1 === fn2 永远是 false。

</details>


❌ 常见错误 vs ✅ 正确做法

情形❌ 错误✅ 正确
组件接收 object prop<C config={{size:50}} />useMemo(() => ({size:50}), [])
组件接收 callback<C onClick={() => fn(x)} />useCallback(() => fn(x), [x])
忘记依赖useMemo(() => compute(a), []) 当 a 变化时useMemo(() => compute(a), [a])
过度使用对简单 string/number prop 用 useMemo直接传值,原始类型自动 compare by value

何时用 / 何时不用 / When to Use vs Not

✅ 该用:

  • 子组件有 React.memo 包裹 + prop 是 object/function
  • 计算量大的派生数据(如排序/过滤大数组)
  • 传给 useEffect 依赖数组的函数引用

❌ 不该用:

  • prop 是 string/number/boolean(原始类型,自动 value compare)
  • 组件渲染本来就很轻(memo 本身有成本!)
  • 没有实际性能问题时(premature optimization)

Rule of thumb: profile first, memoize second. React DevTools Profiler shows which components re-render unnecessarily.


📚 References

🧒 ELI5

React.memo 像保安,拒绝没变化的 props 进门。但如果你每次都带着"新袋子"(新的 object/function 引用),保安认不出你,还是放你进去了。useMemo 和 useCallback 是让你每次带同一个袋子——这样保安才真的能拦住重复进入。

React.memo is a bouncer that blocks unchanged props. But if you bring a "new bag" (new object/function reference) every time, the bouncer doesn't recognize you and lets you in anyway. useMemo/useCallback ensure you always bring the SAME bag — so the bouncer actually works.

🤖 AI — Day 36

Multimodal AI — Vision, Audio, and Beyond


什么是多模态 AI?/ What is Multimodal AI?

早期 AI 是"盲聋"的——只能处理文字。多模态 AI 打破了这个限制:同一个模型可以同时理解图片、音频、视频和文字,就像人类用眼睛、耳朵、嘴巴感知世界一样。

Early AI was "blind and deaf" — text only. Multimodal AI breaks this barrier: one model that simultaneously understands images, audio, video, and text, like humans perceiving the world through eyes, ears, and language.


直觉解释 / Intuitive Explanation

核心思想:把不同模态转换成同一种"语言"——向量。

Text ──▶ Text Encoder ──▶ [0.2, 0.8, -0.3, ...] Image ──▶ Vision Encoder──▶ [0.2, 0.7, -0.4, ...] ← 语义相近! Audio ──▶ Audio Encoder ──▶ embedding space │ Shared Transformer (cross-attention) │ Text / Image Output

CLIP(Contrastive Language-Image Pre-training)是多模态的奠基之作。它训练时用对比学习:把"一只猫的照片"和"a photo of a cat"拉近,和不相关的文字/图片推远。训练后,文字和图片在同一个向量空间里——这让零样本图像分类成为可能。

The core insight: convert different modalities into the same "language" — vectors. CLIP pioneered this with contrastive learning: pull matching image-text pairs together, push non-matching pairs apart. After training, images and text live in the same embedding space.


主要架构类型 / Architecture Types

类型代表模型做法
对比学习CLIP, ALIGN图文对齐到同一空间
生成式多模态GPT-4V, GeminiVision encoder + LLM decoder
扩散多模态DALL·E, Stable Diffusion文字 → 图片生成
统一模态Gemini 1.5, GPT-4o原生多模态,端到端

GPT-4o 的突破: 不再是"视觉插件",而是在预训练阶段就原生处理图片和音频,延迟更低,理解更深。

GPT-4o's breakthrough: vision isn't a plugin — it's natively pretrained with images and audio, giving lower latency and deeper understanding.


应用场景 / Applications

  1. 视觉问答(VQA): 上传发票截图,自动提取金额、日期、供应商
  2. 医疗影像: X光、MRI 自动初筛异常
  3. 代码截图 → 代码: 截图变成可运行代码(GPT-4V / Claude 3)
  4. 实时语音对话: GPT-4o 实时音频对话,延迟 ~300ms
  5. 视频理解: Gemini 1.5 Pro 可以处理整部电影(~1小时视频 = ~750K tokens)

可运行代码 / Runnable Code

# pip install openai Pillow requests
import base64
from openai import OpenAI

client = OpenAI()

def analyze_image(image_path: str, question: str) -> str:
    """Ask GPT-4o a question about a local image."""
    with open(image_path, "rb") as f:
        image_data = base64.b64encode(f.read()).decode("utf-8")
    
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{image_data}"
                    }
                },
                {
                    "type": "text",
                    "text": question
                }
            ]
        }]
    )
    return response.choices[0].message.content

# Example
result = analyze_image("receipt.jpg", "What is the total amount and date?")
print(result)
# → "Total: $47.32, Date: May 6, 2026"

关键挑战 / Key Challenges

  1. 幻觉在图片上更严重: 模型会"看到"图片里没有的东西(特别是小字、数字)
  2. 对齐代价高: 图片 patch 数量远多于文字 token,序列长度爆炸(1024x1024 图 = ~1000 patches)
  3. 评估困难: 文字对错好判断;图片理解的"对"很主观
  4. 数据集偏差: LAION 等训练数据包含大量西方文化图片,非英语/非西方场景表现差

Hallucinations are worse on images, alignment is compute-expensive (1M pixel image ≈ 1000 tokens), evaluation is subjective, and training data skews heavily Western.


📚 References

🧒 ELI5

以前 AI 就像只会读书的瞎子——只能处理文字。现在多模态 AI 像有了眼睛和耳朵:它先把图片"翻译"成一种特殊的数字语言,再和文字一起理解。就像你看到一张猫的照片,大脑里自动想到"猫"这个词——AI 现在也能做到这件事了。

Before, AI was like a blind person who could only read — text only. Now multimodal AI has eyes and ears: it "translates" images into a special numeric language, then understands them together with text. Like how you see a cat photo and your brain instantly thinks "cat" — AI can do that now too.

Attention Heatmap

When the model processes it, it attends most strongly to animal.

Theanimaldidntcrossthestreetbecauseitwastootired
it →