Redis 入门:五种数据结构、缓存策略与持久化一次讲清
Redis 是一种把数据主要放在内存里的开源数据结构存储系统,常被用作缓存、消息队列和实时排行榜等场景的组件。它的速度来自内存读写和简单高效的数据结构,但这也意味着使用方式和传统关系型数据库很不一样:键的设计、过期时间的设置、持久化策略,都会直接影响系统的正确性和稳定性。本文从新手视角出发,把五种常用数据结构、缓存常见问题和持久化取舍一次讲清楚。
一、先建立一个正确的认知:Redis 适合做什么
适合用 Redis 的场景,通常具备以下一个或多个特征:数据访问频率很高、对延迟敏感、数据结构相对简单、允许在极端情况下丢失一部分非核心数据(例如缓存)。不适合把它当作唯一的、强一致性的主数据库来用,尤其是不适合存放没有备份、丢失后无法重建的核心业务数据。
另一个新手容易忽略的点是:Redis 的快是有代价的。内存容量有限、成本高于磁盘,单条大键、过多的键、没有过期时间的键,都可能让内存快速膨胀。因此用 Redis 之前,先问三个问题:这个数据丢了能不能重建?需不需要过期?访问模式是点查、范围查还是集合运算?答案会直接决定选哪种数据结构。
二、五种数据结构:用法、命令与典型场景
1. String:最基础,也最常用
String 是单个键对应单个值的结构,值可以是字符串、数字或二进制数据。常用命令包括 SET、GET、DEL、INCR、DECR、SETEX 和 MSET。
典型用法有:缓存用户基本信息、页面片段等读多写少的字符串数据;用 INCR 做计数器,例如文章阅读量、接口调用次数;用 SET key value NX EX 做分布式锁的基础语义,即只有键不存在时才设置,并同时设置过期时间,避免锁长期不释放。需要注意的是,生产环境的分布式锁还要处理锁续期、锁归属校验等问题,不能只靠一条 SET 命令就认为万无一失。
2. Hash:把一个对象拆成多个字段
Hash 是一个键对应多个字段的结构,常用命令有 HSET、HGET、HGETALL、HMGET、HDEL 和 HEXISTS。
典型用法是存储对象,例如用户信息:键可以命名为 user:1001,字段包括 name、age、city 等。这样做的好处是只更新一个字段时,不需要把整个对象读出来再写回去;同时,字段可以按需读取,避免传输无用数据。需要注意的是,一个 Hash 里字段过多、单个字段值过大时,内存和传输开销都会上升,对象过大时仍应考虑拆分。
3. List:有序队列,适合任务流
List 是按插入顺序排列的字符串列表,常用命令有 LPUSH、RPUSH、LPOP、RPOP、LRANGE 和 LLEN。
典型用法有两种思路:把 List 当作队列,生产者用 LPUSH(或 RPUSH)放任务,消费者用 RPOP(或 LPOP)取任务;把 List 当作最新动态列表,例如最近浏览记录、最新消息,配合 LTRIM 只保留固定长度,避免列表无限增长。需要注意的是,List 按索引访问中间元素并不高效,它更适合从两端操作,而不是随机访问。
4. Set:无序、不重复,适合做集合运算
Set 是不重复字符串的无序集合,常用命令有 SADD、SREM、SMEMBERS、SISMEMBER、SINTER、SUNION 和 SDIFF。
典型用法包括:用户标签集合、共同好友、抽奖参与名单;用交集求两个群体的共同成员,用并集求去重后的总集合,用差集求“在 A 中但不在 B 中”的成员。Set 的强大之处在于服务器端直接完成集合运算,客户端不需要把全部数据拉回本地再计算。需要注意的是,Set 不保证顺序,如果业务需要按顺序展示结果,还要结合排序或使用 ZSet。
5. ZSet:带分数的有序集合,排行榜的首选
ZSet 在 Set 的基础上给每个成员附加一个分数,并按分数排序,常用命令有 ZADD、ZRANGE、ZREVRANGE、ZRANK、ZSCORE 和 ZINCRBY。
典型用法是排行榜:用分数表示积分、销量或热度,用 ZREVRANGE 取前 N 名,用 ZRANK 查询某个成员的名次,用 ZINCRBY 原子地增加分数,避免并发下出现计数丢失。除了排行榜,ZSet 还可以做延迟任务的近似实现:把执行时间作为分数,定时取出分数小于当前时间的成员处理。不过延迟任务涉及重复投递、失败重试等细节,简单场景可用,复杂调度仍建议使用专门的任务队列。
一张简表可以帮助记忆:String 存单个值、做计数和简单缓存;Hash 存对象字段;List 做队列和最新列表;Set 做去重和集合运算;ZSet 做排序和排行榜。选型时先看“要不要排序、要不要去重、要不要按字段更新”,答案往往直接指向一种结构。
三、缓存三大问题:穿透、击穿、雪崩
这三个词经常一起出现,但成因和应对思路并不相同,区分清楚才能对症下药。
1. 缓存穿透:请求的数据根本不存在
成因是大量请求查询一个数据库中也不存在的数据,例如不存在的用户编号、被恶意构造的非法参数。请求每次都绕过缓存,直接打到数据库,缓存形同虚设。
常见应对思路包括:在入口做参数校验,拦截明显非法的请求;对查询结果为空的情况也写入缓存,设置较短的过期时间,避免同样的空查询反复打到数据库,同时注意空值缓存不要设置过长,否则正常写入后仍会读到空结果;数据量和请求模式允许时,可以在缓存前加布隆过滤器,先快速判断一个键“一定不存在”,从而提前拦截。
2. 缓存击穿:热点键恰好过期
成因是某个热点键过期的一瞬间,大量并发请求同时发现缓存缺失,一起涌向数据库。问题不在数据不存在,而在过期时刻的并发集中。
常见应对思路包括:对热点数据提前预热、设置较长的过期时间或永不过期并由后台异步刷新;对缓存重建过程加互斥锁,只允许一个请求回源数据库、其余请求等待或返回旧值;采用逻辑过期,即在缓存值里附带过期时间,过期后由后台线程异步刷新,前台仍可先返回旧值。这些方法的共同点是把“同时回源”变成“有序回源”。
3. 缓存雪崩:大量键同时失效,或缓存服务整体不可用
成因有两种:一是大量键设置了相同的过期时间,到期时一起失效;二是缓存服务宕机、重启,请求全部落到数据库。
常见应对思路包括:给过期时间加上随机抖动,避免同一批键同时到期;引入多级缓存,例如本地缓存加分布式缓存,降低单点失效的影响;对数据库侧做好限流、熔断和降级,避免数据库被瞬间流量拖垮;服务重启或大促前提前预热热点数据。雪崩本质上是系统性风险,单靠某一个技巧往往不够,需要过期策略、容量规划和降级预案配合。
四、持久化:RDB 与 AOF 的区别与取舍
Redis 把数据放在内存里,重启后数据会丢失,因此需要持久化机制把数据落到磁盘。最常见的两种方式是 RDB 和 AOF。
RDB 是快照方式:在某个时间点把内存中的全部数据生成一个紧凑的快照文件。优点是文件体积相对较小、恢复时加载速度较快,适合做备份和灾难恢复。缺点是快照是定期的,两次快照之间写入的数据在异常宕机时可能丢失;生成快照时需要派生子进程,在数据量很大时会对系统造成一定开销。
AOF 是追加写命令的方式:把每一条写命令追加到日志文件中,重启时重放日志恢复数据。优点是数据丢失的窗口可以控制得更小,例如每秒刷盘一次时,最多丢失一秒左右的数据(具体取决于配置和系统行为);缺点是文件通常比 RDB 大,恢复时重放日志需要更长时间。AOF 还支持重写机制:定期把当前数据重写为最精简的命令集合,避免日志无限膨胀。
常见的取舍思路是:两者结合使用。RDB 用于快速恢复和备份,AOF 用于减少数据丢失;如果业务可以接受丢失最近一段时间的数据、且更看重恢复速度,可以偏向 RDB;如果数据丢失代价较高、且能接受更大的文件和稍慢的恢复,可以偏向 AOF。无论选哪种,都要定期验证备份文件确实可以恢复,否则备份只是心理安慰。
五、给新手的上手建议
第一,先在本地或测试环境跑起来。学会启动服务、连接客户端、执行 SET、HSET、ZADD 等基本命令,观察 TTL、内存占用和命令返回值的变化,再谈架构选型。
第二,养成良好的键命名习惯。建议用冒号分隔层级,例如 user:1001:profile、order:20260001:status,命名清晰、可读、可按前缀管理,避免使用无意义的键名。
第三,能设置过期时间就尽量设置。缓存类数据尤其如此,避免“临时缓存”变成“永久占用”。同时注意不要给同一批键设置完全相同的过期时间,给过期时间加一点随机抖动。
第四,避免几个常见坑:不要在生产环境随意执行 KEYS 命令,它会遍历全部键,可能阻塞服务,排查时优先使用 SCAN;不要把超大对象或超大集合塞进单个键;不要把 Redis 当作唯一存储,核心数据仍要有关系型数据库或其它可靠存储兜底。
第五,关注内存与淘汰策略。了解 maxmemory 以及 volatile-lru、allkeys-lru 等淘汰策略的含义,根据业务选择“只淘汰带过期时间的键”还是“所有键都可淘汰”,并设置告警,避免内存打满后写入失败。
结尾
Redis 的入门门槛并不高,难的是在真实业务里用对它:根据访问模式选数据结构,根据失效模式做缓存防护,根据数据丢失代价选持久化策略。掌握 String、Hash、List、Set、ZSet 的典型用法,理解穿透、击穿、雪崩的区别,再把过期时间、键命名和内存管理这些基本功做扎实,就已经越过了新手阶段最关键的一步。接下来可以沿着主从复制、哨兵、集群分片等方向继续深入,但前提是先把单机用法和缓存边界真正弄清楚。
- 点赞
- 收藏
- 关注作者
评论(0)