ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Redis学习路线与核心机制:从数据类型到高可用实战

Redis学习路线与核心机制:从数据类型到高可用实战 之前我一直把 Redis 当黑盒用——项目里缓存直接 set/get连接串从同事那里复制粘贴分布式锁用别人封装好的注解直到有一次线上缓存同时大面积失效请求瞬间全打到数据库我才意识到自己对这个“缓存中间件”的理解有多浅。也正是那次事故让我下定决心系统地啃一遍 Redis把欠下的功课补上。这篇学习日志就是我的笔记沉淀从安装、数据类型、持久化一直延伸到分布式锁、集群部署和线上排查会持续补充。适合跟我一样从“只会 set/get”起步的后端开发以及正在准备面试、想系统梳理 Redis 知识体系的人。1. 学习日志怎么记我的Redis学习路线设计1.1 为什么先把“Redis能干什么”想清楚很多人的 Redis 学习之路是从背命令开始的我也不例外。字符串、哈希、列表、集合、有序集合每种类型的命令几十个背完就忘忘了再背最后真正写业务时还是只会 string 和 hash。后来我才明白问题不是记忆量不够而是没有先把“这个中间件到底在系统里扮演什么角色”想清楚。Redis 最常见的角色就是缓存把热点数据放在内存里挡住大部分读请求保护后面的 MySQL。但它的能力远不止缓存分布式锁、限流计数器、排行榜、消息队列、Session 共享、延迟队列这些都是 Redis 的典型应用场景。我后面会逐个展开。如果你刚开始学建议先有一个整体认知Redis 是一个单线程但极快的键值存储系统它用内存换速度用丰富的数据结构换灵活性用持久化机制换安全性用主从和集群换高可用。想清楚这些再去看命令、去读配置文件你就会发现每个设计都说得通了。比如为什么 Redis 能单线程支撑十万级 QPS因为操作全在内存、数据结构经过精心设计、多路 I/O 复用解决网络瓶颈。再比如为什么生产环境几乎都要求开启持久化因为纯内存意味着进程一退出数据全丢这在很多业务场景下是不可接受的。1.2 我的四个学习阶段拆解我把整个学习过程拆成了四个阶段这个顺序是踩过不少坑之后总结出来的。第一阶段是环境准备把 Redis 装起来、跑起来、连上可视化工具知道去哪儿看日志、怎么改配置。第二阶段是数据类型和常用命令这是日常开发用得最多的部分也是后面理解内部机制的基础。第三阶段是进阶机制包括持久化、过期淘汰、缓存穿透击穿雪崩、分布式锁这些是线上问题和面试的高频地带。第四阶段是架构层面的东西主从复制、哨兵、集群、容器化部署。这个顺序不是随便定的。比如持久化如果你不先理解 RDB 和 AOF 的区别就没办法理解为什么主从复制里 replica 重启之后要重新全量同步如果你不理解 Redis 是单线程事件循环就没办法理解为什么生产环境要避免 bigkey因为一个大 key 的删除操作会阻塞整个进程几十毫秒。知识是一层一层叠上去的跳过基础直接看集群大概率是看天书。2. 环境搭建从安装Redis到连上第一个Key2.1 跨平台安装Redis的四种方式汇总学习第一步永远是先把环境跑起来。Redis 官方其实不支持 Windows但国内开发者用 Windows 的很多所以社区维持了一个 Windows 移植版。我这台主力机是 Windows后来在 Mac 和 Linux 服务器上也各装过一遍把安装方式都记录在这里。Windows 上最简单的方案是下载 redis for windows 免安装包解压后直接运行 redis-server.exe比如社区常用的 5.0.14.1 版本。这种方式胜在零依赖适合快速体验。但要注意这个移植版版本偏老生产环境如果跑在 Windows 上还是建议用 WSL 或者直接上 Docker。macOS 上安装 Redis 最简单的是用 Homebrew一行命令搞定brew install redis brew services start redis # 以服务方式后台运行如果不想注册成系统服务也可以redis-server直接前台启动另开一个终端窗口跑redis-cli -p 6379验证。Linux 服务器上的步骤类似包管理器不同而已Debian/Ubuntu 用apt install redis-serverCentOS/RHEL 用yum install redis或编译安装。我个人最推荐的方式其实是 Docker。用容器跑 Redis 的好处是环境隔离、版本切换方便一台机器可以同时跑多个不同版本的 Redis 实例出问题直接删容器重建完全不污染宿主机。比如拉取官方镜像并运行docker run -d --name redis-dev -p 6379:6379 redis:7.0这里指定了 7.0 版本是因为 6.x 之后的 Redis 在 ACL 权限控制、多线程 I/O、RESP3 协议上都有不少改进学习时尽量贴近新版本。运行成功之后docker ps能看到容器状态再用docker exec -it redis-dev redis-cli ping返回 PONG 就算通了。2.2 连接工具选型桌面客户端怎么选装好服务端之后光靠命令行敲命令也能学习但总归不方便尤其是看 key 的过期时间、内存占用、慢日志这类信息时图形化工具的效率高很多。我把市面上常见的几个客户端都试用了一遍简单做个对比。Redis Desktop Manager 是老牌的桌面客户端了但新版本变成商业收费软件了免费版功能有限。社区接盘维护的 Another Redis Desktop Manager 是我目前的主力工具免费、跨平台、功能全支持 SSH 隧道、内存数据实时监控、命令执行历史界面响应也快。Redis 官方在 2022 年前后推出了 RedisInsight免费且开源可视化效果不错尤其是内存分析、命令分析这些诊断功能很强。我的建议是日常开发用 Another Redis Desktop Manager 就够用谁用谁知道下载安装包之后几乎不用配置填上 host、port、密码就能连。如果你想深入分析线上 Redis 的内存碎片率和 key 分布那 RedisInsight 的图形化诊断会让你舒服很多。命令行工具redis-cli也一定要熟练很多紧急排查场景下没有图形界面可用redis-cli配合--stat、--bigkeys、MONITOR命令才是真正的救命稻草。2.3 常用命令盘点先掌握这十来个命令这东西不需要一开始就背全把最常用的掌握好后面自然会慢慢扩展。我整理了一份学习期必练的命令清单按照数据类型分组。字符串类型是 Redis 最基础的单元SET key value、GET key、SET nx实现不存在才写入、INCR自增、TTL查看剩余过期时间。哈希类型的HSET、HGET、HGETALL适合存对象。列表类型的LPUSH、RPOP、LRANGE适合做消息队列。集合类型的SADD、SISMEMBER、SINTER适合做标签交集。有序集合的ZADD、ZRANGE BYSCORE、ZREVRANGE适合做排行榜。光看命令不够要理解每个命令背后的复杂度。比如KEYS *千万不要在生产环境用它会阻塞 Redis 单线程去遍历全部 key线上执行一次可能卡住几秒甚至更久。替代方案是SCAN命令它通过游标分批次扫描每次返回一部分结果不阻塞业务。这就是为什么我强调学命令要理解原理而不是只记语法。2.4 配置密码与安全检查开发环境无所谓但只要你把 Redis 暴露到了服务器就必须设置密码。Redis 配置里有两个核心参数requirepass用来设置访问密码bind用来限制监听地址。默认配置监听127.0.0.1也就是只允许本机访问这点默认值其实是比较安全的。但如果为了局域网内其他机器访问改成0.0.0.0同时又没设密码那 Redis 就完全裸奔在网上了任何人都可以连上来读写数据。Windows 下设置密码的方式是在 redis.windows.conf 里找到requirepass这一行去掉注释改成requirepass yourpassword然后重启服务。Docker 方式则是在启动命令里加参数docker run -d --name redis-dev -p 6379:6379 redis:7.0 --requirepass yourpassword我踩过的坑是改了配置文件之后没注意 Redis 是否真的加载了。验证方式很简单redis-cli进去执行CONFIG GET requirepass如果能返回设置的值就说明生效了。3. 数据类型的正确打开方式从命令背下来到结构想明白3.1 五种基本类型的适用场景对照这个部分是我学 Redis 时最想穿越回去重新听一遍的内容。第一次接触的时候觉得数据类型就是“五种存放数据的方式”背完命令就过了完全没去想每种结构为什么被设计出来。后来在工作中被问“为什么这里用 Hash 不用 String”我才发现自己答不上来。我把五种基本类型和典型场景整理成了一个对照表每次选型不确定的时候就翻出来看一眼。类型底层结构要点典型场景为什么不建议用别的类型String简单动态字符串缓存、计数器、分布式ID有对象更新需求时散落多个 key难管理Hash数组链表小或哈希表大存对象、购物车对象拆成 String 存会导致序列化整写整读List双向链表消息队列、最新文章列表用 String 存列表没法做范围裁剪Set哈希表整数集合优化去重、共同好友、抽奖用 List 去重需要手动判断存在性能差ZSet跳表哈希表排行榜、延时队列排序需求用 List 只能自己排序String 看起来最万能但一旦你用大量拼接前缀的 String 去模拟对象维护难度就会指数上升。比如用户对象有 id、name、age 三个字段用 String 存就成了user:100:name、user:100:age三个 key更新其中一个字段没问题但要整体查看用户信息得三次网络请求。换成 Hash 存一个user:100键内部三个 field一次HGETALL全拿到还能单独更新某一个字段这就是结构设计的价值。ZSet 是我工作中用得最多的类型之一。它内部是跳表加哈希表给每个成员关联一个 score可以非常高效地按分数范围查询。排行榜、按时间排序的 feed 流、延时队列都可以靠它实现。延时队列的思路是把任务 ID 作为 member、执行时间戳作为 score不断用ZRANGE BYSCORE取 score 小于当前时间戳的任务去执行。这个技巧面试也常考。3.2 九种数据类型全景新版Redis新增了什么Redis 从 3.2 版本开始陆续引入了 Bitmap、HyperLogLog、Geo、Stream 等扩展类型。虽然日常用得少但它们是“面试加分项”也能在某些场景下成为系统优化的杀手锏。Bitmap 本质是 String 上的位操作适合状态统计比如用户签到一个 bit 代表一天一年只需要 46 个字节。HyperLogLog 是一个概率数据结构用极小内存估算巨大基数的去重数量比如统计百万 UV标准误差 0.81% 左右内存永远不超过 12KB 左右。Geo 是地理位置类型底层用有序集合实现支持附近的人这类 LBS 应用。Stream 是 5.0 引入的消息队列实现相比 List 做队列它支持消费者组、消息确认、pending 列表算是 Redis 官方推荐的 MQ 轻量替代方案我在后面的中间件部分再仔细说。学习这些扩展类型不需要全部实践但至少要清楚每个类型解决什么痛点、底层大致用什么结构。比如 Stream 的消费确认机制跟 Kafka 的 offset 机制有相似之处理解了 Kafaka 再看 Redis Stream 会特别快。3.3 序列化的坑RedisTemplate与中文乱码问题如果你用 Java 的 Spring Boot 开发几乎一定会遇到 RedisTemplate 的序列化问题。最典型的症状就是在 Redis 里存了一个字符串用命令行redis-cli一看key 变成了一串类似\xac\xed\x00\x05t\x00的乱码Value 也带着奇怪的前缀。这是因为 RedisTemplate 默认使用 JdkSerializationRedisSerializer它把 Java 对象序列化成了一堆二进制字节Redis 本身不关心这些字节但人眼无法辨认。解决方式是为 key 和 value 分别指定序列化器。通常 key 用 StringRedisSerializervalue 如果存 JSON 就用 GenericJackson2JsonRedisSerializer如果存对象就自定义 JSON 序列化。一个常见配置如下Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); StringRedisSerializer stringSerializer new StringRedisSerializer(); GenericJackson2JsonRedisSerializer jsonSerializer new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; }这个坑的教训是序列化器不一致会导致同一个客户端存的数据另一个客户端读出来类型不匹配。比如你用 StringRedisTemplate 存了一个字符串又用 RedisTemplate 去读会直接抛序列化异常。后来我形成习惯先约定团队的 Redis key 规范和数据格式规则再写代码。4. 进阶机制持久化、过期策略与缓存治理4.1 持久化机制详解RDB、AOF与混合持久化Redis 毕竟是内存数据库如果不做持久化宕机之后内存里的数据会全部丢失。学习到这里我特意去翻了官方文档把 RDB 和 AOF 两条持久化路线都梳理清楚了。RDB 是定期把内存中的全量数据快照写入磁盘通过save或bgsave触发默认配置如save 900 1表示 900 秒内至少有 1 次写入就生成一次快照。RDB 的优点是文件紧凑、恢复速度快适合做备份和灾难恢复缺点是两次快照之间的数据会丢快照生成过程即使 fork 子进程也会有一定性能开销。AOF 是把每一条写操作以日志形式追加到文件末尾恢复时重新执行日志即可。AOF 提供了更细粒度的持久化可以配置 appendfsync 策略为 always每条命令同步刷盘、everysec每秒刷一次或 no交给操作系统决定。everysec 是默认推荐最多丢一秒的数据。AOF 的缺点是文件体积会比 RDB 大很多恢复速度也慢一些。4.0 之后 Redis 支持混合持久化AOF 重写时直接生成 RDB 格式的快照然后再追加增量命令日志。这样重启恢复时先加载 RDB 部分再做增量重放兼顾了恢复速度和数据安全性。我个人的实践建议是学习阶段两种都要亲手开关一遍用redis-check-rdb和redis-check-aof工具检查生成的持久化文件看看实际内容长什么样。4.2 缓存穿透、击穿与雪崩的治理方案这一块是面试重灾区也是线上事故的常见根源我单独拿出来仔细说。缓存穿透指查询一个缓存和数据库都不存在的数据每次请求都会绕过缓存直接打数据库。攻击者如果故意构造不存在的 key能把数据库压垮。治理方案有三板斧第一是缓存空对象把一个短暂的空值比如 null 或者间隔符号也缓存下来设置几十秒过期时间第二是布隆过滤器把所有存在的 key 预先加入过滤器查询前先判断 key 是否可能存在不存在直接返回第三是参数校验非法参数直接拒绝。缓存击穿指一个热点 key 在缓存过期的瞬间大量请求同时涌入数据库。解决思路是对加锁或者逻辑过期单机可以用 synchronized分布式就用 Redis 分布式锁保证只有一个线程去重建缓存其他线程短暂等待后从缓存读取逻辑过期是把过期时间放在 value 里后台异步刷新。缓存雪崩是大量 key 在同一时间段集中过期或者 Redis 实例宕机导致所有请求直接到达数据库。应对的方式包括设置过期时间时加入随机偏移量比如base random()避免同时失效做多级缓存本地缓存比如 Caffeine挡一层Redis 做高可用部署主从加哨兵宕机自动切换。我的体会是穿透、击穿、雪崩这三个名词听着抽象但本质上是在回答同一个问题——缓存不可用了怎么保护下游数据库。面试的时候别光背名词把因果关系说清楚把你实战中怎么落地的细节讲出来效果会好很多。4.3 分布式锁的落地从setnx到Redisson分布式锁是 Redis 做中间件最经典的场景之一。网上很多资料还在讲SETNXEXPIRE两行命令实现锁但那个方案其实有个老坑如果 SETNX 成功之后进程崩了EXPIRE 还没来得及执行锁就永远不会释放其他线程全部阻塞。正确做法是用一条带过期时间的原子命令SET lock_key unique_value NX PX 30000NX 表示只有 key 不存在时才设置成功PX 设置过期毫秒数unique_value 是每个线程的唯一标识用来在释放锁时校验自己是不是持锁人。释放锁时不能用简单的 DEL要先用 GET 比对 value是自己的再删中间还要保证原子性所以推荐用 Lua 脚本if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end如果你用的是 JavaRedisson 已经把这个过程封装好了它的RLock还支持看门狗自动续期避免业务执行时间超过锁的过期时间导致锁提前释放。我个人踩过的坑是锁的过期时间设得太短业务逻辑还没跑完锁就过期了第二个线程就进来了导致并发执行设得太长如果持锁方真的宕机其他线程要等很久。Redisson 的看门狗默认续期机制正好解决这个问题这也是面试时值得提的一个点。5. 高可用部署主从、集群与容器化5.1 从单机到主从Docker Compose 快速搭建读写分离单机 Redis 最大的风险是“一次宕机全部丢失”即使有持久化恢复期间服务也是不可用的。主从复制的思路是一台主库负责写、多台从库负责读和备份主库数据变更后实时同步到从库。从库还可以配置只读防止误写数据。我之前在本地用 Docker Compose 搭过一个一主一从的最小环境配置文件只需要一个docker-compose.ymlversion: 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 ports: - 6380:6379 command: redis-server --appendonly yes --slaveof redis-master 6379启动之后进入从库容器执行INFO replication能看到 role:slave 以及 master_link_status:up说明主从同步正常。这里有个细节Redis 5.0 之前主从复制的配置项叫slaveof5.0 之后官方建议改为replicaof虽然旧写法还能用但新项目尽量用新配置。主从只能解决读扩展和故障恢复的部分问题如果主库宕机从库不会自动变成主库需要人工切换或引入哨兵。哨兵机制本质是一个独立的监控进程集群监控主从健康状态主库挂了之后在从库中推举一个新主库。不过哨兵本身也需要部署和运维生产环境更常见的是直接上 Redis Cluster。5.2 Redis Cluster与K8s部署要点Redis Cluster 是官方原生的分布式方案从 3.0 版本开始支持。它把数据通过哈希槽分布到多个节点上总共 16384 个槽位每个主节点负责一部分槽位。客户端请求任意节点如果 key 不在当前节点负责的槽位节点会返回 MOVED 重定向客户端根据返回信息跳到正确节点。用 Docker 搭建集群需要至少 6 个节点3 主 3 从步骤比较繁琐但能加深理解。重点是理解槽位分配机制为什么 16384官方解释是节点数量上限约 1000 个16384 个槽位在节点间迁移时够用而且心跳消息用 bitmap 传播槽位信息时体积合适。这些细节在面试设计题里能体现出你真的理解集群原理。K8s 环境部署 Redis Cluster 又是另一套复杂玩法核心难点在于实例间需要互相通信StatefulSet 保证稳定的网络标识。如果学习阶段想快速体验可以用 Helm 安装 Redis Operator 或者 Bitnami 的 Redis 集群图表。但我的建议是K8s 部署调度这一块项目里没实际用过的话先搞懂原理就够面试用了不必非要在本地把集群调通。真正值钱的是你理解 Redis Cluster 的四个核心机制节点间 Gossip 通信、哈希槽分配、主从故障转移、客户端重定向。6. 踩坑实录异常排查与日志解读6.1 Redis常见报错速查表学习过程中不可避免会遇到各种报错我把自己高频遇到的报错整理成了一张排查表分享出来。报错信息常见原因快速排查方法Could not connect to Redis服务未启动、端口被占用、防火墙拦截检查redis-cli ping是否通ERR Client sent AUTH, but no password is set客户端配置了密码服务端没设密码删除客户端密码配置或给服务端设置 requirepassNOAUTH Authentication required服务端设了密码客户端没带客户端增加密码参数WRONGTYPE Operation against a key holding the wrong kind of value数据类型用错了用TYPE key查看类型OOM command not allowed when used memory maxmemory达到了内存上限且淘汰策略不允许写检查 maxmemory 和淘汰策略MISCONF Redis is configured to save RDB snapshots, but its currently unable to persist on disk磁盘空间不足或 RDB 写入失败查看磁盘剩余空间调整持久化配置最气人的一种报错是 redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException。Spring Boot 默认的 Lettuce 客户端在 Redis 响应超时后抛这个异常。这个报错不一定是 Redis 真的挂了更可能是 Redis 单线程被大 key 操作阻塞或者客户端连接池被占满。排查思路是先用redis-cli -p 6379 --latency看网络延迟再用SLOWLOG GET看慢命令重点排查有没有 keys 操作、大 value 读写。Lettuce 相比 Jedis 是线程安全的能复用连接但它的连接池参数调优是个细致活。6.2 Docker环境下的报错排查Docker 安装 Redis 时碰到的报错和物理机不一样。有一次我执行docker search redis直接返回了 500 Internal Server Error伴随一大串关于 api version 的错误。这个问题通常不是 Redis 镜像本身的问题而是 Docker Desktop 的引擎没起来或者 Docker 桌面版的 API 版本和客户端不匹配。解决办法是先重启 Docker Desktop再docker version确认 Server 端正常返回然后再执行搜索。在 Windows 上使用 Docker 还经常遇到 WSL2 内核版本太旧导致的启动失败。我的排查顺序一般是启动 Docker Desktop 看日志 → 确认 WSL 状态 → 检查镜像 → 重新拉取官方 Redis 镜像。容器日志也是一个重要入口docker logs redis-dev能直接看到 Redis 实例的启动信息和报错堆栈这比在容器内部猜问题高效得多。6.3 日志该怎么看从启动日志到慢查询日志Redis 的日志主要分三类服务器运行日志、持久化日志、慢查询日志。运行日志通过配置文件里的logfile指定默认直接输出到标准输出Docker 里就是容器日志。如果你发现 Redis 启动后一条日志都没有可能只是日志级别设成了 warn看不到 INFO 级别的信息。慢查询日志是排查性能问题的第一入口。Redis 配置里有slowlog-log-slower-than和slowlog-max-len两个参数前者指定阈值微秒后者指定记录条数。我一般把阈值设成 10 毫秒超过就记下来。查询方式很简单SLOWLOG GET 10返回的每条记录包括执行时间戳、耗时、命令参数。有一次我在线上发现某个接口偶尔超时用慢日志一查发现是有个列表类型的大 key 执行 LREM 操作耗时 200 多毫秒正好卡在 Redis 的单线程上影响了所有后续请求。这就是日志的价值用数据定位问题而不是靠猜。6.4 面试八股高频题速查既然是学习日志把面试里高频的 Redis 题目也整理一下。第一类是基础原理题Redis 为什么快单线程为什么还这么快IO 多路复用是什么第二类是数据类型题五种基本类型的底层实现是什么ZSet 的跳表是怎么工作的第三类是机制题持久化怎么选型过期删除策略和内存淘汰策略有什么区别缓存穿透、击穿、雪崩解决方案讲一下第四类是架构题主从复制怎么同步缓存和数据库一致性怎么解决分布式锁的实现原理别小看这些“八股”很多问题背后都对应着真实的工程决策。比如问你“缓存和数据库一致性怎么解决”表面是操作顺序问题实际是在考你懂不懂 Cache Aside 模式、先删缓存还是先更新数据库、延迟双删的适用场景。我在准备面试的时候把这些问题都整理成了卡片每张卡片上写场景、原因、方案、坑而不是干背答案面试时反而觉得很多问题都能聊起来。写在最后的学习建议这套日志是我边学边记录的Redis 这个体系越往里学越深网络模型、内存管理、异步复制、Lua 脚本、模块扩展每一个点都可以继续挖下去。我自己最大的体会是学习中间件最忌讳只看不练尤其要主动去制造故障——手动 kill 掉 Redis 进程然后快速重启观察数据恢复情况用 debug 命令手动触发 RDB 快照对比磁盘文件大小把一个 100 万成员的集合做删除操作看阻塞延迟。只有亲手推倒过这些组件你对它的理解才会真正牢靠。后续我还会继续补充 Cluster 扩容缩容、Redis 7.0 新特性、以及更多线上真实案例保持这份日志的鲜活劲儿。
返回列表