1. 项目背景

1.1 业务场景

订单系统最怕重复提交。用户在支付页面连点两次"提交订单"按钮、客户端因超时自动重试、网关因网络抖动重复转发——如果后端不做防护,一次购买就会变成两张订单。虽然数据库唯一索引能兜底,但如果所有重复请求都打到数据库执行完整的插入—冲突—回滚流程,在秒杀高峰期仍然会浪费大量数据库资源。

Redis 分布式锁被广泛用于做"入口防重"——同一个用户、同一个业务单号,在一小段时间内只允许一个请求进入核心业务逻辑。相比于数据库行锁,Redis 锁的优势是轻量、快、跨进程可见。但它的风险也显而易见——锁会过期、业务会超时、客户端可能误删别人的锁、Redis 主从切换可能带来锁的一致性问题。

1.2 锁的演进史

阶段 实现 问题
阶段 1 SETNX lock 1 + EXPIRE lock 10(两条命令) 两步之间宕机 → 死锁
阶段 2 SET lock token NX EX 10(原子命令) 锁过期后业务没做完 → 误删别人锁
阶段 3 原子加锁 + Lua 校验 token 释放 锁过期与业务超时仍无法完美匹配
阶段 4 看门狗自动续期 续期线程本身有可靠性问题
阶段 5 Redlock(多节点投票) 有工程争议,时钟和 GC 影响安全性

1.3 本章目标

  • 实现标准的 Redis 锁:SET key token NX PX ttl + Lua 释放。
  • 讨论锁过期、业务超时、误删保护、可重入问题。
  • 理解看门狗续期的收益与风险。
  • 客观评价 Redlock——它能解决什么、不能解决什么。
  • 明确:Redis 锁降低并发冲突概率,但不提供强一致保证。

2. 项目设计

2.1 第一幕:SETNX 怎么就不行了?

小胖:"锁不就是 SETNX lock:order 1 吗?拿到了就干活,干完 DEL 掉。"

小白:"如果你的服务拿到锁后宕机了——锁永远不会被删除,这个订单号就会被永久锁住。所以锁必须有过期时间。但如果你用两条命令——SETNX lock 1 然后 EXPIRE lock 10——在两条命令之间宕机,EXPIRE 没执行,又死锁了。"

大师:"所以加锁必须是一条原子命令。Redis 2.6.12 起,SET 支持 NX(不存在才设)和 EX/PX(过期时间),一条命令完成加锁+设 TTL。"

# 错误!两条命令不原子
SETNX mall:lock:order:9001 1
EXPIRE mall:lock:order:9001 10

# 正确!一条原子命令
SET mall:lock:order:9001 "token-uuid-001" NX EX 10

技术映射:原子加锁 = SET key token NX EX ttl。token 必须是唯一随机值(UUID),用于释放时校验"是不是我持有的锁"。

小胖又问:"那释放锁直接 DEL 不就完了?"

大师:"不能直接 DEL。如果你的业务执行超时了,锁自动过期了,另一个请求拿到了新锁——你再去 DEL,删的是别人的锁。所以释放前必须校验 token。"

# 错误!直接 DEL → 可能误删别人的锁
DEL mall:lock:order:9001

# 正确!Lua 校验 token 后删除
EVAL "if redis.call('GET',KEYS[1]) == ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 1 mall:lock:order:9001 "token-uuid-001"

2.2 第二幕:锁过期了业务没做完怎么办?

小胖:"那我 TTL 设长一点不就行了?设 30 秒肯定够了。"

小白:"你永远不知道业务会多慢。数据库突然慢查询、下游服务超时、Full GC——任何一点抖动都可能导致业务耗时超过 TTL。TTL 设太长——服务宕机后别人等太久;设太短——业务没完成锁就过期了。"

大师:"这里有两个策略:"

策略 A: 不做续期,TTL 略大于 P99 执行时间
  适用: 业务执行时间稳定、可预测
  风险: 如果偶尔超过 TTL → 锁提前释放 → 可能出现两个请求同时执行
  防护: 业务内部仍有数据库唯一约束兜底

策略 B: 看门狗自动续期
  实现: 获取锁后启动后台线程,每隔 TTL/3 检查并续期(PEXPIRE)
  适用: 业务执行时间波动大
  风险: 续期线程宕机/GC 暂停 → 锁仍可能过期;异常任务被无限续期
  防护: 设置最大续期次数或最大锁持有时间
// 看门狗续期示意
public class WatchDogLock implements AutoCloseable {
    private final String lockKey;
    private final String token;
    private final ScheduledExecutorService watchdog;
    private final AtomicBoolean running = new AtomicBoolean(true);

    public WatchDogLock(String lockKey, String token, int ttlMs) {
        this.lockKey = lockKey;
        this.token = token;
        this.watchdog = Executors.newSingleThreadScheduledExecutor();

        // 每 ttl/3 毫秒检查并续期
        watchdog.scheduleAtFixedRate(() -> {
            if (running.get()) {
                // 续期:仅当锁还是自己的
                redis.eval("if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) else return 0 end",
                    Collections.singletonList(lockKey),
                    Arrays.asList(token, String.valueOf(ttlMs)));
            }
        }, ttlMs / 3, ttlMs / 3, TimeUnit.MILLISECONDS);
    }

    @Override
    public void close() {
        running.set(false);
        watchdog.shutdown();
        // Lua 安全释放
        redis.eval(UNLOCK_LUA, Collections.singletonList(lockKey), Collections.singletonList(token));
    }
}

技术映射:看门狗不是银弹——它让锁更可靠,但也让故障恢复更慢。如果你使用了看门狗,必须设定最大持有时间,防止异常任务无限占用锁。

2.3 第三幕:Redlock——五个 Redis 投票,更安全了吗?

小胖:"我听说 Redlock 算法——向 5 个独立 Redis 主节点申请同一把锁,超过半数成功才算获取。这样即使一个 Redis 挂了,锁还是安全的。"

大师:"Redlock 确实能降低单点 Redis 故障导致的锁失效。但它有两个前提条件,在现实环境中很难完美满足——第一,所有节点的时钟漂移必须在极小范围内(这本身就很难保证);第二,客户端不会因为 GC 暂停而导致锁过期后仍认为自己持有锁。"

Redlock 流程:

  Client                          Redis1  Redis2  Redis3  Redis4  Redis5
    │                                │       │       │       │       │
    ├── SET lock token NX PX 10 ──▶  ✓       │       │       │       │
    ├── SET lock token NX PX 10 ──▶          ✓       │       │       │
    ├── SET lock token NX PX 10 ──▶                  ✓       │       │
    ├── SET lock token NX PX 10 ──▶                          ✗ (超时)│
    ├── SET lock token NX PX 10 ──▶                                  ✓
    │                                                                │
    │  3/5 成功 → 锁获取成功                                          │
    │  实际有效时间 = TTL - 网络耗时 - 时钟漂移预算                    │

大师的结论:"Redlock 减少了但未消除风险。Martin Kleppmann 和 Redis 作者 antirez 之间的著名辩论已经说明了——Redlock 不能提供像 ZooKeeper 或 etcd 那样的强一致保证。我的工程建议是——如果操作的失败代价可接受(如重复发了一封邮件),单实例 Redis 锁足够。如果涉及资金或关键状态,以数据库事务和唯一约束为准,Redis 锁只做前置削峰。"

方案 安全性 复杂度 适用场景
单实例 Redis 锁 中等 低 防重提交、定时任务互斥
单实例 + 看门狗 中高 中 业务耗时不确定的互斥
Redlock 高(但非强一致) 高 对单点故障敏感的场景
数据库唯一约束 强 低 最终一致性兜底

3. 项目实战

3.1 环境准备

docker exec -it redis-lab redis-cli -a redis123

3.2 实战一:标准加锁与释放

# 加锁
127.0.0.1:6379> SET mall:lock:submit:order:9001 "token-abc-123" NX EX 10
OK
# 第二个请求尝试加锁
127.0.0.1:6379> SET mall:lock:submit:order:9001 "token-xyz-456" NX EX 10
(nil)     # 锁已存在,获取失败

# 查看锁的持有者
127.0.0.1:6379> GET mall:lock:submit:order:9001
"token-abc-123"

# 安全释放(Lua 比较后删除)
127.0.0.1:6379> EVAL "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 1 mall:lock:submit:order:9001 "token-abc-123"
(integer) 1     # 释放成功

# 用错误的 token 尝试释放
127.0.0.1:6379> EVAL "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 1 mall:lock:submit:order:9001 "wrong-token"
(integer) 0     # 释放失败

3.3 实战二:订单防重复提交完整实现

public class OrderSubmitService {
    private static final int LOCK_TTL_MS = 5000;
    private static final String UNLOCK_LUA =
        "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end";

    public SubmitResult submit(String userId, String requestId, OrderData data) {
        String lockKey = "mall:lock:submit:user:" + userId + ":request:" + requestId;
        String token = UUID.randomUUID().toString();

        // 1. 尝试获取防重锁
        Boolean locked = redis.setIfAbsent(lockKey, token, Duration.ofMillis(LOCK_TTL_MS));
        if (!Boolean.TRUE.equals(locked)) {
            return SubmitResult.DUPLICATE_REQUEST;  // 重复提交
        }

        try {
            // 2. 幂等检查(数据库唯一约束)
            if (orderRepository.existsByRequestId(requestId)) {
                return SubmitResult.ALREADY_PROCESSED;
            }

            // 3. 执行核心业务
            Order order = createOrder(userId, data);
            orderRepository.insertWithRequestId(order, requestId);  // 唯一约束防重

            return SubmitResult.success(order.getId());

        } finally {
            // 4. 释放锁
            redis.eval(UNLOCK_LUA,
                Collections.singletonList(lockKey),
                Collections.singletonList(token));
        }
    }
}

3.4 实战三:模拟锁过期与误删保护

# 终端 A: 加一个 3 秒过期的锁
127.0.0.1:6379> SET mall:lock:demo "token-A" NX EX 3
OK

# 等待 3 秒后锁过期...

# 终端 B: 获取新锁
127.0.0.1:6379> SET mall:lock:demo "token-B" NX EX 10
OK

# 终端 A(还不知道自己锁过期了)尝试释放锁:
127.0.0.1:6379> EVAL "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 1 mall:lock:demo "token-A"
(integer) 0     # ← 不会误删!因为 token 不匹配了

3.5 实战四:不同锁方案的对比压测

# 测试锁的获取性能
# 无竞争时 SET NX EX 的延迟约 0.2-0.3ms
redis-benchmark -a redis123 -t set -n 10000 -q

# 有竞争时(模拟多个客户端同时尝试同一把锁):
# 用 Lua 脚本模拟竞争:
# redis-cli -a redis123 EVAL "return redis.call('SET',KEYS[1],ARGV[1],'NX','EX',ARGV[2])" 1 lock:test "token" 10

3.6 实战五:生产级锁工具封装

public class RedisLock implements AutoCloseable {

    private final StringRedisTemplate redis;
    private final String lockKey;
    private final String token;
    private final long ttlMs;

    private static final String UNLOCK_LUA =
        "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end";

    public static RedisLock tryLock(StringRedisTemplate redis, String lockKey, long ttlMs) {
        String token = UUID.randomUUID().toString();
        Boolean ok = redis.opsForValue()
            .setIfAbsent(lockKey, token, Duration.ofMillis(ttlMs));
        if (!Boolean.TRUE.equals(ok)) return null;
        return new RedisLock(redis, lockKey, token, ttlMs);
    }

    private RedisLock(StringRedisTemplate redis, String lockKey, String token, long ttlMs) {
        this.redis = redis;
        this.lockKey = lockKey;
        this.token = token;
        this.ttlMs = ttlMs;
    }

    @Override
    public void close() {
        redis.execute((RedisCallback<Object>) conn ->
            conn.eval(UNLOCK_LUA.getBytes(),
                org.springframework.data.redis.connection.ReturnType.INTEGER,
                1,
                lockKey.getBytes(), token.getBytes()));
    }

    // Try-with-resources 用法:
    // try (RedisLock lock = RedisLock.tryLock(redis, "lock:order:123", 5000)) {
    //     if (lock == null) return "重复请求";
    //     // 执行业务...
    // }
}

3.7 实战六:可重入锁实现

有时同一个线程需要多次获取同一把锁(如在递归业务中),需要可重入锁:

-- reentrant_lock.lua
-- KEYS[1]: 锁 key
-- ARGV[1]: token(线程标识)
-- ARGV[2]: TTL(毫秒)
-- 返回: 1=加锁成功(首次或重入), 0=锁被持有且不是同一线程

local key = KEYS[1]
local token = ARGV[1]
local ttl = tonumber(ARGV[2])

local currentToken = redis.call('GET', key)
if currentToken == false then
    -- 锁空闲 → 加锁,重入计数=1
    redis.call('HINCRBY', key .. ':reentry', token, 1)
    redis.call('SET', key, token, 'PX', ttl)
    return 1
end

if currentToken == token then
    -- 同一线程 → 可重入
    redis.call('HINCRBY', key .. ':reentry', token, 1)
    redis.call('PEXPIRE', key, ttl)  -- 续期
    return 1
end

return 0  -- 锁被其他线程持有

3.8 实战七:锁的监控与告警

@Component
public class LockMonitor {

    // 监控锁的持有时间分布
    public void recordLockMetrics(String lockKey, long holdMs) {
        metricsCollector.recordTimer("redis.lock.hold_time", holdMs);

        if (holdMs > 3000) {
            log.warn("锁持有时间过长: key={}, holdMs={}", lockKey, holdMs);
        }
    }

    // 定时扫描无 TTL 的锁(死锁检测)
    @Scheduled(fixedDelay = 60000)
    public void scanDeadLocks() {
        String cursor = "0";
        do {
            ScanResult<String> result = redis.scan(cursor, 
                ScanOptions.scanOptions().match("mall:lock:*").count(100).build());
            for (String key : result.getValues()) {
                Long ttl = redis.ttl(key);
                if (ttl != null && ttl == -1) {
                    log.error("发现无 TTL 的锁: {}", key);
                    alerts.send("Redis 死锁告警: " + key);
                }
            }
            cursor = result.getCursor();
        } while (!"0".equals(cursor));
    }
}

3.9 实战八:ZooKeeper/etcd 与 Redis 锁的选型决策

维度 Redis 锁 ZooKeeper/etcd
一致性 最终一致(主从异步) 强一致(Raft/ZAB)
性能 极高(0.2ms) 中等(数毫秒)
运维 已有 Redis 即可 需要独立集群
锁的安全性 可能丢失(主从切换) 不会丢失
适合场景 性能敏感、可容忍极小概率失效 资金安全、强一致要求
决策树:
  锁失败代价是什么?
  ├── 重复发了一封邮件/重复记录日志 → Redis 锁足够
  ├── 可能导致短暂数据不一致(可自动修复) → Redis 锁 + 数据库兜底
  └── 涉及资金/关键状态/安全事故 → ZooKeeper/etcd 锁 + 数据库事务

4.3 分布式锁故障案例

案例 表象 根因 修复
定时任务重复执行 每天凌晨的数据报表生成两次 锁 TTL 300s,但任务运行了 400s,锁过期后被第二个实例获取 加看门狗续期,TTL 设为任务最大时长的 2 倍
订单重复创建 同一个 requestId 创建了两张订单 释放锁时用 DEL 没校验 token,误删了别人的锁 改用 Lua 校验 token 后删除
服务重启后死锁 部署后某功能一直提示"处理中" 旧代码用 SETNX+EXPIRE 两条命令,服务在两者之间被 kill 改为 SET key token NX EX 一条原子命令
GC 导致双重锁 用户投诉被重复扣款 Full GC 暂停 5s,锁过期,另一个线程拿到锁,GC 结束后旧线程继续执行 锁内操作加"锁持有校验"——每次操作前重新检查 token

4.4 自测清单

## 分布式锁自测清单

- [ ] 加锁是否使用原子命令(`SET NX EX/PX`)?
- [ ] token 是否是随机唯一值(UUID)?
- [ ] 释放锁是否用 Lua 校验 token?
- [ ] 锁 TTL 是否大于 P99 业务执行时间?
- [ ] 业务超时后是否有兜底机制?
- [ ] 最终一致性是否有数据库唯一约束兜底?
- [ ] 是否有无 TTL 的锁 key 巡检?
- [ ] 如果涉及资金,是否依赖了 Redis 锁做唯一保障?

5. 锁的实战检查脚本

#!/bin/bash
# 分布式锁验证脚本

echo "=== 测试 1: 原子加锁 ==="
TOKEN=$(uuidgen)
redis-cli -a redis123 SET "test:lock:1" "$TOKEN" NX EX 10
redis-cli -a redis123 GET "test:lock:1"
echo "加锁 token: $TOKEN"

echo "=== 测试 2: 重复加锁拒绝 ==="
redis-cli -a redis123 SET "test:lock:1" "another-token" NX EX 10
echo "预期返回 (nil) → 已锁定"

echo "=== 测试 3: 误删保护 ==="
redis-cli -a redis123 EVAL "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 1 "test:lock:1" "wrong-token"
echo "预期返回 0 → 不是我的锁,不删"

echo "=== 测试 4: 正确释放 ==="
redis-cli -a redis123 EVAL "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 1 "test:lock:1" "$TOKEN"
echo "预期返回 1 → 释放成功"

echo "=== 测试 5: 锁过期 ==="
redis-cli -a redis123 SET "test:lock:2" "token-expire" NX EX 1
sleep 2
redis-cli -a redis123 GET "test:lock:2"
echo "预期返回 (nil) → 已过期"

6. 各种锁方案代码速查

场景 加锁 释放 续期
简单互斥 SET key token NX EX 10 Lua: GET==token → DEL 不需要
防重提交 同上 + 业务幂等表 同上 不需要
长任务 SET key token NX EX 30 Lua 释放 每 10s: GET==token → PEXPIRE 30s
可重入 Lua: GET==false→SET 或 GET==token→HINCRBY Lua: HINCRBY -1, if 0→DEL 每次重入刷新 TTL
公平锁 ZSet: ZADD queue now+ttl token + ZPOPMIN ZREM 不需要(锁按优先级排序)
Redlock 向 N/2+1 个 Redis 节点申请 向所有节点执行 Lua 释放 不推荐续期(增加时钟风险)

6.1 公平锁 Lua 实现

-- fair_lock.lua: 基于 ZSet 的公平排队锁
-- KEYS[1]: 锁队列 key
-- KEYS[2]: 锁标识 key
-- ARGV[1]: token
-- ARGV[2]: TTL (毫秒)
-- ARGV[3]: 当前时间戳 (毫秒)

local queueKey = KEYS[1]
local lockKey = KEYS[2]
local token = ARGV[1]
local ttl = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

-- 1. 清理已过期的排队者
redis.call('ZREMRANGEBYSCORE', queueKey, '-inf', now)

-- 2. 加入排队队列 (score = 期望最晚获取时间)
redis.call('ZADD', queueKey, now + ttl, token)

-- 3. 检查是否轮到自己(队列中 score 最小的是自己吗?)
local first = redis.call('ZRANGE', queueKey, 0, 0)
if first[1] ~= token then
    -- 不是自己 → 返回排队位置
    local rank = redis.call('ZRANK', queueKey, token)
    return -rank  -- 负数表示还需等待的人数
end

-- 4. 轮到我了 → 尝试获取锁
local ok = redis.call('SET', lockKey, token, 'NX', 'PX', ttl)
if ok then
    redis.call('ZREM', queueKey, token)
    return 1  -- 获取成功
end

return 0  -- 获取失败(异常情况,可能已有其他持有者)

6.2 锁的性能基线

操作 单 Redis 实例 Redlock (5 节点)
无竞争加锁 0.2ms 1-2ms
有竞争加锁 0.3ms 2-5ms
安全释放 0.1ms 0.5-1ms
续期 0.1ms 不推荐续期

关键结论:单实例 Redis 锁的性能是 Redlock 的 5-10 倍。只有在对单点故障有零容忍要求的场景下才考虑 Redlock。

6.3 代码审查检查点

当你在 Code Review 中看到 Redis 锁代码时,重点检查:

□ 加锁是原子命令吗? → SET ... NX EX
□ token 是随机值吗? → UUID, 不是 "1" 或线程名
□ 释放锁有 token 校验吗? → Lua GET-then-DEL
□ 锁 TTL 设置合理吗? → 大于 P99 但小于业务 SLA
□ 有数据库兜底吗? → 唯一约束、幂等表
□ 如果锁获取不到,有什么降级策略? → 直接拒绝? 等待重试?
□ 锁 key 命名是否符合规范? → 包含业务含义

6.4 锁的常见反模式

反模式 代码 为什么危险 正确做法
固定 token SET lock "1" NX EX 10 释放时无法校验持有者 SET lock UUID NX EX 10
两步加锁 SETNX lock 1 → EXPIRE lock 10 两步之间宕机=死锁 SET lock token NX EX 10
直接 DEL DEL lock 可能误删别人的锁 Lua: if GET==token then DEL
无业务兜底 只依赖 Redis 锁做唯一性 Redis 锁可能因主从切换丢失 数据库唯一约束+幂等表
锁内长耗时 锁内查数据库、调外部 API 超过 TTL 后锁自动释放 锁只包短流程,长任务用状态机
锁 key 无前缀 lock:1 无法识别归属 mall:lock:order:{id}
释放锁后继续操作 unlock() 后继续用锁保护的数据 数据可能已被其他线程修改 锁内一次性完成数据操作,释放后不再使用

扩展阅读:关于 Redlock 的学术争议,推荐阅读 Martin Kleppmann 的《How to do distributed locking》和 antirez 的回应《Is Redlock safe?》。这场辩论的核心启示是:分布式系统中没有银弹——任何依赖于"时间"和"网络"假设的方案都有边界条件。在 Redis 锁的使用上,记住三点核心法则:原子加锁(一条命令完成 SET+EXPIRE)、校验释放(Lua 比较 token)、业务兜底(数据库唯一约束)。这三点做到,95% 的 Redis 锁场景就已经安全了。锁是工程工具,不是学术命题——实践中的正确性比理论上的完美更重要。

6.5 锁选型决策流程总结

你需要分布式锁吗?
├── 单进程内 → synchronized / ReentrantLock 足够
├── 跨进程 + 性能敏感 + 可容忍极低概率失效 → Redis 锁
├── 跨进程 + 强一致要求 + 资金安全 → ZooKeeper / etcd
└── 跨进程 + 最终一致性 → 数据库唯一约束 + 乐观锁

记住,最好的锁是没有锁——如果业务可以通过幂等设计或数据库唯一约束避免并发冲突,那比任何分布式锁都更可靠。

4. 项目总结

4.1 锁的黄金法则

原则 说明
原子加锁 SET key token NX EX ttl,不能分两步
唯一令牌 value 必须是 UUID,不能是固定值
Lua 释放 先 GET 比较再 DEL,不能直接 DEL
业务兜底 锁只降低并发冲突概率,真正的一致性靠数据库唯一约束
TTL 合理 TTL 略大于 P99 执行时间,不能是业务补偿时间

4.2 Redis 锁的"不能"清单

不能做的事 原因
不能替代数据库事务 Redis 锁没有回滚能力
不能锁跨系统的资源 锁只保护 Redis 客户端之间的互斥
不能在锁内做慢操作 业务超过 TTL 后锁自动释放
不能依赖锁做资金安全 资金安全必须以数据库事务为准
不能把锁 TTL 当业务超时 锁 TTL 是避免死锁,不是业务 SLA

4.3 思考题

  1. 设计题:你需要在分布式环境中实现一个"定时任务只在一台机器上执行"的能力。如果使用 Redis 锁,应该怎么设计锁的 key、TTL 和续期策略?如果执行任务的机器宕机,如何保证锁被释放?

  2. 辩论题:如果你的公司目前使用单实例 Redis 锁保护订单提交,CTO 听说 Redlock 更安全,要求全部迁移。你会怎么回应?请从成本、收益和风险三个维度分析。

(答案提示将在下一章末尾给出。)

上一章思考题答案提示(第17章):

第1题(Bloom Filter 内存+兜底):100 万数据+0.1% 误判率 ≈ 1.7MB 内存。即使 Bloom Filter 误判放行,后续还有第三层"空值缓存"兜底——数据库返回 null 时会写入短 TTL 的空标记,后续请求在 TTL 内不会重复穿透。

第2题(逻辑过期不可接受的场景):① 支付金额展示——用户看到的金额必须是实时的;② 库存数量——虽然可以短暂不一致但可能导致超卖;③ 用户实名认证状态——认证通过后必须立即生效。

延伸阅读与资源

Redis 8 实战精讲:从 CRUD 到源码,构建高可用缓存系统
Java 工程师进阶:从 JVM 生产排障到OpenJDK原理
MySQL Server 9从入门到进阶的实战之旅
Nacos 3.x 注册配置中心实战修炼:从入门到源码扩展
Elasticsearch从入门到进阶的实战之旅
RabbitMQ从入门到进阶的实战之旅:从单机到大促高可用架构
Celery 入门到进阶之路:从异步任务到自研调度平台
LangGraph 生产级实战进阶:从零到生产级Agent工作流开发
Dify 从入门到源码:LLM 应用平台实战修炼
从零到生产级:FastAPI 异步高并发、源码与 SRE 实战
实战SQLAlchemy 2.0: 从 CRUD 到生产级架构
从零打造企业级 AI 助手:LangChain RAG、Agent 与生产实战
后端工程师 AI 转型课:Ollama 私有化大模型从入门到生产
MongoDB 实战进阶与内核修炼
NumPy 从入门到生产落地:全链路实战指南(科学计算/向量化/性能调优)
Milvus向量数据库实战修炼:从 0 到 1 精通向量检索与生产落地
Python 3实战精进:从脚本到高并发订单引擎
python入门:Rquests从菜鸟脚本到企业级SDK的网络实战圣经

posted on 2026-11-13 11:52  一天不进步,就是退步  阅读(9)  评论(0)    收藏  举报