
起步先交代一下背景。我做后端开发这些年Redis几乎是每个项目都绕不开的组件从最初的缓存到分布式锁、限流、排行榜、消息队列处处都是它的身影。但很多人对Redis的认知停留在set/get层面一到生产环境就各种踩坑数据序列化乱码、increment报错、缓存穿透把数据库打挂、主从切换丢锁……这些问题我在实战中几乎全都遇到过。这篇文章就是把这些年积累的Redis核心知识点和实战踩坑经验整理出来从安装部署到底层原理从分布式锁到缓存治理再到高频面试题和排障技巧一次性吃透。写的对象一是刚接触Redis、想系统入门的同学二是已经在用但总被各种线上问题折磨的开发者。我会尽量用大白话讲原理把操作步骤和参数背后的“为什么”也讲清楚大家可以直接照着做、照着排查。1. 安装部署先把环境跑起来1.1 Windows下安装Redis这几个坑要提前知道很多人在Windows上装Redis第一个问题就是找不到官方Windows版本。Redis官方从很早开始就只维护Linux版本了Windows安装包现在大多是第三方维护的移植版比如tporadowski/redis这个GitHub项目功能上跟Linux版没有太大差异日常开发完全够用。下载下来是个zip包解压后目录里有redis-server.exe、redis-cli.exe、redis.windows.conf这几个核心文件。最省事的启动方式是在cmd里切换到解压目录直接执行redis-server.exe redis.windows.conf看到“Ready to accept connections”就说明起来了。但有几个坑我提醒一下直接双击redis-server.exe会闪退基本都是配置文件路径不对或者端口6379被占用了。在cmd里运行可以看到具体报错比双击靠谱得多。Windows下想注册成服务用redis-server --service-install redis.windows.conf启动服务用redis-server --service-start这样能开机自启。只是改配置之后记得重启服务。默认配置是没有任何密码的本地开发无所谓但如果你把6379端口映射到了外网那就等着被挖矿病毒扫吧。务必在配置文件里加requirepass密码别用123456这种。Windows版的appendonlyAOF持久化默认也是关的开发时一般不用管但如果你的开发环境需要模拟线上建议把appendonly yes打开。当然如果你有Docker Desktop或WSL我更推荐直接跑Linux容器或WSL里的Redis毕竟跟生产环境一致少踩很多平台差异的坑。Windows版本说白了就是图个方便别指望它在生产环境扛高并发。1.2 用Docker搭主从复制5分钟搞定高可用基础Docker部署Redis主从是面试里爱问、生产里爱用的基础架构。主从复制在Redis里承担两个职责一是读写分离把读流量分散到从节点二是为主节点宕机后的故障转移提供数据副本。它的核心机制不复杂主节点把写命令同步给从节点从节点重放命令保持数据一致。搭建过程很简单。先拉镜像我用的是redis:7.07.x版本比6.x在内存效率上又有改进生产推荐docker pull redis:7.0启动主节点docker run -d --name redis-master \ -p 6379:6379 \ -v /data/redis/master:/data \ redis:7.0 redis-server \ --requirepass 123456 \ --appendonly yes启动从节点docker run -d --name redis-slave \ -p 6380:6379 \ -v /data/redis/slave:/data \ redis:7.0 redis-server \ --replicaof 192.168.1.10 6379 \ --masterauth 123456 \ --requirepass 123456 \ --appendonly yes参数说明一下--requirepass是Redis自身的访问密码--masterauth是从节点连接主节点时用的密码。主节点设置密码后从节点必须带着masterauth才能完成同步否则日志里会一直刷“MASTER - REPLICA sync started”然后失败。--replicaof后面跟主节点的IP和端口7.x用replicaof老版本写slaveof现在版本都兼容。启动完在主节点执行info replication看到role:masterconnected_slaves:1从节点返回role:slavemaster_link_status:up就说明复制关系建立成功了。这里有个细节容易漏两个容器之间要能互通如果用docker run而不是docker-compose建议加--network host或者放在同一个自定义network里不然从节点访问不到主节点的IP。主从复制第一次是全量同步主节点生成RDB快照推给从节点后续才走增量同步通过repl_backlog_buffer缓存最近一段时间的写命令。所以从节点刚挂载时短暂阻塞、主节点内存开销升一点是正常的。1.3 可视化客户端到底选哪个我的选择逻辑这项排名不分先后纯属我个人体验。Redis Desktop ManagerRDM现在叫RedisInsight的老牌桌面端是老牌工具功能全面支持SSH隧道、集群模式、JSON格式化Key。缺点是新版本改成订阅制收费很多人还在用老的0.9.x版本但对Redis 6/7的新命令支持不全。Another Redis Desktop ManagerARDM是国产开源免费工具跨平台界面清爽支持慢日志查询、命令行、多标签页我目前主力就是它。GitHub上搜“AnotherRedisDesktopManager”就能找到。如果你不想装桌面端redis-cli加--raw参数也很好用。redis-cli --raw -h 127.0.0.1 -p 6379 -a 密码带--raw能避免中文被转义成十六进制看数据方便很多。平时线上我只用redis-cli因为生产服务器一般不会给你装桌面工具而且命令行查问题效率更高。所以我的建议是本地开发装ARDM线上运维用redis-cli 监控平台。可视化工具解决的是看数据和调试的问题真正定位性能瓶颈还得靠命令行和监控指标。2. 底层原理数据类型、编码与序列化2.1 五种数据类型用起来是花架子底层编码才是真功夫Redis命令里的String、List、Hash、Set、ZSet每个都对应不止一种底层编码结构。这也就是很多人看到object encoding key返回结果跟想象中不一样的原因。String底层有int、embstr、raw三种编码。存整数时用int编码直接做INCR/DECR不带一点多余操作字符串小于44字节用embstr只分配一次内存效率高超过44字节变成raw要分配两次内存。这就是为什么短字符串存储要省内存的原因之一。List在3.2之前用ziplist压缩列表或linkedlist双向链表3.2之后统一改成quicklist相当于用多个ziplist拼成一个链表既保留双向链表插入删除快的特性又用压缩方式省内存。7.0以后还在逐步用listpack替换ziplist。Hash底层是ziplist或hashtable当字段数少、每个字段值短时用ziplist存储紧凑省内存。一旦字段超过512个或者某个字段值超过64字节就转成hashtable。Set底层是intset或hashtable。当集合全由整数且数量不多时用intset能节省大量内存否则用hashtable。ZSet底层是skiplist hashtable的组合跳表负责排序和范围查询哈希表负责O(1)查找分数。为什么用跳表不用平衡树跳表实现更简单范围查询也不用像平衡树那样做中序遍历性能足够应付百万级元素。理解编码有什么用用途就是让你知道为什么有的key占用内存小为什么某些操作会变慢为什么hash在某些场景下能大幅节省内存。面试里被问“Redis为什么快”除了单线程和内存之外数据结构的精巧设计也是核心因素。顺便提一句Redis 7.0开始用listpack逐步替代ziplist改名成了compact list的相关逻辑细节有变化但核心思想没变。2.2 序列化是把双刃剑RedisTemplate存进去的字符怎么变乱码了很多人第一次用Spring的RedisTemplate存数据去RDM里一看key前面多了一堆\xAC\xED\x00\x05t\x00value也是乱码。这是因为Spring默认用JdkSerializationRedisSerializer做序列化它是Java对象二进制序列化不是给人看的文本而且序列化出来的体积比原始字符串大好几倍类结构变了还会导致反序列化失败。序列化器有四种常见选择序列化器特点适用场景JdkSerializationRedisSerializerSpring默认支持任意Serializable对象但体积大、乱码、类升级不友好不推荐生产用StringRedisSerializer只处理字符串人类可读key推荐用它value需要手动转JSONJackson2JsonRedisSerializer转JSON可读需指定对象类型反序列化时带类型信息一般对象存储推荐GenericJackson2JsonRedisSerializer自动保存类型信息反序列化不需要手动指定类型类型杂但能接受JSON带class字段我的方案是key统一用StringRedisSerializervalue用GenericJackson2JsonRedisSerializer。这样RDM里看key是正常文本value是JSON排查数据问题省很多时间。注意一旦你配置了固定的序列化器project上线后不要随便改因为新旧序列化器不兼容以前存进去的数据全读不出来。另外要提一嘴StringRedisTemplate和RedisTemplate的区别StringRedisTemplate默认全用StringRedisSerializer所有值都以字符串存储RedisTemplate默认是JDK序列化器。如果你用StringRedisTemplate写入的数据再用RedisTemplate去读会因为序列化方式不一致导致读到null或报错。这是团队协作里经常踩的坑建议整个项目统一使用一种Template和一种序列化组合。2.3 Java里RedisTemplate的increment()报“not an integer or out of range”原因和解决这个报错其实分两种场景。第一种是keystore里这个key对应的value本身不是纯数字字符串做INCR时Redis直接拒绝。第二种是Spring侧报NumberFormatException: For input string: ...”原因是value经过序列化后带着二进制头或者引号Redis端看到的是个非数字字符串。我刚工作那会儿就踩过用RedisTemplate存了一个值代码是redisTemplate.opsForValue().set(count, 1)然后用redisTemplate.opsForValue().increment(count)结果报ERR value is not an integer or out of range。查了半天发现set(count, 1)时Spring把Integer 1通过JDK序列化成了二进制数据Redis存进去的根本不是文本“1”increment自然没法解析。解决办法有几个按推荐顺序用StringRedisTemplate操作incrementStringRedisTemplate的opsForValue().set(count, 1)再increment(count)就没问题因为存进去就是文本1。如果项目里统一用RedisTemplate就把valueSerializer设置为StringRedisSerializer确保数值以字符串存储。千万不要自己手动set(count, 1)这种带引号的写法这是把字符串1又包了一层increment照样报错。还有一个隐藏点increment返回的是Long如果你的业务里后续要拿这个值做乘除记得用longValue()取原始值不要直接转int大并发下计数超过int最大值会溢出。3. 生产实战分布式锁、缓存治理与日志监控3.1 分布式锁为什么不能用setnxexpire两步正确写法看这里分布式锁是Redis最经典的场景之一但也是翻车重灾区。最典型的错误写法是if (jedis.setnx(lock, 1) 1) { jedis.expire(lock, 30); // 业务逻辑 jedis.del(lock); }问题有两个。第一setnx和expire不是原子的如果setnx成功之后、expire之前进程崩溃锁永远不释放其他线程全部饿死。第二删除锁时没有校验持有者如果线程A的锁超时自动过期线程B抢到锁此时A执行完把锁删了等于把B的锁误删掉。正确的实现从Redis 2.6.12开始很简单用一条SET命令完成加锁和过期SET lock_key unique_value NX PX 30000其中NX表示不存在时才设置PX 30000表示30秒过期unique_value是整个加锁操作生成的唯一标识比如UUID用于释放时的归属校验。释放锁用Lua脚本保证原子性if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end这个脚本先比较当前值是不是自己加的锁是才删避免误删别人锁。在生产里我强烈建议直接使用Redisson它封装了看门狗机制如果你的业务还没执行完锁快到期时会自动续期默认每10秒续到30秒避免锁过期导致并发进入临界区。相比自己写脚本Redisson已经把你容易忽略的边界问题都处理好了。顺带说一句RedLock的争议它通过向多个独立Redis节点加锁来防止单点故障理论完美但很多专家认为它在实际工程中过于复杂且仍有安全隐患。绝大多数业务用Redisson的单点或主从模式就够了别为了追求完美把系统搞复杂。3.2 缓存穿透、击穿、雪崩一次讲清解决套路这三个概念面试必问生产也必出放一起讲一次。缓存穿透查询一个根本不存在的数据缓存里没有数据库里也没有所以每次请求都打到数据库。如果有人恶意构造不存在的ID数据库压力瞬间爆表。解决思路一是缓存空值把不存在的key也缓存一个短过期时间的空值二是布隆过滤器启动时把存在的ID全量放进过滤器查询前先判断ID是否存在不存在直接返回存在才继续往下去查。布隆过滤器有个小概率误判说存在不一定存在但说不存在一定不存在应对穿透场景已经够了。缓存击穿某个热点key刚好在某一刻过期此时大量并发请求同时打到数据库。解决思路一是互斥锁重建缓存只允许一个线程去数据库查并回写缓存其他线程等锁二是逻辑过期value里存一个逻辑过期时间读到时发现过期就让当前线程返回旧值同时另起一个线程去数据库刷新缓存。逻辑过期的好处是读多写少的场景不会因为重建而阻塞但可能短暂读到旧值业务能接受就行。缓存雪崩大量key在同一时间段集中过期或者是Redis节点宕机了导致大量请求直接打到数据库。解决思路一是过期时间加随机值比如TTL base random(0, 300)避免同一秒集体失效二是做集群、哨兵、多级缓存本地缓存CaffeineRedis提升可用性三是服务做限流降级宁可返回旧数据也千万别把数据库打挂。还有一个常被忽略的缓存一致性话题。目前的通用方案是Cache Aside更新操作先写数据库再删除缓存读取时缓存没有就从数据库读并回写。为什么是删缓存而不是更新缓存因为更新缓存涉及并发写顺序问题删掉之后下次读再回填天然规避了更新错乱的问题。如果对一致性要求更严可以加延迟双删先删缓存再更新数据库隔几百毫秒再删一次缓存虽然不能100%消灭脏数据但能大幅降低概率。3.3 Redis日志和监控别等线上挂了才看一眼很多人对Redis日志的关注度远低于MySQL慢查询日志但Redis其实有一套轻量的排查手段。第一个是慢查询日志配置项如下CONFIG SET slowlog-log-slower-than 10000 CONFIG SET slowlog-max-len 128slowlog-log-slower-than单位是微秒默认10000微秒10毫秒超过这个时间就记录。生产环境我一般设到5000微秒也就是5毫秒这样能抓到更多慢命令。查看方式用SLOWLOG GET如果发现大量KEYS、SORT、ZRANGE等耗时命令就要考虑换成scan类型命令或优化数据结构。第二个是INFO命令它能输出Redis的整体健康指标。重点看这几个:connected_clients客户端连接数如果异常飙高查一下是不是连接池配置太小或者有代码泄漏。used_memory和used_memory_rss内存使用量和操作系统视角的常驻内存如果rss远超used_memory说明存在内存碎片Redis会定期整理。evicted_keys因为maxmemory限制被淘汰的key数量持续增长说明内存不够用应该扩展内存或者检查大key。hits和misses计算命中率hits / (hits misses)命中率低于80%要审视缓存策略是否合理。latest_fork_usec最近一次RDB持久化fork进程耗时如果很高说明写入量太大子进程导致主进程停顿。线上建议开一个定时任务脚本每30秒用redis-cli info采样一次指标打到监控平台PrometheusGrafana有很多现成的Redis Exporter。不要等用户反馈才开始看日志慢查询、内存暴涨这种指标提前告警能救回来的概率非常大。4. 高频面试题与排障速查4.1 这几个面试题理解底层才能不被问倒Redis为什么快这个问题要从三方面答第一是纯内存操作内存随机读取纳秒级这跟磁盘不在一个数量级第二是高效的IO模型Redis基于Reactor模式实现了IO多路复用redis的IO多路复用底层用epoll单线程避免了上下文切换和锁竞争第三是底层数据结构全部是精心设计过的复杂度低。注意补充一点Redis 6.0之后引入了IO多线程但实际只是用多线程处理网络IO的读写命令执行仍然是单线程这一点在面试里主动说出来会很加分。Redis持久化方案有哪些怎么选RDB是定期生成全量快照恢复快但可能丢数据AOF是追加写命令日志默认everysec每秒刷盘最多丢一秒数据文件比RDB大很多Redis 4.0之后有混合持久化方案重写AOF时同时写入RDB格式的头部和增量AOF尾部兼顾重启速度和数据完整性。生产上我建议开启AOF且appendfsync everysec如果对数据丢失零容忍再考虑always但性能会明显下降。过期key是怎么删除的Redis用了惰性删除定期删除两种策略配合。惰性删除是每次访问key时检查是否过期过期就删省CPU但可能积累大量过期key依赖定期删除去清理定期删除是每100ms随机抽取一批设置了过期时间的key检查并清理控制对主线程的占用。这两种策略都会让过期key的清理存在延迟所以内存中可能出现短暂“没删干净”的过期数据。4.2 常见问题排查速查表现象可能原因排查命令/思路应用大量报连不上Redis连接数接近maxclients连接池参数不合理INFO clients查看connected_clients检查jedis pool的maxTotal和maxIdle某个key操作很慢大key例如一个list几百万个元素用redis-cli --bigkeys扫描慎用KEYS、SMEMBERS等全量命令主从复制延迟大网络带宽不足从节点执行慢查询repl-backlog太小INFO replication看master_repl_offset和slave_repl_offset差值查看从节点慢日志value在客户端读写不一致key或value序列化器不统一检查代码中RedisTemplate与StringRedisTemplate混用的位置用RDM查看实际存储内容内存突然暴涨未设置过期时间有写入风暴内存碎片高INFO memory查看used_memory、mem_fragmentation_ratio用MONITOR观察写入频率服务重启后缓存数据丢失持久化没开或者保存频率低CONFIG GET saveCONFIG GET appendonly确认RDB/AOF配置正确4.3 我踩过的几个坑写出来给你们当避雷针第一生产环境禁止用KEYS。刚开始用Redis的时候我觉得KEYS挺好用的直到有一次在线上一个几百万key的实例上跑了一次KEYS整个服务卡了好几秒才缓过来。正确做法是用SCAN游标式遍历SCAN 0 MATCH user:COUNT 1000分批次加载不阻塞主线程。第二大key一定要及早治理。一个value有几十上百MB的key执行读写时整个Redis实例都会被打穿删除它也会阻塞主线程。Redis没有直接暴露大key但可以用redis-cli --bigkeys或者debug object key查看一个key的具体占用大小。发现大key后建议拆分存储hash场景可以拆到多个hash key里或者用list分片存储。第三maxmemory-policy别用默认的noeviction。noeviction是内存满了直接拒绝写入业务立刻报错。我在生产环境一般用allkeys-lru让Redis淘汰最少使用的key虽然可能把一些冷门但重要的数据淘汰掉但总比服务不可用强。特定业务如果只希望淘汰设置了过期时间的key用volatile-lru。这个参数一定要提前想清楚否则内存打满时你会很被动。第四危险命令rename掉。线上实例我会在配置文件里把FLUSHALL、FLUSHDB、KEYS、CONFIG这些高危命令重命名防止意外执行或者被恶意利用rename-command FLUSHALL rename-command FLUSHDB rename-command KEYS admin:keys rename-command CONFIG admin:config修改完重启Redis这条命令就禁止了或者只能通过自设的名字调用。团队内部要有运维审批通道否则误执行FLUSHALL的代价是灾难性的。第五缓存预热不是可选项。每次发版、活动前提前把热点数据刷进缓存而不是等用户请求来了慢慢回源。预热做得好能规避我会遇到的击穿场景。预热脚本用pipeline批量写入别一条一条set否则预热阶段反而会把Redis IO打高。结尾说点实际操作后的心里话跟Redis打了这么多年交道我最大的体会是它看起来简单难在执行细节。很多问题不是Redis本身出Bug而是使用姿势不对。序列化器选型、过期时间策略、大key治理、主从网络波动这些在生产环境一旦触发排查起来极其消耗精力。前期多花十分钟把配置调好、把规范定好远比线上救火划算。强烈建议每个后端开发手里都有一套自己的Redis监控脚本和排查checklist能帮你省一半的排障时间。如果你也踩过什么有意思的Redis坑想找个安静的地方聊聊那我只能送上最老套也最实在的祝福少踩坑早下班。