资讯动态

Redis基础指令实战:从缓存到数据结构,掌握正确使用场景和性能优化

发布时间:2026/10/13 8:04:23 来源:尧图企业网站定制
Redis 的基础指令看起来就那么几十个但能把它们用明白、用对场景的人真正写代码的时候比那些一口气背下 200 条命令的人要稳得多。我接触 Redis 大概是从给一个访问量突然上涨的接口做缓存开始的当时满脑子只有一个 GET 和 SET等真正开始把一个接口从 200ms 优化到 20ms 之后才发现字符串指令只是冰山一角哈希、列表、集合、有序集合每一套指令都有它不该被替代的场景。这篇文章就把我实际项目里反复用到的那些基础指令整理出来每一条都会讲清楚它解决什么问题、怎么用、有什么坑适合刚接触 Redis 的读者也适合已经会用但想系统梳理一遍的人。1. 初学 Redis 指令时最该有的三个认知1.1 别再把它当成普通缓存它是一套内存数据结构服务很多人一提到 Redis 就想到缓存这个印象不坏但容易把思维框住。Redis 本质上是一套跑在内存里的数据结构服务它把键值对按不同的数据结构组织起来并且每种结构都给你配齐了专门的操作指令。理解这一点之后你就会明白为什么有些场景明明用字符串能做到我却不建议你那么做——因为 Redis 为你准备好了更贴合业务形态的类型。举个最简单的例子存一个用户对象。新手会这么做SET user:1001 {name: 张三, age: 28, city: 北京} GET user:1001这样确实能存能取但如果你只想改 age 这个字段就得先 GET 出来在业务代码里解析 JSON改完再整体 SET 回去。数据量大一点、并发高一点这个操作就是性能杀手。换成哈希指令呢直接针对字段做更新就行这个后面会专门展开。1.2 指令的粒度是命令不是函数可能有人会觉得Redis 指令不就是一个个函数吗这个类比大体对但不准确。Redis 指令在网络模型上是客户端发一条命令文本服务端解析执行后返回结果。这意味着每发一条指令就有一轮网络往返所以有些看起来很琐碎的操作是有代价的。理解这一点之后你会主动去关注 MSET、MGET、PIPELINE 这些批量操作也会理解为什么要把多次读写合并成一次交互。1.3 过期时间是 Redis 的灵魂但大多数人用得不够认真很多人会把 EXPIRE 当成一个锦上添花的配置其实它应该成为键设计的一部分。数据该不该过期、过期时间多长、过期后业务怎么兜底这些应该在写代码之前就定清楚。后面我会用专门一小节讲键管理和过期时间配合的细节这里先记住一句话没有过期时间的缓存数据都是潜在的内存泄漏。2. 字符串指令最常用的一批但也最容易用糙2.1 SET、GET、DEL 三板斧的正确姿势字符串类型是 Redis 里最基础的数据结构对应指令也是每个新手最早接触的。三件套就是 SET、GET、DEL这个没什么好说的真正值得说的是 SET 的扩展参数。SET user:1001:token abc123 EX 7200这里的 EX 7200 表示 7200 秒后自动过期特别适合存 token、验证码这类有时效性的临时数据。还有 NX 参数只在键不存在时才设置成功这可以用于实现简单的分布式锁SET lock:order:1001 locked NX EX 10返回 OK 表示获取锁成功返回 nil 表示锁已经被别人拿走了。我在项目里就用这一条指令做过一个全员可用的简易锁处理多个实例同时操作同一订单的问题效果稳定。不过要注意这只是一个基础的互斥工具如果对可靠性要求很高还是需要认真设计续期和释放时的校验逻辑。2.2 INCR、DECR、INCRBY计数器别用先 GET 再 SET计数器是字符串指令用得最多的场景之一。比如记录某个用户当天登录次数、限流器里统计每秒请求数新人的第一反应是 GET 出来加一再 SET 回去。但在高并发下这样会有明显的竞态问题——两个请求同时读到同一个值各自加一后写回实际只加了一次。正确做法是用 INCRINCR user:1001:login:count INCRBY user:1001:score 5 DECR user:1001:stockINCR 是原子操作无论多少个并发请求同时过来加一的动作都不会互相覆盖。配合 EXPIRE 还能实现一个很经典的自定义限流逻辑INCR api:limit:user:1001 EXPIRE api:limit:user:1001 60这段逻辑的问题在于如果 EXPIRE 执行失败这个键就变成永不过期的累加器。更稳妥的办法是先用带过期时间的 SET 初始化或者用 Lua 脚本把两条指令包起来。我在早期就踩过这个坑后来养成了一个习惯凡是先判断再操作的逻辑能合并成一条指令就绝不用两条。2.3 MSET、MGET 与批量操作的性能直觉单个 SET 和 GET 在并发低的时候感受不出差别一旦进入 QPS 几千甚至上万的阶段每条指令都要经历一次网络往返延迟会迅速放大。把多组键值合并到一条 MSET 里发出去指令本身没有减少服务端的工作量但显著减少了网络交互次数效果立竿见影。MSET user:1002:name 李四 user:1002:age 30 user:1002:city 上海 MGET user:1002:name user:1002:age user:1002:city实际项目里批量初始化一堆键、批量读取配置项时这个操作几乎是必备的。字符串类型的指令表面简单但它的批量语义和原子操作特性才是它在生产环境里的真正价值。3. 哈希指令把对象拆开存改动一行算一行3.1 HSET、HGET、HGETALL 的日常用法哈希类型把键值对组织成一个key 下面的多个 field-value特别适合存对象。刚开始我用 JSON 字符串存对象后来改成哈希体验完全不一样。用法非常直观HSET user:1001 name 张三 age 28 city 北京 HGET user:1001 name HGETALL user:1001HGETALL 会把所有字段和值一次性返回适合对象全量展示的场景。如果字段特别多也可以先用 HKEYS 看有哪些字段再按需取。哈希带来的最大好处是改一个字段的时候其他字段完全不需要经过网络传输。3.2 HINCRBY 与部分更新的原子性字符串的 INCR 能做计数器哈希里也有对应的原子计数指令HINCRBY user:1001 loginCount 1 HINCRBY product:2001 stock -1这个指令配合哈希类型可以直接在 Redis 里维护嵌套对象里的数值字段。比如用户对象里有积分、登录次数、经验值这类需要频繁加减的字段用 HINCRBY 就等于把读-改-写整个动作压缩成了服务端的一次原子操作既省代码又稳。3.3 什么时候该用哈希什么时候还是字符串更合适哈希也不是万能的。如果一个键下面只有一个字段比如一个独立的 token或者一个简单的字符串开关注册状态用哈希反而多套了一层结构完全没有必要。哈希更擅长的是一个对象有多个稳定字段这种场景比如用户信息、订单详情、商品详情。另外一个容易被忽略的点是哈希的字段数量如果非常大HGETALL 返回的数据体积也会很恐怖生产环境里用 HSCAN 来遍历比一次性拉全量更安全。4. 列表指令右侧入队左侧消费消息与日志都靠它4.1 LPUSH、RPUSH、LRANGE 的配合方式列表类型本质上是一个双向链表两头都能加节点。所以它天然适合两类业务先进先出的队列以及后进先出的栈。RPUSH task:queue job-001 RPUSH task:queue job-002 LRANGE task:queue 0 -1LPUSH 从左边塞RPUSH 从右边塞LRANGE 按区间看内容。日常开发里最标准的队列模型是生产者用 RPUSH 把任务丢到列表右侧消费者从左侧 LPOP 取出最早的任务去处理任务量大时可以起多个消费者但要注意多个消费者同时 LPOP 并没有分布式队列那样的消费确认机制处理失败的任务会丢需要自己设计重试。4.2 LPOP、RPOP 消费数据时的两个经验LPOP 从左侧弹出并返回一个元素弹出去之后就没了。这个删一个返回一个的特性让它很适合在临时列表里做数据搬运。我在一个模拟项目 X 里就做过一个临时渠道列表一批数据先 RPUSH 进去后台任务每处理一条就 LPOP 一条处理完整个列表自然清空非常清爽。另一个经验是如果列表已经空了LPOP 会返回 nil业务代码要注意这个逻辑。很多新手没判断空值的习惯拿到 nil 直接往一个强类型变量里塞结果报空指针。Redis 的 nil 和空字符串是两个概念这条要记牢。4.3 BLPOP 和 BRPOP别让 CPU 空转如果消费者循环调用 LPOP 等数据列表为空时它会空转很多次白白消耗 CPU 和网络。Redis 提供了阻塞版指令 BLPOP你可以给它一个超时时间没有数据时连接会挂着等待直到有数据入队或超时才返回。BLPOP task:queue 0后面的 0 表示无限等待不设超时。我在早期做任务系统时消费者进程就是用一个死循环 LPOP sleep(1) 来轮询后来换成 BLPOP 之后CPU 占用立刻降了下来任务响应也更快了因为数据一进来消费者立刻被唤醒不用等那一秒的 sleep。这里唯一的注意事项是阻塞命令会占用一个连接如果创建的连接池比较小可能会出现连接不够用的情况需要合理调配。5. 集合与有序集合指令去重、标签、排行榜一手搞定5.1 SADD、SMEMBERS、SISMEMBER 的去重能力集合类型最核心的语义是成员唯一这就让它非常适合做标签、关注列表、黑白名单这类天然要去重的数据。基本指令很简单SADD user:1001:tags backend redis java SMEMBERS user:1001:tags SISMEMBER user:1001:tags redisSADD 重复添加同一个值不会报错也不会重复存储这就是集合和列表最直观的区别。SISMEMBER 判断一个值在不在集合里时间复杂度 O(1)非常适合做白名单鉴权。5.2 集合间的运算交集、并集带来的便利集合类型真正值钱的不只是去重还有运算能力。比如两个用户的好友集合想看共同好友一件只需要一条指令SINTER user:1001:friends user:1002:friends要判断两个人是否有共同兴趣标签可以用交集要给用户推荐相关文章可以把文章标签集合与用户偏好集合取交集按重合度排序。这些操作在关系型数据库里往往要 JOIN 好几张表用集合指令就是一条命令的事。5.3 ZADD、ZRANGE 和排行榜的实现原理有序集合是 Redis 里最闪亮的数据结构它同时具备按成员值去重和按分数排序两个特性排行榜、评分排序这些需求几乎是量身定做。最基础的三条指令ZADD ranking:week 100 userA ZADD ranking:week 85 userB ZADD ranking:week 120 userC ZRANGE ranking:week 0 -1 WITHSCORES加了 WITHSCORES 之后会连分数一起返回。ZRANGE 默认按分数从小到大的顺序返回如果要从高到低显示排行榜用 ZREVRANGEZREVRANGE ranking:week 0 99 WITHSCORES我在做某个活动系统时每周活跃榜就用这个实现数据量大之后表现依旧很稳因为有序集合底层用了跳跃表加哈希表的组合查排名和按区间取数据的效率都很高。5.4 ZSCORE、ZRANK查分数和查名次除了区间遍历有序集合还提供了几个很实用的点查询指令ZSCORE ranking:week userB ZRANK ranking:week userBZSCORE 直接取某个成员的分数ZRANK 返回它在当前集合里的排名。这两个指令配合起来就能做出查看某个人当前是第几名、分数是多少的功能而且速度都很快。如果业务里需要展示 TopN 又要同时展示个人名次用这两条指令可以少写不少代码。有一点要特别注意有序集合的分数类型是浮点数业务上用整数做分数时没问题但如果拿它存金额或者对精度要求很高的数值要格外小心浮点误差必要时在业务层做转换而不是直接依赖 Redis 返回的浮点数。6. 键管理指令过期时间、类型判断和遍历策略6.1 EXPIRE、TTL、PERSIST 的日常配合前面说了过期时间很重要这里专门看配套指令。EXPIRE 给一个已存在的键设置过期秒数TTL 查看剩余存活时间PERSIST 把有过期时间的键变成永久键。EXPIRE user:1001:token 7200 TTL user:1001:token PERSIST user:1001:tokenTTL 返回 -1 表示没有过期时间返回 -2 表示这个键不存在。很多人会混淆这两个负数的含义我建议把这两个值背下来排查问题的时候特别有用。还有一个很容易忽略的细节EXPIRE 只能对已经存在的键生效如果你对一个根本不存在的键执行 EXPIRERedis 会返回 0。所以先 SET 再 EXPIRE这种两步操作如果中间出现异常键就可能变成永久的前面提到的用 SET EX 合并写法就是更稳的选择。6.2 KEYS 与 SCAN全量遍历与增量遍历的取舍KEYS pattern 指令能按通配符匹配出所有的键比如 KEYS user:。听起来很方便但生产环境下这是一个高危指令。因为 Redis 是单线程执行命令的KEYS 需要遍历整个键空间数据量一大这个命令执行期间其他所有指令都会被阻塞线上服务可能瞬间变慢。我见过有同事在几百万个键的环境下执行 KEYS user:结果整个 Redis 卡了好几秒这个教训相当深刻。正确做法是用 SCAN 指令做增量遍历SCAN 0 MATCH user:* COUNT 100SCAN 每次返回一个游标和一批数据你用返回的游标继续发起下一次 SCAN游标变为 0 时表示遍历结束。它不会一次性阻塞整个服务是生产环境里安全遍历键空间的推荐做法。虽然它可能需要多次请求换来的是不影响线上实时请求这笔账怎么算都划算。6.3 TYPE、EXISTS、RENAME 的关键细节日常开发里还有一些小但实用的键管理指令。EXISTS 判断键是否存在TYPE 看类型EXISTS user:1001 TYPE user:1001RENAME 可以用来重命名一个键但要注意它会直接覆盖目标键原有的值。如果你不确定目标键是否存在先用 EXISTS 判断一下或者用 RENAMENX——只有目标键不存在时才重命名成功。这些指令看着简单但在写运维脚本、做数据迁移时非常顺手。7. 入门之后一定要养成的几个习惯7.1 键命名规范和可读性问题Redis 没有表结构约束也没有索引约束它是绝对自由的但越自由越需要自律。键的命名直接决定你后期排查问题的效率我推荐使用业务域:对象:ID[子ID] 的格式比如 user:1001:profile、order:2001:status。这种命名在 SCAN 和按前缀匹配时特别方便而且一眼就能看懂。切忌用一堆含义不明的缩写或者没有规则的随机串一个月之后你自己都会认不出来。7.2 连接复用和命令合并写业务代码时每发一条 Redis 指令都会占用一次网络交互。在访问量不高的阶段感觉不出什么一旦遇到突发流量连接数猛增和网络往返变多会成为新的瓶颈。除了用 MSET、MGET 合并读取外还可以考虑用连接池复用连接减少反复建连的开销。高并发、高吞吐的场景里连接池配置本身就是一个很大的话题但至少入门阶段要明白Redis 的连接不是越快建越多越好频繁创建和销毁连接的成本远比很多人想象的高。7.3 线上别用、少用的指令清单最后给你一份实操总结。生产环境里有几个指令能不用就不用或者说用之前必须想清楚后果指令风险点替代方案KEYS全量阻塞严重的可用性事故SCAN 游标遍历FLUSHALL / FLUSHDB一键清空整个库误操作不可恢复禁用或严格权限控制MONITOR持续输出所有命令性能开销大定期短时抓取别常开DEBUG 系列调试指令生产环境危险禁用SMEMBERS超大集合一次性返回全集内存和网络压力巨大SSCAN 分页读取这几点是每次我带着新人看 Redis 问题时都会反复强调的内容。基础指令本身不难真正拉开差距的是你会不会在合适的场景用正确的指令以及遇到问题之后能不能顺着指令的执行逻辑一步步排查下去。建议你打开一个本地 Redis 实例照着这篇文章把每条指令敲一遍重点看看 TTL 的变化、LPOP 之后的列表状态、SCAN 的游标行为自己动手感受一次比看十遍文档都管用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑