1. 先说清楚Redis 到底解决的是什么问题很多刚接触后端开发的朋友第一次听到 Redis往往是在看到某个项目的技术栈列表里有一排名字MySQL、Redis、Kafka、ES……大家对 MySQL 比较熟悉知道是用来存数据的轮到 Redis 就有点模糊了知道它是个数据库但又不太清楚它跟 MySQL 有什么区别更不明白为什么大家都在强调把热点数据放 Redis 里。这篇内容就围绕 Redis 入门这条主线把它的核心概念、常用数据类型、持久化机制、缓存场景里最常踩的坑串一遍帮你从“知道名字”快速过渡到“敢在自己的项目里用起来”。先给一个最直观的类比如果把 MySQL 当成一本必须落盘保存的账本Redis 就像桌面上一张写满常用信息的便签纸。账本要慢慢翻、慢慢记便签纸伸手就能拿到。Redis 的数据保存在内存里读写速度是纳秒级别单机轻松支撑每秒几万到十几万次操作而像 MySQL 这种基于硬盘的关系型数据库再优化也难在这个数量级上跟它比。所以 Redis 最常见的定位就是给上层业务加一层高速缓存挡住大部分查询请求让后端系统在流量上来的时候不至于被打崩。当然Redis 不只是一个缓存工具。它的数据类型丰富支持字符串、哈希、列表、集合、有序集合还能做简单的消息队列、分布式锁、排行榜、去重统计等。学了 Redis 之后你就会发现很多以前要绕远路才能实现的功能换个思路放到 Redis 上几行命令就能搞定。这篇内容适合的技术画像大概是写过一点后端代码知道 MySQL 的基本操作想搞明白 Redis 怎么用、为什么这么用、有哪些坑要避开。不需要你提前装环境我会一步步带着走关键的配置和命令都有实操示例照着敲一遍基本就能上手。2. 五种核心数据结构每个类型背后都藏着一种场景Redis 之所以好用很大程度上要归功于它有多种数据结构。初学的时候不用贪多先把 String、Hash、List、Set、ZSet 这五种吃透就能覆盖项目里绝大多数需求。下面逐个拆开聊重点讲它们适合什么场景以及在真实项目中我是怎么选型的。2.1 String一切的基础最简单的键值对String 是 Redis 最基础的类型value 可以是字符串、整数、浮点数甚至可以把一个序列化后的 JSON 对象塞进去。它适合保存那些结构简单、直接以某个 key 取值的场景比如接口缓存、计数器、分布式锁的占位标记。举一个非常典型的例子某订单系统需要展示用户在某段时间内的下单次数这个数字如果每一次都去 MySQL 里SELECT COUNT(*)表一大会慢得让人头疼。更好的办法是在 Redis 里维护一个计数器key 设计成order:count:{userId}用户每下一单后端就执行一次INCR order:count:{userId}查询的时候直接GET这个 key。Redis 的INCR操作是原子的并发环境下不会出现计数丢失这一点在流量高的场景里特别有价值。用 String 的时候还有一个细节值得注意value 长度不要设计得过大。虽然 Redis 的 String 理论上能存 512MB但大 value 会导致网络传输变慢、内存占用升高、持久化时也更容易出现阻塞风险。我见过有同事把一整张报表 JSON 序列化后存成 String一个 key 几十 MB结果每次读请求都拖慢整个实例后来拆成按字段缓存才解决问题。所以建议 String 类型的 value 控制在几十 KB 以内凡是超过这个体量的优先考虑换 Hash 或者拆分存储。2.2 Hash对象型数据的正确打开方式Hash 类型是一个 key 下面挂多个字段field和值value非常适合保存“一个对象的多项属性”。最常见的例子就是用户信息userId 作为 key姓名、年龄、性别、手机号各自作为 field。存储用户信息HSET user:1001 name 张三 age 25 city 北京读取某几个字段HMGET user:1001 name age修改单个字段HINCRBY user:1001 age 1跟“把整个对象序列化成 JSON 存进 String”相比Hash 的优势很明显可以只读取或更新某一个字段不需要把整个对象取出来再反序列化再写回去。尤其是在字段频繁更新的场景下Hash 避免了“大对象反复读写”带来的性能损耗网络传输量也小得多。不过 Hash 也有它的小毛病如果字段数量特别多比如几千个 field 挂在同一个 key 下面管理成本会上升HGETALL这种操作会把所有字段一次性取出来容易拖慢性能。所以用 Hash 的时候要把 key 的类型和粒度设计好不要把无限增长的维度全堆在一个 key 里可以按时间、按业务模块做拆分。2.3 List队列与“最近列表”的双面选手List 是一个双向链表支持在头部和尾部插入、弹出元素。它最经典的两个用途一个是当作简单的消息队列另一个是保存“最新动态”这种需要倒序展示的数据。当作消息队列时典型的交互是一端执行LPUSH往列表左边塞任务另一端执行BRPOP从右边阻塞式地取任务。BRPOP会阻塞住连接直到队列里有了数据这样比轮询更节省资源。但这种玩法有两个局限一是没有消费者确认机制任务取走之后如果处理失败消息就丢了二是没有定时和延迟消息的概念。所以它适合任务量大但失败后允许补偿的场景真要追求消息可靠性还是得上专门的消息中间件。保存“最新动态”时List 的索引访问能力很关键。比如一个资讯 App 要展示“最新公告”用LPUSH把新公告塞进去然后LRANGE key 0 19就能取到最新的 20 条天然倒序不用自己排序。不过要注意 List 长度不能无限增长长时间不清理会积累大量无用数据建议配合LTRIM做裁剪每次插入后只保留最近 N 条LPUSH notices 新公告内容 LTRIM notices 0 99这两条指令组合谁能起到“保留最新 100 条公告”的效果后面的数据会自动丢弃内存占用被控制住。2.4 Set 与 ZSet去重、交集、排行榜三件套Set 是无序、不重复的字符串集合最擅长做去重和集合运算。比如一个抽奖活动要保证一个用户只能中奖一次就可以用SADD lottery:1001 {userId}返回值如果是 0 说明用户已经存在已经中过奖了。要统计某天活跃用户数就把当天访问过的用户 ID 一个个SADD进去然后SCARD拿到集合大小直接就是去重后的数量。Set 还有一个利器是集合运算。要找出“既关注了 A 又关注了 B”的用户用SINTER user:1:follow user:2:follow直接取交集MySQL 里要写好长一段 JOIN 才能完成的事情Redis 一条命令就搞定了。ZSet 比 Set 多了一个“分数score”的概念每个元素都绑定一个数字Redis 按分数排序存储。排行榜就是它的经典主场。比如给在线答题系统做一个实时排行榜用户每答对一题就ZINCRBY rank:quiz:2024 10 user:1001这样用户每答对一题分数加 10随时用ZREVRANGE rank:quiz:2024 0 9 WITHSCORES就能取出前十名以及各自的分数。ZSet 底层实现结合了哈希表和跳表插入和查询的效率都很稳定大数据量下依然能保持很好的性能。做技术选型的时候最终还是要回到业务诉求上需要的是简单缓存就上 String需要操作对象部分字段就上 Hash需要排队取任务就考虑 List需要去重和集合运算就上 Set需要排序和排行榜就上 ZSet。搞清楚了每个类型的“脾气”后面写代码的时候会顺很多。3. 持久化与内存策略数据安全和使用边界的双重保障Redis 把数据放在内存里换来的是高性能付出的代价则是“内存空间有限”和“进程退出数据可能丢失”这两个问题。针对这两个问题Redis 分别给出持久化和过期/淘汰机制两套方案。入门阶段不要求你精通所有调优参数但至少要知道它们各自在什么情况下起作用。3.1 RDB 与 AOF两条持久化路线思路完全不同RDB 的机制可以理解为“定期拍照”。Redis 会按照配置的规则把某一时刻的内存数据全量写入一个二进制快照文件默认文件名为 dump.rdb。触发条件在配置文件里可以调整比如save 900 1 save 300 10 save 60 10000这组配置的含义是900 秒内有 1 次 key 变化就执行一次快照300 秒内有 10 次变化执行一次60 秒内有 10000 次变化执行一次。只要满足任意一条就会触发。RDB 的优点是文件紧凑恢复速度快适合做备份和灾难恢复缺点是两次快照之间的数据如果 Redis 宕机这部分增量就丢了。AOF 则采用“写日志”的思路每一次写操作命令都会以追加的方式记录到日志文件里恢复的时候就重新执行一遍这些命令。AOF 的恢复速度比 RDB 慢但数据安全性更高因为它可以做到“每条写命令都落盘”即使宕机也最多丢失几秒甚至更少的数据。为了控制日志文件体积Redis 支持 AOF 重写定期把日志压缩成对当前数据状态的最小命令集避免文件无限膨胀。现在的主流用法是把两者结合启用 RDB 做整体备份同时开启 AOF 记录增量操作。对一个入门级应用来说在 redis.conf 里确认这一点就够了appendonly yes appendfsync everysecappendfsync everysec表示每秒把缓冲区的命令刷到磁盘兼顾了性能和安全性。实际项目中我一般不会关掉持久化哪怕只是缓存场景重启之后内存里一片空白瞬间的高并发流量可能直接把后端数据库打挂。所以“Redis 挂了数据还能回来”这件事不仅关系到数据本身还关系到整个系统在重启后的生存能力。3.2 过期清理与内存淘汰别让缓存变成内存炸弹Redis 给 key 设置过期时间很简单SET token:1001 abc EX 7200这条命令表示 7200 秒后 key 自动删除。缓存场景里这个功能非常重要它能保证数据不会无限堆积。但这里有一个初学者很容易忽略的机制Redis 删除过期 key 有两种方式一种是惰性删除也就是 key 被访问的时候才发现过期了再删另一种是定期删除后台每隔一段时间抽样检查一批过期 key 并清理。这两种策略配合能覆盖大多数情况但依然可能有少量已过期的 key 暂时还留在内存里。所以不能把内存清理完全寄托在“过期时间”上必须设置内存上限。在 redis.conf 里maxmemory 2gb maxmemory-policy allkeys-lrumaxmemory决定 Redis 最多能占用多少内存maxmemory-policy则决定内存满了之后怎么处理。allkeys-lru的意思是从所有 key 里按最近最少使用算法淘汰适合缓存场景。如果你的 Redis 里存了必须保证不淘汰的重要数据就要考虑volatile-lru只淘汰设了过期时间的 key或者用noeviction在内存写满时直接报错而不是静默淘汰。这里的选择完全取决于业务对数据完整性的容忍度。我在实际项目里踩过一个很典型的坑团队里有人为了让缓存数据不丢失把 maxmemory 设成 0表示不限制也没配淘汰策略结果一个线上活动产生了海量缓存键内存被打满整个实例响应开始抖动最后只能手动清理并补上淘汰策略。记住一个原则缓存是一个有边界的东西没有内存上限和淘汰策略的 Redis用不了多久就会变成一颗定时炸弹真正需要长久保存的数据应该老老实实放进 MySQL 或者其他持久化存储里而不是全塞给 Redis。4. 环境搭建与核心命令实操从零把一个键值写到线上留了不少篇幅讲原理现在真正动手跑一遍。我会把从安装到完成一次业务缓存的完整链路走一遍这套流程在 Linux 服务器上最为常用macOS 和 Windows 的安装方式略有区别但思路一致关键是理解它启动之后到底发生了什么事。4.1 安装与启动二三分钟让 Redis 跑起来在 Debian/Ubuntu 系列的服务器上用系统包管理器安装最省事sudo apt update sudo apt install redis-server -y安装完成后Redis 默认会以守护进程方式运行检查一下状态systemctl status redis-server如果没有自动启动手动拉起来sudo systemctl start redis-server sudo systemctl enable redis-server然后用命令行客户端测试连接redis-cli ping能看到PONG这个回复就说明服务已经起来了。如果是自己编译安装流程是下载源码、make、make install然后指定配置文件启动这里就不展开生产环境通常建议使用官方稳定版自行管理但入门阶段用系统包就够了。还有一个很要紧的配置项需要提前提个醒如果你用的云服务器Redis 默认监听在127.0.0.1也就是只有本机能访问这是安全的默认值。千万别为了图方便直接把bind改成0.0.0.0且不设密码这种情况下 Redis 端口一旦暴露在公网等于把数据裸奔放在门口被勒索和植入挖矿程序只是时间问题。入门可以先从本机连接玩起等真正要部署到服务器再研究requirepass设置密码和绑定内网 IP 这几件事。4.2 常用命令速查先记住这一张表命令很多一开始不用背全记住下面这些足够覆盖日常操作类型常用命令说明通用KEYS pattern按模式匹配 key生产环境慎用会阻塞实例通用EXISTS key判断 key 是否存在通用TTL key查看 key 剩余过期时间-1 表示永不过期通用EXPIRE key seconds给已有 key 设置过期时间StringSET key value [EX seconds]存入键值对可附带过期时间StringGET key取键值StringINCR key自增 1常用于计数器HashHSET key field value设置对象字段HashHGET key field取对象单个字段HashHGETALL key取对象全部字段ListLPUSH key value左端插入ListBRPOP key timeout阻塞式右端弹出SetSADD key member往集合加元素返回 1 表示新增成功SetSINTER key1 key2取两个集合的交集ZSetZADD key score member往有序集合加元素ZSetZREVRANGE key start stop WITHSCORES按分数从大到小取一段元素这里特别提一下第一个命令KEYS。很多初学者学完这一节为了图方便会在生产环境执行KEYS *去看实例里有什么 key。在数据量大时这个操作全表扫描量太大会阻塞 Redis 主线程线上访问直接卡顿。短时间排查可以用SCAN命令替代它在遍历方式上做了优化不会一次性阻塞住 RedisSCAN 0 MATCH user:* COUNT 200SCAN 返回的是一批 key 和下一个游标用游标继续迭代直到游标回到 0 表示遍历结束。看起来麻烦一点但至少不会拖垮服务。4.3 完整业务演练把用户信息做成缓存用一个模拟场景串一遍整个过程某后台管理系统要展示用户资料用户量不大但读取频率很高每次去 MySQL 拿会浪费连接资源。我的方案是 MySQL 作为持久层Redis 作为缓存层读了 Redis 没命中再去数据库同时把结果回填到 Redis。第一步设计 key 和 value。userId 为 1001 的用户用 Hash 存储HSET user:1001 name 李四 age 28 department 研发部 EXPIRE user:1001 300设置 300 秒过期防止用户更新后缓存长期是旧数据。第二步在后端代码里模拟读取逻辑。以常见流程为例可以先查缓存缓存命中直接返回缓存没命中则走到 MySQL把结果写入 Redis 后再返回# 用 Redis 命令模拟实际是业务代码中执行 HGETALL user:1001第一次执行会返回空结果因为缓存里还没有数据。这时候去数据库查到用户信息再执行上面的 HSET 和 EXPIRE把数据回填。第三步模拟缓存过期后数据被重新加载。等 300 秒过期后再执行HGETALL会再一次得到空结果触发一次数据库回源然后把新数据写回 Redis。这套流程通俗点说叫 Cache Aside 模式也是目前最主流、最容易理解的缓存策略。这里有一个非常关键但入门者经常会漏掉的细节缓存回填一定别忘了设置过期时间。如果不设过期时间用户更新资料的成本会变得很高因为不仅要更新 MySQL还要手动清理 Redis 里的旧数据而一旦漏了清理步骤用户看到的就是一个长期不更新的脏数据。设置过期时间会让缓存有一个自愈的能力最坏情况也就是过期后多查一次数据库合理得多。5. 缓存穿透、击穿、雪崩三个实战问题的应对方案用上了 Redis 做缓存就必然会面对这三个问题。很多新手第一次听到“穿透、击穿、雪崩”会被名字吓到其实它们描述的都是非常具体、可复现的状况。把这三种问题搞明白了以后线上出事故排查起来心里至少有个谱。5.1 穿透、击穿、雪崩到底分别指什么缓存穿透查询一个根本不存在的数据。比如拿一个不存在的 userId 去查Redis 缓存里当然没有于是请求一路打到 MySQLMySQL 里也没有。如果攻击者用大量不存在的 ID 循环请求数据库会被这些无效查询压垮。缓存击穿某一个热点 key 在过期的瞬间大量请求同时涌进来。比如某个爆款商品的详情数据是热点恰好它在某刻过期同时来了几万个请求所有请求都没命中缓存一起往数据库打数据库瞬间压力暴涨。缓存雪崩大量 key 在同一时间段集体过期。比如系统启动时一次性缓存了大量数据又统一设置了 30 分钟过期时间到点后所有缓存集体失效所有请求都回源数据库数据库直接被冲垮。雪崩的范围比击穿大很多击穿是单点问题雪崩是面状问题。5.2 针对穿透的处理缓存空值 布隆过滤器应对穿透最直白的办法是“缓存空值”查询结果为空时也把空结果缓存起来设置一个比较短的过期时间比如 5 分钟SET user:notexist:10099 EX 300这样同一个不存在的 ID 再次请求时Redis 直接返回空值不会再请求数据库。如果恶意请求使用了大量随机且不同的非存在 ID这种“缓存空值”的办法效果会打折扣因为每个 ID 都要专门缓存一个空 key浪费内存且缓存价值不高。更合理的方案是引入布隆过滤器。布隆过滤器的特点是“宁可错杀三千绝不放过一个”它说“不存在”的数据一定不存在它说“存在”的数据可能实际不存在但可以挡掉大量无意义的查询。在业务代码里写入数据时同步更新布隆过滤器查询前先过一遍过滤器过滤器判定不存在就直接返回数据库连看都不用看。分布式系统里通常会把布隆过滤器放在服务内或者使用专门的集群版实现入门阶段理解它的作用已经够用。5.3 针对击穿与雪崩的处理互斥锁与过期时间打散处理击穿的常用思路有两个互斥锁和逻辑过期。互斥锁的思路是当缓存失效时不是让所有请求都去查数据库而是只允许一个请求拿到分布式锁由它负责回查数据库并重建缓存其他请求暂时等待等缓存重建完成后再正常读取。实现起来不复杂加锁时有几个注意点锁一定要设过期时间防止加锁的线程中途崩溃导致死锁释放锁时只用比对确是自己加的锁再删避免误删别人刚重建的锁。这种方式效果直接但会牺牲部分可用性因为请求被阻塞等待了对响应时间敏感的接口要谨慎使用。逻辑过期的思路更巧妙不给 key 设置真实的过期时间而是给 value 里塞一个逻辑过期时间戳。读取时发现逻辑时间已经过期不直接走数据库而是先返回过期数据同时后台起一个异步线程去更新缓存。这种方式响应更快业务层感知不到阻塞但实现复杂度稍高而且返回的数据短暂不是最新的适合对数据一致性要求不是极端的场景。处理雪崩最核心的手段就是“错开过期时间”。比如在代码里给所有统一设置的过期时间加上一个随机值SET key value EX {300 random(0,60)}这个随机偏移会让大量 key 的过期时刻均匀散开避免集中失效。更进一步还可以在架构层面做多级缓存Node 本地再放一层短时间缓存这样即使 Redis 集群不健康应用本地缓存还能扛住一部分流量为恢复争取时间。这三个问题处理起来没有“唯一正解”本质都是“在哪些环节花多少代价”的权衡取舍。实际做方案的时候要先评估业务对一致性的容忍度、对响应时间的要求、对基础设施复杂度的承受能力再决定用哪种方式组合。入门阶段我建议先把“缓存空值”“过期时间加随机值”“热点 key 不设置过期或永不过期”这三个基本操作落实到位大部分场景已经能覆盖一大半风险。6. 常见问题排查与避坑经验线上踩过的几个实在坑文章最后这部分分享一些实操中反复遇到的排查问题和处理心得。很多知识看文档是看不到的只有动手跑项目、被线上事故教训过才能体会到。这里整理成问题清单给准备把 Redis 用到实际项目里的读者一个提前预警。6.1 Redis 突然变慢了如何定位Redis 变慢的原因往往是多方面的最常见的有这么几类。第一big key 问题。某个 value 特别大比如一个 Hash 有几十万字段或者一个 List 有几百万个元素操作它的时候耗时很长还会拖慢后续所有命令。排查时用--bigkeys参数对实例做一次分析redis-cli --bigkeys它会扫描整个实例告诉你哪些 key 的体量最大。建议在低峰期执行并且不要频繁跑避免给实例增加压力。发现 big key 后处理方式一般是拆分Hash 按业务维度分段List 分批裁剪String 考虑换用压缩序列化或者改存文件地址。第二慢查询日志。Redis 有慢查询日志功能配置一个慢日志阈值slowlog-log-slower-than 10000单位是微秒10000 微秒也就是 10 毫秒超过这个耗时的命令会被记录。用SLOWLOG GET 10就能看到最近的慢操作挨个分析是哪些命令不听话了。第三频繁的 fork 阻塞。RDB 持久化或者 AOF 重写会触发后台 forkfork 过程中会短暂阻塞主线程。如果内存快满了阻塞时间会更明显。这种问题靠升级硬件或者调低save触发频率能缓解但很难完全避免所以之前强调 maxmemory 一定要提前规划好。6.2 内存占用高居不下怎么办内存暴涨的常见原因包括key 过期了但还没被及时清理、空值缓存太多、Redis 哈希表结构因为大量删除操作导致内存碎片膨胀。排查流程一般是先用INFO memory查看内存分布redis-cli INFO memory重点看used_memory和used_memory_rss的比值以及内存碎片率。如果碎片率高可以考虑开启自动内存碎片整理或者在低峰期重启实例让内存重排。如果是因为过期 key 没有及时清理调整hz参数默认 10可以加快定期清理的频率但也会增加 CPU 开销属于一个需要权衡的旋钮。内存居高不下的另一个常见原因是缓存内容根本没有设置 TTL。我见过不少项目代码里写SET key value却没带 EX 参数也没有在事后主动设置过期时间结果缓存变成了“长命数据”堆积到内存报警。统一的做法是在缓存写入的工具类里强制带上过期时间参数从代码层面杜绝这种问题。6.3 给入门者的几条实操建议第一条先规划 key 的命名空间。Redis 的 key 是全局唯一的如果项目里多个模块都用name:xxx很容易互相覆盖。建议统一格式业务:实体:标识比如user:profile:1001、order:info:20240101。虽然 Redis 没有“必须这样命名”的强制约束但好的命名习惯能让排查和定位快很多。第二条连接要复用。很多刚入门的朋友在代码里每次操作都新建一个连接用完就关闭性能反而被网络握手拖垮了。正确做法是使用连接池让一批连接反复复用这是高并发场景下的基本要求。哪怕本地开发也要习惯用连接池而不是裸写连接。第三条不要把 Redis 当成万能存储。很多开发习惯把任何数据都往 Redis 里塞比如记录日志、存储大文件这类数据量大且不需要高频访问放进 Redis 只是占用内存。一个健康的系统Redis 只存“访问频率高、实时性强”的热数据其他老老实实待在持久化存储里。最后再说一点个人体会学 Redis 最好的方式不是一口气读完一本大部头而是先跑起来、写几个小 Demo把 String、Hash、List 这些类型都亲手敲一遍再回来看原理和官方文档。等你真正在项目里跨过分层架构的边界排查过一次线上缓存问题很多零散的概念会自动连成一张网。入门阶段没必要追求把每个配置项都调到最优先把最核心的数据类型、持久化策略和三个缓存问题理解透你已经能超过不少只停留在“会用 SET/GET”层面的开发者了。