
去年线上出过一次事故缓存服务一报警订单服务跟着超时整个链路像多米诺骨牌一样往下塌。复盘的时候我把自己关在小黑屋里对着Redis一连问了十二个问题从基础原理问到线上排障。后来发现这十二个问题不光保住了那次复盘的脸面也成了我之后面试候选人和排查故障的固定清单。今天原封不动整理出来每一个问题都附上我自己的分析思路和踩坑教训希望能帮你把Redis从“会用”推向“用明白”。1. 第1问为什么Redis这么快——“内存快”不是完整答案1.1 “内存快”为什么不是标准答案很多人张口就来“因为Redis是内存数据库”这个回答不能说错但只能得一半分。内存确实快DDR4内存的随机访问延迟大约在几十到一百纳秒级别而普通SSD的随机读延迟是几十微秒机械硬盘得几毫秒。但问题是同样部署在内存里的其他系统不见得能跑出Redis这种单实例十万级QPS的吞吐量。所以面试官真正想听的是Redis在“软件层面”做了什么。1.2 单线程、IO多路复用和高效数据结构三件套缺一不可先纠正一个误区Redis并不是完全单线程。从6.0开始网络IO读写引入了多线程但命令执行仍然是单线程的你可以把整个模型理解成“前台接待可以有多名真正拍板办事的只有一个人”。单线程执行命令意味着没有锁竞争、没有上下文切换、没有并发修改同一个数据结构的问题这是它能稳的关键。但单线程也怕阻塞一旦某个命令执行时间太长后面所有命令都要排队。这时候“IO多路复用”就上场了Redis基于epoll机制在一个线程里同时盯着成千上万个连接只要连接上有数据可读可写就立刻处理避免了“每个连接一个线程”那种资源爆炸的模型。再加上SDS、跳表、压缩列表这些针对不同场景设计的数据结构内存访问加上极低的时间复杂度才组合出最终的高性能。我习惯用一句话概括快是结果是“内存存储 单线程模型 多路复用IO 高效数据结构”四者的合力单独归功于内存是不公平的。1.3 快的前提是“别拖后腿”这几个命令我劝你少用单线程模型下O(N)级别的命令就是灾难。KEYS *这种扫描全库的命令我在线上一次都不敢用真要遍历就用SCAN游标分批处理。还有针对大Key的DEL、SUNION、SORT这种复杂度高的命令执行期间会把整个实例拖垮。提示如果你要删除一个大Hash或者大List别直接DEL。先用HLEN或LLEN评估规模再用HSCAN分批取出来删。Redis 4.0以后提供了UNLINK命令它是异步删除主线程不会阻塞这个才是处理大Key的正确姿势。2. 第2问五大基础数据类型你真的选对了吗2.1 从使用场景反推数据结构比背命令更靠谱String是最常见但最容易用错的类型。它除了存字符串还能存数字、二进制内容。但如果你要存一个对象的多个字段用String拼JSON就会遇到“改一个字段要整个读出来重新序列化”的尴尬。这时候Hash就合适多了底层类似一个字典HGET、HSET可以单独操作某个字段修改用户昵称不用动整条数据。List用在一个“有序队列”的场景比如简单消息队列左侧LPUSH右侧BRPOP还能实现阻塞读。但它不是个严格意义上的消息系统消息没有确认机制消费端挂了消息就丢了。真要可靠投递Redis 5.0引入的Stream是更好的选择支持消费者组和ACK确认。Set是无序集合适合做去重、交集并集运算比如“共同好友”“随机抽奖”。ZSet是带分数的有序集合底层是跳表加哈希表排行榜、延时队列、限流窗口都靠它。2.2 底层编码方式决定内存和性能的平衡同一种类型Redis内部会根据数据规模切换编码方式。拿Hash举例字段少、值小的时候用listpack老版本叫ziplist的压缩列表内存省但读写要逐个解析字段多了就升级成hashtable空间换时间。这套自动升级机制用户无感知但你要知道它存在——如果你的Hash长期在临界值附近徘徊频繁转换会带来额外的性能抖动和内存碎片。ZSet在小数据量时用ziplist超过zset-max-ziplist-entries阈值默认128转成跳表。跳表的查询是O(logN)插入删除也稳定这就是排行榜能扛住高并发的原因。2.3 扩展类型里藏着的实用技巧很多人不知道Redis还有BitMap、HyperLogLog、GEO。比如“用户签到”这种布尔型记录用BitMap一个字节存8个用户万级用户也才几KB内存UV统计不在乎精确时用HyperLogLog标准误差只有0.81%内存占用恒定在12KB左右。选类型的原则我一直是先算内存量和操作复杂度再决定用什么类型而不是顺手就String一把梭。3. 第3问RDB和AOF打架你选谁——持久化机制详解3.1 RDB快照恢复快但丢数据的窗口你得认RDB是把某个时间点的全量数据拍成二进制快照。Redis用fork子进程的方式生成快照就算数据量巨大也不会阻塞主线程。这里有个关键名词“写时复制”fork出来的子进程和主进程共享内存页子进程做快照时主进程若改了某个内存页系统会复制一份再改保证快照看到的是fork那一刻的数据。RDB的恢复速度是所有方案里最快的因为就是加载一个二进制文件。代价是数据丢失窗口大如果你配置save 900 1意味着900秒内有1次写操作才触发极端情况下可能丢最近15分钟的数据。而且大实例fork的时候如果内存页修改特别频繁复制开销会让主进程卡顿这就是所谓“fork抖动”。3.2 AOF每笔写操作都记日志完整性好但文件膨胀AOF默认每秒刷盘一次appendfsync everysec最多丢一秒数据。它的核心问题有两个一是写入性能相对RDB低AOF文件是文本协议日志二是日志越来越大所以要靠AOF重写机制瘦身。重写的本质是把当前内存里的数据用最精简的命令重新生成一份AOF这个动作同样由子进程完成主线程只负责把重写期间的新命令记录到缓冲区。如果你追求最稳用appendfsync always每次写操作都刷盘性能会明显下降但最多丢一条命令。我自己的生产经验是只要不是银行转账级别的强一致场景everysec是性价比最高的选择。3.3 生产环境到底怎么配混合持久化了解一下很多人纠结RDB和AOF二选一其实Redis 4.0以后提供了混合持久化AOF文件开头是RDB格式的快照后面追加增量命令。开启aof-use-rdb-preamble yes后既有RDB的恢复速度又有AOF的完整性。我给的参考配置是RDB作为兜底备份保留最近几份快照丢到冷存储AOF开启everysec刷盘二者同时开。重启时Redis会优先加载AOF因为它的数据更完整。对比项RDBAOF数据完整性可能丢分钟级数据everysec丢1秒always丢1条恢复速度快慢需重放日志文件大小紧凑膨胀需重写对性能影响fork瞬间可能卡顿刷盘策略影响写入性能生产建议做备份冷备主持久化手段提示备份不只是配置持久化我通常还会写个定时脚本用redis-cli --rdb定时把RDB文件转储到对象存储或者远端。真遇到服务器磁盘全挂的极端情况你还能从冷备里救回来。4. 第4问键过期了怎么办内存淘汰和过期删除别再傻傻分不清4.1 过期键的“被动主动”双通道删除Redis给每个带过期时间的key维护了一个过期字典。键过期后并不是立刻删除而是靠两种机制配合惰性删除和定期删除。惰性删除是指访问一个key时先检查是否过期过期就删掉再返回空。这种策略省CPU但会导致过期key一直占着内存不释放。定期删除则是Redis每100毫秒默认hz 10抽查一批设置了过期时间的key删除其中已过期的循环往复。官方注释里写得很清楚每次抽查上限约20个、如果过期比例超过25%就继续抽查避免一次性删太多导致阻塞。4.2 内存淘汰策略不是“删过期键”而是“腾空间”内存淘汰和过期删除完全是两回事。当Redis达到maxmemory上限后新写入命令会触发淘汰策略它可能把一个没过期的正常key直接挤掉。这就是为什么有些缓存莫名其妙就没了数据像是“凭空消失”。8种策略里最常用的是allkeys-lru和volatile-ttl。allkeys-lru会对所有key按LRU近似算法淘汰适合把所有数据都当缓存的场景。volatile-ttl只淘汰设置了过期时间的key且优先淘汰剩余存活时间最短的适合“有过期时间的数据是缓存、没设置的不能丢”的场景。4.3 我踩过的坑allkeys-lru把热点全挤没了有次上线后缓存命中率急剧下降查了半天才发现配置写的是allkeys-random热点key和数据冷门key被随机淘汰命中率当然雪崩。后来我还发现很多人忽略了一点volatile-*系列的淘汰策略不会动那些“没设置过期时间”的key。如果你代码里某些缓存忘了设置TTL内存满时它们就成了“钉子户”淘汰策略根本不敢碰最终的结果是大量新数据写不进去。生产上我的建议是业务缓存全部设置TTL内存淘汰策略用allkeys-lru。如果存在“不能丢、无过期时间”的数据单独Redis实例存放和缓存实例做物理隔离别用一个实例混跑。5. 第5问缓存穿透、击穿、雪崩三兄弟你能一眼分辨吗5.1 穿透查了个根本不存在的数据恶意请求或者代码bug导致查询一个数据库里也不存在的ID缓存没命中就会一路打到数据库。如果这个ID是随机变化且不存在的数据库会被空查拖垮。三板斧第一接口层做参数校验非法ID直接拒绝第二用布隆过滤器判断ID是否存在不存在就直接返回不需要碰Redis和数据库第三把“查不到”的结果也缓存起来设置短TTL比如5分钟防止同一个空值反复击穿。5.2 击穿热点key过期那一瞬间流量全怼到DB和穿透不同击穿是针对一个“特别热”的key。缓存里明明有数据偏偏在过期的那一秒几十万请求同时来查缓存没有全部穿透到数据库。处理方案业界通用的是互斥锁和逻辑过期。互斥锁的思路是缓存miss后先拿分布式锁只有拿到锁的线程才去查数据库回填缓存其他线程等待后重新读缓存。逻辑过期更容易理解缓存value里塞一个逻辑过期时间后台异步线程负责刷新读线程永远不会miss但有可能读到旧数据。提示击穿场景下我强烈建议用逻辑过期方案因为互斥锁虽然严谨但热点key过期期间请求全部阻塞延迟会飙升。而逻辑过期最多让少部分请求读到几秒前的旧数据绝大多数场景完全可接受。5.3 雪崩大面积key同时过期Redis直接被打穿雪崩有两种一是大量key设置了相同TTL同一时间集体失效二是Redis实例本身宕机缓存整体不可用。前者的解法很朴素TTL加随机值比如基础60秒再加0到300秒的随机抖动或者把过期时间分散到一天的不同时段。后者的解法是Redis高可用哨兵、集群加多级缓存兜底本地缓存如Caffeine扛掉一部分流量数据库限流熔断。这三兄弟容易混我记方法是穿透是“查不存在”击穿是“一个热点key过期”雪崩是“大面积key过期或实例挂掉”。处理手段也从“防御参数校验”到“单点加锁”再到“全局高可用”完全不是一回事。6. 第6问缓存和数据库一致性先更新谁才对6.1 为什么“先更新数据库再更新缓存”看着合理其实很坑最常见的一个错误做法是更新数据库后直接SET缓存。问题出在并发场景两个线程同时更新同一条数据线程A先更新DB为值1线程B后更新DB为值2但线程B的缓存写操作先执行完线程A的缓存写操作后执行完最终DB是值2缓存却是值1——永久的脏数据。6.2 Cache Aside模式的核心是“删除缓存而不是更新缓存”业界最经典的做法是Cache Aside读请求先读缓存miss后读DB再写缓存写请求先更新DB然后删除缓存。为什么是删除而不是更新因为删掉后下一次读请求miss就会从DB重新加载天然避免了“并发写顺序不一致”的问题。那“先更新DB再删缓存”和“先删缓存再更新DB”选哪个理论上都有窗口。先删缓存的情况下线程A删了缓存、还没更新DB线程B读缓存miss查出DB旧值回填缓存等线程A更新DB后缓存里又是旧值。所以就有了“延迟双删”先删缓存再更新DB然后隔几百毫秒再删一次缓存把并发读回填的旧缓存清掉。这个方案在绝大多数场景能把不一致窗口缩小到毫秒级。6.3 删除失败了怎么办别把命脉压在缓存服务上延迟双删有个硬伤如果第二次删除失败缓存里还是会残留旧值。我在生产环境常用的兜底方案是监听MySQL的binlog变更把写操作的订阅消息推给一个异步任务由它专门负责删缓存。这样即使业务代码里删缓存失败异步任务会重试直到删除成功。提示一致性从来是分级的。淘宝首页的推荐列表和电商库存的余额对一致性的要求完全不同。做架构设计时先问清楚业务能容忍多长时间的最终一致再决定要不要上延迟双删和binlog异步兜底。为了极致一致性而牺牲性能和复杂度往往得不偿失。7. 第7问Redis分布式锁setnx一下就完事了7.1 经典写法SET key value NX EX才是标准姿势很多人还在用两段式SETNX成功后再EXPIRE设置过期时间。这两条命令不是原子的如果SETNX之后进程崩溃锁就永远不释放了这是最常见的生产bug。正确的做法是用一条命令搞定SET lock_key unique_value NX EX 30。NX保证只有键不存在时才能设置成功EX设置自动过期。这里还有两个细节value必须是唯一标识比如UUID用来确认锁是自己的过期时间必须有防止持有锁的进程崩溃导致死锁。7.2 释放锁要用Lua脚本因为“判断删除”必须原子释放锁时不能直接DEL key。假如线程A的锁过期了线程B拿到锁此时线程A才执行完业务一个DEL就把线程B的锁删了。所以标准流程是先比较value是否自己的再删除。但比较和删除是两个操作中间同样有被插入的风险必须用Lua脚本保证原子性if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end7.3 锁续期业务没跑完锁先过期了怎么办设置30秒过期但业务逻辑执行了40秒锁自动释放另一个线程进来两个线程同时执行临界区分布式锁形同虚设。这个问题用Redisson的看门狗机制解决默认锁过期时间30秒拿到锁的后台线程每隔10秒检查一次如果锁还持有就自动续期到30秒。自己实现也不难启动一个定时任务在锁剩余时间的1/3处续期即可。再说深一层RedLock就是那位国外大牛提出的多节点防主从切换丢锁方案但业界争议很大。我的看法是如果用单节点Redis做分布式锁一定要接受“主从切换瞬间可能丢锁”这个事实。对锁极度敏感的业务比如金额扣减要么上RedLock多写几个节点要么直接用ZooKeeper这类强一致协调服务别在Redis一棵树上吊死。8. 第8问主从复制是怎么工作的全量同步和增量同步傻傻分不清8.1 从节点启动时的全量复制是怎么回事主从拓扑里从节点启动后发PSYNC ? -1命令表示“我什么都不知道全量给我吧”。主节点收到后开始后台生成RDB快照同时把新收到的写命令写入复制积压缓冲区。RDB生成完发给从节点从节点加载RDB然后主节点再把缓冲区里的增量命令发给从节点最终达到一致。这里有一个很多人忽视的点全量复制不只是传RDB文件还包含“快照生成期间积累的增量命令”。如果主节点是4GB的内存但生成快照的几秒里写了1万条命令这1万条命令也必须完整传给从节点漏一条主从数据就对不上。8.2 断线重连为什么走“部分重同步”主从之间的连接断开后从节点重连会带上自己的复制偏移量offset和主节点的runid。主节点检查复制积压缓冲区repl-backlog里是否还存着从节点缺失的数据如果缺失的数据还在缓冲区内就只把缺失部分发给从节点这就是部分重同步成本远低于全量复制。repl-backlog-size默认1MB太小的话从节点断线一会儿就超过了缓冲范围只能降级成全量复制。我当时把生产环境的repl-backlog-size调到了64MB因为业务高峰期主库一秒产生的写命令可能超过几十MB。8.3 从节点延迟和全量复制风暴主从复制有一个天然问题延迟。从节点读取到的数据可能落后主节点几百毫秒甚至几秒如果你把读流量打到从节点必须接受数据延迟。我在项目里是把从节点定位成“异步分析、报表查询、备份”绝对不承担账务类强实时读。再提一个高阶坑如果多个从节点同时断线重连它们会同时向主节点索要全量复制主节点瞬间fork多个子进程生成多份RDB内存和磁盘IO都可能被打爆这就是“全量复制风暴”。解决方案是主节点下挂多个从节点时控制从节点个数、用树形拓扑分层复制让部分从节点不直接连主节点而是级联挂到其他从节点下。9. 第9问哨兵到底是干嘛的它怎么知道主节点挂了9.1 哨兵的三件套监控、通知、自动故障转移哨兵不是一条队列也不是一个代理层它是独立运行的一组进程职责是盯着主从节点。它每隔1秒向被监控的实例发送PING如果超过down-after-milliseconds默认30秒没收到有效回复就判定主节点为“主观下线”。注意“主观”这个词因为单个哨兵看到的可能只是网络分区不代表主节点真的挂了。当多个哨兵都判定主节点主观下线且数量达到配置的quorum比如配置sentinel monitor mymaster 127.0.0.1 6379 2中的2就会升级为“客观下线”触发故障转移。9.2 选主的规则优先级、数据完整性、runid一个都不能少客观下线后哨兵们要选出一个新的主节点。选举规则依次是优先级slave-priority配置越小的从节点越优先0表示永不提升为主复制进度和数据最接近主节点的从节点也就是复制偏移量最大的优先runid都满足时按runid字典序小的优先选出新主后哨兵会向其他从节点发送SLAVEOF命令让它们改认新主然后通知客户端新主地址。整个过程客户端无感知前提是配置了正确的哨兵连接方式。9.3 脑裂场景下如何保命主节点没真挂但哨兵和主节点之间网络分区哨兵那边触发了主从切换选了一个从节点当新主。此时旧主还在正常工作继续接收写入数据等网络恢复后旧主的写数据因为和新主不一致被当成从节点强制全量同步——这段时间的写入就丢了。这就是经典的脑裂数据丢失问题。解决办法是给主节点加“最小写可用性”限制配置min-replicas-to-write 1和min-replicas-max-lag 10意思是主节点至少要有一个可同步且延迟不超过10秒的从节点才接受写请求。一旦脑裂导致旧主写不出去它就自动拒绝写请求保住数据的最后防线。10. 第10问Cluster集群的槽位和重定向机制你真的懂吗10.1 16384个槽怎么分数据怎么路由Cluster集群把整个键空间划分为16384个槽位。写入一个key时先计算CRC16(key) % 16384得到槽位再由槽位归属的节点处理。每个节点只负责一部分槽槽位分配通过CLUSTER ADDSLOTS完成也可以让集群自动分配。缓存客户端连接任意节点都行如果请求的key不在当前节点节点会返回MOVED错误里面带上正确节点的IP和端口客户端拿到后重新请求。这是和单机Redis最大的差异客户端必须内置槽路由逻辑否则每次都得走一次重定向性能打折。10.2 扩容迁移的实操reshard到底迁了什么给集群加一个节点核心流程是新节点加入集群然后从其他节点迁一部分槽到新节点。官方提供的redis-cli --cluster reshard命令会让交互式指定迁移槽数和新节点底层做的事是把槽里的key逐条迁移到目标节点。迁移期间有个细节需要注意如果客户端在迁移中途访问一个正在迁移的key源节点会返回ASK重定向告诉客户端“数据已经在挪去目标节点的路上了你去目标节点找”。我看过不少文档没把MOVED和ASK区别讲清楚其实很简单MOVED表示槽的归属权彻底变了以后都找新节点ASK表示槽还在源节点只是正在迁移的这一个key暂时在目标节点下一次访问还得问源节点。10.3 集群的限制用不好真的会踩雷Cluster集群对多key操作有严格限制MGET、事务、Lua脚本里的多个key必须属于同一个槽。不同槽的key没法在一个命令里处理。解决思路是hash tag在key里加一对花括号{}Redis只对花括号内的部分计算槽位把相关数据固定到同一个槽。比如user{10086}:profile和user{10086}:orders会落到同一个槽就能用事务合Lua了。另一个坑是集群模式下不能使用SELECT切换数据库所有键都在db0。我见过老项目从单机迁到集群代码里带着select(1)直接报错。迁移前先清理这类操作比上线后再排查成本低得多。11. 第11问线上Redis连接超时、内存突增完整排查链路长什么样11.1 那个熟悉的“Redis command timed out”到底是谁的问题很多用Spring Boot的团队都见过这个报错io.lettuce.core.RedisCommandTimeoutException: Command timed out after 10 second(s)。网上搜到的解释五花八门我自己经历过几轮排查后总结出最可能的几个原因Redis服务端慢命令阻塞比如大key的DEL、大集合的SORT单线程模型下这些命令一执行所有连接都在排队命令自然超时客户端连接池耗尽Lettuce默认是单连接复用但如果你用共享连接不当或者并发太高命令在客户端的队列里排队等待网络问题跨机房访问、带宽打满、防火墙丢包排查的第一步不是改代码而是先看Redis侧INFO commandstats和SLOWLOG GET确认服务端是否有慢命令。我遇到的一次就是某接口在循环里执行了上百次EXISTSLettuce的连接被这个循环占满其他请求全部饿死。11.2 内存突增和BigKey定位三板斧内存突增先看是业务增长还是数据异常我一般按这个顺序来INFO memory看used_memory和碎片率MEMORY STATS看各部分占用用redis-cli --bigkeys扫描大key它会依次统计每个类型最大的key用redis-cli --memkeys或者SCAN配合DEBUG OBJECT看具体key的编码和内存占用BigKey的危害不止内存它是慢查询和集群间数据倾斜的双重源头。在Cluster环境里一个大Hash如果全落在同一个槽对应节点的数据量就会明显高过其他人。解决方式是拆分或者换结构比如把大Hash拆成多个小Hash用HSCAN位移分桶。11.3 生产巡检命令和容器部署的细节日常巡检我固定跑这几条INFO看内存和连接数、SLOWLOG GET 50看慢查询、INFO stats看过期key淘汰情况、MONITOR只在低峰期偶发用一下——生产环境开MONITOR会显著降低性能我从不让它长时间运行。关于Docker部署Redis热搜词里也有人问主从怎么搭我提醒三件事第一数据目录一定要挂载宿主机卷否则容器重建数据全没第二主从容器之间要用自定义网络通信别用默认bridge模式然后靠IP拼凑第三Docker的日志驱动默认json-fileRedis的日志量一旦起来会撑爆磁盘建议限制max-log-size或改用local驱动。12. 第12问RedisTemplate序列化那些事为什么你存的键全是乱码12.1 “乱码”不是Redis的问题是序列化器的选择问题用Spring Data Redis操作Redis时最经典的现象是你用RedisTemplate插入的数据在redis-cli里看起来像\xAC\xED\x00\x05t\x00\x05name这种二进制乱码。原因很简单RedisTemplate默认用的是JdkSerializationRedisSerializer把对象按JDK序列化成二进制字节后存进去可读性全靠命令行工具打交道时基本为零。解决方法是显式配置序列化器。我通常这样配Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // key用String序列化保证可读性 template.setKeySerializer(new StringRedisSerializer()); // value用JSON序列化兼顾跨语言和可读性 template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; }12.2 两个Jackson序列化器的区别用错会埋雷Jackson2JsonRedisSerializer和GenericJackson2JsonRedisSerializer长得像行为不同。前者不存类型信息反序列化时必须手动指定目标类型后者会在JSON里多写一个class字段自动恢复成原来的类型但代价是JSON体积变大而且如果实体类改名或迁移包名老的缓存数据反序列化就会失败。我的建议是业务DTO缓存的用GenericJackson2JsonRedisSerializer省心但如果你的实体类结构会演进记得做好版本兜底要么上线前清缓存要么实现自定义的反序列化容错。我踩过最惨的坑是重构了实体包路径没清理线上缓存结果用户请求一进来全在反序列化报错。12.3 可视化工具和连接排错数据乱码也常被误以为是可视化工具的问题其实它们是两码事。另一款开源的Another Redis Desktop Manager在平时看排查时好用因为可以直接看TTL、执行SLOWLOG、浏览各库的key但工具本身不带容错键名是\xAC\xED的它也照原样显示。先保证序列化配置正确再谈工具好不好用。这里我把自己的排查顺序列一下先查redis-cli ping确认服务存活再看INFO clients连接数是否打满然后SLOWLOG和MONITOR定位慢命令最后检查代码里的序列化配置和模板调用方式。按这条路走90%的“Redis连接超时”“数据乱码”“缓存不生效”问题都能在半小时内找到根因。十二个问题说完了。其实Redis的每个知识点都藏着“为什么”的追问面试官问你是表象你自己能往底层多钻一层很多坑根本不会踩到。这套清单我每隔半年都会重新过一遍每过一遍都会有新的体会——尤其是线上出了故障之后再回头看那些原理性的东西才是真正救命的。