免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Redis基础指令实战:五大数据类型、过期策略与常见坑

Redis基础指令实战:五大数据类型、过期策略与常见坑 如果让我给刚接触 Redis 的朋友一句建议我会说先把基础指令练到不看文档也能随手敲出来的程度。很多人一上来就研究集群、哨兵、分布式锁结果连SET和GET到底怎么设计过期时间都没搞明白出了问题只能干瞪眼。这篇就是为 Redis 基础指令学习准备的我会从下载安装讲起把 String、Hash、List、Set、ZSet 这五大数据类型最常用的指令一条条过一遍再结合缓存、计数器、消息队列这些真实场景说说指令怎么组合起来用最后把新手最容易踩的坑和排查经验整理出来。无论你是准备面试还是要在项目里真正用上 Redis这篇都值得花半小时过一遍。1. 先把环境跑起来安装、启动与第一条指令1.1 为什么要从基础指令入手Redis 虽然提供了各种各样的客户端库Java 有 Jedis、LettucePython 有 redis-pyGo 有 go-redis但无论封装得多好底层都是通过 TCP 发送一串符合 Redis 协议的命令。你调用client.set(key, value)本质上就是往 Redis 进程发送了SET key value这条指令。所以基础指令掌握得越扎实你排查问题越快。举个最常见的例子线上缓存值不对你找半天代码最后用redis-cli敲了一句GET user:1001:info发现值根本不是代码里写的那条问题立刻定位到写入逻辑。这种场景下客户端工具帮不了你可视化工具也要一层层点反而是命令行最直接。而且Redis 官方文档、网上各种教程、面试题全都是围绕指令来组织的。你把基础指令理解透了后面看淘汰策略、持久化、主从复制、集群甚至去啃 Redis 源码都有一个稳固的抓手。1.2 安装Windows / Linux / Docker 三种方式怎么选Redis 本身是 Linux 生态的软件官方推荐在 Linux 或 macOS 上跑。Windows 上没有官方正式版只有第三方移植版本。如果你只是学习基础指令我建议优先用 Docker一条命令就能起来还不污染系统环境。安装方式适用平台适合场景注意事项源码编译 / 包管理器Linux、macOS生产环境、深入学习编译需要 gcc记得看版本Windows 移植版Windows本地体验、学习指令性能略弱遇到问题先怀疑环境Docker全部平台快速学习、隔离环境需要先装 Docker端口映射别写错Linux 用包管理器最省事。Debian/Ubuntu 系执行sudo apt install redis-serverCentOS/RHEL 系执行sudo yum install redis装完用systemctl start redis启动再用redis-cli ping验证返回PONG就说明通了。Windows 下最省心的方案不是找安装包而是装 WSL 后用 Ubuntu 里的 Redis或者直接用 Docker Desktop。如果只是想快速体验命令行GitHub 上的tporadowski/redis这个移植版本也够用下载 zip 解压后直接运行redis-server.exeRedis 默认监听 6379 端口。Docker 的方式我贴一下这套命令我反复用过很多次docker run -d --name redis-local -p 6379:6379 redis:7想带上密码和持久化可以加环境变量和挂载参数docker run -d \ --name redis-local \ -p 6379:6379 \ -v /myredis/data:/data \ -v /myredis/conf/redis.conf:/etc/redis/redis.conf \ redis:7 redis-server /etc/redis/redis.conf注意一个细节-p 6379:6379左边是宿主机端口右边是容器端口写反了外部就访问不到。学习阶段建议保持默认不要映射到公网。1.3 redis-cli 连接与第一条指令Redis 装好之后命令行客户端redis-cli是和它打交道的核心工具。连接本地默认实例直接执行redis-cli指定 IP、端口和密码的话可以这样redis-cli -h 127.0.0.1 -p 6379 -a yourpassword这里的-a参数会直接出现在历史记录里有安全洁癖的话更推荐用环境变量export REDISCLI_AUTHyourpassword redis-cli -h 127.0.0.1 -p 6379连上之后先敲ping服务器会返回PONG。这是确认 Redis 是否活着最经典的方式。然后再试两条指令SET hello world GET hello返回world说明整个链路已经通了客户端 - TCP 连接 - Redis 服务端 - 返回结果。退出连接用quit或者直接Ctrl C。这些看起来不起眼但面试时被问你平时怎么连 Redis 排查问题能清晰说出这一套流程比空谈概念有用得多。2. 五大数据类型与最常用的存取指令Redis 最核心的模型就是键值对但值不是一个单纯的字符串它分成五种基础类型String、Hash、List、Set、ZSet。学习基础指令本质上就是学会怎么操作这五种结构。下面我用最直白的方式逐个过一遍。2.1 String最基础的字符串与计数器String 是 Redis 里最简单的类型一个键对应一个字符串值最大能存 512MB。它不只是SET和GET这么简单还承担着计数器、分布式锁、对象缓存等重任。最常用的指令组合SET name zhangsan GET name SET name lisi EX 60 SET name wangwu NX第二条给键设置了 60 秒过期时间第三条只有键不存在时才写入这两个参数在缓存场景里非常常用。用MSET和MGET可以批量写入和读取MSET user:1:name zhangsan user:1:age 25 MGET user:1:name user:1:age再来看计数器。INCR会把键对应的数字自动加 1而且这个操作是原子性的并发环境下不会出现重复计数SET page:view:100 0 INCR page:view:100 INCRBY page:view:100 5对应还有DECR和DECRBY。热点文章点击量、库存扣减底层都是这套指令。注意一个坑如果键的值不是数字INCR会直接报错所以写入时要保证值能转成整数。补充一个面试常考点SETNX指令全称是 SET if Not eXists只在键不存在时才会写入成功。它就是分布式锁最原始的雏形后面我会详细讲怎么用它。2.2 Hash适合存对象的字段级操作Hash 类型相当于 Java 里的MapString, String或 Python 里的字典适合存一个对象的多个字段。比如用户信息用 String 存整个 JSON 其实也可以但如果你想只改其中一个字段String 就得先把整个 JSON 取出来反序列化再改回去Hash 完全可以避免这个麻烦。核心指令HSET user:1001 name zhangsan age 25 city beijing HGET user:1001 name HGETALL user:1001 HINCRBY user:1001 age 1 HDEL user:1001 city HLEN user:1001HGETALL会把所有字段和值都取出来写 demo 没问题生产环境要注意如果一个 Hash 很大HGETALL会产生很大的网络流量这种场景应该用HSCAN分步读取或者只取需要的字段。还有一组批量操作HMSET user:1002 name lisi age 30 HMGET user:1002 name cityHMSET和HSET在 Redis 4.0 之后的用法基本一致现在都是推荐直接用HSET。判断某个字段存不存在用HEXISTS给字段数字做累加用HINCRBY和HINCRBYFLOAT。Hash 最适合存对象型数据像购物车、用户资料、文章详情都是典型场景。我习惯用对象名:ID作为键名字段名直接对应对象属性这样语义非常清晰。2.3 List队列和栈的指令组合List 是一个按插入顺序排列的字符串链表从两端都能操作。学习的时候可以类比成一根管子从左边塞和取从右边塞和取。基本指令LPUSH messages msg1 RPUSH messages msg2 LPOP messages RPOP messages LLEN messages LRANGE messages 0 -1LPUSH从左边推入RPUSH从右边推入LPOP从左端弹出RPOP从右端弹出。如果只从一端推入和弹出就变成了栈从一端推入、另一端弹出就是队列。LRANGE是查看整个列表的常用指令0 -1表示从第一个元素到最后一个元素。压测时想快速造数据可以用循环脚本批量LPUSH。这里我要单独说一个指令BRPOP它是阻塞式弹出如果列表为空客户端会一直等在那里直到有数据进来才返回可以设置超时时间BRPOP messages 5这 5 秒就是最长等待时间超时返回空数组。后面讲消息队列时LPUSH配合BRPOP是很经典的生产消费模型。List 的坑也明显LRANGE取全部数据时如果列表很长一样会造成大流量取之前先预估一下长度。2.4 Set去重、交集、并集一把梭Set 是String 类型的无序集合自动去重支持集合运算。它特别适合做有没有交并差这种操作。常用指令SADD tags java SADD tags python redis SMEMBERS tags SISMEMBER tags java SCARD tags SREM tags pythonsadd添加元素重复添加会返回 0这就是天然去重。SISMEMBER判断元素是否存在返回 1 或 0。SCARD获取元素数量。SMEMBERS获取全部元素但大集合下要慎用建议改成SSCAN。集合运算才是 Set 的精髓SADD user:1001:follow user:2001 user:2002 SADD user:1002:follow user:2002 user:2003 SINTER user:1001:follow user:1002:follow SUNION user:1001:follow user:1002:follow SDIFF user:1001:follow user:1002:followSINTER求交集可以查出两个用户共同关注了谁SUNION求并集合并去重SDIFF求差集找出第一组有但第二组没有的元素。这些都是社交场景里高频的操作用 SQL 也能实现但数据量大时性能差很多Set 是更合适的方案。2.5 ZSet带权重的有序集合ZSet 在 Set 的基础上给每个元素加了一个 double 类型的分数score所有元素按分数从小到大排列。它的独特能力就是排序。最常用的指令ZADD leaderboard 100 player1 ZADD leaderboard 200 player2 150 player3 ZRANGE leaderboard 0 -1 ZREVRANGE leaderboard 0 -1 ZSCORE leaderboard player1 ZINCRBY leaderboard 10 player1 ZRANK leaderboard player1ZADD添加元素时指定分数重复添加会更新分数。ZRANGE按分数从小到大取出ZREVRANGE从大到小取出这就是排行榜的核心逻辑。ZINCRBY给分数累加适合实时加分场景。ZRANK查询某个元素的排名。再配合ZRANGEBYSCORE可以按分数区间查询比如取 90 分到 100 分之间的所有元素ZRANGEBYSCORE leaderboard 90 100这里要注意当多个元素的分数相同时Redis 会按字典序排列所以排行榜名次可能不是唯一的做产品时要提前约定规则。ZSet 的经典应用就是排行榜、热点排序、延迟队列用分数模拟时间戳。它也是 Redis 五种类型里唯一能直接做有序范围查询的类型。3. 键生命周期管理过期、删除与高效扫描学会了读写指令下一步要解决键的死活问题。Redis 是一个缓存数据库如果键永远不删内存迟早会被写爆。这一章讲的就是键生命周期管理。3.1 过期时间EXPIRE / TTL 与过期策略给键设置过期时间最直接的方式SET session:1 abc EX 300等价于SET session:1 abc EXPIRE session:1 300EXPIRE的过期时间单位是秒还有更精细的PEXPIRE单位是毫秒。想查看键还能活多久用TTLTTL session:1返回-2表示键不存在返回-1表示键存在但没有设置过期时间返回正数就是剩余秒数。这里有个常见的认知误区很多人以为设置了过期时间键到了时间就立即从内存里消失。其实 Redis 的过期删除机制是惰性删除 定期删除 的组合。惰性删除是指访问键时才检查是否过期过期就删定期删除是指后台每 100ms 抽样检查一部分过期键并删除。所以在过期时间刚过的那一刻键可能仍然存在直到被访问或下一次定期删除扫描到。这带来的实际问题就是用TTL看一个键明明设置的 60 秒到了 59 秒后再看键可能还在只是 TTL 显示为 0 或接近 0。对业务来说等GET返回空值就当作过期了这样更符合实际。3.2 删除键DEL、UNLINK 与异步删除最直接的删除命令是DEL可以同时删多个键DEL user:1001 session:1但DEL是同步删除如果键是一个几百万元素的 Set 或 List删除动作会阻塞 Redis 主线程造成服务短暂卡顿。在比较大的键上我建议用UNLINKUNLINK bigkeyUNLINK把键从命名空间里移除然后由后台线程真正释放内存不会阻塞主线程。这是 Redis 4.0 引入的能力生产环境删大 Key 时应该养成用UNLINK的习惯。顺带说一句Redis 自己的过期删除如果遇到大键同样会阻塞主线程。这在高并发场景下很要命所以设计数据时就要控制单个键的体积别把大对象塞进一个 Key。3.3 KEYS 与 SCAN扫描键的正确姿势很多新人会敲KEYS *来查看所有键然后惊讶地发现列表很多甚至卡了一下。原因在于KEYS是全表遍历所有键都要过一遍键数量大时会把 Redis 卡住生产环境严禁使用。要扫描键正确姿势是SCANSCAN 0 MATCH user:* COUNT 100SCAN是游标式遍历每次返回一批键和下一个游标位置。游标返回0表示遍历结束。它和KEYS最大的区别是每次只遍历一部分不会一次性把所有键都拉回来所以对主线程的影响可控。想看某个前缀有多少键可以用SCAN配合MATCH模式去数但注意MATCH的过滤是在内存里做的你匹配user:*时可能遍历了全库的所有键。如果只是想快速验证某个具体键是否存在直接用EXISTS user:1更高效。3.4 键命名规范冒号分层的实际作用Redis 的键名建议使用业务:实体:ID这种冒号分隔的层级结构比如user:1001:info、order:20240101:count。这么做不是 Redis 强制要求而是为了方便肉眼识别也方便用SCAN按前缀批量处理。我见过一种反面教材键名直接叫a、b、c1上线一周后根本不知道哪个键是哪个业务的排查问题全靠猜。键名稍微长一点不会让性能变差多少但可维护性好非常多。再强调一个细节键名里面不要包含空格和换行符否则在redis-cli里输入时要加引号容易踩坑。键名统一用小写加数字不要用中文省得后续日志和分析工具处理起来麻烦。4. 基础指令的组合实战解决真实业务问题基础指令单个学完很多人还是不知道实际怎么用。这一章我拿五个最典型的业务场景把指令组合起来讲清楚。4.1 用 SETNX 实现一个分布式锁分布式锁是面试高频题也是基础指令最经典的组合。最原始的用法就是SETNXSET lock:order:1001 1 NX EX 30这条命令的含义是只有lock:order:1001不存在时才设置成功而且带 30 秒过期时间。加锁成功返回OK可以直接进入业务逻辑加锁失败返回空说明别人已经持锁。这里有几个细节要注意第一锁必须带过期时间否则持有锁的进程 crash 后锁永远不释放会造成死锁。第二SET加NX加EX必须是一条命令不能拆成SETNX再EXPIRE因为两条命令之间有间隔万一中间进程挂掉过期时间就设置不上了。第三释放锁时不能直接DEL因为可能误删别人的锁推荐用 Lua 脚本先比较 value 再删除但那是进阶话题了。4.2 INCR 做计数器与简单限流INCR可以做单机接口限流。比如限制某个用户 1 分钟内最多请求 10 次可以这样设计SET rate:user:1001 0 NX EX 60 INCR rate:user:1001每次请求先执行第一条保证键存在再执行第二条把计数加 1。如果返回值大于 10说明请求超限直接返回请求过于频繁。当然这个方案在分布式下还有竞态问题跨多个 Redis 实例时需要更复杂的滑动窗口算法。但如果只是单实例、单服务的场景这套基础指令组合完全够用而且性能非常好每次请求只需要两次内存操作比查数据库快几个数量级。4.3 List 实现简单的消息队列用LPUSH生产消息、BRPOP消费消息是最轻量的消息队列模型生产者LPUSH queue:task task-1 LPUSH queue:task task-2消费者BRPOP queue:task 0BRPOP的第二个参数是超时秒数0 表示永久阻塞等待。客户端收到消息后处理完再继续BRPOP下一条。这套模型在异步任务里非常常见比如把发邮件、生成报表这些耗时操作丢到队列里慢慢处理。不少知名框架也在用这个模式。比如结合 Python 的 Celery常常把 Redis 既当成消息队列 broker又当成任务结果存储 backend底层就是 List 的关键操作再加一些序列化约定。但这种模式有个明显的坑如果消费者在处理消息过程中崩溃消息可能丢失。因为BRPOP弹出的那一刻消息就已经从 Redis 里删掉了消费者没来得及保存。要确保可靠消费需要引入备份队列或 ack 机制这就是为什么重业务会去用 RabbitMQ、Kafka而不是只靠 Redis。学习基础指令时能意识到这个边界非常重要。4.4 缓存穿透、击穿、雪崩的基础应对思路缓存场景里三个老大难问题几乎人人都会遇到。用基础指令能做的应对我从最浅的层面讲一下。缓存穿透指的是查一个根本不存在的数据缓存没有数据库也没有请求每次都打到数据库。最简单的应对是查数据库返回空值后也把这个空值写进缓存设置一个较短的过期时间SET user:9999:info EX 60这样后续同样的请求直接命中缓存不会再打到数据库。缓存击穿指的是某个热点 Key 过期瞬间大量请求同时打到数据库。基础应对是加锁类似前文说的SETNX分布式锁只允许一个请求去查库重建缓存其他请求等一会再取缓存。缓存雪崩是指大量 Key 在同一时间段过期数据库压力骤增。在设置过期时间时可以加一个随机值避免大面积同时过期。这些应对思路本质上都是用现成的SET/GET/EXPIRE组合出来的没有引入新的复杂概念。4.5 用 Pipeline 批量提升指令效率如果要循环执行 1000 条SET指令逐条发送会产生 1000 次网络往返RTT时间绝大部分花在网络延迟上。Redis 提供 Pipeline 机制把多条指令一次性发给服务端再一起取回结果。在redis-cli里可以用管道模拟printf SET k1 v1\nSET k2 v2\nGET k1\n | redis-cli --pipe实际开发中各种客户端库都支持 pipeline 方法。我在压测时见过同样的 1 万次写入普通循环耗时 3 秒改用 pipeline 后只要 0.1 秒左右性能提升非常明显。需要注意的是Pipeline 并不是事务它只是批量发送指令之间没有原子性保证。如果某条指令执行失败其他指令仍然会执行。想要原子性就得用MULTI和EXEC组成事务但那是进阶内容了。5. 新手常见问题与排查经验学习 Redis 基础指令的过程中总会遇到一些看起来莫名其妙的问题。我把自己踩过的坑和帮别人排查过的典型问题整理成了一张速查表。5.1 连不上 Redis先分清客户端问题还是服务端问题redis-cli执行后卡住或者报Could not connect to Redis at 127.0.0.1:6379: Connection refused说明客户端根本没有建立 TCP 连接。先按顺序排查这几项第一Redis 进程是否在运行用ps -ef | grep redis-server查看。第二监听端口是否配了127.0.0.1如果是外部无法访问需要改成0.0.0.0并配好防火墙。第三是否设置了密码如果设置了requirepass不带密码连接也会报错。用redis-cli连本机最省事默认就是127.0.0.1:6379。远程连接时不要为了省事把 Redis 暴露到公网非常危险。如果只是调试建议用 SSH 隧道或者内网环境。5.2 键到期了但内存还很高先确认过期删除是否生效有同学说我明明给键设了过期时间为什么 Redis 内存还是越来越高。这有两种可能一是大量键还没被惰性删除扫描到二是键本身是活跃数据还没到过期时间。更关键的是如果 Redis 内存写满会触发maxmemory策略在 64 位系统上默认策略是noeviction就是不再写入任何新键但也不会自动删掉你的键。排查命令INFO memory重点看maxmemory、used_memory、evicted_keys这几个指标。evicted_keys如果一直在涨说明确实发生了内存淘汰。基础学习阶段我建议把maxmemory-policy设置成allkeys-lru能避免写满后直接拒绝写入的问题但生产环境要根据业务选策略。5.3 指令执行很慢用 SLOWLOG 和 MONITOR 定位某条指令突然很慢很多人第一反应是网络问题但很多时候是大 Key造成的。Redis 是单线程执行指令一个复杂的指令会阻塞后面所有指令。比如对几百万元素的 Set 执行SMEMBERS或者对一个很大的 List 执行LRANGE 0 -1都会造成明显的卡顿。定位慢指令的方法SLOWLOG GET 10看最近 10 条慢查询日志重点看durition字段。还有一个现场排查工具叫MONITOR它会打印出 Redis 收到的每一条指令适合临场抓包式排查但生产环境不要长时间开着因为MONITOR本身也会消耗性能。5.4 重启后数据没了检查持久化配置重启机器后发现键全部丢失大概率是没开启持久化或 RDB 快照没触发。Redis 默认有 RDB 快照配置比如save 900 1表示 900 秒内至少 1 次写入就触发一次快照但如果数据量小或者没有达到触发条件重启后就是空的。学习阶段想确保数据不丢可以把 AOF 打开CONFIG SET appendonly yes生产环境更规范的做法是修改 redis.conf 后重启不能只靠运行时的CONFIG SET因为重启后配置会丢失。无论用 RDB 还是 AOF都要理解一个基本事实Redis 首先是缓存数据库持久化只是锦上添花核心数据不能只放在 Redis 里。5.5 面试问基础指令怎么答才不显得外行面试官问 Redis 基础指令不是在考你背没背过命令而是想看你有没有真实使用经验。我建议按这个思路回答先讲清楚五大数据类型分别适合什么场景每种类型说两三个最核心的指令再举一个实际例子。比如你说用 Hash 存用户信息能说出为什么不用 String 存 JSON因为修改字段不需要反序列化整个对象再说分布式锁时能说出SET key value NX EX而不是老掉牙的SETNX还要点出为什么不能拆成两条命令。这种细节马上就能让面试官觉得你是真用过。另一个加分项是提SCAN而不是KEYS能说出KEYS会阻塞主线程、生产环境禁用。这些不是说教而是线上真实经验比背指令列表有说服力得多。我个人在实际操作中的体会是Redis 基础指令的学习重点不是背命令而是把这个数据结构适合解决什么问题深深刻在脑子里。我刚入门时也犯过只记命令不理解的毛病后来开始用 Redis 做缓存、做分布式锁、做消息队列踩了各种坑才慢慢明白指令只是工具选型和用法才是关键。如果你刚开始学不妨先把这些基础指令在本地反复敲熟然后再去研究淘汰策略、持久化、集群这些进阶内容。基础打牢之后再复杂的架构问题你也会有一条清晰的解决思路。
返回列表