Redis 是什么 Redis 是内存型键值数据库 ,读写速度在微秒级,是后端缓存的绝对主力。核心价值:把高频读的数据放内存,减轻数据库压力 。
一、五大数据类型 1. String(字符串) 最基础的类型,存 JSON 序列化结果、计数器、验证码:
1 2 3 4 5 6 SET user:1 '{"name":"张三","age":25}' GET user:1 INCR visit:count INCRBY article:100:like 2
注意 :INCR 是原子操作,并发下不会出错,这是 String 做计数器的原因。
2. Hash(哈希) 适合存对象,字段可单独操作:
1 2 3 4 HSET user:100 name 张三 age 25 HGET user:100 name HGETALL user:100 HINCRBY user:100 age 1
场景:用户信息、购物车(HSET cart:1001 sku_1 2)。
3. List(列表) 双向链表,适合消息队列 和时间线 :
1 2 3 4 5 6 LPUSH queue:task task1 task2 RPOP queue:task LRANGE feed:news 0 9
4. Set(集合) 自动去重,适合标签、关注关系 :
1 2 3 4 5 6 7 SADD user:1:tags 前端 后端 SISMEMBER user:1:tags 前端 SINTER user:1:tags user:2:tags SADD lottery 1001 1002 1003 SPOP lottery
5. ZSet(有序集合) 带分数(score)排序,适合排行榜 :
1 2 3 4 5 6 7 ZADD rank:game 9500 player_a ZADD rank:game 8800 player_b ZINCRBY rank:game 500 player_a ZREVRANGE rank:game 0 9 ZSCORE rank:game player_a ZRANK rank:game player_a
二、过期时间:缓存的命根子 1 2 3 SET session:token abc123 EX 3600 EXPIRE cache:key 60 TTL cache:key
所有缓存都必须设过期时间 ,否则一旦数据变了,用户永远读到旧值。
三、Node.js 接入(ioredis)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const Redis = require ('ioredis' )const redis = new Redis ({ host : '127.0.0.1' , port : 6379 , password : process.env .REDIS_PASSWORD , maxRetriesPerRequest : 3 , retryStrategy : (times ) => Math .min (times * 200 , 2000 ) }) async function getUser (id ) { const cached = await redis.get (`user:${id} ` ) if (cached) return JSON .parse (cached) const user = await db.query ('SELECT * FROM users WHERE id = ?' , [id]) await redis.set (`user:${id} ` , JSON .stringify (user), 'EX' , 600 ) return user }
四、三个经典缓存问题 1. 缓存穿透:查不存在的数据 请求一个永远查不到 的 key(如恶意遍历 id),每次都打到数据库。
解决 :缓存空值 + 布隆过滤器
1 2 3 4 5 6 7 8 const cached = await redis.get (`user:${id} ` )if (cached !== null ) { return cached === 'NULL' ? null : JSON .parse (cached) } const user = await db.query (...)await redis.set (`user:${id} ` , user ? JSON .stringify (user) : 'NULL' , 'EX' , 300 )
2. 缓存击穿:热点 key 过期 某个超高并发的 key 过期的瞬间,大量请求同时打数据库。
解决 :互斥锁(只让一个请求去重建缓存)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 async function getHotData ( ) { const cached = await redis.get ('hot:data' ) if (cached) return JSON .parse (cached) const lock = await redis.set ('lock:hot:data' , '1' , 'EX' , 10 , 'NX' ) if (lock) { try { const data = await db.query (...) await redis.set ('hot:data' , JSON .stringify (data), 'EX' , 600 ) return data } finally { await redis.del ('lock:hot:data' ) } } else { await sleep (50 ) return getHotData () } }
3. 缓存雪崩:大量 key 同时过期 很多 key 同一时刻过期,数据库瞬时压力爆表。
解决 :过期时间加随机偏移
1 2 await redis.set (key, value, 'EX' , 600 + Math .floor (Math .random () * 60 ))
五、缓存一致性问题(必知) 缓存和数据库双写,怎么保证一致?业界共识:先更新数据库,再删缓存 (Cache Aside):
1 2 3 await db.query ('UPDATE users SET name=? WHERE id=?' , [name, id])await redis.del (`user:${id} ` )
不要先删缓存再更新库 :中间会有并发读到旧数据写回,缓存永远脏。
六、常见坑
Redis 当数据库用 :内存贵、有淘汰策略(LRU),只放高频热数据;
没设过期时间 :缓存永不失效,数据更新后一直脏;
大 key 问题 :单个 value 几 MB 会导致 Redis 阻塞,大数据要拆分;
连接数打满 :每个请求都 new Redis 实例会耗尽连接,用连接池(ioredis 默认复用);
生产环境 Redis 没密码/没绑定内网 :默认可被外网扫描爆破,务必配置。
总结 Redis 的掌握路径:五大类型记场景(String 缓存/计数、Hash 对象、List 队列、Set 去重、ZSet 排行榜)→ 过期时间必须设 → 用 ioredis 接入 → 解决穿透/击穿/雪崩 → 记住”先更库再删缓存” 。这套是后端高频缓存的标准答案。