ARTICLE DETAIL

资讯详情

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

Redis从入门到落地:缓存治理、分布式锁与高可用实战指南

Redis从入门到落地:缓存治理、分布式锁与高可用实战指南 说实话这些年做后端开发Redis 几乎成了我项目里最离不开的基础组件。不管是给 MySQL 挡查询压力、做分布式环境下的锁、还是存个临时会话状态Redis 永远是那个最先被请出来的技术方案。很多刚入行的朋友问我“Redis 到底怎么用”我的建议从来都是别只背数据类型和面试题回到真实项目里把它的脾气摸清楚比什么都强。这篇内容我就结合自己在一线项目里的实操经验从部署、数据结构选型、与 Spring Boot 的集成到缓存治理、高可用搭建和面试高频考点完整拆一遍 Redis 在项目里的落地路径。如果你是刚接触 Redis或者正在自己的系统里做技术选型这篇内容可以直接当作第一手参考资料收藏起来。1. 项目里的 Redis 是怎么落地的整体设计先想明白很多项目一开始用 Redis 的时候根本没有“设计”这个环节。最常见的情况是系统压力上来了发现 MySQL 扛不住于是赶紧把缓存加进去先顶一晚上再说。这种做法短期内有效但后面问题会源源不断——缓存和数据库数据对不上、热点 key 打挂单节点、重启后缓存全丢。我在复盘自己参与过的项目时发现Redis 能不能用好其实在上线之前就已经注定了。1.1 哪些业务场景真正值得用 Redis我通常会在项目里把 Redis 的用途分成三类一是缓存这是占比最高的场景把热点数据从数据库搬到内存里让查询从毫秒级变成微秒级二是分布式锁和计数器比如秒杀场景里的库存扣减、同一个订单的防重复提交三是队列和发布订阅像异步处理用户操作日志、实时通知等。举个例子我之前做的一个电商中台项目商品详情页的访问量非常大QPS 高峰期能到几千如果每次都去 MySQL 里查商品信息和库存数据库根本撑不住。我们把商品详情数据做了两级缓存——本地缓存一份Redis 再放一份Redis 里的 key 设计为product:info:{productId}有效期根据商品的运营策略设置为 30 到 60 分钟。线上跑下来MySQL 的读压力直接降了 80%效果立竿见影。但这里有个容易踩的坑不要什么都往 Redis 里塞。像用户实名认证信息、支付流水这种强一致、强管控的数据绝不能只放缓存必须以数据库为准缓存只做加速。说白了缓存里可以脏一点、旧一点但数据库必须绝对正确。在做技术选型时我会先问自己一个问题这个数据一旦出现短暂不一致业务上能不能接受如果不能接受就别用缓存。1.2 版本选型与部署方式怎么权衡Redis 的版本一直更新很快我最初用的是 3.x后来项目升级到 4.x、5.x现在新项目里基本都用 6.x 或 7.x。但有一点必须先说清楚生产环境强烈建议用 Linux 部署不建议用 Windows 版本。Windows 版本虽然下载安装简单适合本地学习用但官方对 Windows 的支持并不好高并发下性能也跑不上去真没必要在生产环境里赌这个。目前新项目我一般用 Redis 7.x它在性能、内存管理和 AOF 持久化方面做了很多优化比如 Redis 7 用 Multi Part AOF 解决了原来 AOF 重写时可能阻塞的问题。如果项目有历史包袱用的是 6.x也没必要急着升6.x 本身已经非常成熟稳定了。部署方式方面我见过三种单机部署、主从 哨兵、Redis Cluster。单机部署只适合学习、测试和小型项目一旦节点挂了缓存直接全丢数据库瞬时压力爆表。主从 哨兵能保证高可用一个 Master 挂了哨兵会自动把 Slave 提升为 Master这个过程业务方基本无感知。Redis Cluster 适合数据量大、写入并发极高的场景它把数据自动分片到多个节点上每个节点只存一部分数据。一个比较稳妥的生产环境方案是用 Docker Compose 部署 Redis 主从加哨兵既不依赖具体服务器的运维脚本也能利用 Docker 的隔离性迁移和扩容都方便。不过这里注意Docker 部署 Redis 的数据卷一定要挂载好否则容器一重启缓存数据全没虽然有持久化文件也不是完全没救但操作起来很被动。我见过有同事直接在 Docker 里裸跑 Redis连配置文件都没挂载出来容器更新一次配置全靠重写这种操作在老项目里太常见了真要出事就是事故。2. 部署和安装环节的实操细节Windows、Linux 和 Docker 三条路线很多人第一次接触 Redis 时最大的困惑反而不是概念而是装不上、起不来、连不上。这一节我把三条安装路线一次性讲清楚每一条都按生产标准来别只做“能跑就行”。2.1 Windows 下快速安装学习用足够了首先要明确一点Windows 下安装 Redis 不是为了生产就是为了本地学习、写 demo。官网不提供 Windows 版本需要去微软维护的仓库下载或者用 5.0 之后的 Memurai 这类兼容替代品。早期还有一些开发者把 Redis 移植到 Windows 上做了绿色包下载解压后直接就能跑步骤如下把压缩包解压到某个目录比如D:\redis。打开命令行进入该目录执行redis-server.exe redis.windows.conf启动服务。另开一个命令行窗口执行redis-cli.exe -h 127.0.0.1 -p 6379连接测试。执行ping如果返回PONG说明服务已经正常。需要注意Windows 下 Redis 默认是前台启动的一旦关闭命令行窗口服务就停了。如果不嫌麻烦可以注册成 Windows 服务用redis-server --service-install redis.windows.conf安装服务然后用redis-server --service-start启动。不过说实话我本地调试的时候还是更推荐后面讲的 Docker 方式干净利落不想用了容器一删一点痕迹都不留。2.2 Linux 下源码或安装包安装生产首选Linux 下安装 Redis 主要有两种方式直接用包管理器安装如yum install redis或apt install redis-server或者官方源码编译安装。包管理器安装简单但版本往往偏旧源码编译能自己控制版本还能自定义编译参数生产环境我更推荐这种方式。源码安装流程大致如下wget https://download.redis.io/releases/redis-7.2.4.tar.gz tar xzf redis-7.2.4.tar.gz cd redis-7.2.4 make make install编译完成后Redis 的可执行文件会被安装到/usr/local/bin目录下包括redis-server、redis-cli、redis-sentinel等。接下来修改配置文件redis.conf# 绑定访问地址生产环境如果只想内网访问写内网IP或127.0.0.1 bind 0.0.0.0 # 修改端口 port 6379 # 以守护进程方式运行 daemonize yes # 配置日志输出路径 logfile /var/log/redis/redis.log # 持久化文件路径 dir /var/lib/redis # 设置密码 requirepass your_strong_password设置密码这一点非常关键。我在第一次部署时偷懒没设密码结果 Redis 服务只绑定了内网 IP本身问题不大但后来发现运维扫描端口时把这种“无认证的高危端口”记录在了案。所以但凡对外开放的 Redis端口、密码、绑定网卡这三个必须一起处理缺一个都等于裸奔。启动的时候用redis-server /path/to/redis.confGUI 工具连接时注意配置文件里的protected-mode yes是否限制了你。2.3 Docker Compose 部署主从 哨兵生产环境的省心方案如果服务器上已经用了 Docker我强烈建议 Redis 也用 Docker Compose 来编排。下面给一套我在生产环境用过的主从 哨兵配置写清楚每一步的作用。首先建立一个目录结构redis-stack/ ├── docker-compose.yml ├── master/ │ └── redis.conf ├── slave1/ │ └── redis.conf ├── slave2/ │ └── redis.conf └── sentinel/ ├── sentinel1.conf ├── sentinel2.conf └── sentinel3.confdocker-compose.yml核心内容如下version: 3.8 services: redis-master: image: redis:7.2 container_name: redis-master ports: - 6379:6379 volumes: - ./master/redis.conf:/etc/redis/redis.conf - master-data:/data command: [redis-server, /etc/redis/redis.conf] restart: always redis-slave1: image: redis:7.2 container_name: redis-slave1 ports: - 6380:6379 volumes: - ./slave1/redis.conf:/etc/redis/redis.conf - slave1-data:/data command: [redis-server, /etc/redis/redis.conf] depends_on: - redis-master restart: always redis-slave2: image: redis:7.2 container_name: redis-slave2 ports: - 6381:6379 volumes: - ./slave2/redis.conf:/etc/redis/redis.conf - slave2-data:/data command: [redis-server, /etc/redis/redis.conf] depends_on: - redis-master restart: always sentinel1: image: redis:7.2 container_name: sentinel1 volumes: - ./sentinel/sentinel1.conf:/etc/redis/sentinel.conf command: [redis-sentinel, /etc/redis/sentinel.conf] depends_on: - redis-master restart: always # 其他两个 sentinel 配置省略结构相同 volumes: master-data: slave1-data: slave2-data:对应的 Redis 主节点配置文件# master/redis.conf bind 0.0.0.0 port 6379 daemonize no dir /data appendonly yes appendfsync everysec requirepass super_secret_password这里把daemonize设为 no是因为 Docker 容器通常要求前台运行。appendonly yes开启 AOF 持久化数据恢复能力更强。从节点配置里必须指定主节点地址# slave1/redis.conf bind 0.0.0.0 port 6379 daemonize no dir /data replicaof redis-master 6379 masterauth super_secret_password requirepass super_secret_password注意Redis 7.x 里已经把slaveof改成了replicaof语义更准确主从都是“副本”。哨兵配置也很关键# sentinel/sentinel1.conf port 26379 daemonize no dir /tmp sentinel monitor mymaster redis-master 6379 2 sentinel auth-pass mymaster super_secret_password sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000 sentinel parallel-syncs mymaster 1这里的quorum为 2意味着至少两个哨兵认为主节点挂掉才会触发切换。生产环境哨兵至少部署 3 个否则无法构成多数派。这套配置实际跑下来主节点宕机后10 到 20 秒内会自动完成主从切换业务方在客户端做一下重试即可。我个人踩过的一个比较隐蔽的坑是主从复制时没配置masterauth。从节点连主节点要用密码主节点配置里没设置 requirepass 时可以不用但一旦设置了 requirepass 就必须在主从节点上配置对应的 masterauth否则日志里会一直报“MASTER aborted replication with error: NOAUTH Authentication required”主从始终连不上。3. 数据类型与序列化方式把存储模型选对能少走一半弯路Redis 之所以能适应那么多场景靠的就是它丰富的数据类型。很多面试题里喜欢问“Redis 有哪几种数据类型”其实背后真正潜台词是你知不知道在什么业务下选哪种类型以及每种类型的坑在哪里。3.1 五大数据类型的适用场景拆解String是最基础的也是我日常用得最多的类型。缓存一个对象的 JSON 串、计数器的自增自减、分布式锁的占位都是它。底层实现是 SDS简单动态字符串追加字符串、计算长度都是 O(1) 操作。需要注意的是String 在存储小于 44 字节时用的是 int/embstr超出后转成 raw内存占用会略高所以在设计缓存时尽量让 value 精简。Hash特别适合存对象或结构化数据。比如用户信息可以设计成 key 为user:info:10001field 为username、avatar、age这样修改单个字段不需要把整个对象读出来再写回去在网络开销上有优势。但要注意两个问题一是 Hash 的 field 数量不要无限制增长会造成 bigkey二是如果对象本身非常扁平就一个 string用 Hash 反而浪费。List本质是个链表适合做消息队列、最新消息列表这类场景。我做过一个项目用 List 存储用户的操作日志每次LPUSH往里推一条新记录再用LTRIM key 0 99只保留最近 100 条既简单又高效。这里的 LTRIM 很关键不加的话 List 会无限增长最终成为 bigkey拖垮整个实例。Set是无序集合适合做去重、共同好友这类运算。比如两个用户各自关注了一批博主直接SINTER取交集就知道共同关注的人有哪些这在关注和粉丝场景里非常常用。ZSet有序集合是排行榜场景的标配。每个成员关联一个分数按分数排序做 TopN 查询只需要一条ZREVRANGE命令。实时运营大屏、积分排行榜基本都是这么做的。3.2 序列化问题缓存乱码和数据不可读的根源很多同学第一次在 Spring Boot 里集成 Redis 后遇到一个很懵的问题用命令行客户端连上去发现 key 变成了\xac\xed\x00\x05t\x00\x05name这样的乱码value 也变成了二进制。这个罪魁祸首就是默认的 JDK 序列化器。Spring Data Redis 默认用JdkSerializationRedisSerializer它对对象序列化的结果肉眼完全不可读而且数据量膨胀严重非常占内存。我现在的做法是手动配置 RedisTemplate字符串 key 用StringRedisSerializerHash 的 field 也用 String 序列化value 统一用GenericJackson2JsonRedisSerializer。配置如下Configuration public class RedisConfig { 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; } }这里有一个隐藏的坑GenericJackson2JsonRedisSerializer在反序列化时会把对象类型信息存在 JSON 里的class字段这样反序列化的时候才能知道要转换成什么类型。但如果你把对象 value 里的一个内部类或 lambda 序列化进去反序列化时容易报类型转换错误或类找不到。我的经验是缓存对象的类一定要单独定义不要用匿名内部类、lambda 或过于复杂的继承结构。否则线上排查时看到各种诡异的反序列化异常真的会让人崩溃。还有一个小技巧如果你只是为了在 Spring Boot 里做缓存且缓存的值就是一个简单的字符串或者数字完全可以优先使用StringRedisTemplate它天然就是 String 对 String不会有序列化问题的烦恼只有存储对象时才需要自定义 RedisTemplate。3.3 可视化客户端选择别让工具拖了你的后腿Redis 装好了、数据也写进去了怎么直观地排查数据、监控 key 的状态我早期刚接触 Redis 时用的是命令行redis-cli后来项目规模大了还是需要可视化工具。目前用得最多的两个是Redis Desktop ManagerRDM和Another Redis Desktop ManagerARDM。Redis Desktop Manager 是老牌工具跨平台功能全面但新版本大部分高级功能开始收费。如果你不想付费可以直接用 Another Redis Desktop Manager它是一款开源免费的工具界面风格和功能设计跟 RDM 很接近连接配置、查看 key、执行命令、查看内存分布都可以做到。这个工具在实际排查问题时特别好用比如想快速看某个 key 的 TTL、Redis 中哪个 key 占内存最大用可视化工具比命令行直观得多。连接时要注意几个点主机地址、端口、密码以及是否启用 SSL。很多云服务器上 Redis 只绑定了内网 IP本地可视化工具直连是不通的需要借助 SSH 隧道或者跳板机。我之前排查过一次“工具连不上但服务器上命令能连上”的问题最后发现是 Redis 配置里 bind 了 127.0.0.1外部机器根本访问不了。所以可视化工具连不上时先检查 Redis 的 bind 配置和防火墙别一上来就怀疑密码错了。4. 持久化、内存淘汰与缓存治理把 Redis 用稳的几条硬经验Redis 是内存数据库内存是有限的数据也不能说丢就丢。这一部分讲的都是我在生产环境踩过的深坑和换来的经验含金量不低。4.1 RDB 和 AOF 持久化的选择逻辑Redis 的持久化方式有两种RDB快照和 AOF命令追加。RDB 是把某一时刻的完整数据写入磁盘文件恢复快文件紧凑适合做备份和灾难恢复缺点是它会按配置的时间间隔触发生成快照中间这段数据丢了就丢了而且生成快照的过程如果数据量大可能会阻塞主进程。AOF 是把每一个写命令追加到日志文件末尾数据安全性高。Redis 7 之后默认的 AOF 策略是everysec也就是每秒钟刷盘一次理论上最多丢失 1 秒的数据。AOF 的缺点是文件体积大恢复速度比 RDB 慢但 Redis 7 对 AOF 的重写机制做了很多优化体积膨胀问题比老版本好很多。生产环境我的选择是AOF 必须开RDB 也开着。用 RDB 做定期备份用 AOF 保证实时数据安全。两者同时开启时Redis 启动时会优先加载 AOF 文件因为它的数据通常更新。Redis 7 的配置文件里默认开启了 AOF但要注意别把 appendonly 设成 no否则都要白给。4.2 内存淘汰策略别等 OOM 了才想怎么办内存是 Redis 最贵的资源不设置最大内存限制Redis 可能会把宿主机的内存打满影响其他服务。一般配置maxmemory为物理内存的 60% 到 70% 是比较稳妥的具体看业务里还跑了什么进程。配置策略上我这边常用的淘汰策略如下表策略含义适用场景noeviction内存满后不淘汰新写入直接报错适合数据绝对不能丢的场景但并发高时会大量报错allkeys-lru在所有 key 中按 LRU 淘汰最常见适合缓存型 Redis热数据留存冷数据淘汰volatile-lru只在设置了过期时间的 key 中淘汰适合既做缓存又做持久化的混合业务allkeys-lfu按访问频率淘汰访问频率差异明显的场景比 LRU 更精细volatile-ttl淘汰剩余过期时间更短的 key适合希望把最接近过期的数据淘汰在缓存治理里我建议用allkeys-lru为主辅助设置短 TTL 应对热点数据。有一个很典型的场景某天一个运营活动突发流量某个 key 的 QPS 飙到好几万如果不设 maxmemory 和合理淘汰策略Redis 直接卡死MySQL 瞬间被压垮最终整个链路雪崩。这类事故我见过太多次了根本原因就是没有一个“兜底策略”。4.3 缓存穿透、击穿、雪崩的治理思路这三件事是 Redis 面试题的高频考点也是真实项目里天天能遇到的威胁。我自己的处理方案基本罗列如下缓存穿透查询一个数据库中也不存在的数据缓存里没有数据库里也没有每次请求都打到数据库。治理方案我一般用两种一是用布隆过滤器预先过滤掉大部分不存在的 key二是对这个不存在的 key 也做一个空值缓存TTL 设置短一点比如 30 秒避免恶意流量反复打库。如果你用的是 Spring Boot 自带的 CacheManager可以用注解配合CacheManager自定义 TTL 的方式来处理。实话说项目里如果代码都是裸操作 Redis实现这两种方案时要注意逻辑边界写的干净点别跟业务代码纠缠成一大坨。缓存击穿某个热点 key 突然过期同时大量请求打过来全部打到数据库上。治本办法是“互斥锁”——只允许一个请求去数据库查查完回填缓存其他请求等待。如果用 Redis 实现就是利用SETNX命令加锁获取到锁的线程去查库回填其余线程 sleep 一小段时间后重试。更平滑的方案是“逻辑过期”——key 不设物理过期时间但 value 里放一个过期时间戳读的时候发现逻辑过期立即返回旧数据然后异步去刷新缓存。这个方案对“读多写少、数据一致性要求不高”的场景尤其好用不会因为锁等待导致整个接口耗时飙升。缓存雪崩大量 key 在同一时间过期或 Redis 宕机所有请求集体落到数据库。解决方案我也不厌其烦地在各个项目里推广一是设置 TTL 时加一个随机噪音比如基础 TTL 300 秒再随机加 0 到 60 秒打散过期时间二是高可用部署哨兵和 Cluster 都能避免单点三是做多级缓存查询时先查 Redis查不到再查数据库数据库前方再挡一层本地缓存或消息队列削峰。这里必须提醒一句分布式锁不是万能的。在单体应用里用锁处理缓存击穿没问题一旦服务多实例部署必须用 Redis 分布式锁否则每个实例都会去查一遍数据库。Redis 分布式锁的坑也不少后面具体讲。5. 高可用与分布式锁Redis 6/7 的底层实践高可用和分布式锁是 Redis 在项目里价值最大的两块也是最容易出现“看似高大上、实则漏洞百出”的地方。我用一个小节专门来讲这两件事。5.1 主从复制和哨兵模式到底解决了什么问题主从复制解决的是“数据备份”和“读写分离”的问题。Master 负责写多个 Slave 负责读这样即使有大量读请求也不会全部打到 Master 上。主从复制的同步过程早期是用SYNC命令全量同步现在优化成了PSYNC可以通过 replid 和 offset 只同步增量数据带宽消耗小很多。但主从复制有一点需要注意主从同步是异步的。如果业务中某个数据写入主库后马上要读到而读请求被路由到了从库有可能会出现短暂读不到的情况。这在强一致场景里是致命的。所以线上做读写分离前一定要评估清楚业务对一致性的容忍程度。哨兵模式则是解决高可用的。它本身是一个独立进程监控 Master 和 Slave 的状态。当 Master 挂了哨兵会从 Slave 里选出一个升级为新的 Master并把其他 Slave 指向新 Master。整个故障转移过程对客户端来说是感知不到的。在这里特别要提一个运维细节我见过好几个朋友搭好了主从和哨兵自己测试时把 Master kill 掉结果等了很久发现哨兵根本没切换后来查看日志才发现哨兵配置文件里监听的 IP 是 localhostDocker 容器之间访问不到。所以部署哨兵时监控的主节点地址一定要用容器内可通的服务名或内网 IP不能用localhost否则哨兵永远认为 Master 正常。5.2 分布式锁的正确写法和常见坑分布式锁是 Redis 面试题里的一大重头戏也是项目里最容易写出问题的代码。很多同学拿到网上随手抄一个RedisTemplate的操作就用了结果线上经常出现锁失效、死锁、误删锁的问题。一个相对正确的 Redis 分布式锁模板如下String lockKey lock:order: orderId; String lockValue UUID.randomUUID().toString(); Boolean locked redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, Duration.ofSeconds(30)); if (Boolean.TRUE.equals(locked)) { try { // 执行业务逻辑 } finally { String currentValue redisTemplate.opsForValue().get(lockKey); if (lockValue.equals(currentValue)) { redisTemplate.delete(lockKey); } } }这里面三个细节缺一不可第一setIfAbsent必须带上过期时间。原生的SETNX本身没有过期时间如果不设置 TTL一旦业务逻辑中抛异常导致 finally 没执行这把锁就永远不释放后续请求全部卡死。Spring Data Redis 里setIfAbsent(key, value, timeout)就是原子性的可以在一条命令里完成占锁和设置过期时间。第二删除锁时要校验 value 是否还是自己持有的。因为锁可能已经过期自动释放了然后被其他线程重新拿到。如果不判断第一线程的 finally 会把第二线程的锁删掉连锁失控。上面的代码用 UUID 作为 value删除前比较一下这是标准做法。第三锁的过期时间要结合业务时长合理设置。如果业务逻辑长于锁的过期时间锁在业务执行中被自动释放会出现临界状态。比较好的方案是使用 Redisson 这样的开源客户端它实现了看门狗watch dog机制锁快过期时自动续期避免业务没走完锁先没了。你要是项目里已经用了 Redisson直接用RLock就行比手写稳妥得多。我还见过一种极端场景一个线程拿到分布式锁后在本地执行了死循环锁一直不被释放其他线程等了很久也没拿到锁。后来排查发现是代码 bug锁内调用了外部接口接口超时没设置导致整个线程卡死。用分布式锁一定要记住锁粒度尽量小锁内逻辑尽量短锁内不要调用不可控的外部依赖。否则把等待锁的请求全部拖死还不如不用锁。6. 项目中的 Redis 参数调优这些配置别用默认值Redis 虽然开箱即用但很多配置默认值放在生产环境里并不合适。这一节我把自己踩过的几个关键配置整理出来可以作为检查清单用。6.1 必须调整的若干配置项maxclients默认值是 10000但实际上系统文件描述符限制、内存资源会对它形成制约。如果并发连接数很大的项目需要估算压测数据把这个值调到一个合适范围避免过高导致资源耗竭。timeout默认是 0也就是永不超时。这种情况下客户端如果建立连接后不释放连接数就会持续堆积。我一般根据业务特点设置成 300 秒空闲连接自动断开配合客户端的连接池使用效果更好。tcp-backlog控制 TCP 连接队列的大小如果连接瞬时涌入量非常大默认值 511 很可能不够导致客户端连接被拒绝。配合内核参数调整后这个值可以适当调大。maxmemory-policy前面已经说过默认是 noeviction生产环境建议根据业务配置成 allkeys-lru 或 volatile-lru给缓存一个“兜底”。appendfsyncAOF 刷盘策略可选 always、everysec、no。生产环境我强烈建议用 everysecalways 在每次写命令后都要刷盘性能影响巨大everysec 最多丢 1 秒数据性能和安全平衡得很好。6.2 内存碎片率和慢日志自查线上 Redis 偶尔会出现内存占用远超数据量本身的情况这时候要关注INFO memory里的mem_fragmentation_ratio内存碎片率。如果这个值在 1 到 1.5 之间属于正常范围如果超过 1.5说明内存碎片很多可能需要重启 Redis 或者手动执行MEMORY PURGE。如果小于 1说明 Redis 使用了 swap 区性能会严重下降这通常意味着物理内存不够用了。慢日志也是一项重要的排障工具在配置文件里设置slowlog-log-slower-than 10000 slowlog-max-len 128然后通过SLOWLOG GET查看最近慢命令。我遇到过一个大 key 问题某个 Hash 类型 key 里有几百万个 field执行HGETALL时耗时几十毫秒直接把单实例请求拖慢。这种场景下慢日志会非常明显地暴露问题制定缓存治理策略时必须考虑 bigkey 拆分。7. Redis 与 Spring Boot 集成项目里最常用的落地姿势Rub 到这一步前面讲的都是基础设施层面的东西现在来聊聊大家问得最多的实际开发问题Redis 在 Spring Boot 里到底怎么写代码。7.1 基础依赖与连接配置现在 Spring Boot 3 项目基本都默认用spring-boot-starter-data-redis底层默认连接池是 Lettuce。引入方式很简单dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependencyapplication.yml里的核心配置spring: data: redis: host: 127.0.0.1 port: 6379 password: your_password timeout: 3s lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 max-wait: 2s关于 Lettuce 和 Jedis 的选择很多人纠结过。早期 Jedis 是主流但它是同步阻塞模型多线程下连接管理比较笨重。Lettuce 基于 Netty天然支持异步和响应式Spring Boot 2 之后默认就是它。我建议就不要换了默认方案可靠性足够。需要特别提醒的是连接池参数。很多同学只配置了 host 和 port连接池默认值可能过高或过低高并发下会出现连接获取超时。配置max-active时要结合并发数和 Redis 实例的处理能力做压测不是越大越好。7.2 注解式缓存和编程式缓存怎么选Spring 提供了一套Cacheable、CacheEvict注解用起来非常方便在方法上加注解就能自动做缓存。例如Cacheable(value userCache, key #userId, unless #result null) public User getUserById(Long userId) { return userMapper.selectById(userId); } CacheEvict(value userCache, key #userId) public void updateUser(Long userId, User user) { userMapper.updateById(user); }这种方式的优点是开发效率高、代码侵入小。缺点是你一旦用到比较复杂的过期时间、缓存穿透保护、多级缓存或者自定义序列化时注解的方式就很笨重。我在项目里更常见的是直接用RedisTemplate或StringRedisTemplate撸把缓存的读写逻辑封装到一个独立的 CacheService 里统一管理 key 的命名规范、TTL、序列化方式。这种方式看着代码多一些但排查问题时特别清晰也方便做精细化控制。一个典型的编程式缓存逻辑public Product getProductDetail(Long productId) { String key product:info: productId; String json stringRedisTemplate.opsForValue().get(key); if (StringUtils.hasText(json)) { return JSON.parseObject(json, Product.class); } Product product productMapper.selectById(productId); if (product ! null) { stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(product), Duration.ofMinutes(30)); } return product; }这段逻辑在工程化项目里基本能直接用也非常适合入门理解。要注意的是在实际项目里还会加上布隆过滤器和空值缓存用来处理缓存穿透。7.3 缓存分页的场景怎么处理热词里有一个“redis分页”这是我见过不少同学卡住的地方。用 Redis 做分页其实不是让 Redis 替代数据库来做分页查询而是在数据库查询结果之前加缓存或者把热点查询结果存成 ZSet/List直接内存里翻页。我参与的一个内容型项目里详情页的“评论列表”就是放在 Redis 的 List 里每次新评论LPUSH然后用LRANGE key start stop取分页数据。这种方式对“最近评论”这种增量追加的场景非常合适查询速度几乎不受数据量大影响。但要注意一旦数据更新特别频繁且需要按条件过滤Redis 直查列表就变得很吃力。比如“按时间范围查订单分页”这种完全可以用数据库索引搞定Redis 做一层结果缓存来加速。别把 Redis 当万能数据库用它不是。它能解决的是“读取热点化、写入有序化”的问题而不是把所有查询逻辑都搬到内存里。8. 面试高频考点梳理从基础到分布式一网打尽既然热词里多次出现了“Redis面试题”这一章就顺便梳理一下我在招人时最常问的 Redis 相关考点以及我自己在准备面试时总结的答题思路。这些内容同时也是你在项目里真正需要清楚的基础。8.1 基础层考点Redis 为什么快从这几个角度回答纯内存操作、单线程避免上下文切换和锁竞争、IO 多路复用机制、高效的数据结构实现SDS、跳表、压缩列表等。Redis 的线程模型是什么6.0 之前核心是单线程但这里要区分Redis 的命令执行是单线程但 IO 读写和持久化在后续版本中做了多线程优化。6.0 引入 IO 多线程处理网络请求但执行命令仍然单线程。String 底层用了什么结构SDS简单动态字符串相比 C 字符串优点在于获取长度 O(1)、内存预分配、惰性空间释放等。Redis 的过期删除策略惰性删除 定期删除 内存淘汰策略三层配合。惰性删除是访问时发现过期就删定期删除是周期扫描删除内存淘汰则是内存不够时兜底。为什么 Redis 默认端口是 6379当时作者选的一个意大利女歌手的名字在手机键盘上对应的数字算个冷知识能答出来会显得你了解背景。8.2 高可用层考点主从复制的原理从节点发送PSYNC命令主节点判断 replid决定全量复制还是部分复制。全量复制生成 RDB 快照发送给从节点部分复制从复制积压缓冲区里取增量数据。哨兵模式的 quorum 是什么意思多少个哨兵一致认为主节点不可用才会触发故障转移。这个值一般设置为哨兵总数的一半加一确保构成多数派。为什么 Redis Cluster 需要至少 3 个主节点因为 Cluster 使用的是哈希槽分配机制总共 16384 个槽至少要三个主节点才能保证数据可以均匀分布同时每个主节点还可以配一个从节点做高可用。分布式锁的底层实现从SETNX到 Redisson 的看门狗机制再到 Redlock 算法。答的时候要主动提到底层是 Lua 脚本保证原子性比如 Redisson 解锁时用 Lua 脚本同时比较和删除。缓存和数据库的一致性怎么保证这个问题没有标准答案但通常会组合使用以下方案写时先更新数据库再删缓存读时先读缓存再读数据库删除失败时加一个补偿任务或者用 binlog 订阅来异步清除缓存。8.3 常见问题速查问题原因排查手段Redis 连接超时网络不通、连接池耗尽、Redis 服务挂掉检查网络、INFO clients查看连接数、redis-cli ping主从无法同步masterauth 未配置、网络隔离、replid 不匹配查看从节点日志、检查主节点 requirepass、确认主从端口可达缓存 key 乱码Spring Data Redis 使用了 JDK 默认序列化显式配置 StringRedisSerializer Jackson 序列化器内存一直涨不降过期 key 未及时清理、bigkey 过多使用--bigkeys参数扫描大 key、监控INFO memoryRedis 服务启动后立即退出配置文件错误、权限不足查看启动日志检查配置文件的目录和权限连接数打满应用连接池配置过大、用完不归还连接开启连接池空闲回收配置 max-active 合理值面试的时候除了把答案背熟最好能结合你真实遇到的一个故障场景来讲。比如“我之前遇到过缓存穿透导致数据库被打挂后来是怎么处理的”这种经验比任何八股文都有说服力。9. 从项目角度谈一点个人体会我自己从最早把 Redis 当成一个“快一点的 Map”来用到现在能独立设计一套包含主从、哨兵、持久化、缓存治理、分布式锁的完整方案中间踩过的坑不止上面写的那些。最大的体会是Redis 上手非常容易用好却需要长期和它相处。千万别停留在“会敲 set 和 get 命令”的阶段多琢磨一下数据结构背后的设计意图多去压测环境里跑一轮高并发很多知识自然就通透了。最后再分享一个小技巧任何时候在项目里新接入 Redis第一件事不是写业务代码而是先设计好 key 的命名规范、TTL 体系和序列化方案。这些在项目初期做好后期能省下无数排查故障的时间。key 的命名规范我一般建议用业务名:实体名:ID的格式比如order:detail:123456既方便排查也方便按前缀批量处理。TTL 的原则是能短则短除非这个数据必须长驻。序列化方案则是前面说的统一用 String 或 JSON别让我在命令行里看到一屏乱码。Redis 的未来还有很多可以玩的东西像 Redis 7 引入的 Function 功能、RedisJSON、RediSearch 这些模块都在把 Redis 从纯缓存推向一个更通用的数据基础设施。但不管它怎么发展底层的数据结构、持久化、高可用和缓存治理这些基本功永远是你在项目里站稳脚跟的地基。希望这篇内容能给你带来一些真正能落地的帮助。
返回列表