ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Redis 实战:从底层数据结构到生产环境高可用与排障

Redis 实战:从底层数据结构到生产环境高可用与排障 做后端开发这些年Redis 基本是我每套系统里最先引入的组件之一。缓存层、分布式锁、排行榜、计数器、热点数据、会话保持几乎都绕不开它。很多同学对 Redis 的印象停留在“看文档能跑通”但一旦进入生产环境内存突增、主从切换失败、频繁持久化阻塞、缓存穿透打垮数据库每一个坑都让人头大。这篇内容不打算给你铺一张庞大而空洞的知识网而是把我实际用 Redis 时反复验证过的东西摊开讲底层到底怎么存数据、RDB/AOF 怎么选、生产部署怎么做、主从哨兵怎么搭、线上问题怎么排以及那些文档里不会写但很值得留意的细节。无论你是刚开始接触 Redis还是已经在项目里踩过坑照着这份思路走能省下不少摸索时间。1. 先把 Redis 的定位说清楚一个内存里的数据结构服务器1.1 为什么大多数团队第一反应就是 Redis我们先回到最根本的问题Redis 到底是干什么的官方称呼是 in-memory data structure store意思是“基于内存的数据结构存储”但它同时也能做消息队列、分布式锁、位图统计甚至当搜索引擎的辅助索引。核心点就两个一是数据放在内存里读写速度可以到十万乃至十万百万级 QPS二是它操作的不是普通 KV而是字符串、哈希、列表、集合、有序集合这些有结构的数据。也正因为内存是真金白银的资源Redis 的设计思路和其他数据库不太一样。它不追求所有数据都放在内存里慢慢淘汰而是默认给你一套“有限内存下的最佳实践”热点数据常驻冷数据按策略淘汰持久化只是兜底而不是主路径。看 REPL 的那个七十年代简洁设计但实际上你要真正用好 Redis就需要按照“有限内存 高性能 可退化”这个思路去看待它而不是把它当成 MySQL 的替代品。1.2 哪些场景才是 Redis 的主场如果你的系统里出现下面这些情况基本可以考虑引入 Redis第一热点数据读多写少比如商品详情、用户资料、配置项数据库扛不住高并发查询时用它做缓存第二需要临时计数的场景比如文章的浏览量、直播间在线人数自增自减操作非常合适第三跨多实例或跨服务协调比如分布式锁、幂等控制、限流计数第四需要相对快速的排行榜有序集合天然支持按分数排序第五发布订阅和简单消息队列比如实时通知推送、异步任务触发。反过来说Redis 不适合做多表关联查询不适合存超大个别的普通消息流也不适合做严格的强一致性存储。经常有人在生产里把几十 GB 的数据一股脑塞进 Redis然后发现内存紧张、持久化卡顿这其实是使用姿势错了不是 Redis 本身不行。2. 数据类型与底层实现你操作的不只是 KV2.1 五种基本数据类型和它们最常用的用法很多人面试或被问 Redis 数据类型都背过String、Hash、List、Set、Sorted Set。但工作中真正重要的是知道它们在什么场景下用什么。String 是最常见的通用缓存结构能存字符串、数字、二进制内容后面自带的 INCR/DECR 可以在并发计数场景里原子操作不必担心超发或重复扣减。Hash 适合存对象比如一条用户记录用 key 存用户 IDfield 存用户名、积分、等级只更新某单字段时比整体序列化写入要更省时间和流量。List 是双向链表或者紧凑列表适合做简单消息队列、时间线分页也能配合 BRPOP 实现阻塞读取。Set 是无序集合天然可以去重适合做共同好友、标签聚合。Sorted Set 则是排行榜场景的首选每个成员带 scoreZADD/ZREVRANGE 一下就能拿到前 N 名。实际使用中要特别留意 key 的粒度。有人习惯把“用户:1001:订单”拆成几十个 key结果内存翻倍、过期管理乱七八糟也有人把所有业务数据都放进一个 hash结果单 key 巨大后续扩容和淘汰都很难处理。我的建议是一个业务对象尽量一个 key字段级功能交给 hash如果发现需要跨 key 做关联操作先退一步想想是不是模型设计错了。2.2 底层编码和内存效率的秘密Redis 在设计编码时花了很多心思同一个 key 在不同数据量和元素大小下实际存储结构可能是不同的。Redis 7.0 之前List、Hash、Sorted Set 在满足“元素很少、值很小”条件时会采用紧凑编码比如 ziplist7.0 后这部分逐渐被 listpack 替代。目标都一样减少内存碎片和指针开销。当你不断向这些结构里追加写入数据超过阈值后 Redis 会把它转换为标准编码比如 Hash 变成 hashtableZset 变成 skiplistList 变成 quicklist。这个设计带来的实际影响是写大量小 key 时内存占用很低但一旦元素量增长或单个元素超过阈值结构转换是隐式的可能导致内存突然上涨和 CPU 短暂波动。如果线上观察到 Redis 内存突然跳变除了数据和 key 量增长还应该想到是不是某个大对象触发了编码转换。判断一个 key 用什么编码用 OBJECT ENCODING key 就能直接查出来排查问题非常有用。2.3 跳表这个结构为什么被用在有序集合里要理解有序集合底层的 skiplist可以把它想象成一个分层的“快速通道”链表最底层是完整的有序链表上面几层只保留部分节点作为“跳过”用的索引。查找时从最高层开始如果当前节点下一个值小于目标就继续往前跳大于目标就下沉到下一层继续走平均复杂度是 O(log n)最坏情况也不至于太差而且实现比平衡树容易调试。Redis 选择 skiplist 而不是红黑树据说一个重要原因是区间查找在 skiplist 上很自然顺序遍历直接沿着底层链表走就行。再加上它还配了一张哈希表用于 O(1) 精确定位 member于是有序集合既能快速按分数取区间也能快速查单个成员的分数。这套“哈希表 跳表”的组合是 Redis 里很典型的设计不单纯追求某一种操作的极致而是让各类操作都有相对稳定的表现。3. 持久化机制数据安全与重启恢复3.1 RDB 快照全量备份的省心方案RDB 是在指定时间点把内存里全量数据生成一份压缩的二进制文件。触发方式有多重配置里的 save 规则、手动执行 BGSAVE、或者关闭服务时自动保存。执行 BGSAVE 时Redis 会 fork 出一个子进程子进程把数据写入临时 RDB 文件写完后原子替换旧文件。父进程继续处理客户端请求不需要阻塞主线程。RDB 的优点在于文件紧凑、恢复速度快特别适合做冷备和灾难恢复也适合在集群间作为初始全量同步数据。需要注意“fork”没有那么轻。如果内存里大量数据、机器内存又紧张fork 瞬间可能消耗大量内存甚至在极端情况造成操作延迟抖动。生产里一般把 auto-compact 配置的触发频率控制住避免频繁的大 RDB 保存同时建议通过 slowlog 和延迟监控观察 fork 耗时。我踩过一个坑某台老机器内存只剩 1GB 空间Redis 又分配了 6GBBGSAVE 时操作系统开始 SWAP服务延迟直接飙到秒级。后来就是硬性保证“RSS 不超过系统总内存的一半”才彻底解决。3.2 AOF 日志写命令的追加日志AOF 记录的是每个写操作的命令类似数据库的 binlog。默认每写完一条命令就会调用 fsync 吗不一定。配置里面的 appendfsync 有三个选择always 表示每条指令都调用 fsync文件最安全但吞吐会明显降低everysec 是每秒刷一次盘兼顾性能和安全性也是生产环境里最常见的选择no 则交个操作系统决定性能最好但异常丢失数据的窗口最大。顺着项目对数据安全的要求去选很多业务接受每秒丢一点用 everysec 就够如果一条都不能丢那建议用 always并做好性能评估。AOF 文件会持续增大Redis 提供了重写机制 AOF rewrite会以重写时的内存快照为基础生成新的最小命令集。触发条件通常由 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size 控制。重写过程同样会 fork 子进程但父进程同时把新写入命令缓存起来重写完成后会合并这些缓存再替换 AOF 文件这个过程在 Redis 7.0 之后的 multi-part AOF 机制里更精细化减少了大文件切换时的风险。生产里不应该让 AOF 无限增长下去也不应该完全依赖自动策略必要时应在业务低峰期手动执行 BGREWRITEAOF。3.3 混合持久化与我的备份习惯Redis 4.0 之后提供了混合持久化能力默认开启。简单说AOF 重写后的文件里先有一份 RDB 格式的全量数据后面再追加一段 AOF 格式的增量命令。重启恢复时顺序会非常快加载 RDB 部分直接还原内存再 replay 一段增量命令比纯 AOF 重放全量命令快得多比纯 RDB 又不容易丢数据。这也是目前主流部署下比较稳妥的方案。但持久化只是兜底不是一切。我自己的备份纪律是两条第一永远把 RDB/AOF 文件从 Redis 节点复制到独立存储或者对象存储里不能只依赖本地磁盘否则机器坏了什么都完了第二定期做一次“恢复演练”拿备份文件在一个干净环境里启动 Redis确认数据能正常加载节点能正常对外服务。很多团队灾难恢复流程看起来完整一演练才发现 Redis 版本的 RDB 格式不兼容或文件权限丢失这种事发生过太多次。4. 生产环境搭建从安装到可视化连接4.1 Redis 下载与安装的几种姿势先说系统环境。Linux 上最省心的方式是直接通过包管理器安装或使用官方源码编译生产环境我更多会用源码编译原因是能精确控制版本和编译参数不会突然被系统升级换掉。官方源码包从 redis.io/download 下载对应版本的 SHA256 要和官网核对。Redis 6.2 和 7.0 以上在稳定性和新特性上有差异现阶段的建议是新项目直接用 7.2 系列老项目如果已经稳定运行在 6.2不必强行升级但要关注安全补丁。Windows 下要特别提醒Redis 官方对原生 Windows 的支持非常有限很多人在网上找到的“windows 版本 redis 下载”都是第三方编译版。如果是本地开发调试用第三方版本或 Redis Desktop Manager 这类工具连过去的做法问题不大但生产服务器请优先使用 Linux别再问为什么 Windows 上主从切换老出诡异问题了。如果你必须习惯在 Windows 上做测试强烈的建议是在 WSL2 里起一个 Linux 环境再跑 Redis这样和生产行为更接近同时可以照常使用 Docker。4.2 Docker 安装与 Compose 生产环境部署Docker 方式确实是团队协作最快的方式。官方镜像可以在仓库里直接下载 redis:7.2-alpine相比标准版它更小也不带不必要的包。开发环境一条命令就能跑起来docker run -d --name redis-dev \ -p 6379:6379 \ redis:7.2-alpine生产环境不建议这样裸跑至少需要加上持久化目录、密码认证、内存限制和日志饮食。下面是我常用的 Compose 模板version: 3.8 services: redis: image: redis:7.2-alpine container_name: redis restart: always command: - redis-server - --requirepass ${REDIS_PASSWORD} - --appendonly yes - --appendfsync everysec - --maxmemory ${REDIS_MAXMEMORY} - --maxmemory-policy allkeys-lru ports: - 6379:6379 volumes: - ./data:/data - ./redis.conf:/usr/local/etc/redis/redis.conf deploy: resources: limits: memory: 2g这里重点解释几个参数的选择requirepass 是防攻击的基本要求公网环境下没密码的 Redis 基本等于裸奔appendonly yes 保证至少能容忍秒级数据丢失maxmemory 给实例设一堵“墙”防止 Redis 内存无限上涨拖垮宿主机maxmemory-policy 则控制达到内存上限后的淘汰策略后面我会单独展开。另外建议始终把 redis.conf 通过 volume 挂进去让你的配置可审查、可版本控制不至于在容器重启后所有参数都回到默认。4.3 可视化工具与客户端选择命令行 redis-cli 永远是最可靠的排障工具但日常开发中我还是会配一个可视化客户端。早期常用的 Redis Desktop Manager 确实好用只是后来部分版本和许可证变化很多团队又在寻找替代品。我目前用得比较多的是 Another Redis Desktop Manager这个东西开源免费跨平台支持 Mac/Windows/Linux也能够显示 key 的 TTL、查看内存分析、执行命令以及管理多个连接对于日常开发调试足够了。连接 Redis 时有个细节先把连通性测试好再去看数据。用客户端连接失败90% 是因为没关闭 Linux 防火墙、Redis 的 bind 配置只监听 127.0.0.1或者 protected-mode 处于开启状态。生产环境不要为了图省事把 protected-mode 设成 no 然后直接暴露公网这是很危险的做法。正确的姿势是绑定内网网卡开启密码最后加一台跳板机或者通过安全组限制来源 IP。5. 进阶实战分布式锁、缓存治理与序列化5.1 分布式锁的正确使用方式在微服务架构里多个服务实例同时操作一份数据时通常需要分布式锁。Redis 的分布式锁本质是利用单线程处理命令配合 Set 指令要做到原子性。很多人最初的写法是先用 SETNX 再设置过期时间结果中途进程崩溃锁永远不释放。这个问题可以在一条命令里解决SET lock:order:10001 token_value NX PX 30000NX 表示只有 key 不存在时才设置PX 表示过期时间单位和业务执行时间要仔细估算。释放锁时不能直接 DEL否则可能把别人刚拿到的锁删掉因为锁还带着一个唯一标识释放脚本要用 Lua 保证“先比较后删除”这一步是原子的if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end实际项目中我建议直接使用 Redisson 这类成熟库提供的 Lock 工具它们内置了看门狗续期机制避免业务代码执行时间超过锁过期时间导致锁被提前释放的问题。但无论用不用 Redisson原理层面至少要理解锁的互斥靠 key set锁的自动释放靠 expire锁的防误删靠唯一标识这三点缺一不可。如果业务允许只依赖单点 Redis 做锁就不要强行搞 RedLock那套跨节点一致性方案在很多团队场景里没必要反而增大复杂度。5.2 缓存治理命中率、TTL 与数据一致性缓存不是“加了 Redis 就完事”核心指标是命中率、数据和的一致性。高命中率意味着数据库压力确实被分担了TTL 设置如果过短大部分请求永远访问不到缓存等于白装 RedisTTL 过长数据更新后缓存和数据库长期不一致。常规经验读多写少的配置类数据TTL 可以设置到数小时甚至一天热点商品详情适当随机化 TTL避免同一时间大量击穿即使改了数据也建议用“先更新数据库再删缓存”而不是“先写缓存”原因在于删缓存可以规避并发写库和缓存覆盖的问题。数据一致性是个系统工程。我先说结论缓存二话不说直接删不要抱着“缓存也更新一下”的想法。因为缓存更新很容易因并发覆盖导致脏数据而删除缓存后下一次请求读到旧值最多触发一次回源。配合 TTL 和消息队列做最终一致基本能覆盖绝大多数业务场景。如果对强一致有要求那就要从应用架构层面重新考虑而不是依赖缓存层来解决。5.3 Redis 序列化别踩这些坑Java 和 Redis 交互时序列化方式是很多团队踩坑的重灾区。使用 Spring Data Redis 时如果直接用 JDK 序列化Redis 里看到的 key 会带一堆二进制前缀肉眼根本分不清是什么而且 JDK 序列化出的对象体积膨胀、反序列化时如果实体类结构变化又容易直接抛异常。另一个常见问题是某些库默认用 JdkSerializationRedisSerializer导致你通过可视化工具想找 key 时看到的不是“user:1001”而是“\xAC\xED\x00\x05t...”。我这里给一套稳妥组合key 统一用 StringRedisSerializer不用把二进制曝光业务对象用 GenericJackson2JsonRedisSerializer 或者手动转换成 JSON前提是类需要有无参构造和 getter/setter否则反序列化时会很头疼。对于高吞吐但结构非常固定的数据也可以考虑 Protocol Buffers只不过有点复杂化了。总之学会想法是Redis 里存的可读性、体积、兼容性三者要一起权衡别选了一个伤害另外两个。6. 高可用架构主从、哨兵与集群6.1 主从复制搭建的详细步骤单机 Redis 一旦宕机缓存全部消失数据库瞬间受到请求风暴冲击所以生产环境至少要有主从架构。主从配置的格式在 5.0 后是 replicaof老版本叫 slaveof含义一样。搭建步骤很简单在从节点配置文件里设置主节点的 IP 和端口同步启动后会从主节点做全量复制之后增量复制持续同步。全量复制依靠 RDB 文件完成从节点先收到 RDB 并加载到内存中再继续处理主节点同步来的缓冲区命令。如果你用 Docker 部署主从可以使用 compose 一次性拉起两个节点。这里给一个最小示例services: redis-master: image: redis:7.2-alpine command: redis-server --port 6379 --requirepass masterpass redis-replica: image: redis:7.2-alpine command: redis-server --port 6380 --replicaof redis-master 6379 --masterauth masterpass depends_on: - redis-master主从复制有几点需要特别注意主节点启动时如果没开启持久化重启后是空库从节点会照单全收把从库也变成空库所以主节点必须配置持久化从节点默认是只读的如果误写入从节点不会同步给主节点还会污染数据复制断线后如果主节点积压的缓冲太小会触发全量重新同步网络不稳定时会有明显延迟。生产监控里要重点看 INFO replication 里的 master_repl_offset 和 slave_repl_offset双方相差过大说明从节点同步远远落后查询从库可能会读到旧数据。6.2 哨兵模式自动故障转移是怎么做到的主从复制只能保证数据有冗余不能保证主节点挂掉后自动恢复。哨兵模式是为了做自动故障转移哨兵进程监控主从节点的状态发现主节点客观下线后会在从节点里筛选一个新主并让其他从节点重新指向新主。配置哨兵时最常用的一条是sentinel monitor mymaster 127.0.0.1 6379 2monitor 后面的“2”表示至少两个哨兵同意主节点不可用才会触发故障转移。生产里哨兵数量至少要 3 个且部署在不同机器上否则哨兵自己挂掉或者网络分区时整个系统反而更容易出问题。有人会遇到“redis 哨兵模式启动未生成 known”的问题在网上一搜经常看到。其实哨兵启动后会周期性从主节点获取从节点信息和其它哨兵信息并打印类似 known-replica 或者 known-sentinel 的内容但这个感知不是瞬时的通常要等几秒到十几秒。如果你等了很久还是没有生成重点检查几件事哨兵配置里的 master 名称是否和主节点配置一致主节点是否设置了 requirepass而哨兵配置里没有配 sentinel auth-pass以及哨兵和各个从节点之间的网络是否互通。纯调试场景下可以用 redis-cli -p 26379 登录哨兵然后执行 SENTINEL master mymaster 和 SENTINEL replicas mymaster观察哨兵到底看到了什么状态。6.3 集群模式当单机内存与写并发扛不住时Redis Cluster 是另一条升级路径通过哈希槽把数据分布到多个节点上。整个集群固定有 16384 个槽每个节点负责一部分客户端根据 CRC16(key) % 16384 计算 key 应该落在哪个节点。集群的好处是不需要把数据复制到所有节点每个节点只管一部分数据写能力可以横向扩展。但对应的代价是多 key 操作只有在它们并且各 key 都属于相同哈希槽时才支持否则要做跨节点操作会报错。生产项目在设计 key 时就该考虑把需要一起操作的 key 统一放在相同哈希槽里比如用 hashtag 的方式。集群搭建的坑也不少。第一不能把所有节点放在一台机器上那只是心理上的高可用第二集群内部除了 6379 端口还需要 16379 这样的总线端口保持节点间通信防火墙没放开集群会一直抖动第三扩容时槽迁移会影响性能尽量避免业务高峰期做 rehash第四数据一致性是最终一致网络分片期间可能丢失写操作所以对强一致要求高的字段还是要靠持久化和应用层保证。7. 生产避坑实战日志、排查与参数调优7.1 用什么指标判断 Redis 状态是否健康很多初级工程师遇到 Redis 卡顿第一反应就是重启服务但重启后往往旧问题又弹出来。真正定位问题要先看指标。INFO stats 里的 total_commands_processed、instantaneous_ops_per_sec 能告诉你当前 QPSINFO memory 里的 used_memory_human、used_memory_rss、mem_fragmentation_ratio 能告诉你物理内存和逻辑内存的差异比值过高意味着碎片或者 swap 问题。INFO replication 用来判断主从同步是否健康INFO keyspace 用来看每个库的 key 数量和过期 key 数量。慢查询是另一个排障入口。Redis 单线程执行命令一条慢命令会阻塞所有后续命令。通过 SLOWLOG GET 10 能看到近期的慢命令配合 CONFIG SET slowlog-log-slower-than 10000 把大于 10 毫秒的操作记录下来。最常见的慢命令是大范围 KEYS 匹配、大量元素的 SMEMBERS、超大 key 的 HGETALL 等。尽量不要在业务线上去跑 KEYS要遍历 key 就用 SCAN 的游标方式分步进行并在低峰期操作。7.2 缓存穿透、缓存击穿和缓存雪崩的应对这三个问题经常被放在一起说但成因和策略完全不同。缓存穿透是指请求根本不存在的 keyRedis 没缓存请求直接打到数据库万一是恶意刷接口数据库会瞬间被打爆。解决思路第一参数校验对明显不合理的 key 直接返回第二对查不到的值也缓存一个空值并设置短 TTL第三用布隆过滤器预先拦截不存在的键这个方案在数据量极大的场景下效果更明显。缓存击穿指的是某个热点 key 过期同时大量请求涌进来都去数据库回源数据库压力瞬时翻倍。应对方式是不让热点 key 同时过期过期时间加一个随机量更保险的做法是用互斥锁在缓存重建期间只允许一个线程查库和回填其他线程等待缓存重建完成后再读取。这里本质上是 spring 和高并发场景里常见的 single-flight 模式能用框架的尽量别自己造。缓存雪崩是大量 key 在同一时间过期或者 Redis 整机不可用导致所有请求直接冲向后端。前者靠时间随机化解决后者则要依赖高可用架构主从 哨兵和服务降级限流。所谓降级不是说 Redis 挂了就直接 fail而是后端访问尽量动态化没有缓存就返回默认值或空列表避免把所有负载都压到数据库上同时把关键业务的核心链路保护住。7.3 内存淘汰策略当 Redis 内存不够时会发生什么Redis 在达到 maxmemory 后会根据 maxmemory-policy 做处理。默认策略是 noeviction也就是不淘汰任何数据直接返回 OOM 错误。这个策略对数据库这种不能丢数据的场景是安全的选择但如果你把它当纯缓存用就会非常尴尬Redis 内存满了之后写不进去新数据全部失败进而影响业务。所以纯缓存场景一般用 allkeys-lru最近最少使用的老数据会被优先淘汰新数据可以正常写入。另外一种常见的 allkeys-lfu 是根据访问频率淘汰适合那些希望低频冷数据尽快被清掉的场景。如果你给不同 key 设置了不同的过期时间可以选 volatile-ttl优先淘汰即将过期的 key。但注意这些策略都是牺牲一定数据可用性换取写入能力的策略核心业务数据不应该靠缓存淘汰来托底。生产上应该把 Redis 内存量、淘汰次数和淘汰 key 数都纳入监控当淘汰量显著增大时就说明容量规划出了问题早点扩容或者做集群拆分而不是等缓存命中率掉到惨不忍睹再补救。7.4 日志和常见诡异问题排查记录Redis 日志位置由 logfile 参数决定生产建议打开并且按天切割不然排查问题时日志文件大得打不开。设置 loglevel warning 和 notice 都行线上不要用 debug调试完记得改回来。常见诡异问题我整理了几个主从切换后客户端抛 no reachable node in cluster多半是客户端没有配置完整节点列表或集群总线端口不通明明设置了 expirekey 还一直存在可能是 EAXPIRE 过期时间单位有的是秒有的是毫秒搞混AOF 重写后磁盘满了Redis 启动不了这时候千万别乱删 AOF先把磁盘清理到安全水位再启动。还有一个容易被忽视的Redis 对内存碎片和 swap 极其敏感。用 free -m 看到 swap 有占用时基本可以确定 Redis 性能已经受影响了因为内存页面被换出磁盘后任何访问都会触发磁盘 I/O。解决办法只有释放内存、加物理内存或者迁移到资源更充足的实例上。总之Redis 是一个看起来“轻”但实际对资源和运维方法要求极高的组件宁可前期多花一点时间配置监控和演练故障恢复也别等线上出了故障再来补救。最后再分享一点个人体会不要为了用 Redis 而用 Redis每个功能引入之前先问一句“没有它还成不成立”。检查项目里引入 Redis 的动机如果只是为了缓存一个一分钟就变的接口并且数据库本身也能轻松扛住并发加点 memory 也不一定是加分项反而是真正的热点数据、高并发写、多实例协调这些场景Redis 的价值才真正被放大。平时多看点官方文档和 release notes自己动手搭一次从下载、配置、主从到哨兵的整套流程比背一百道面试题都管用。
返回列表