尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Redis高级实战:持久化、主从哨兵、分片集群、缓存治理与分布式锁

Redis高级实战:持久化、主从哨兵、分片集群、缓存治理与分布式锁 先把话说在前面如果你只是会在 Spring Boot 里写一个 RedisTemplateset 一个字符串再 get 出来那 Redis 对你来说还是单机玩具。真正让我意识到必须系统学一遍 Redis 高级内容是第一次把服务部署到多台机器之后——session 不共享了、热点数据扛不住了、Redis 进程一重启数据全没了每一个问题都够我忙半天的。黑马这套视频的高级篇正好就是把生产环境里这些硬骨头一块一块啃下来。这篇笔记是我把整套高级篇内容拆开、揉碎又补了一堆实测之后的整理版主线包括持久化选型、主从复制、哨兵、分片集群、缓存穿透/击穿/雪崩治理、分布式锁以及每个模块落地时最容易踩的坑。适合已经会用 Redis 基础命令、正准备把它真正用进项目的开发者。1. 从单机到生产高级篇到底在解决什么问题1.1 基础篇和高级篇之间缺了什么基础篇教你的是五种数据类型怎么用key 怎么设过期Java 客户端怎么调这些内容能让你把 Redis 当做一个好用的 Map来使。可一旦面对生产环境问题就变了。第一个问题是数据安全进程退出内存数据是不是直接蒸发第二个问题是高可用一台 Redis 挂了系统怎么做到不感知第三个问题是容量单机内存几十个 G 到头了数据量继续涨怎么办第四个问题是并发下的正确性多个服务同时扣库存怎么保证不超卖这些问题不是靠多记几个命令就能解决的它们需要的是架构层面的认知。黑马这个高级篇的精髓就是没有停留在这个命令会返回什么的层面而是把 Redis 放在一个真实的生产拓扑里去讲。1.2 四个生产问题对应的高级特性我用一张表把这四场硬仗和对应的解决方案理出来后面每一节都会展开生产问题对应高级特性核心知识点进程退出丢数据持久化RDB 快照、AOF 日志、混合持久化单点故障主从复制 哨兵全量同步、增量同步、自动故障转移单机容量瓶颈分片集群哈希槽、节点伸缩、Gossip 协议并发竞争与一致性分布式锁 缓存治理SETNX、Redisson、缓存三大问题这里我想强调一个学习顺序的问题。持久化应该最先看因为主从复制的全量同步依赖 RDB哨兵的故障转移依赖主从关系链路是一层叠一层的。缓存治理和分布式锁相对独立你可以在搞懂持久化和高可用之后再单独啃不用怕跳着看会脱节。1.3 这套高级篇内容里最容易被忽略的部分很多人看完这套视频记住的是 Cluster 怎么搭、Redisson 怎么用觉得这些大招才是重点。但以我自己的实战体验真正在项目里回报率最高的是持久化配置和缓存穿透/击穿/雪崩治理。原因很简单集群你未必用得上但 Redis 数据持久化几乎没有项目不用缓存三大问题则是只要上缓存就一定会遇到。所以这篇笔记里我也会把笔墨重点放在这两块。2. RDB 与 AOF 持久化数据安全的第一层防线2.1 RDB 快照不是实时备份RDB 是 Redis 默认开启的持久化方式它会定期把内存里的数据生成一份二进制快照文件默认叫dump.rdb。触发方式有几种手动执行save或bgsave满足配置的 save 规则执行flushall或者正常关闭 Redis 时也会触发。需要特别注意save和bgsave的区别。save是同步操作直接在主线程里干会阻塞 Redis生产环境基本没人用它。bgsave才是正路主进程fork出一个子进程子进程负责把数据写入临时文件写完后原子替换原来的 RDB 文件。fork之后为什么子进程能拿到一致的数据快照这里的关键是操作系统的Copy-On-WriteCOW机制。你可以把它理解成拍照fork 一瞬间子进程复制的是父进程的页表不是所有内存数据所以非常快。之后父进程继续处理写请求一旦某个内存页要被修改操作系统会把这个页复制一份给父进程用子进程始终看到的是 fork 时刻的内存状态。这套机制保证了 bgsave 不会影响正在服务的主进程。但 COW 也带来一个隐形坑如果 fork 之后父进程写操作特别多需要复制的内存页就多内存开销会翻倍。实操中如果 Redis 实例占用了 20GB 内存bgsave 期间可能额外需要几 GB 甚至十几 GB 的物理内存所以大实例的 fork 非常容易引起主线程卡顿。这也解释了一个经验法则单机 Redis 内存最好不要超过 20~30GB太大了不如拆实例或者上集群。2.2 AOF 日志每条写命令都留痕AOFAppend Only File的思路和 RDB 完全不同每一条写命令都以 Redis 协议格式追加到日志文件里重启时把日志里的命令重新执行一遍就能恢复数据。它的核心配置是appendfsync对应三种刷盘策略策略行为安全性性能always每个写命令都同步刷盘最多丢一条命令最差everysec每秒刷一次盘最多丢 1 秒数据折中默认推荐no由操作系统决定刷盘时机可能丢几十秒数据最好默认的everysec是最常用选择因为 Redis 本身是内存数据库主打高性能always的性能损耗在写密集场景下很难接受。如果你对数据安全要求极高比如想极端一点可以临时改成 always但代价是吞吐量明显下降。AOF 还有一个绕不开的操作重写。随着运行时间变长AOF 文件会越来越大里面有大量中间状态命令。比如对一个 key 做了一百次 INCR文件里可能存了一百条 INCR 命令但实际只需要 SET 一次最终值就够了。bgrewriteaof会对 AOF 文件进行压缩原理是按当前内存状态生成最小命令集合同样也是 fork 子进程来做。Redis 4.0 之后引入了混合持久化aof-use-rdb-preamble yes开启后重写出来的 AOF 文件头部是 RDB 格式的快照后面再追加增量命令。这样既有 RDB 恢复速度快的优点又能尽量少丢数据是目前生产环境的主流方案。2.3 真实项目中的持久化配置这里直接给一份我在项目里验证过的基础配置appendonly yes appendfilename appendonly.aof appendfsync everysec no-appendfsync-on-rewrite yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-use-rdb-preamble yes几个配置怎么理解no-appendfsync-on-rewrite yes表示在 AOF 重写期间不刷盘。因为 AOF 重写会带来很高的磁盘 IO如果此时还要保证每秒刷盘两者叠加容易造成 IO 阻塞。开了这个选项重写期间最多会多丢一些数据换取主线程的稳定性。auto-aof-rewrite-percentage 100和auto-aof-rewrite-min-size 64mb是自动触发重写的条件AOF 文件比上次重写时增长了 100%且文件超过 64MB。另一个常见坑是启动恢复优先级。如果 AOF 和 RDB 同时存在Redis 优先用 AOF 恢复因为 AOF 丢数据更少。但如果你不小心把 AOF 文件搞坏了会导致 Redis 启动失败。修复办法是使用redis-check-aof --fix appendonly.aof工具它能帮你删掉受损的尾部命令让实例先启动起来。实测中我用这个工具救过一次数据值得记一下。3. 主从复制与哨兵把单点故障摁死3.1 主从复制的两种同步模式主从复制解决的问题很直接Redis 是单点的它挂了整个服务就断了所以要有一份实时的备份。配置方式是在从节点执行replicaof 主节点IP 主节点端口建立关系后主节点会持续把写命令同步给从节点从节点默认只服务读请求replica-read-only yes。主从之间第一次建立复制关系时走的是全量同步。流程我用自己的话描述一遍从节点发送psync ? -1表示我没有历史数据主节点收到后执行 bgsave 生成 RDB 快照同时把新收到的写命令写入复制缓冲区快照生成完后发给从节点从节点清空旧数据并加载 RDB最后主节点把缓冲区里的增量命令再发给从节点。这一套下来主从数据就对齐了。全量同步之后就是增量同步。主节点会维护一个环形缓冲区repl_backlog_buffer写命令会同时写入这个缓冲区。从节点会记录自己已经同步到的偏移量之后每次只需要发送psync runid offset主节点把缓冲区里偏移量之后的命令发给从节点即可。环形缓冲区的坑在于它的容量是有限的默认大小是 1MB。如果从节点断连时间过长或者主节点写入量太大backlog 里的数据被新命令覆盖了从节点就只能退回全量同步。生产环境建议把这个参数调大比如 64MB 甚至 256MB具体看你的写入量。3.2 用 Docker 搭建一套最小主从我用 Docker 搭过一套三个节点的主从遇到最多的坑是容器 IP 写死。容器一旦重建IP 就变了replicaof配置就失效。正确做法是创建自定义网络用容器名互相访问docker network create redis-net docker run -d --name redis-master --network redis-net -p 6379:6379 \ redis redis-server --appendonly yes docker run -d --name redis-slave1 --network redis-net -p 6380:6379 \ redis redis-server --replicaof redis-master 6379 docker run -d --name redis-slave2 --network redis-net -p 6381:6379 \ redis redis-server --replicaof redis-master 6379启动之后在任意节点上用info replication查看复制状态。如果看到master_link_status:up说明复制链路正常。我再多提一句主从复制的写命令传播是异步的主节点不会等从节点确认成功就返回给客户端。这意味着主节点刚写完还没来得及同步就宕机从节点顶上时会丢一部分数据。这是 Redis 主从的固有特性不要指望它做到“零丢失”如果你对这个要求极其严格需要考虑强一致方案但通常业务场景 1 秒内的数据丢失是可以接受的。3.3 哨兵自动化的故障转移主从架构还有一个问题主节点挂了从节点不会自动升级。你需要哨兵Sentinel来做监控和故障转移。哨兵的核心逻辑是每个 Sentinel 进程定时向 Redis 节点发送心跳如果一个 Sentinel 发现主节点不可用会把它标记为主观下线sdown多个 Sentinel 都发现主节点不可用达到 quorum 数量后就升级为客观下线odown接着 Sentinel 集群会选出一个 leader由它执行故障转移从从节点里选一个提升为主节点并通知其他从节点切换复制目标。这里有个特别容易理解的“为什么”问题Sentinel 至少需要部署几个答案是奇数且大于等于 3。因为客观下线的判定需要投票两个 Sentinel 在其中一个宕机时只剩一个无法构成多数派整个系统就失效了。我用一句话总结 Sentinel 对数量敏感的原因故障转移本身也需要防脑裂。一份常见的 Sentinel 配置如下sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000monitor后面2表示至少 2 个 Sentinel 同意主节点不可用才会开始故障转移。故障转移完成后主节点地址变了客户端如果还连着旧地址就废了所以在项目里我们通常还会配合配置中心或者网关做一层动态感知。3.4 主从架构最容易踩到的脑裂与丢数据问题即使是主从 哨兵架构依然存在一个经典问题脑裂。场景是主节点发生了网络分区哨兵联系不上它判定它挂了然后提升了从节点为新的主节点。但旧主节点并没有真的宕机它还在继续接收客户端的写请求。等网络恢复后旧主节点被降级为从节点它之前接收的那些写请求因为没能同步给新主节点直接被清空数据就丢了。Redis 提供了一套参数从根上减少这种风险min-replicas-to-write 1 min-replicas-max-lag 3这两行的意思是主节点至少要有 1 个从节点在 3 秒内有正常心跳同步才接受写请求如果所有从节点都失联了主节点直接拒绝写入。这样脑裂发生时旧主节点的写入口会被切断数据丢失范围大幅缩小。这个参数按理说应该在每套主从环境都配但很多教程都只提半句这里单独拎出来强调一下。4. 扩容到分片集群当单机内存彻底不够用4.1 哈希槽才是集群的数据分片方式主从复制和哨兵解决的是高可用却没有解决容量问题。无论多少从节点它们存的都是同一份完整数据总内存仍然受单机限制。数据量大到几十个 G、上百个 G 之后就得考虑分片集群。Redis Cluster 的做法是把整个 key 空间划分为 16384 个哈希槽每个 key 通过CRC16(key) % 16384计算它属于哪个槽然后集群把这些槽平均分配给各个主节点。你访问任何一台节点如果 key 的槽不在当前节点上节点会返回MOVED错误并告诉你正确节点的地址支持 Cluster 协议的客户端会自动完成跳转所以客户端需要专门支持集群模式。为什么是 16384 而不是更多这个和集群节点心跳消息的大小有关每个节点在 Gossip 通信时都会带上自己的槽位信息16384 个槽位用 Bitmap 表示只要 2KB即使几百个节点心跳包也能控制在合理范围内。如果槽位太多心跳开销会成为问题太少则数据分布不够均匀。这个数字是性能和均匀度之间的平衡点。另外Cluster 模式支持多 key 操作但要求这些 key 落在同一个槽里。官方提供的机制叫Hash Tagkey 中用{}包住同一部分Redis 计算槽位时只对花括号内的内容做 CRC16。例如user:{1001}:name和user:{1001}:score因为{1001}相同会被分配到同一个槽事务、Lua 脚本和多 key 命令就能生效了。4.2 三主三从集群的搭建实操我用 Docker 搭建一套三主三从集群的过程可以直接复现docker network create redis-cluster-net for port in 6379 6380 6381 6382 6383 6384; do docker run -d --name redis-cluster-$port \ -p $port:6379 \ --network redis-cluster-net \ redis redis-server --port 6379 --cluster-enabled yes \ --cluster-config-file nodes.conf --appendonly yes done启动之后需要把节点互相发现并分配槽位。用官方提供的redis-cli --cluster create命令一步完成redis-cli --cluster create \ 172.20.0.2:6379 172.20.0.3:6379 172.20.0.4:6379 \ 172.20.0.5:6379 172.20.0.6:6379 172.20.0.7:6379 \ --cluster-replicas 1这个命令会提示你输入 yes 确认槽位分配方案。--cluster-replicas 1表示每个主节点带一个从节点最终槽位被平均分配到三个主节点。内存里记住两个校验命令cluster info查看槽位分配和集群状态cluster nodes查看节点间的关系。搭好之后用可视化工具连接会比较直观。RedisInsight 和 Another Redis Desktop Manager 都支持 Cluster 模式填一个节点的地址就能自动识别整个集群拓扑查看每个节点负责的槽位区间比命令行一个个敲舒服很多。4.3 集群扩容与槽位迁移集群不是建完就完事的数据量增长后要加节点。添加一台新主节点的流程是先redis-cli --cluster add-node 新节点地址 任意老节点地址把节点加入集群然后再执行redis-cli --cluster reshard 新节点地址指定要从哪些老节点迁移多少个槽位过来。槽位迁移的过程其实就是在搬数据。Redis 会把槽位对应的 key 逐个从源节点迁移到目标节点迁移期间key 可能在源节点也可能在目标节点客户端会通过ASK重定向找到正确的位置。这个过程中集群对外是正常服务的但你如果在一个大 key 上执行并发读写可能会感受到轻微延迟生产环境扩容一般选在低峰期做。我的建议是除非单机内存确实见底了或者 Redis 的 CPU 单线程瓶颈实在顶不住否则不要轻易上 Cluster。原因在于它给业务代码带了很多限制跨槽位事务、跨槽位 Lua、批量操作都变得麻烦排查问题时也要同时面对多个节点。能用主从 哨兵解决就不上集群这是我在项目里牢牢记住的选型原则。4.4 集群模式下的使用限制与客户端注意点集群模式下最容易踩的坑就是原来能用的命令现在报错事务MULTI/EXEC里如果包含多个不同槽位的 key会直接报CROSSSLOT错误。Lua 脚本同理脚本里操作的所有 key 必须在同一个槽位。没有做 Hash Tag 的多 key 操作例如MSET user:1 name user:2 name也会报错。这些问题的最佳解法不是去改集群配置而是在设计 key 的时候就给相关联的数据加上 Hash Tag。比如把同一个用户的属性集中存到一个 hash 里构造user:1001这样的 key天然就在同一个槽。客户端的选型也得注意普通的 Jedis 不支持集群路由逻辑要用 JedisClusterSpring Boot 默认用的 Lettuce 自带集群支持配置一下节点列表即可。我在生产环境用 Lettuce 比较多底层连接共享和自动重定向都做得比较成熟但如果你对底层连接池有强需求JedisCluster 也不差关键是把连接超时、重试次数这些参数在压测时调好。5. 缓存穿透、击穿与雪崩三个必须处理的坑5.1 缓存穿透查了一个一定不存在的数据缓存穿透的本质是请求的数据在数据库里压根不存在所以缓存永远不可能命中每个请求都会直接打到数据库。攻击者可以故意构造大量不存在的 ID 来打垮数据库。最有效的两种方案我按使用成本排序第一种是缓存空值。查询 DB 返回为 null 时也往缓存里写一个 null 占位并设置一个较短的过期时间比如 60 秒。实现简单但缺点是有大量不存在的 key 时会占缓存空间而且攻击者换着 ID 打每个 ID 都能产生一条空缓存。所以我通常会给空值 key 设置 2~3 分钟的 TTL并配合布隆过滤器兜底。第二种是布隆过滤器。核心原理申请一个位数组写入数据时用多个哈希函数把 key 映射到位数组的多个位置并置为 1查询时同样计算哈希如果发现任意一个位是 0则可以确定 key 一定不存在如果所有位都是 1则可能存在因为哈希冲突会产生误判。在缓存查询之前先经过布隆过滤器能过滤掉绝大多数不存在的 key。我自己的落地策略是空值缓存 布隆过滤器一起用。布隆过滤器挡掉绝大多数恶意穿透空值缓存兜住过滤器误判后打到 DB 的真实空结果。需要注意的是布隆过滤器不适合频繁删除的场景因为删除一个 key 需要把对应的位清 0可能导致其他 key 误判所以如果有大量删除操作缓存空值方案会更合适。5.2 缓存击穿热点 key 过期的一瞬间缓存击穿和穿透名字很像但场景完全不同它特指一个热点 key 在过期的瞬间大量并发请求同时发现缓存没命中全部涌向数据库。因为只持续很短时间很多系统都被大流量瞬间打崩过。两种主流的解决思路互斥锁和逻辑过期。互斥锁的思路是当缓存 miss 时先尝试获取一个分布式锁只有拿到锁的请求才允许去查数据库其他请求先等待一段时间再重新查缓存。实现不难但有一个隐含问题大量线程会阻塞等待影响接口响应时间。逻辑过期是黑马这套视频里讲得比较多、我也觉得更优雅的方式不给 key 设置真正的 TTL而是在 value 里额外存一个过期时间字段。当请求发现 value 里的时间已经过期时不会立刻删除 key而是先返回旧数据同时异步去数据库更新缓存。这种方案的好处是用户体验更好不会因为缓存 miss 导致响应变慢坏处是数据一致性弱一点因为过期后的第一个请求拿到的是旧数据。我的实际选择是对一致性要求高、并发也不低的场景用互斥锁对一致性容忍度较高的场景用逻辑过期。两个方案都不是银弹要结合业务判断。5.3 缓存雪崩成片 key 同时过期或 Redis 直接宕机雪崩的触发条件比击穿更大片大量 key 的过期时间都设置在同一个时间点或者 Redis 实例直接挂掉导致所有缓存请求全部落到数据库。解决方案分两个层面第一个层面是避免过期时间同质化。做法是在设置缓存 TTL 时加入随机值比如原来是 1 小时现在按1小时 随机0~600秒分布让过期时间自然错开。代码上就是setTimeout(3600 random.nextInt(600))成本极低效果显著。第二个层面是降低 Redis 宕机时的冲击。这里就是前面说的主从 哨兵 集群能真正发挥作用的地方Redis 高可用让宕机时间变短但宕机期间数据库还是会接收大量流量。所以还要配合限流降级当缓存不可用时只允许部分请求穿透到数据库其他请求直接返回默认值或错误提示防止数据库被打死。顺带说一句多级缓存也是缓解雪崩的有效手段。本地 JVM 缓存比如 Caffeine做第一层Redis 做第二层数据库做第三层。Redis 挂了之后本地缓存还能扛住一部分流量给故障恢复争取时间。这也是为什么很多资深架构师会强调不要把所有鸡蛋放在 Redis 一个篮子里。5.4 缓存更新策略与一致性真正上了生产你会发现比穿透/击穿/雪崩更磨人的是缓存和数据库的一致性问题。最常见的做法是 Cache Aside Pattern读的时候先读缓存没命中就读库并回填写的时候先更新数据库再删除缓存。为什么是删除缓存而不是更新缓存因为更新缓存的代价更高而且并发下更容易出问题。删掉缓存让下一次读取时重新加载是一种懒惰但安全的做法。但先更新 DB 再删缓存依然有个经典并发漏洞线程 A 更新 DB 为值 2删除了缓存线程 B 在 A 删除之前的瞬间读到了旧缓存值 1回填了缓存。要彻底解决业界常用的一招是延迟双删先删除缓存、更新 DB、等几百毫秒再删一次缓存。把晚一步回填旧值的窗口尽量压小虽然不能数学上绝对消除但在绝大多数业务场景已经够用了。如果你对一致性要求极高还可以用订阅数据库 binlog 的方式异步删缓存比如通过 Canal 监听 MySQL 的 binlog 变更然后通知 Redis 删除对应 key。这套方案侵入业务代码少但需要额外部署 Canal 组件复杂度上升一个量级。5.5 RedisTemplate 序列化一个绕不开的细节和 Spring Boot 整合时最容易踩的深坑就是序列化。RedisTemplate默认使用 JDK 序列化存进去的 key 会带着一长串\x00前缀value 是二进制乱码用 RedisInsight 或者 Another Redis Desktop Manager 查看时完全没法读排查数据问题困难十倍。我的做法是自定义一个RedisTemplatekey 使用StringRedisSerializervalue 使用GenericJackson2JsonRedisSerializer。改成 JSON 序列化之后至少在可视化工具里能直接看到内容调试体验好很多。但要注意一个副作用JSON 序列化的 value 会保存对象的类型信息如果对象结构发生了变化反序列化可能直接报错这属于缓存数据序列化方案固有的兼容性问题。在架构评审阶段就应该定下来不要等到线上跑出乱码再改。6. 分布式锁从 setnx 到 Redisson6.1 为什么 synchronized 在多实例下失效单机应用里synchronized或者ReentrantLock能解决多线程竞争问题因为它们锁的是 JVM 进程内的对象。一旦应用部署成多实例请求会通过负载均衡随机落到不同的 JVM 进程上每个进程的锁互相独立库存超卖、重复下单这类问题就会冒出来。这时候需要一把全局唯一的锁Redis 天然适合这个角色。6.2 第一版SET NX EX 加 Lua 释放Redis 实现分布式锁最基础的一条命令是SET lock_key unique_value NX EX 30NX表示只有 key 不存在时才能设置成功EX 30表示锁的自动过期时间是 30 秒。拿到锁的业务逻辑执行完后需要释放锁。释放有个很关键的细节只能释放自己持有的锁所以 value 通常用唯一标识比如 UUID释放前先比较 value一致才删除比较和删除必须是一个原子操作用 Lua 脚本实现if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end这个版本能跑通基本流程但存在几个隐患锁过期时间设置多少才合适业务执行超过 30 秒锁就自动释放了其他线程会趁虚而入。锁不是可重入的。同一个线程再次获取锁会失败除非你自己用 hash 结构记录加锁次数。Redis 主节点宕机时会丢锁。锁写入主节点还没同步到从节点主节点挂了从节点顶上后锁就没了。6.3 Redisson 解决了什么实际项目中我基本不手写上面的逻辑直接用 Redisson。Redisson 的分布式锁本质是用 Lua 脚本 Hash 结构实现了可重入锁hash 的 field 存线程标识value 存加锁次数同一个线程可以多次加锁每次释放减计数计数归零才真正删除锁。最值得说的是它的Watchdog 看门狗机制。Redisson 加锁成功后会启动一个定时任务默认锁的有效期是 30 秒每 10 秒会检查一次只要锁还持有中就自动续期到 30 秒。这样业务执行时间超过锁过期时间的问题就消失了你不需要自己拍脑袋设置一个估计够用的过期时间。用法非常简单RLock lock redissonClient.getLock(order: orderId); try { if (lock.tryLock(3, TimeUnit.SECONDS)) { // 业务逻辑 } } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } }6.4 RedLock 和主从故障时的锁安全问题Redisson 也不是万能的。前面提到主从切换时锁可能丢失客户端 A 在主节点拿到了锁主节点还没来得及同步给从节点就宕机了哨兵把从节点提升为主节点此时客户端 B 也去加锁它也能拿到同一把锁。两个客户端同时持锁分布式锁的核心语义就被破坏了。Redis 官方提出过 RedLock 方案同时向多个独立的 Redis 节点申请锁超过一半节点加锁成功才算获得锁。但这套算法在不少分布式系统专家的论文里被论证过仍然有时间窗口问题而且实现复杂、性能开销大生产实践中争议很多。我的看法是如果你需要的是绝对正确的锁应该考虑 ZooKeeper 或 etcd 这类带强一致协议的组件如果你的业务能容忍极小概率的两个客户端同时执行业务Redisson 已经够用了。绝大多数互联网场景比如防止重复下单、库存扣减业务上还要靠数据库唯一约束兜底分布式锁只是第一道防线。7. 高级篇学完之后我在项目里是怎么落地的如果让我给一个落地顺序清单我建议按下面的优先级来第一优先级持久化配置。任何新项目接 Redis先把 AOF 打开设置everysec同时保留 RDB。这不是特性是底线。第二优先级缓存治理。项目里只要用了 Redis 做缓存就把穿透、击穿、雪崩的应对方案设计进架构尤其是热点数据要提前想好。第三优先级主从 哨兵。服务正式上云或者部署到多台机器时主从复制和哨兵高可用是必须项它决定了 Redis 挂了你的系统能不能继续转。第四优先级分布式锁。只有真正遇到多实例并发写同一个资源的时候再上 Redisson不要一开始就为所有接口都加锁过度设计比不加锁更糟糕。第五优先级分片集群。单机容量、单线程 CPU 顶不住的时候再考虑越晚越好。记笔记我也说两句。黑马这套视频很多知识点是连贯的比如主从全量同步依赖 RDB、哨兵依赖主从、集群故障转移依赖投票机制。我自己的笔记方法是每看完一个模块就在本地搭一个最小环境把视频里的命令完整跑一遍然后把info输出和启动日志里的关键行截下来贴上。图解可以省但配置参数一定要亲测因为视频用的 Redis 版本可能和你本机不一样有些配置已经改名或者废弃了。这套高级篇让我最受益的地方不是某个具体命令而是它把 Redis 从一个数据结构服务器升级成了分布式系统基础设施的视角。你会开始思考数据怎么不丢、服务怎么不挂、流量怎么不冲垮数据库这些才是生产级工程师每天都在面对的问题。如果你也刚开始啃这块内容别求快一个模块一个模块吃透比囫囵吞枣刷完整个视频有用得多。
返回列表