1. 先搞清楚Redis在你项目里到底扮演什么角色三年前我第一次在生产环境用Redis干过一件特别蠢的事把所有用户session都存在Redis里但忘了配持久化重启完直接全员掉线。那一刻我才意识到Redis这玩意儿的定位你搞不清楚后面全是坑。很多人学Redis第一反应是这不就是个缓存数据库吗其实这个认知至少耽误了你三层功力。Redis全称是Remote Dictionary Server本质是一个基于内存的键值存储系统它之所以能在无数项目里占据核心位置靠的不是快这么简单而是快数据结构丰富原子操作生命周期可控这四个特性的组合拳。我习惯把Redis在一个项目中的作用分成三个层次来理解第一个层次是缓存层。把热点数据从MySQL里捞出来放Redis查询走内存读写吞吐量直接从每秒几百飙到几万。这个层次最入门但也是最容易出问题的——缓存穿透、击穿、雪崩三大坑全在这个层里。第二个层次是数据结构层。Redis不止有String还有List、Hash、Set、ZSet这意味着你可以用它做排行榜、做去重、做队列、做计数器。这一层用好了很多原本需要单独中间件解决的问题一个Redis就能顶住。第三个层次是原子操作层。Redis是单线程执行命令的所有操作天然串行、天然原子配合SET NX EX、INCR、Lua脚本这些能力分布式锁、秒杀防超卖、幂等控制这些高并发场景你都能在Redis上直接搞定。所以这篇笔记我不会按官方文档的目录顺序给你罗列命令而是按照我自己从入门到在生产环境稳定运行的完整路径来写先落地部署、再吃透数据结构、然后打通持久化与高可用、最后用SpringBoot把Redis真正接入业务并在结尾把缓存治理的实战经验一并交代清楚。2. 安装部署与客户端选型别在第一公里就踩坑2.1 Windows与Linux环境下的安装差异Redis官方其实不支持Windows这个点很多人不知道。你在Windows上装的那些Redis基本是微软老版本的分支或者用WSL跑起来的Linux版本。如果只是本地学习Windows版本完全够用但如果要上生产请老老实实用Linux。Windows下最简单的安装方式是去Redis的GitHub Releases页面找带Windows标识的压缩包解压后直接运行redis-server.exe默认端口6379不用改任何配置就能跑起来。需要注意的一点是解压根目录下的redis.windows.conf才是实际生效的配置文件启动时建议带上这个文件redis-server.exe redis.windows.conf不带配置文件启动也能跑但你会失去密码验证、持久化开关、最大内存限制这些关键配置。Linux下安装Redis 7.0的完整路径是这样的# 更新包索引并安装编译工具 sudo apt update sudo apt install -y build-essential tcl # 下载源码包 wget https://download.redis.io/releases/redis-7.0.11.tar.gz tar xzf redis-7.0.11.tar.gz cd redis-7.0.11 # 编译安装 make sudo make install # 启动前先配置好后台运行 redis-server --daemonize yes很多人卡在make那一步直接报错往往是因为没装build-essential。另外还有一个高频坑如果你的Linux是ARM机器比如树莓派或者某些云主机编译命令是同样的三件套make、sudo make install但下载的源码包地址不变Redis源码对ARM的支持很完备不需要单独找ARM版本。2.2 Docker部署主从结构一条命令的事如果你手头有Docker我强烈建议学习阶段直接走容器化路线省去一堆环境依赖的破事。拉一个镜像起来做单机实验是基本功这里我更想展示怎么用Docker Compose一键拉一套主从结构因为主从复制是Redis高可用的地基你迟早要面对它。docker-compose.yml的核心内容如下version: 3.8 services: redis-master: image: redis:7.0 container_name: redis-master ports: - 6379:6379 command: redis-server --appendonly yes redis-slave: image: redis:7.0 container_name: redis-slave depends_on: - redis-master ports: - 6380:6379 command: redis-server --slaveof redis-master 6379这个配置里主节点开了AOF持久化appendonly yes从节点直接用--slaveof指向主节点。启动后你可以在主节点写入数据再去从节点读会发现数据已经同步过去了。这里我想特别提醒一个坑主从复制的数据同步是单向的从节点默认是只读的你在从节点写数据会直接报错这是设计如此不是配置坏了。2.3 可视化客户端的选型思路命令行redis-cli是基本功但人眼看不方便。工具我推荐两个第一个是官方的RedisInsight功能最全支持内存分析、慢日志查询、可视化数据浏览就是启动稍重第二个是Another Redis Desktop Manager也就是搜索里的another redis desktop manager开源免费跨平台直连部署简单日常查看Key和Value完全够用。两个工具连接时都需要注意如果你的Redis配置了密码连接地址填服务器IP、端口填6379、密码填你设置的requirepass。如果连不上先telnet一下端口通不通大概率是服务器安全组没放行6379跟工具本身没关系。3. 五大数据类型不是背命令是理解底层数据结构3.1 String与Hash的本质区别内存不是白省的String是Redis里最基础的类型一个key对应一个valuevalue最大512MB。但新手最容易踩的坑是把对象序列化成JSON字符串塞进String里比如用户信息、订单信息这种结构化数据存了一堆类似user:10001的key每个都是冗长的JSON串。问题是你更新用户昵称时就得整串读出来、改完、再整体写回去费流量还费CPU。Hash类型才是这类场景的正解它相当于Redis里的一个小MapHSET user:10001 name 张三 age 25 city 北京 HGET user:10001 name HINCRBY user:10001 age 1字段级别的读写让你不需要动整个对象HINCRBY还能对某个字段做原子自增。我自己实测过存储100万条用户数据用Hash比用JSON String节省约30%的内存原因在于Redis对Hash做了ziplist或listpack的紧凑编码优化。3.2 List、Set、ZSet在不同业务里的定位List是双向链表结构典型场景是消息队列的简化版——LPUSH从左边推入RPOP从右边弹出天然实现了FIFO。但它有个致命弱点消息丢失不负责消费者宕机了队列里的数据也还在但要是Redis本身重启且没持久化你的队列就没了。所以List做轻量级队列可以别指望它当Kafka用。Set是无序去重集合适合做当天活跃用户文章点赞人这类数据SADD和SISMEMBER两个命令就能搞定去重和判断。它还有一个隐藏用法就是集合运算SINTER取交集可以做共同关注功能SUNION取并集可以做好友推荐功能一行命令搞定原本要在业务代码里写循环的数据操作。ZSet是Redis里最有价值的数据结构每个元素带着一个scoreRedis按score排序存储。最经典的应用就是排行榜直播打赏榜、积分排行榜、热销商品榜。ZADD leaderboard 100 user1查询Top10用ZREVRANGE leaderboard 0 9效率极高。更妙的是ZSet可以做范围查询比如积分在1000到2000之间的用户一条命令返回结果直接省掉了数据库里的慢SQL。3.3 键的过期策略与内存淘汰机制数据类型之外每个key都可以设置过期时间这是缓存系统最关键的能力SET token abc123 EX 3600 EXPIRE user:10001 86400 TTL user:10001EXPIRE设置的是相对时间SETEX可以在写入时直接指定过期秒数。但你可能不知道Redis的过期删除不是实时扫描所有key的而是三种策略的组合惰性删除访问key时才检查是否过期过期就直接删掉返回空。主动删除后台任务每100ms随机抽一批设置了过期时间的key发现过期的就删。内存淘汰如果上面的策略没跟上内存满了就按配置的maxmemory-policy来清数据常见的有allkeys-lru最近最少使用、volatile-lru只在设了过期时间的key里做LRU。生产环境我建议设maxmemory上限策略用allkeys-lru这样即使缓存数据激增也不会把Redis内存打爆导致OOM崩溃。4. 持久化机制数据安全与性能的权衡4.1 RDB与AOF各自的脾气Redis默认开启RDB快照持久化它做的事是fork一个子进程把当前内存里的全量数据写入磁盘上的dump.rdb文件。优点是恢复速度快、文件紧凑缺点是快照之间有窗口期如果你Redis宕机了最后一次快照之后写入的数据全部丢失。AOF则是追加写日志Redis每执行一条写命令就把这条命令追加到appendonly.aof文件末尾。优点是数据安全性更高可以配appendfsync everysec最多丢一秒数据缺点是文件体积增长快而且恢复时要重放所有命令速度比RDB慢。两者不冲突生产最佳实践是同时开启用RDB做冷备和快速恢复用AOF做崩溃时的数据兜底。Redis 7.0引入了AOF多部分文件格式重写时不再生成全量临时文件再切换而是采用manifest清单管理多个AOF片段重写期间的写入不再阻塞。4.2 我在日志排查里最常见的持久化故障redis日志是热搜词里的常客我见过太多人去服务器上看redis.log发现大量这样的警告WARNING overcommit_memory is set to 0这个警告的意思是Linux内核的内存过度分配策略被设置为0当Redis做RDB快照fork子进程时如果物理内存不足子进程可能启动失败。修复方式是修改系统参数sysctl vm.overcommit_memory1再往配置文件里加repl-backlog-size调大复制缓冲区。另一个让我踩过坑的是主从复制时从节点的redis.log里全是MASTER - REPLICA sync: Master tried to disconnect之类的报错排查半天发现是主节点repl-backlog-size默认只有1MB同步压力大时缓冲区直接被撑爆。把主节点的repl-backlog-size调到64MB问题立刻消失。4.3 备份策略的实战建议RDB文件本质上就是你的数据快照我建议配合定时任务每天做一次异地备份。简单的思路是写个cron任务把dump.rdb用scp传到备份服务器保留7天。AOF文件也可以定时拷贝但因为它持续在写直接拷贝可能拿到不完整的文件建议用BGREWRITEAOF触发一次重写后再拷贝。恢复流程也说一下如果Redis所在机器崩溃了重新装好Redis后把备份的rdb文件放到配置的dir目录下用redis-server redis.conf启动Redis启动时会自动加载这个rdb文件。AOF恢复则是在启动时把appendonly.aof里的命令逐条重放。我个人的习惯是RDB为主AOF为辅两者同时在Redis启动时优先加载AOF因为AOF的数据通常更新。5. 主从、哨兵与集群高可用到底在解决什么问题5.1 主从复制数据的安全副本主从复制的第一个作用是数据冗余。主节点挂了从节点还有完整数据不会一夜回到解放前。第二个作用是读写分离读操作全部打给从节点主节点专心处理写请求减轻单机压力。复制过程有一个细节值得理解全量同步与增量同步。首次同步时从节点发PSYNC命令主节点做一次BGSAVE生成RDB全量快照发给从节点。之后主节点的写命令会持续发给从节点这叫增量同步。如果从节点断线重连Redis会从repl_backlog_buffer里找断线期间遗漏的命令进行补发这个缓冲区就是我在上面提到的repl-backlog-size它不够大就会触发一次全量同步。5.2 哨兵模式如何实现自动故障转移主从复制解决不了主节点挂了之后怎么自动选主的问题。哨兵Sentinel就是干这个的它负责监控所有主从节点的存活状态当主节点被判定为客观下线后会从从节点中选举一个新的主节点并通知所有客户端更新连接地址。哨兵模式部署至少要三个哨兵节点原因很简单哨兵之间需要投票达成共识只有一个哨兵会形成主观误判——它觉得主节点挂了其实是网络抖动。哨兵数量为奇数方便多数派决策。Docker Compose部署哨兵可以在前文主从配置的基础上增加sentinel服务配置文件核心一行sentinel monitor mymaster redis-master 6379 2这里的2表示至少两个哨兵同意主节点下线才触发故障转移。你可以在测试环境手动kill掉主节点容器观察哨兵自动把一个从节点提升为主节点整个过程大概10秒。5.3 集群模式解决的是容量问题哨兵解决高可用但依然是单主节点写入内存上限受制于一台机器。Redis Cluster把数据分片存储在多个主节点上整个Key空间被分成16384个槽位每个节点负责一部分槽位。计算方式是对key做CRC16校验然后对16384取模。集群模式至少需要3个主节点官方建议每个主节点至少配一个从节点也就是6个节点起步。部署时用redis-cli --cluster create命令指定节点列表Redis会自动分配槽位并建立主从关系。客户端访问某个key时如果请求被路由到了错误的节点节点会返回MOVED错误并把正确的节点地址告诉客户端客户端再重新请求。集群的另一个隐藏规则必须知道多key操作在集群里有限制比如MGET只能操作hash tag相同的key否则会报CROSSSLOT错误。这个限制在你从单机迁移到集群时会卡很多团队提前设计好key的命名规范很重要。6. SpringBoot整合实战序列化、increment()报错与分布式锁6.1 RedisTemplate与StringRedisTemplate到底选谁SpringBoot整合Redis是Java后端的高频场景。操作姿势有两种主流方式一个是StringRedisTemplate一个是RedisTemplate。它们的本质区别在于序列化器StringRedisTemplate默认用String序列化key和value都是字符串人类可读。RedisTemplate默认用JDK序列化结果是二进制串console里看是一堆乱码。很多初学者直接用RedisTemplate存数据发现Redis Desktop Manager里看到的是\xAC\xED\x00\x05t\x00开头的乱码这就是JDK序列化的锅。我给你的建议非常明确如果没有存储对象的强需求一律用StringRedisTemplate如果一定要存对象手动指定Jackson序列化器不要用默认的JdkSerializationRedisSerializer。6.2 热搜里那个increment()报错的排查全链路java中redis使用redistemplate的increment()报错不是integer or out of range这个热搜词说明这个问题不是个例。我当初也被坑过现在完整复盘一遍排查思路。报错信息一般是Caused by: org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR value is not an integer or out of range先明确一点Redis的INCR命令只支持整型值它会把value解析成长整型然后加一。报错只有两种可能第一种key对应的value确实不是整数。常见的产生原因是序列化方式不一致——之前用RedisTemplate写入了一个对象的引用之后用StringRedisTemplate去INCR这个keyRedis读出来的是JDK序列化后的二进制串当然不是整数。第二种这个key之前被设置成了字符串类型比如你用SET user:10001 abc存过字符串再对这个key执行INCR自然报错。排查步骤我建议这样走# 第一步看这个key的type TYPE user:10001 # 第二步看这个key的value GET user:10001如果GET出来的值带引号比如100说明value不是纯整数而是字符串。原因是你的序列化器把数字转成了带引号的字符串Redis的INCR无法把它当整数处理。解决办法有两个一是确保使用StringRedisTemplate或者手动配置StringRedisSerializer让value真正以数字形态存储二是如果是别人写入的脏数据先删掉这个key再重新初始化stringRedisTemplate.delete(user:10001); stringRedisTemplate.opsForValue().increment(user:10001);将redis的数减一这个搜索词也顺手说一下对应命令是DECR在SpringBoot里的写法是redisTemplate.opsForValue().decrement(key)。常踩的误会是不知道INCR和DECR本身就是原子操作根本不需要先GET再SET在并发环境下你那种先查后写的方式一定会丢数据。6.3 分布式锁从SET NX到Redisson的演进分布式锁是Redis在微服务场景下最重要的应用之一。最原始的写法是用SETNX命令只有key不存在时才能设置成功正好用来抢锁SETNX lock_key my_value但这版实现有漏洞如果拿到锁的线程崩溃了没有执行解锁操作这个锁就永远不会被释放其他线程全部卡死。所以有了加过期时间的改进SET lock_key my_value NX EX 30NX保证只有key不存在时才设置EX 30确保30秒后自动释放。但过期时间也有坑如果业务执行超过了30秒锁被自动释放了另一个线程拿到了锁这时第一个线程执行完手动删除锁删掉的是别人刚拿到的锁这就产生了并发问题。解决方式是引入Redisson这个库它实现了自动续期的看门狗机制默认锁超时30秒每10秒自动续期一次只要业务线程没结束锁就不会过期同时删除锁时用Lua脚本比较value确保只能删除自己持有的锁。这是我在生产环境验证过的最省心的方案引入依赖后使用非常简便RLock lock redissonClient.getLock(order:pay:10001); if (lock.tryLock(3, 30, TimeUnit.SECONDS)) { try { // 业务逻辑 } finally { lock.unlock(); } }Redisson的锁底层用的就是RedisLua脚本既保证了原子性又保证了正确性。如果你要在面试里聊分布式锁Redisson的看门狗机制和RedLock算法是绕不开的两个知识点。6.4 序列化方案对比把这块单拎出来是因为它值得。我见过无数团队因为序列化方案没统一导致的生产事故——缓存读写不一致、AOF文件膨胀、主从复制数据错乱源头全在序列化器上。序列化器可读性存储效率兼容性适用场景StringRedisSerializer高低Java原生简单字符串缓存、计数器Jackson2JsonRedisSerializer高中跨语言对象缓存、接口响应缓存JdkSerializationRedisSerializer底层二进制低仅Java不推荐产生乱码我目前的SpringBoot项目统一做法是key一律用String序列化器value用Jackson序列化器并且开启activateDefaultTyping的多态支持。这样存进去的value是JSON字符串既能在Redis Desktop Manager里看懂跨语言消费也方便。7. 缓存治理穿透、击穿、雪崩的完整应对方案7.1 缓存穿透查询一个不存在的key缓存穿透指的是请求查询的数据在数据库里根本不存在导致缓存永远不命中每次请求都直接打到数据库。攻击者可以用一个不存在的ID疯狂刷接口数据库分分钟被压垮。解决方案最常用的是缓存空值就算数据库查不到也在Redis里缓存一个空值或特殊标记设置短过期时间比如60秒。这样同一个不存在的key在60秒内不会再次穿透。进阶方案是布隆过滤器在请求进缓存之前先用布隆过滤器判断key是否存在过滤器说不存在就一定不存在直接返回。布隆过滤器的缺点是存在误判率它说存在不一定真的存在但说不存在就一定不存在正好适合拦截非法ID。7.2 缓存击穿热点key突然失效缓存击穿和穿透容易混淆击穿指的是一个热度极高的key在过期的瞬间大量并发请求同时打到数据库。比如秒杀商品详情页缓存key刚好在秒杀开始前过期几十万请求瞬间把MySQL打满。应对手段有两个方向。第一个是互斥锁当缓存过期时不是所有请求都去查库而是先尝试获取分布式锁只有一个线程能拿到锁去查数据库并回填缓存其他线程等锁释放后直接从缓存拿数据。第二个是逻辑过期不给key设置物理过期时间而是在value里存一个逻辑过期时间戳查询时对比时间戳判断是否过期如果过期就返回旧数据同时异步更新缓存。逻辑过期方案的优点是性能好缺点是数据一致性弱一些适合读多写少但数据实时性要求不高的场景。7.3 缓存雪崩大量key同时失效雪崩是指大量key在同一时间段集中过期或者Redis节点直接宕机导致海量请求全部落到底层数据库。比起穿透和击穿雪崩的影响范围往往更大。应对策略分多个维度第一TTL随机化。给缓存key设置过期时间时加一个随机偏移量比如基础过期时间5分钟再加0到60秒的随机值。这样所有key不会在同一秒集体失效。我当时的一个项目实践是把整个缓存体系的TTL分散在180到300秒之间随机分布效果立竿见影。第二多级缓存。Redis之上再加一层本地缓存比如CaffeineRedis挂了还有本地挡一层虽然数据一致性变差了但至少数据库不会被瞬时流量打垮。第三Redis本身的高可用。用前文讲的哨兵或集群模式主节点挂了自动切换从节点服务不中断。第四熔断降级。如果Redis和数据库都扛不住直接返回降级响应比如商品详情页返回缓存中的旧版本或者基础兜底数据宁可页面数据旧一点不能把数据库打挂。7.4 缓存一致性的核心矛盾缓存治理到最后绕不开一个终极问题数据库更新了缓存怎么保证一致最粗暴的方案是更新数据库后删除缓存下次查询时重新加载。这个方案在绝大多数场景下够用因为它允许短暂的不一致窗口。另一个方案是延迟双删先删缓存再更新数据库隔几百毫秒再删一次缓存为了解决并发场景下旧数据回填缓存的问题。更严谨的方案是监听MySQL的binlog变化用Canal把数据变更同步推送给Redis更新这也是很多中大型团队采用的方案。我个人的分级建议是业务允许短暂不一致用更新数据库后删除缓存业务要求秒级一致加MQ异步重试删缓存业务要求严格一致别用Redis做缓存了直接走数据库或者换专业的分布式缓存协议。一致性永远是程度问题不是非黑即白。8. 从学习笔记到生产实战的最后一段体会学Redis最忌讳的是只记命令、不建体系。命令是死的你就算把官方文档里的所有命令背下来遇到缓存击穿用什么锁AOF文件膨胀怎么办主从延迟怎么优化照样一头雾水。我的切身体会是这个领域性价比最高的学习路径是先用Docker快速拉起一主两从三哨兵的环境把故障转移手动触发几次亲眼看看主节点挂了系统是怎么自愈的再把SpringBoot项目里的缓存场景全部用Redis重写一遍Set与ZSet的实用价值会在你的代码里自然浮现最后再把缓存穿透、击穿、雪崩的防护代码逐个落地配合压测工具观察QPS变化你会对Redis的能力边界有非常真实的感觉。最后分享一个小技巧把生产环境Redis的slowlog get 100定期翻一翻那些执行时间超过10毫秒的命令会清清楚楚告诉你哪些key的设计是不合理的。我靠这个命令发现过好几处大Value的隐患改完主从延迟立刻降了一半。这大概是Redis运维里性价比最高的一项检查比任何监控面板都直接。