资讯动态

Redis从入门到生产实战:缓存、持久化、分布式锁与高可用架构全解析

发布时间:2026/9/13 2:25:44 来源:尧图企业网站定制
1. 先正面回答一个问题为什么Redis叫数据库却被所有人当缓存用我在刚接触Redis的时候最困惑的就是它的定位。你说它是数据库数据能持久化到磁盘可生产环境里大家拿它当缓存用Redis Desktop Manager连上去看到一堆key怎么看都像一个超大号的HashMap。后来学习深入了才慢慢想明白这背后其实是一套非常精巧的设计取舍。Redis全称是Remote Dictionary Server也就是远程字典服务早期它的核心定位就是一个高性能的内存键值存储系统。它的数据默认全部放在内存里读写速度是微秒级别这个性能指标是传统磁盘数据库完全没法比的。MySQL跑一次简单查询可能要几毫秒到几十毫秒而Redis的读写延迟基本在1毫秒以内甚至能达到几十万每秒的并发处理能力。但关键问题在于一个把数据全放在内存里的系统为什么还要叫数据库这就要说到Redis和其他纯缓存组件比如Memcached的本质区别了。Memcached是一个真正的用完即走的缓存系统数据完全放在内存里重启就没了你也不会想着去持久化它。而Redis从一开始就内置了RDB和AOF两种持久化机制你可以配置它定期把内存数据快照写入磁盘也可以配置每条写命令追加到日志文件里。这意味着Redis可以做到重启之后数据还在这不是缓存这就是数据库的功能。我刚学的时候犯过一个错误就是把Redis当成万能缓存什么数据都往里塞。后来做线上服务遇到一次Redis实例OOM整个业务链路都受到连累才意识到一个问题缓存系统数据丢失可以容忍但你应该让缓存永远只承担加速职责而不是把核心业务数据唯一的副本放进Redis。Redis无论怎么设计它跟MySQL这类关系型数据库的定位还是有本质区别的。所以我的理解是Redis是一个具备数据库持久化能力的内存存储系统它的最佳使用场景是作为缓存层给上层业务加速同时也能承担一些特殊的数据存储任务比如排行榜、会话管理、分布式锁、消息队列等。现在网上很多教程把Redis和MySQL放在一起比较说Redis比MySQL快、比MySQL好用这其实是误导。两者的数据可靠性模型完全不同你不可能用Redis替代MySQL存储用户订单数据除非你能接受极端情况下丢失部分数据。Redis的底层数据结构设计得也很有意思。它对外提供了String、List、Hash、Set、ZSet五种基本类型但内部实现其实有多种编码方式比如String类型有int编码和embstr编码List类型在数据量小的时候用压缩列表数据量大了才转换成双向链表。这些细节在面试时经常被问到但实际使用中你更需要的是了解每种数据类型最适合承载什么业务场景。这个我后面会专门用一个部分来展开。给刚开始学Redis的朋友一个建议不要一上来就去看源码先把下面几件事搞清楚你就已经超过大部分人了。Redis的单线程事件循环模型为什么单线程还能这么快它的五种基本数据类型分别适合什么场景RDB和AOF持久化各有什么优劣生产环境怎么配置Redis才不容易丢数据、不容易OOM主从复制、哨兵、集群这三者的区别和适用场景分布式锁的正确实现方式以及常见的坑上面这几条其实就是我从0开始学Redis的完整路线图。下面我按照这条路一条一条拆开来讲。2. 安装不是只有一条路Windows原生、Linux编译、Docker镜像三选一很多人学Redis卡在第一步不是因为它难装而是因为不知道选哪条安装路径。这里我把自己实际用过的三种方式都列出来你根据自己手头的环境选一个就行。2.1 Windows环境下安装Redis的两种方式Redis官方其实没有提供Windows原生版本官网只提供Linux和macOS版本。Windows上能用的Redis基本是微软团队维护的旧版本移植或者从Redis 5.0之后社区维护的Windows移植包。如果你是在Windows上做开发学习最简单的做法是下载Redis的Windows压缩包常见的版本像redis-6.2.5-windows或者redis-7.0.x-windows解压之后就是下面这几个文件。redis-server.exe redis-cli.exe redis-benchmark.exe redis.windows-service.conf redis.windows.conf直接在bin目录下双击redis-server.exe就可以启动Redis服务默认端口6379然后用同目录的redis-cli.exe连上去。# 启动服务 redis-server.exe # 另开一个终端窗口连接客户端 redis-cli.exe -h 127.0.0.1 -p 6379 # 测试连接 127.0.0.1:6379 ping PONG看到PONG就说明服务已经起来了。这种方式适合快速体验但有两个问题一是Windows移植版的版本通常比官方Linux版旧二是它不能用于生产环境性能和稳定性都差一些。如果你需要更规范一点的Windows环境也可以用Docker Desktop跑Redis镜像。现在Windows上装Docker已经很成熟了装好Docker Desktop之后一条命令就能把Redis拉起来。docker pull redis:7.2 docker run --name my-redis -p 6379:6379 -d redis:7.2这样启动的Redis虽然是跑在Linux虚拟机里的但网络端口映射到了Windows本地的6379开发调试完全不受影响。镜像启动之后Windows本地的客户端工具也能直接连上。这种方式的好处是你本地跑的环境跟服务器上Docker部署的环境保持一致后面技能平滑迁移。2.2 Linux编译安装适合你系统学习如果你用的是云服务器或CentOS环境的服务器推荐手动编译安装一次这个完整过程会让你看清楚Redis的目录结构和启动流程对后续排查问题帮助很大。# 下载源码 wget https://download.redis.io/releases/redis-7.2.4.tar.gz # 解压 tar -zxvf redis-7.2.4.tar.gz # 进入目录 cd redis-7.2.4 # 编译 make # 安装到指定目录 make install PREFIX/usr/local/redis # 把配置文件复制到安装目录 cp redis.conf /usr/local/redis/编译完成之后Redis的可执行文件会出现在/usr/local/redis/bin目录下。启动方式和前面Windows版本的逻辑完全一样。/usr/local/redis/bin/redis-server /usr/local/redis/redis.conf这里需要注意一个细节直接不带参数启动redis-server它会使用默认配置此时Redis只能通过本机访问外部客户端连不上。如果你通过客户端工具连接不成功优先检查配置里的bind和protected-mode这两个参数。2.3 Docker Compose部署生产环境配置如果是生产环境我比较推荐用Docker Compose来部署Redis尤其是配合主从复制或多实例的场景。一份简单的docker-compose.yml长这样。version: 3 services: redis: image: redis:7.2 container_name: redis-single restart: always ports: - 6379:6379 environment: - TZAsia/Shanghai volumes: - ./data:/data - ./conf/redis.conf:/etc/redis/redis.conf command: [redis-server, /etc/redis/redis.conf]挂载配置文件到容器里再挂载一个数据目录这样容器的数据就不会因为重建而丢失。这里的redis.conf里通常会配置AOF持久化和数据目录具体配置我放到后面持久化部分详细讲。使用Docker方式还有个好处就是部署主从结构非常方便扩展一个从节点只需要在docker-compose.yml里加一个service配置就行不需要在服务器上单独安装一套Redis。2.4 选一款合适的可视化客户端命令行工具redis-cli功能很全但日常开发调试时一个趁手的可视化工具能让你一眼看清当前Redis里有哪些key、数据是什么结构。我自己的使用体验是命令行永远是最底层的保底方式但可视化工具可以大幅提升效率。目前主流的几款工具工具平台特点Redis Desktop Manager全平台老牌工具社区版免费整体比较稳定Another Redis Desktop Manager全平台我用得最多的一款免费开源支持SSH隧道、集群模式性能不错Tiny RDM全平台界面做得简洁对新手很友好我第一次用Redis Desktop Manager连接远程服务器时踩过一个坑远程Redis默认protected-mode是开启的只允许本机访问。后来排查了半天才发现需要在配置文件里设置密码并显式指定bind或者直接关闭protected-mode。可视化工具本身连接不上大概率不是工具的问题而是Redis服务端的安全配置没放行。对学习者来说连接可视化工具主要是为了直观地看key的变化执行set命令之后去刷新一下看到对应的key出现理解就会更直观。3. 五大数据类型逐个实测String、List、Hash、Set、ZSet的应用边界Redis提供的五种基本数据类型是学习过程中必须掌握的硬核基础。我这里不讲花里胡哨的原理就讲每一种数据类型怎么用、适合解决什么问题、用完容易踩什么坑。3.1 String类型最基础也最能扛场景String是Redis里最基础的数据类型一个key对应一个valuevalue最大能存512MB。它最典型的应用场景就是缓存比如把用户会话信息、页面缓存、配置数据都存成String。# 设置一个key SET user:2024:info {\name\:\张三\,\age\:30} # 获取 GET user:2024:info # 设置过期时间单位是秒 SETEX order:8888:status paid 3600 # 原子自增自减 SET user:1001:score 0 INCR user:1001:score DECR user:1001:scoreINCR和DECR这个原子操作在很多场景下非常好用比如做计数器、限流统计、库存扣减。注意它之所以叫原子操作是因为Redis单线程执行命令同一时刻不可能存在两个线程同时对同一个key执行INCR这天然避免了并发竞争。这里有个实际使用中的坑很多人存JSON字符串用String类型但Redis的String类型没有结构化查询能力想更新一个JSON里的某个字段只能先把整个字符串取出来在客户端改完再覆盖回去。如果你的数据确实有结构化修改需求应该考虑用Hash类型这个我下面会讲。3.2 List类型一个底子是双端链表的结构List底层是一个双向链表可以从头部和尾部推入弹出元素。最长用的场景是消息队列的简单实现、最新动态列表、关注时间线等。# 从右边推入左边弹出就是标准的先进先出队列 LPUSH msg:queue message-1 LPUSH msg:queue message-2 RPOP msg:queue # 返回 message-1 # 获取列表范围 LRANGE msg:queue 0 -1很多人会拿List实现简单的消息队列但我要提醒一句Redis的List消息队列不支持消息确认机制消费者把消息RPOP出来了如果处理失败了这个消息就永久丢失了。生产环境的可靠消息队列还是建议用Redis Stream或者RabbitMQ这类专业组件。List做最新列表场景倒是挺稳妥的。比如做用户操作日志每一条日志LPUSH到列表头部然后用LTRIM只保留最近100条这个操作成本很低效果却很好。3.3 Hash类型处理对象结构的正确姿势Hash是一个string字段跟string值的映射表非常适合存储一个对象的多项属性。举个例子要存一个用户的信息用String类型你得把整个对象序列化成JSON但用Hash就能把每个字段分开存。# 把用户nickname、age、email分别存到user:1001这个key下的三个字段里 HSET user:1001 nickname 阿甘 age 28 email testexample.com # 获取某个字段 HGET user:1001 nickname # 获取所有字段 HGETALL user:1001 # 对Hash里某个字段做自增 HINCRBY user:1001 age 1Hash最讨喜的一点是它支持对单个字段做更新操作不需要把整个对象序列化后再覆盖写回。比如计数器场景里你要维护每个商品被多少个用户访问过这种对象的部分字段实时变化的需求用Hash就很顺手。生产环境做购物车功能也经常用Hashkey是用户IDfield是商品IDvalue是商品数量。用户往购物车里加东西就HINCRBY一次逻辑非常清爽。3.4 Set类型无序集合的天然去重Set底层是一个无序的字符串集合它可以自动去重并且支持集合间的交、并、差运算。它的用途非常清晰去重和集合运算。# 添加元素 SADD fans:user:1001 user_001 SADD fans:user:1001 user_002 SADD fans:user:1002 user_002 SADD fans:user:1002 user_003 # 计算共同关注 SINTER fans:user:1001 fans:user:1002 # 返回 user_002 # 获取集合大小 SCARD fans:user:1001在一些社区类产品的共同好友可能认识的人这些功能里Set的SINTER运算直接帮你算好交集连业务层代码都不用写。还有一个常见的用途是抽奖去重用户参与抽奖就是SADD一次自动保证一个用户不能重复中奖。3.5 ZSet类型排行榜的唯一正解ZSet是最让我觉得这个设计有点东西的类型。它在Set的基础上给每个元素加了一个score分数Redis会按照score排序存储。这意味着你可以直接取Top N也可以按分数范围查数据。# 给用户加分数 ZADD ranking:2024 10 user_1001 ZADD ranking:2024 5 user_1002 ZADD ranking:2024 8 user_1003 # 获取排名前两位 ZREVRANGE ranking:2024 0 1 WITHSCORES # user_1001 score10, user_1003 score8 # 给用户加分 ZINCRBY ranking:2024 3 user_1002 # 获取某个用户的排名 ZRANK ranking:2024 user_1002排行榜、实时热门、延时队列这些场景ZSet都是最优雅的解法。我之前做过一个积分排行榜刚开始用MySQL的ORDER BY排序用户量一上来就慢得不行。后来改成ZSet每次用户积分变动就ZINCRBY一下取排行榜直接ZREVRANGE性能提升非常明显。3.6 一种被忽略的分页方案使用ZSet做游标分页有个热词叫redis分页这里顺便分享一下。很多人以为Redis查数据不能分页只能拉全量数据到内存里再分页其实用ZSet可以做一个轻量级的分页方案。# 假设数据按时间戳作为score写入 ZADD news:feed 1700000001 news_id_1 ZADD news:feed 1700000002 news_id_2 # ... # 第一页最新10条 ZREVRANGE news:feed 0 9 # 第二页上一页最后一条的score作为起点 ZREVRANGEBYSCORE news:feed (1700000001 -inf LIMIT 0 10这种方案适合数据量不大、但需要按排序分页的场景比如内容流、评论列表的冷数据加速。不过要注意直接用ZRANGE的索引分页在数据量大的情况下性能会有问题要走score游标。4. 持久化机制取舍RDB与AOF的对比以及组合使用的正确姿势我刚学Redis时对持久化这块的理解很浅以为Redis默认会把数据自动保存到磁盘后来有一次测试环境断电重启Redis里的数据全没了我才意识到默认配置只适合内存缓存场景要把Redis当成可以恢复数据的存储必须自己配持久化。这可能是大多数从0学Redis的人都会遇到的认知盲区。Redis提供了两种持久化方案RDB快照和AOF日志。4.1 RDB快照全量备份恢复快但可能丢数据RDB就是周期性把内存中的全部数据生成一个二进制快照文件dump.rdb。默认配置下Redis会在900秒内如果有1次写操作、300秒内10次写操作、60秒内10000次写操作时自动触发快照生成。# 手动触发RDB快照 SAVE BGSAVESAVE会阻塞Redis主进程直到快照生成完成BGSAVE是fork一个子进程来做快照主进程不阻塞。生产环境一般只会用BGSAVE。RDB的优点很明显文件是二进制压缩过的体积小恢复速度快加载RDB文件恢复数据比AOF快很多适合做备份和灾难恢复比如每天凌晨把dump.rdb拷贝到另一台机器缺点也同样明显快照是周期性的两次快照之间的数据会丢失数据量大的时候fork子进程消耗内存和CPU资源用生活化的例子理解RDB就像你每隔一段时间给电脑做个完整系统镜像平时丢一两个新装软件也没关系但出问题恢复镜像之后镜像做完之后新装的软件全没了。4.2 AOF日志记录每一次写命令数据更安全AOFAppend Only File的机制是每次写命令执行后把命令追加到aof文件的末尾。恢复时重放这堆命令就能还原出原始数据。默认的追加频率是everysec也就是每秒同步一次到磁盘。# 开启AOF appendonly yes # 追加策略always 每命令同步 / everysec 每秒同步 / no 交给操作系统 appendfsync everysec # AOF文件触发重写的阈值 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mbAOF比RDB安全因为追加频率很高最多丢1秒的数据。但它的缺点也直观AOF文件大小增长很快需要定期重写恢复速度比RDB慢很多AOF重写机制我提一句它会把内存中的当前数据转换成最少的命令集合重新写一份新的AOF文件替换掉旧的。比如你对一个key执行了100次INCR最终结果只要一个SET命令就搞定了重写就是干这个事的。4.3 我推荐的持久化配置生产环境正确的做法不是二选一而是两者都开。配置项值说明save900 1 300 10 60 10000RDB快照触发条件appendonlyyes开启AOFappendfsynceverysec每秒同步AOFrdbcompressionyes开启RDB压缩aof-use-rdb-preambleyesAOF文件头部复用RDB格式这样组合的好处是首选RDB文件做数据恢复速度快如果RDB文件丢失或损坏再用AOF日志恢复到最多丢1秒之前的状态。Redis 4.0之后支持AOF文件里嵌入RDB格式的头部兼顾了AOF的安全性和RDB的恢复速度。这里再讲一个只有实操过才知道的细节AOF文件的损坏问题。如果服务器突然断电AOF文件的末尾可能写着半条写命令Redis启动时会报错拒绝恢复。修复方式是执行redis-check-aof工具自动修复。redis-check-aof --fix appendonly.aof这个工具会截掉处理不了的残留命令所以大概率你只会损失最后几条写操作而不是整个数据文件。我自己经历过一次当时没备份AOF修复成功之后只能庆幸数据没丢多少。5. 生产环境三座山事务特性、分布式锁实现与内存淘汰策略学Redis简单命令很容易但如果你想把Redis带到生产环境一定会遇到这三个问题Redis事务为什么跟我熟悉的MySQL事务不一样、分布式锁怎么加才是正确的、内存满了怎么办。这三个问题也几乎是Redis面试题里必定出现的三连问。5.1 Redis事务没有回滚只有排队执行Redis提供了MULTI、EXEC、DISCARD、WATCH这几个事务相关命令。它的工作流程是客户端发送MULTI命令后续命令进入一个队列但不会立即执行再发送EXECRedis会按顺序执行队列里的所有命令。127.0.0.1:6379 MULTI OK 127.0.0.1:6379 SET order:1001 paid QUEUED 127.0.0.1:6379 INCR paid_count QUEUED 127.0.0.1:6379 EXEC 1) OK 2) (integer) 1Redis事务跟MySQL事务最大的区别是它不支持回滚。如果队列中间某条命令执行失败了前面的命令不会回退后面的命令也会继续执行。这就意味着你不能指望Redis事务提供像关系型数据库那种原子性保证。Redis事务能提供的保证是批量命令依次执行中间不会被其他客户端的命令插入。这其实更像一个流水线而不是事务。当初我理解不了这一点后来有个学长给我打了个比方Redis的MULTI/EXEC就像在食堂排队的队伍所有人按先后顺序买饭中途有人发现钱包忘带了他后面的同学依然正常买饭不会因为前面这个人出错就全员解散。所以实际开发时Redis事务的使用场景很受限。一个比较常见的用法是对某个key做批量操作又不想每条命令都跟客户端发生一次网络轮询。如果你想实现如果key没有变化才更新这种CAS的操作Redis也提供了WATCH命令来完成乐观锁。WATCH balance balance GET balance new_balance balance - 100 MULTI SET balance new_balance EXECWATCH开启乐观锁在执行EXEC之前如果balance被其他客户端修改了执行会失败返回nil你就需要重试。5.2 分布式锁别再用SETNX单命令了分布式锁是Redis里面试频率最高的问题。很多早期教程会说用SETNX就能实现分布式锁这个说法在单机场景下凑合但放到生产环境就漏洞百出。我们先看一个不推荐的旧版写法。# 不推荐的写法 SETNX lock:order:1001 client-a EXPIRE lock:order:1001 30这个写法的问题是SETNX和EXPIRE不是原子的。如果设置锁之后业务代码还没执行到EXPIRE就崩了这把锁永远不会过期其他客户端永远拿不到锁。正确的单机Redis分布式锁应该用一条SET命令带多参数SET lock:order:1001 client-a NX EX 30NX表示只有key不存在时才设置成功EX 30表示过期时间30秒。这一个命令同时完成了加锁和设置过期时间没有再分开两步的风险。释放锁的时候也需要小心不能直接DEL因为有可能你持有的锁已经过期却被另一个客户端重新拿走了这时候DEL会把别人的锁误删。正确方式是使用Lua脚本来保证先比对再删除是原子操作。if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end这一整套逻辑被整理成了Redisson这个Java客户端的RLock实现。Redisson还做了一件事就是给锁加了一个自动续期的看门狗机制锁快过期了会自动续期避免业务代码执行时间超过锁的过期时间导致锁提前释放。如果你在用Java做生产项目不要手写锁直接用Redisson很多坑它都帮你填了。Redis分布式锁最大的局限在于主从复制场景下的数据一致性问题这也是面试官最喜欢追问的。想象这个场景客户端A在主节点加锁成功主节点还没来得及把数据同步给从节点就宕机了哨兵把从节点提升为主节点。这时客户端B用同一个key去新主节点加锁会加锁成功。在极短的时间窗口内两个客户端同时持有了同一把锁。要解决这个问题严格的做法是使用Redlock算法部署多台互相独立的Redis节点必须在大半数节点上加锁成功才认为加锁成功。但Redlock在业界本身也有争议因为它依赖系统时钟如果某个节点的时钟发生跳变还是会出现问题。我给一个务实的建议如果你的系统要求这么强的锁一致性最好换用ZooKeeper或etcd这类具备强一致性共识算法的组件如果你的系统能容忍极少数的锁冲突那Redis分布式锁本身就是性价比最高的方案。5.3 内存淘汰策略Redis满了以后会发生什么Redis是内存数据库内存不是无限的所以必须提前想好内存满了之后怎么办。默认情况下Redis的memory-policy是noeviction意思是内存满了再写数据会报错这在一些缓存场景里会直接导致业务异常。Redis提供了多种内存淘汰策略这里按重要程度列一下。策略含义适合场景noeviction内存写满后不淘汰任何key写命令返回错误不允许丢数据的场景但要做好监控报警allkeys-lru从所有key里淘汰最近最少使用的纯粹的缓存场景volatile-lru从设置了过期时间的key里淘汰最近最少使用的混合场景希望尽量保留没有过期时间的核心keyallkeys-random随机淘汰任意key访问分布均匀的缓存volatile-ttl从设置了过期时间的key里淘汰剩余时间最短的希望优先淘汰快要过期的key实际生产中最常用的是allkeys-lru和volatile-lru。如果是纯缓存比如存热点数据allkeys-lru简单粗暴效果也好如果Redis里除了缓存还存了一些不允许被淘汰的业务数据那就选volatile-lru。我见过不少团队在使用Redis时忽略内存淘汰策略然后遇到Redis写不进去了的报警才赶紧去改配置。其实应该在部署之初就想清楚这台Redis是纯缓存还是有业务数据然后设定好对应的策略和maxmemory限制。# 限制Redis最多使用2GB内存 maxmemory 2gb # 内存满了优先淘汰最近最少使用的key maxmemory-policy allkeys-lru还有一个建议监控Redis内存不能只看maxmemory你用info memory命令看到的used_memory是数据占用的内存但Redis实际进程内存占用还要加上碎片、客户端缓冲区、持久化相关开销。所以生产环境里maxmemory最好只设置成机器物理内存的70%左右剩下的留给fork子进程快照和操作系统本身的缓冲。6. 高可用是绕不开的课主从复制、哨兵模式与常见启动故障排查学完单机Redis的所有功能之后下一个必然面对的问题是单机挂了怎么办。这里谈的主从复制和哨兵模式是Redis生产环境对标高可用的标准方案。6.1 主从复制的基本原理主从复制的作用简单说就是让一台Redis节点主节点的数据实时同步到其他节点从节点。读写分离的场景下主节点负责写从节点负责读可以分摊读压力另一个场景是数据容灾主节点挂了从节点可以顶上。配置主从复制其实只要一句命令在从节点上执行# 在从节点上执行 replicaof 192.168.1.10 6379或者修改从节点的redis.confreplicaof 192.168.1.10 6379主从复制分为两个阶段第一阶段是全量同步主节点会生成一个RDB快照发给从节点从节点加载这份快照第二阶段是增量同步主节点之后收到的所有写命令都会实时推送给从节点。这个过程是异步的意味着从节点可能比主节点落后一点点读从节点可能读不到刚写入主节点的数据。这个延迟通常只有几毫秒但在强一致敏感的业务里需要特别注意。做读写分离时刚写入的数据立刻去从节点读可能读不到旧值。6.2 哨兵模式自动故障转移主从复制解决了数据冗余和读扩展问题但有一个痛点没有解决主节点挂了从节点不会自动升级为主节点业务流量还是往那个挂了的主节点上写系统就不可用了。哨兵模式Sentinel就是为了解决这个问题。Sentinel本身是一个独立的Redis进程它会监控所有Redis节点的心跳。当它确认主节点不可用时会做两件事第一从从节点中选一个提升为主节点第二配置文件里登记的其他从节点改为复制新主节点。部署哨兵模式的简版步骤# 1. 搭建主从假设现在有 # 主节点192.168.1.10:6379 # 从节点192.168.1.11:6379 # 从节点192.168.1.12:6379 # 2. 在三台机器或三台容器里分别启动哨兵 redis-sentinel /etc/redis/sentinel.confsentinel.conf至少要包含这样几行sentinel monitor mymaster 192.168.1.10 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000这里有三个关键参数monitor后面那个2表示多少个哨兵认为主节点不可用才触发故障转移建议至少部署3个哨兵2是合理的。down-after-milliseconds表示主节点多少毫秒内没响应就判定为下线。failover-timeout是故障转移的超时时间。我在实际运维中踩过一个哨兵模式的坑跟开头提到的热搜词redis哨兵模式启动未生成known有点类似。当时我在一套环境里启动了哨兵运行了一段时间后重启了哨兵进程然后发现日志里一直看不到对主节点的监控只有一个unknown的master状态。排查了半天才发现sentinel.conf里写的主节点地址跟实际网络不通哨兵启动时会尝试连接配置里指定的master连不上就会把它标记为unknown。修复方式是把配置文件里的master地址改成实际可达的IP然后重启哨兵进程。排查这个问题的完整链路是这样的先看哨兵日志发现用了sentinel-monitor但master是unknown状态在哨兵机器上手动ping主节点IP发现网络不通排查发现主节点绑定的网卡改了IPsentinel.conf里还是旧IP修改配置文件重启哨兵恢复正常很多哨兵没生效的问题本质上不是哨兵配置错了而是主从节点之间的网络可达性出了问题。所以你先别急着去调哨兵参数先确认主从之间能ping通能telnet上6379端口。6.3 数据分片与集群模式当内存还不够多怎么办如果业务数据量超过单机内存Redis Cluster集群模式就派上用场了。Redis Cluster把数据按哈希槽进行分片总共16384个槽位。key通过CRC16算法计算哈希值对16384取模决定这个key落在哪个槽。每个主节点负责一部分槽位客户端请求可以被自动路由到正确的节点。集群模式对客户端有要求客户端需要支持集群协议才能正确处理MOVED重定向。常见的客户端比如Jedis、Lettuce都支持。对于学习阶段我的建议是先彻底掌握主从和哨兵理解清楚故障转移和数据同步的原理再上升到集群模式。因为集群模式背后的很多问题比如扩容、缩容、槽位迁移、客户端路由都是建立在这些基础概念之上的。7. 缓存治理与序列化穿透、击穿、雪崩的应对方案最后这个部分我想把缓存治理和序列化的问题放在一起讲。因为这两件事是实际开发里碰到最多、也最容易在面试里被问到的点。7.1 Redis缓存三大经典问题的成因和对策缓存穿透、缓存击穿、缓存雪崩这三个问题虽然名字很像但成因完全不同解决方案也非常不一样。缓存穿透请求的数据在数据库里和缓存里都不存在所以每次请求都会绕过Redis直接打到数据库上。攻击者可以利用这个特点发送大量不存在的ID直接把数据库拖垮。应对方案有三种对空结果做缓存即使数据不存在也把空值缓存起来设置一个比较短的过期时间比如60秒布隆过滤器用bitmap判断数据是否存在不存在就直接返回不查数据库参数合法性校验看起来简单但很多人忽略比如ID的取值范围、格式校验很多非法请求直接就被拦截了缓存击穿某个热点key在缓存过期的瞬间同时有大量请求涌入全部穿透到数据库。这和穿透的区别是这个key在缓存中本来是存在的只是因为过期导致瞬时压力。应对方案互斥锁当缓存失效时只放一个请求去数据库加载数据其他请求等待逻辑过期不主动设置物理过期时间而是给value里塞一个过期时间戳后台任务定期检查并刷新缓存。这个方案避免了缓存过期瞬间的锁等待在高并发场景下很实用缓存雪崩大量key在同一时间过期或者Redis实例本身挂了导致所有请求集中打到数据库。应对方案给不同的key设置随机的过期时间避免同时过期用集群或哨兵保证Redis实例的高可用加多级缓存比如本地缓存加Redis避免Redis不可用时流量直接打到数据库7.2 Spring Boot集成Redis时序列化方式必须显式指定我用Java开发比较多Spring Boot里集成Redis特别方便但新手最容易踩的一个坑就是key和value的序列化器。默认情况下Spring Data Redis的RedisTemplate使用JdkSerializationRedisSerializer这会导致两个问题存在Redis里的key会变成一串带二进制前缀的乱码你用Redis Desktop Manager看时看到的key前面有\xac\xed\x00\x05t这一串东西这就是Java序列化特有的头value也是Java对象序列化后的二进制数据其他语言比如Python、Go根本没法读取这对跨语言场景就是灾难正确的做法是显式配置StringRedisSerializer和Jackson2JsonRedisSerializer或者统一使用GenericJackson2JsonRedisSerializer。Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // key使用String序列化 StringRedisSerializer stringSerializer new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); // value使用Jackson JSON序列化 Jackson2JsonRedisSerializerObject jsonSerializer new Jackson2JsonRedisSerializer(Object.class); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; }还要注意一点用Jackson2JsonRedisSerializer时如果value是复杂对象反序列化时可能出现类型丢失的问题。解决方式是用GenericJackson2JsonRedisSerializer它会在JSON里额外存一个class字段用于反序列化时还原类型代价是多占一点存储空间。序列化问题不只是Java引入的问题。你用Python的pickle、Ruby的Marshal、Go的gob等语言特有的序列化格式同样会遇到跨语言不可读的问题。所以我的建议是只要你的Redis将来有可能被多个服务或多种语言访问value统一用JSON格式key统一保持简单字符串这是最不容易出错的。Redis序列化还牵涉到一个性能问题JSON多了字段名占用的内存比二进制格式大。如果value特别大、请求量又高可以考虑换成压缩算法比如Snappy、Zstd但这是优化阶段的事初学阶段先把JSON格式搞对就已经赢在起跑线上了。7.3 缓存治理核心是先想清楚缓存的数据边界我在实际做缓存治理时最深的一点体会是缓存治理方案再花哨也不如业务侧想清楚哪些数据该缓存、缓存的过期策略是什么、数据一致性怎么保证。出现穿透、击穿、雪崩本质上就是你把这些边界问题搞模糊了。比如做更新操作时到底是先更新数据库还是先删缓存这个顺序在没想清楚之前很可能引入脏数据。业界比较推荐的Cache Aside Pattern是先更新数据库再删除缓存。读取时先读缓存如果缓存没有再从数据库加载并写回缓存。这种模式下缓存删除失败会导致旧值残留所以引入了延迟双删的策略也就是更新数据库之后先删除一次缓存过几百毫秒再删除一次确保并发读请求不会把旧值重新写回缓存。这些经验看着有点绕但真的都是踩坑踩出来的。我建议初学者先把一个最简单的链路跑通MySQL里有一张表接口被调用时先查Redis未命中再查MySQL把结果写回Redis设置过期时间。然后在这个链路上去模拟各种故障场景缓存穿透会发生什么、缓存雪崩会怎么体现、数据不一致是什么原因一次一次加深理解才有意义。8. 从能跑到会用我给入门者的一份精简自查清单我学Redis的过程最大的弯路就是陷入了一种收藏教程但从不实操的状态。看了大量的文档和视频但自己动手敲命令的次数很少结果一到真正需要写生产代码时各种细节全想不起来。后来我给自己定了一个最低限度的操作清单每完成一项就勾一项这套方法对入门者还挺有效的分享出来供你参考。能在命令行和可视化工具里连上Redis执行基础的增删改查能熟练操作五种基本类型至少为每种类型想出一个实际业务场景能背出String、List、Hash、Set、ZSet对应的常用命令不是死记是敲出来的印象能独立配置RDB和AOF理解两者的区别并能解释为什么生产环境要同时开启能自己动手搭一个一主两从的主从架构并观察主节点写入后从节点同步的过程能配置哨兵手动杀掉主节点进程观察哨兵自动完成主从切换能写一个带过期时间的SET命令实现分布式锁再写一个Lua脚本实现安全释放能说清楚缓存穿透、击穿、雪崩的区别并给出至少一种应对方案能用Redis的ZSet实现一个排行榜并且理解分数有小数时的精度陷阱最后分享一个我在实际项目里得到的教训Redis不是越复杂用越好恰恰相反能用String解决的不要上ZSet能用单机解决的不要上集群。架构越简单出问题的概率越低排查问题的难度也越低。把基础数据类型、持久化配置、缓存策略这几个核心点打磨扎实比追逐一堆花哨的高级特性有用得多。我到现在还在坚持的习惯是每接触一个新的Redis场景先问自己三个问题这个数据放在Redis里是为了加速还是为了存储如果Redis重启数据丢了能不能接受如果Redis内存飙升会拖垮哪些业务流程这三个问题想清楚了很多技术选型上的纠结自然就有了答案。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价