Week 4 · Chapter 1 · 系统设计场景题

复习难度:⭐⭐⭐⭐⭐ | 预计时长:6-8小时 | 重点程度:高


解题套路

1. 听清需求不要急着画图先问清楚
   - QPS多少用户量峰值多少
   - 需要强一致还是最终一致
   - 功能范围边界是什么

2. 算容量
   - 读多写少  缓存优先
   - 写多读少  消息队列削峰
   - 存储量 = 单条大小 × 条数 × 副本系数

3. 画出核心架构
   - 接入层  服务层  存储层
   - 标注清楚关键决策点

4. 讲清 trade-off
   - 选了什么放弃了什么
   - 可能的瓶颈在哪里

场景1:设计审批流引擎(你简历里有)

需求拆解

- 多业务系统接入(20+)
- 4种审批模式:顺签/抢签/多人会签/组内抢签+组间会签
- 5000+员工,累计57万+审批单
- 需要规则引擎 + 时效管理 + 催办机制

核心数据模型

// 审批流程定义
type Workflow struct {
    ID       string
    Name     string
    Nodes    []Node   // 审批节点列表
    Edges    []Edge   // 流程连线
    RuleID   string   // 关联规则引擎
}

// 审批节点
type Node struct {
    ID         string
    Type       string  // START/APPROVER/COPY/END
    ApproveType string // SINGLE/SIGN/RUSH/SIGN_RUSH
    Assignees  []string // 用户ID列表或用户组ID
    TimeoutMs  int64    // 超时时间(毫秒)
}

// 审批单
type ApprovalOrder struct {
    ID           string
    WorkflowID   string
    Status       string  // PENDING/APPROVED/REJECTED/CANCELLED
    CurrentNodeID string
    CreatedAt    time.Time
    ExpiredAt    time.Time  // 时效截止时间
}

规则引擎设计

设计思路
  规则 = 条件Condition+ 动作Action

  条件表达
    - 申请金额 > 10000  需要部门总监审批
    - 申请人属于XX部门  路由到XX审批节点
    - 时效超时72h  自动催办

实现方案
  - 简单版决策表Decision Table
  - 中等决策树Decision Tree
  - 复杂DroolsJava规则引擎 / 自己实现表达式引擎

  表达式引擎示例Go):
    evaler := NewEvaluator("amount > 10000 && dept == 'IT'")
    result, _ := evaler.Eval(map[string]any{"amount": 50000, "dept": "IT"})

高并发抢签(Redis分布式锁)

抢签 = 同一时间只一个人能签
实现:
  1. SELECT FOR UPDATE 加行锁(DB层)
  2. Redis SETNX + Lua 脚本原子判断

Redis 抢签:
  key = "approval:rush:{orderId}"
  Lua:
    if redis.call('GET', key) then return 0 end
    redis.call('SET', key, uid, 'EX', 300)
    return 1

场景2:设计权限系统(RBAC)

双轨 RBAC 设计(你简历里有)

功能型角色:定义"能做什么"(如:审批管理员、查看员)
数据型角色:定义"能对谁做"(如:只看本部门数据)

绑定关系:
  用户 → 功能角色 → 权限
  用户 → 数据角色 → 数据范围

例:
  用户A(属于IT部门)→ 功能角色=审批人 + 数据角色=IT部门可见
  → 只能审批IT部门提交的单据

权限快速鉴权

// 传统方式:用户→角色→权限,N次查询
func CheckPermission(userID, resource, action string) bool {
    roles := db.GetRolesByUser(userID)           // 查N次
    perms := db.GetPermsByRoles(roles)           // 再查N次
    return perms.Contains(resource, action)
}

// 优化:权限数据加载到 Redis,毫秒级鉴权
func CheckPermissionCache(userID, resource, action string) bool {
    key := fmt.Sprintf("perm:%s", userID)
    perms := redis.Smembers(key)
    permKey := fmt.Sprintf("%s:%s", resource, action)
    return perms[permKey]
}

// 优化2:布隆过滤器快速过滤(不存在一定不存在,存在再做精确判断)

场景3:设计高并发抢单

需求:限时抢单,1000 QPS,库存有限
核心问题:
  1. 库存超卖
  2. 分布式会话
  3. 下游服务压力

防超卖

// DB层:乐观锁
UPDATE inventory SET stock = stock - 1 
WHERE id = ? AND stock > 0

// Redis层:Lua原子扣减
local stock = redis.call('GET', 'inventory:'..itemId)
if not stock or tonumber(stock) <= 0 then return 0 end
redis.call('DECR', 'inventory:'..itemId)
return 1

整体架构

限流网关 → 抢单服务 → Redis库存预扣 → 消息队里 → 异步下单
                        ↓
                  库存不足 → 直接返回抢单失败

场景4:百万级零差错(你的简历亮点)

背景:游戏活动奖励发放,百万级用户参与
核心要求:
  1. 奖励不能多发(超发)
  2. 奖励不能少发(漏发)
  3. 数据一致性

解法

1. 幂等发放每个用户每个活动只有一条领取记录UNIQUE KEY
2. 事务保证活动参与 + 记录创建在同一个事务里
3. 异步补偿MQ消费失败  定时任务扫描未完成  重试
4. 对账机制日终对账统计应发/实发/差异
// 幂等发放核心逻辑
func AwardUser(tx *gorm.DB, userID, activityID string) error {
    // 1. 幂等检查:是否已发放
    var record ActivityAwardRecord
    err := tx.Where("user_id=? AND activity_id=?", userID, activityID).First(&record).Error
    if err == nil {
        return nil // 已发放,直接返回
    }

    // 2. 事务保证:扣库存 + 写发放记录
    err = tx.Transaction(func(tx *gorm.DB) error {
        // 扣库存(乐观锁)
        result := tx.Exec(`UPDATE activity_stock SET stock=stock-1 
            WHERE activity_id=? AND stock>0`, activityID)
        if result.RowsAffected == 0 {
            return errors.New("库存不足")
        }

        // 写发放记录
        return tx.Create(&ActivityAwardRecord{
            UserID:     userID,
            ActivityID: activityID,
            Status:     "AWARDED",
        }).Error
    })

    return err
}

高频追问

Q:如何保证接口幂等性?

① 唯一键(DB唯一索引 / Redis SETNX) ② 每次操作带全局唯一请求ID,服务端去重 ③ Token 机制:先获取 Token,提交时校验 Token 并删除

Q:设计一个延迟任务队列?

① RabbitMQ 延时插件(最大2小时) ② RocketMQ 延时消息(定时投递) ③ Redis ZSet(score=执行时间,定时扫描) ④ 死信队列(TTL过期)