Redis历险记:当缓存之王变成程序员的噩梦(附自救指南)

370 阅读5分钟

某个月黑风高的夜晚,某电商公司的程序员小王正在享受人生第一次值班。突然,刺耳的手机警报响起——秒杀系统挂了!监控大屏上,Redis集群的内存曲线宛如窜天猴直冲云霄。老板的电话比警报更快到达:"小王啊,咱们的秒杀活动页面变成404大促销了?"

第一章:内存刺客的温柔一刀

业务背景:某社交平台用户突然集体变身话痨,单日动态发布量暴增300%

当Redis的内存使用率达到120%(别问为什么能超过100%),这个乖巧的缓存系统就会突然变成冷酷的刺客。你可能会看到这样的魔幻场景:

  • 执行SET user:9527 "三好学生"却返回

"(error) OOM command not allowed when used memory > 'maxmemory'"

  • 明明设置了maxmemory-policy为allkeys-lru,但淘汰策略突然失效
  • 运维同学在凌晨三点接到告警:"您的Redis正在尝试申请诺贝尔膨胀奖"

致命陷阱

  1. 在hash结构中存储了500个字段的用户对象,每个字段都像俄罗斯套娃
  2. 把所有粉丝列表都塞进了一个set,结果某明星的粉丝set比《辞海》还厚
  3. 相信了"反正内存便宜"的鬼话,把Redis当成了垃圾回收站

复活术

  • 使用redis-cli --bigkeys找出内存黑洞,像找WiFi信号一样扫描大key
  • 对hash字段超过100的改用ziplist编码(在redis.conf设置hash-max-ziplist-entries 512)
  • 给每个key设置TTL,就像给食品贴保质期(建议使用EXPIREAT配合业务低峰期)

高阶秘籍

-- 带内存检查的写入脚本
if redis.call("MEMORY", "USAGE") < 0.8 * tonumber(redis.call("CONFIG", "GET", "maxmemory")[2]) then
    return redis.call("SET", KEYS[1], ARGV[1], "EX", 3600)
else
    return "ERROR: 内存告急,呼叫增援!"
end

第二章:持久化的量子纠缠

业务背景:某金融系统在断电重启后发现昨日交易记录集体穿越了

当AOF重写遇到RDB持久化,就像让闪电侠和快银赛跑——你永远不知道谁会先到达终点。某次血泪教训:

# 查看持久化状态时的你
127.0.0.1:6379> INFO persistence
...
aof_enabled:1
aof_rewrite_in_progress:1
rdb_bgsave_in_progress:1 
# 此刻的Redis如同同时走钢丝和骑独轮车

灵异现象

  • RDB快照时间戳显示文件创建于明天
  • AOF文件突然减肥成功,但丢失了关键数据
  • 主从同步时出现"您要同步的数据已掉进时空裂缝"的提示
时空修复方案:

混合持久化配置(Redis 4.0+福音):

conf
aof-use-rdb-preamble yes
让AOF文件头部变成RDB格式,身体保持AOF格式,就像给数据穿了防弹衣+雨衣

主从接力赛:

bash
# 让从节点负责持久化
redis-cli -h slave-node CONFIG SET save "900 1 300 10 60 10000"
监控持久化进度脚本:

python
while True:
    info = redis.info('persistence')
    if info['aof_rewrite_in_progress'] or info['rdb_bgsave_in_progress']:
        print("⚠️ 检测到量子纠缠现象!暂停写入操作!")
        time.sleep(10)

第三章:分布式系统的狼人杀

业务场景:某物联网平台需要管理百万级设备在线状态

当Redis Cluster遇上网络抖动,就像在玩一场随时会反转的狼人杀:

  • 节点突然自刀:"我是主节点,我选择死亡"
  • 从节点跳预言家:"我现在是主节点了"
  • 客户端像平民一样懵逼:"到底该相信谁?"

血泪时刻

# 节点间的对话比言情剧还狗血
[MOVED 1234 192.168.1.2:7001] 
[ASK 1234 192.168.1.3:7002]
[CLUSTERDOWN The cluster is down]

生存指南

1、设置合理超时:

cluster-node-timeout 15000
cluster-replica-validity-factor 10
既不让节点像初恋少女般敏感,也不像中年大叔般迟钝

2、客户端配置策略:

JedisCluster jc = new JedisCluster(nodes, 
  2000, // 超时时间
  2000,
  5, // 最大重试
  new GenericObjectPoolConfig()
);
像训练导盲犬一样训练客户端:遇到障碍就重试,但别撞南墙不回头

3、脑裂防护罩:

min-replicas-to-write 1
min-replicas-max-lag 10
确保写操作至少有1个从节点确认,且延迟不超过10

第四章:缓存穿透的鬼故事

业务场景:某内容平台突遭黑客攻击,每秒百万次查询不存在的ID

当缓存层变成透明玻璃,数据库就像被扔进丧尸群的幸存者:

Redis命中率曲线表演高台跳水
MySQL连接数上演垂直极限
程序员的表情管理逐渐失控

捉鬼大师速成班

1、布隆过滤器(Bloom Filter)护体:

from redisbloom.client import Client
rb = Client()
rb.bfCreate('article_filter', 0.01, 1000000)

# 写入时
rb.bfAdd('article_filter', new_id)

# 查询前
if not rb.bfExists('article_filter', query_id):
    return "不存在的鬼魂请勿查询!"

2、空值缓存战术:

public String getData(String key) {
    String value = redis.get(key);
    if (value == null) {
        if (redis.setnx(key + "_mutex", "1")) {
            // 查库并回填
            redis.expire(key, 300); // 正常数据
        } else {
            Thread.sleep(100);
            return getData(key);
        }
    } else if (value.equals("NULL")) {
        redis.expire(key, 30); // 空值短缓存
        return null;
    }
    return value;
}

3、请求合并术:

func QueryHandler() {
    requestChan := make(chan string, 1000)
    go func() {
        batchKeys := make([]string, 0, 100)
        for {
            select {
            case key := <-requestChan:
                batchKeys = append(batchKeys, key)
                if len(batchKeys) >= 100 {
                    processBatch(batchKeys)
                    batchKeys = batchKeys[:0]
                }
            case <-time.After(50 * time.Millisecond):
                if len(batchKeys) > 0 {
                    processBatch(batchKeys)
                    batchKeys = batchKeys[:0]
                }
            }
        }
    }()
}

终章:Redis防坑十二时辰

此刻你的表情:

防坑检查清单

  1. 内存三连问:
  2. 所有Key都有TTL吗?
  3. 大Key是否已分解?
  4. 内存策略是否适配业务?
  5. 持久化灵魂拷问:
  6. 最近一次备份是什么时候?
  7. 能否承受丢失最近1分钟数据?
  8. 主从节点的持久化配置是否错开?
  9. 集群生存测试:
  10. 拔掉任意两个节点网线,系统还能撑多久?
  11. 客户端是否有重试策略?
  12. 监控是否覆盖了集群健康度?

终极思考题
当Redis遇上以下场景,该如何接招?

  • 双十一零点,Redis集群突然集体唱《难忘今宵》
  • 黑客用DEL命令攻击,但发现所有Key都有保护期
  • 老板要求用Redis实现IM系统的消息同步