
Redis 说难不难说简单也不简单。这段时间我连续处理了好几个团队报上来的 Redis 线上问题有装完起不来的有执行 systemctl restart redis 直接报 Job failed 的有 RedisTemplate.increment() 突然抛 “value is not an integer or out of range” 的还有主从同步断了、重启之后数据丢了大半的。每一种单看都不算疑难杂症但串起来看就会发现根子都在最开始那几步——安装配置没做对、数据结构场景选错、持久化方案没想清楚、高可用和安全加固被拖到上线之后。这篇文章我就按实际接手这些问题的顺序把 Redis 从安装到高可用、从数据持久化到分布式锁的完整使用心得梳理一遍。不打算让你背命令重点是把每个环节“为什么会出问题”“正确的姿势是什么”“遇到问题往哪个方向查”讲明白。无论你是刚把 Redis 装起来还没用熟的新人还是已经上过生产环境、踩过坑的开发者应该都能在里面找到能直接拿去用的东西。1. 先认清 Redis 在你系统里的角色再谈怎么用1.1 它本质上是一个单线程的内存数据结构服务很多把 Redis 用出问题的人第一步就理解偏了。Redis 不是“数据库的缓存壳”它本质上是一个基于内存的数据结构服务器以单线程模型处理命令通过 I/O 多路复用机制支撑高并发。单线程听起来好像是瓶颈但正因为单线程它避免了并发访问时的锁竞争和上下文切换开销在纯内存读写场景下单实例压测跑到十万级 QPS 并不夸张。这个特性决定了 Redis 的强项是“快”但也决定了它不适合做大对象的存储一旦某个 key 的值有几 MB 甚至几十 MB单线程模型下所有命令都得等它处理完这就是“大 key”会阻塞整个实例的根本原因。理解了这一点后面聊内存淘汰、大 key 治理、持久化阻塞时就有了判断方向。1.2 从高频问题看大家的真实痛点我大致翻了下这段时间搜 Redis 相关内容的高频词基本集中在这些方向方向高频词背后暴露的问题安装部署redis下载、redis安装、systemctl restart redis job for redis.serv装完起不来、配置不生效、不知道日志怎么看数据使用redis数据类型、redis序列化、increment()报错类型用错场景、跨语言/框架序列化乱码高可用主从哨兵模式、docker安装redis主从、持久化单点扛不住、重启丢数据、主从切换不会配治理与安全缓存治理、内存淘汰策略、未授权漏洞缓存穿透击穿雪崩、内存爆掉、裸奔在公网这些关键词本身就是一个完整的学习路径。很多人一上来就盯着数据类型命令背背完之后到生产环境照样翻车因为真正的坑从来不在命令本身而在“选型、配置、网络、数据格式”这些看不到的细节上。下面我就按这条路径一步步展开。2. 安装与启动Linux、Windows 和 Docker 三条路线上的真实差异2.1 Linux 编译安装与 systemd 管理重点是那个 Job failed先说不带 Docker 的标准路线。官方源码编译安装其实是最稳的一种方式下载稳定版源码包后tar xzf redis-7.2.4.tar.gz cd redis-7.2.4 make -j$(nproc) make install编译前确认系统有 gcc否则 make 会直接报错。make install之后redis-server、redis-cli、redis-check-aof、redis-check-rdb 这些可执行文件会装到 /usr/local/bin可以直接用。接下来复制一份 redis.conf 到 /etc/redis/mkdir -p /etc/redis /var/lib/redis cp redis.conf /etc/redis/redis.conf useradd -s /sbin/nologin redis chown -R redis:redis /var/lib/redis网上很多教程会让你直接改 redis.conf 里的 daemonize yes然后手动启动。这在 systemd 环境下是一个很经典的坑你把 daemonize 设为 yesRedis 主进程会 fork 到后台systemd 认为服务进程已经退出结果就是systemctl restart redis时提示 Job for redis.service failed because the control process exited with error code。如果你用 systemd 管理 Redis配置里应该写成daemonize no让前台进程由 systemd 托管。配一个最小可用的 systemd 单元文件[Unit] DescriptionRedis Server Afternetwork-online.target [Service] Userredis Groupredis Typesimple ExecStart/usr/local/bin/redis-server /etc/redis/redis.conf ExecStop/usr/local/bin/redis-cli -a yourpass shutdown Restarton-failure [Install] WantedBymulti-user.target写完systemctl daemon-reload systemctl enable --now redis就能跑起来。记住一个原则改完配置不要只听“感觉”一定要看实际日志。再回到热搜里那个[root... systemctl restart redis job for redis.serv场景。实际操作中我建议按这个链路排查systemctl status redis --no-pager -l看服务状态和最近错误这一步能排除配置语法、权限这类显而易见的问题。journalctl -u redis -n 100 --no-pagersystemd 管理的服务日志全在这里。如果看到Cant chdir to /var/lib/redis或者Cant open the log file: Permission denied基本都是目录没建对或者 redis 用户没权限。直接前台启动试一次/usr/local/bin/redis-server /etc/redis/redis.conf所有报错都会直接打在终端上。如果能启动但 systemd 启动不了十有八九就是 daemonize 或 PIDFile 的问题。如果日志里出现Bad file format reading the append only file说明 AOF 文件损坏Redis 会拒绝启动。这时用redis-check-aof --fix /var/lib/redis/appendonly.aof修复后再启动。这套排查链路对 Docker 部署同样适用只是把systemctl status换成docker logs redis把journalctl换成docker logs --tail 100 redis思路完全一致。2.2 Windows 上装 Redis我更推荐 Docker Desktop 而不是“绿色版”Windows 环境下的 Redis 是个老大难。官方一直不提供 Windows 原生安装包网上能找到的“windows版本redis下载”基本都是社区维护的旧版本比如基于 Redis 5.0 的第三方构建稳定性和性能都比不上官方原版。我最推荐的方案是 Docker Desktop 官方 redis 镜像一条命令就能把干净的环境拉起来docker run -d --name redis -p 6379:6379 --restart unless-stopped -v redis-data:/data redis:7这里把数据目录挂到 named volume 里容器怎么重建数据都不会丢。如果你连 Docker 都不想装另外一个可行方案是 WSL2在 WSL 里跑 Linux 版 Redis性能和兼容性比任何 Windows 原生构建都好。至于 Memurai 这类 Redis 兼容实现生产环境我持保留态度API 兼容是一回事生态和社区验证是另一回事出了问题可排查的资料会少很多。2.3 连接工具redis-cli 够用Redis Desktop Manager 系列看需求排查问题的时候我最常用的还是 redis-cli因为它能直接执行命令看原始返回不存在“工具翻译”带来的信息损耗。强调一个容易被忽略的细节redis-cli -a带密码时终端会输出 warning 提示如果你在脚本里用建议设置环境变量REDISCLI_AUTH或者干脆-a后面加--no-auth-warning输出会更干净。图形化工具方面Redis Desktop Manager 和 Another Redis Desktop Manager 是两代人都用得最多的。对 Windows 用户来说 ARDM 更推荐开源、免费、跨平台支持按 key 前缀过滤、批量删除、可视化查看不同类型 key 的 value日常排查效率比纯命令行高不少。不过要注意连接生产环境时图形化工具最好只开只读操作权限避免手滑删掉 key。3. 数据类型与序列化别只看命令要看底层结构和适用场景3.1 五种基础类型选型对照Redis 的数据类型是面试高频考点但面试只考命令生产却考选型。我用一张表把最核心的选型逻辑列出来类型底层结构高频命令适用场景容易踩的坑StringSDS 动态字符串SET/GET/INCR/DECR缓存、计数器、限流、分布式锁把大对象硬塞进 String形成大 keyListquicklistLPUSH/RPUSH/LPOP/RRANGE消息队列、时间线流无脑当成无限队列不设消费速度控制Hashlistpack/hashtableHSET/HGET/HGETALL对象字段存储字段特别多时效率下降序列化整对象反而更简单Setintset/hashtableSADD/SISMEMBER/SINTER去重、共同好友、标签大集合交集运算阻塞线程ZSetskiplist dictZADD/ZRANGE/ZSCORE排行榜、延迟队列、限流窗口score 和 member 设计不合理导致无法按业务维度查询选类型的核心原则是先想你的读模式是什么再决定用什么结构。比如我要做排行榜需求是“按分数倒序取前 N 名”ZSet 天然支持如果我用 Hash 存用户分数再在应用层排序每次都要全量拉取数据量一大就是事故。反过来如果只是按 ID 查对象属性Hash 效率很高但如果你还要按某个属性做范围查询Hash 就做不到该想的就不只是存储类型而是索引方案了。3.2 Java 场景下 RedisTemplate 序列化和 increment() 报错这是很多 Java 开发者都遇到过的问题调用redisTemplate.opsForValue().increment(counter)时控制台抛异常redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range表面看是“值不是整数”实际上绝大多数情况是序列化策略不一致导致的。Spring 默认的 RedisTemplate 使用 JdkSerializationRedisSerializer存进去的 value 是 Java 对象序列化后的二进制字节流。你第一次set(counter, 1)时Redis 里存的根本不是整数 1而是一段以\xAC\xED\x00\x05t...开头的二进制垃圾。等到你在别的地方或者用同一个 template 调 increment()Redis 发现这个 key 的值没法按整数解析就直接报错。排查链路我建议这样走先用redis-cli连接同一实例GET counter看实际 value。如果显示成\xac\xed开头的二进制字符串就是序列化问题。看看你的项目里是不是同时注入了 RedisTemplate 和 StringRedisTemplate两个 template 序列化器不同互相读写同一个 key 时就会产生“我用 String 写、你用 JDK 读”的错位。确认这个 key 之前有没有被非数字类型写过。比如先opsForValue().set(counter, abc)再去 increment 也会报同样的错。修复方案是统一序列化配置。最简单的做法是通过配置指定序列化器Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); StringRedisSerializer keySerializer new StringRedisSerializer(); GenericJackson2JsonRedisSerializer valueSerializer new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(keySerializer); template.setHashKeySerializer(keySerializer); template.setValueSerializer(valueSerializer); template.setHashValueSerializer(valueSerializer); return template; } }对于计数器这类“天生就是数字”的 key还有一个近乎零成本的方案直接使用 StringRedisTemplate它的 value 序列化器是 StringRedisSerializerset 进去的是纯字符串数字increment() 自然能解析。这里要提醒一点检查 RedisTemplate 的序列化配置时value 序列化器不一定要追求通用。GenericJackson2JsonRedisSerializer 会在 JSON 里写入class类型信息反序列化确实方便但也带了安全隐患和冗余数据。如果是内部系统可以接受如果有外部输入请评估是否需要更受限的 ObjectMapper 配置。我见过好几个项目因为序列化器太“自动”结果缓存里塞了不该出现的类信息最终变成反序列化漏洞入口这个细节值得警惕。4. 持久化RDB 与 AOF 不是二选一是工程取舍4.1 RDB 与 AOF 各自的原理边界Redis 的持久化绕不开两套机制RDB内存快照和 AOF追加日志。RDB 的原理是 fork 出子进程把当前内存数据生成一份压缩快照写入 dump.rdb。触发条件由 save 配置控制比如save 900 1表示 900 秒内有 1 次写操作就触发一次快照。包括最后一次正常 shutdown 和主从全量同步也会触发 RDB。它的优点是文件紧凑、恢复快缺点是从最后一次快照到故障发生之间的数据会全部丢失。更隐蔽的一个问题是 fork 和写快照期间如果实例内存很大子进程通过写时复制机制复制页表可能会造成 CPU 毛刺和内存突增生产环境大实例上经常遇到。AOF 则是把每条写命令追加到 appendonly.aof 文件通过 fsync 决定刷盘时机。appendfsync always每条命令都刷盘最安全但最慢everysec每秒刷一次最多丢 1 秒数据no交给操作系统最不可控。AOF 文件会越来越大所以 Redis 提供了重写机制AOF 足够大时fork 出一个子进程基于当前内存状态生成最小命令集替换掉旧日志。从“丢数据窗口”看RDB 可能丢几十秒甚至几分钟AOF everysec 最多丢 1 秒。很多上了生产的团队最后都会开 AOF但又担心 AOF 文件太大恢复太慢这时候 Redis 4.0 引入的混合持久化就是答案aof-use-rdb-preamble yesAOF 文件的头部先写一段 RDB 快照后续再用增量命令记录变化。写入时快照恢复快丢失窗口只依赖 AOF 增量部分属于工程上的最优解。4.2 重启后数据“消失”的排查链路处理过太多“Redis 重启后数据丢了”的问题我总结了一套固定排查顺序确认启动命令真正加载的配置文件。很多人redis-server 直接启动绕过了 redis.conf那持久化配置自然不生效。用ps -ef | grep redis-server看实际参数。确认 dir 配置指向哪里。RDB 和 AOF 文件默认生成在 dir 目录如果 conf 里 dir 指向 /var/lib/redis而你实际找文件时去解压目录翻当然找不到。检查文件权限。Redis 进程用户对 dump.rdb 和 appendonly.aof 需要有读写权限否则启动时要么拒绝加载要么写不进去。查日志。启动时日志里会有一条DB loaded from disk或Loading RDB。如果你的日志明确显示加载成功但数据仍少那大概率不是“没持久化”而是崩溃发生在最后一次快照之后属于丢失窗口问题要调整持久化策略而不是找文件。排查“人为删除”。内存告警时有人FLUSHALL清库之后又没有触发新的 RDB/AOF 重写那旧快照文件里其实还有数据但 Redis 重新启动时会加载新生成的空快照数据。这个场景下找到旧的 dump.rdb 备份并用redis-check-rdb检查是唯一出路。这里分享一个实用习惯生产环境 Redis 实例我通常同时开 AOF 和 RDBAOF 保证秒级丢损RDB 作为定期快照兜底和备份源。再配合定时把 dump.rdb 拷贝到异地存储就能覆盖“整个实例磁盘损坏”这种极端情况。4.3 AOF 文件损坏后的恢复路径进程意外断电、磁盘故障后AOF 文件头损坏导致 Redis 拒绝启动这是比较常见的事故现场。修复命令是redis-check-aof --fix /var/lib/redis/appendonly.aof它会扫描文件遇到不完整或损坏的命令会截断或删掉。注意修复前一定先备份原文件因为修复过程可能丢失文件尾部的最新数据有备份才有后悔药。修复完再启动数据恢复到损坏点之前的状态。同理RDB 文件损坏可以用redis-check-rdb检查但 RDB 是二进制快照可修复的信息量比 AOF 少更重要的工作其实是备份。5. Docker 部署主从与哨兵从单点走向高可用5.1 主从同步为什么会有延迟主从架构的作用不只是读扩展更重要的是为高可用提供基础。Redis 的主从复制是异步机制主节点写入后立刻返回客户端同时把写命令传播给从节点从节点通过偏移量master_repl_offset 和 slave_repl_offset和主节点校准进度。理解了“异步”你就知道主从必然存在延迟而延迟过大的主从在故障转移时会丢数据。查看INFO replication时如果slave_repl_offset长期落后master_repl_offset说明网络或从节点性能跟不上。常见的根治手段是确保主从之间网络延迟很低同时避免从节点上跑大 key 全量同步。还需要注意repl-backlog-size它决定增量同步的缓冲大小默认 1MB 太小高写入量时从节点短暂断线再重连可能触发全量重同步直接把主节点拖垮。5.2 用 Docker Compose 一次性搭建一主两从Docker 部署 Redis 主从的一大好处是环境隔离、起停复现都简单。下面这个 compose 文件可以直接用version: 3 services: redis-master: image: redis:7 container_name: redis-master command: redis-server --appendonly yes --requirepass masterpass ports: - 6379:6379 volumes: - master-data:/data redis-slave1: image: redis:7 container_name: redis-slave1 command: redis-server --appendonly yes --replicaof redis-master 6379 --masterauth masterpass --requirepass slavepass depends_on: - redis-master ports: - 6380:6379 volumes: - slave1-data:/data redis-slave2: image: redis:7 container_name: redis-slave2 command: redis-server --appendonly yes --replicaof redis-master 6379 --masterauth masterpass --requirepass slavepass depends_on: - redis-master ports: - 6381:6379 volumes: - slave2-data:/data volumes: master-data: slave1-data: slave2-data:启动后可以验证docker exec redis-slave1 redis-cli -p 6379 -a slavepass INFO replication看到 role:slave 并且 master_link_status:up就说明同步正常。这个配置里有几个必须注意的坑从节点连接到主节点使用--masterauth主节点本身的--requirepass也要设置只设一边会导致认证失败日志里持续刷NOAUTH Authentication required。服务名在容器间是可解析的这就是redis-master这种主机名能直接用的原因你不能在宿主机上用 localhost 去代替服务名。把container_name固定下来避免 Docker 重建容器后 IP 变化导致从节点找不到主节点。虽然 Redis 主从用的是配置里的 host但稳定的容器名能减少后续运维干扰。生产环境绝对不要用默认端口直接暴露公网后面安全章节会专门说。5.3 哨兵模式从“有从节点”到“能自动切换”主从架构只解决了读容量和单点备份的问题主节点本身挂了写入能力就没了需要人工干预。这就是哨兵Sentinel存在的意义。哨兵是独立部署的进程监控所有 Redis 节点。一个最小可用的 sentinel.confsentinel monitor mymaster redis-master 6379 2 sentinel auth-pass mymaster masterpass sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 10000这里的quorum是 2表示至少 2 个哨兵认为主节点主观下线才会触发客观下线并执行故障转移。启动方式docker run -d --name redis-sentinel \ --network你的docker网络 \ -v /path/to/sentinel.conf:/etc/redis/sentinel.conf:ro \ redis:7 \ redis-sentinel /etc/redis/sentinel.conf哨兵自动把从节点提升为主节点后客户端怎么知道新的主节点在哪答案是客户端通过哨兵获取当前主节点的地址。如果你用的是传统客户端需要自己实现“从哨兵拉主节点地址”的逻辑如果使用 Redisson 或 Lettuce 等现代客户端配置里只要填写哨兵地址列表客户端内部会处理主从切换。这个知识点是生产可用和 demo 可用的分水岭很多人实现了哨兵却还是配置固定主节点地址主从切换后客户端连的还是旧节点等于把哨兵白部署了。6. 分布式锁、事务与 Lua高并发场景的底线操作6.1 分布式锁的正确写法以及最经典的死锁翻车案例Redis 做分布式锁的核心思想是“某个 key 同时只能被一个客户端持有”。网上最常见的错误示例是两步走SETNX lock_key client_id EXPIRE lock_key 30问题在于SETNX 和 EXPIRE 不是原子操作。如果执行完 SETNX 后进程崩溃或网络超时锁没有过期时间其他客户端永远拿不到这个 key形成事实上的死锁。正确姿势是用一条命令同时设置值和过期时间SET lock_key client_id NX PX 30000释放锁也不是简单DEL。如果客户端 A 的业务执行超过了 30 秒锁自动过期客户端 B 获取了锁此时 A 执行完毕来 DEL就会把 B 的锁误删。所以释放锁前必须校验持有者身份这个过程要用 Lua 保证原子性if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end普通的getdel两条命令组合在校验完到删除之间会有窗口期Lua 脚本在服务端原子执行才是完整方案。6.2 业务超时后怎么办锁续期与 Redisson客户端 A 的业务逻辑没跑完锁就过期了这是分布式锁的另一个经典问题。手动设置一个很长的过期时间不可取因为实例宕机会让锁长时间不释放。更稳的方案是给锁动态续期后台线程每过一段时间就检查“锁还是不是我的”是则续期。Redisson 的RLock已经封装了这个逻辑默认锁超时 30 秒watchdog 每 10 秒自动续期一次。不是 Java 也没有问题思路完全可以自实现关键是弄清续期的触发条件和停止时机。锁的时间设置经验值我一般取“业务最大耗时的 3 到 5 倍”太小容易误伤慢请求太大则故障恢复时间变长。还要记住锁的粒度要细最好按资源维度分 key比如下订单锁order:{orderId}而不是全局一个product_stock锁到底否则并发能力会被拍死。6.3 事务和 LuaRedis 的“原子性”到底是什么Redis 事务用 MULTI/EXEC 包裹多个命令保证的是“按顺序执行、不被其他客户端命令插入”但它不像关系型数据库那样提供回滚。EXEC 之前如果命令语法错误整个队列不会执行但 EXEC 执行过程中某个命令运行时报错其他命令照常执行已经入队的命令不会回滚前面成功的命令。需要真正原子执行一组业务逻辑时我的选择是 Lua 脚本。比如一个常见的“库存扣减并判断是否扣成功”的脚本local stock tonumber(redis.call(get, KEYS[1])) if not stock or stock 0 then return 0 end redis.call(decrby, KEYS[1], ARGV[1]) return 1EVAL提交脚本后Redis 在服务端整个执行完才处理别的命令中间不可能穿插其他客户端操作。这正是分布式锁校验删除、限流窗口滑动这类多指令组合的最佳实践。Lua 也有需要注意的边界脚本里不要做耗时操作不要循环太大不要依赖不确定的系统时间否则单线程的 Redis 会被你拖死。6.4 Redis 事务与 WATCH 的乐观锁应用如果你的业务需要做“先检查再更新”的 CASCompare And Swap场景可以用 WATCH 实现乐观锁。最典型的就是转账扣款WATCH balance balance GET balance new_balance balance - 100 MULTI SET balance new_balance EXECEXEC 执行前如果 balance 被其他客户端修改过EXEC 会返回 nil表示操作失败你需要重试。这个方案相比分布式锁少了一次锁阻塞适合“读多写少、冲突不频繁”的场景但写入冲突高时重试成本会直线上升届时不如直接上分布式锁或者 Lua 脚本更可控。7. 缓存治理穿透、击穿、雪崩以及内存淘汰的最终防线7.1 三种缓存故障的定义和常规应对“缓存治理”是热搜里的常客也是面试必问但很多资料只把名词解释了一遍没有讲清判断和落地顺序。实际使用中我将它们区分得很明确缓存穿透请求的数据在缓存和数据库里都不存在每次请求都会绕过缓存打到底层存储。攻击者可以用不存在的 ID 批量请求直接把数据库压垮。应对手段是布隆过滤器先拦截不存在的 key或者在缓存里写入空值并设短过期时间。对空值加过期时间要注意别太长否则大量假数据会占满内存。缓存击穿某个热点 key 在过期瞬间被大量请求同时打到数据库。它和穿透的区别是key 在缓存里存在过只是过期了。常见手段是互斥锁重建缓存让同一时刻只有一个线程去查库写缓存其他线程等待或走旧值更优雅的是“逻辑过期”value 里存一个过期时间戳异步去更新不阻塞读请求。缓存雪崩大量 key 在同一时间段过期或者 Redis 实例整体宕机导致流量全部落到数据库。前者通过“过期时间加随机数”打散过期窗口即可解决后者必须靠高可用架构兜底比如主从哨兵、分片集群、多级缓存同时在应用层做降级逻辑直接返回默认值而不是把请求打到数据库。这三类问题经常同时出现我处理线上事故时第一步永远是看监控是特定 key 命中率骤降还是整体 key 同时过期再决定用哪套方案。别一上来就上布隆过滤器先判断是不是一次过期时间设置不合理。7.2 缓存与数据库的一致性为什么推荐“删缓存”而不是“更新缓存”缓存更新的标准姿势叫 Cache Aside读请求先读缓存没命中就读数据库并存缓存写请求更新数据库然后删除缓存。这里最容易理解错的是更新数据库后应该删缓存而不是更新缓存。原因是更新缓存涉及两次并发写最后写回的值可能不是最新值而删除缓存让下一次读请求去查库重建天然规避了写入顺序问题。由于“删缓存”和“更新数据库”之间仍存在极短的时间窗口极端并发下可能读到旧数据所以有了延迟双删先删缓存、再更新数据库、隔一小段延迟再次删除缓存。这个延迟值通常取 500ms 到 1s目的是把并发读重建缓存的时间窗口也覆盖掉。但如果对一致性要求更高延迟双删依然不够完美更彻底的做法是通过 binlog 订阅比如 Canal把“数据库变更”异步转成“缓存删除”解耦业务代码也降低人为遗漏的概率。记住一个原则任何缓存架构都有不一致窗口目标不是消除而是把窗口压到业务可接受的范围。7.3 内存淘汰策略选型上限内存必须提前设Redis 默认是可以无上限使用内存的受物理内存限制但一旦内存写满根据配置的 maxmemory-policy 决定行为策略行为适用场景noeviction不淘汰写命令直接报错数据不可丢失宁可失败allkeys-lru所有 key 按近似 LRU 淘汰纯缓存允许任何 key 被淘汰volatile-lru仅在设置了过期时间的 key 中按 LRU 淘汰缓存和永久数据混合的实例allkeys-lfu按访问频率淘汰冷热差距明显、热 key 稳定volatile-lfu在设过期时间的 key 中按 LFU 淘汰对低频访问有要求的缓存allkeys-random / volatile-random随机淘汰数据访问均匀、无冷热区分volatile-ttl淘汰剩余过期时间最短的 key希望尽量保留快过期的数据我的默认推荐是allkeys-lru因为纯缓存场景下数据本来就允许重建淘汰任何 key 都比服务不可用强。如果是“部分 key 不能被淘汰、部分能淘汰”的混合实例用volatile-lru然后保证可淘汰的 key 一定设置过期时间。最大坑在于noeviction且 maxmemory 设置过小缓存写着写着实例开始报 OOM command not allowed when used memory maxmemory业务拿缓存当数据库用最后把整个服务打挂。无论选哪种策略maxmemory一定要设置。32 位系统和部分内置配置默认 maxmemory 为 0意味着不限制一旦业务流量上涨内存耗尽触发系统 OOM KillerRedis 进程直接被杀那是比淘汰更难接受的事故。结合监控合理值一般是“热点数据总估算量 30% 缓冲”。7.4 大 key 体检与处理思路大 key 指的是单个 key 的 value 特别大比如超过几十 MB或者集合类 key 的元素特别多。它会带来三个连锁问题读写该 key 时单线程阻塞、主从全量同步时网络和内存压力、集群模式下节点数据倾斜。排查手段很直接redis-cli --bigkeys这个命令会扫描实例列出各类 key 中最大的那些。更精确地看到某个 key 的编码和大小可以用DEBUG OBJECT key输出里的 serializedlength 是序列化后的字节数。定位到大 key 后常见处理方案是拆分比如一个 Hash 有 100 万个字段按业务维度拆成多个 key大 String 考虑压缩或者拆成多个小分片集合类型用 HSCAN/SSCAN 分批删除不要一次性 DEL否则会阻塞实例。8. Redis 未授权访问一次加固胜过十次补漏洞8.1 未授权访问是怎么发生的以及你为什么会中招Redis 未授权访问是很多安全报告里的常客。它的成因并不复杂Redis 默认配置监听 0.0.0.0也就是说它在所有网络接口上开放 6379 端口如果同时没有设置 requirepass 密码而 protected-mode 又被一些自定义配置或老版本绕过那么任何能访问到这个端口的人都可以直接执行 Redis 命令。有同学会觉得“我们这个是内网 IP别人扫不到”。实际上现在大量的自动扫描器每天都在扫公网 IP 段和常见端口只要 6379 暴露在公网被扫描到只是时间问题。攻击者连上来之后可以做的包括FLUSHALL 清空所有数据、重命名或覆盖已有 key、把 Redis 当作跳板在内网进一步扩散、在机器上写入恶意数据。这也是为什么网上搜“redis未授权访问漏洞利用总结”会看到各种攻击手法的详细记录——真正的安全重点不是研究这些手法而是确认自己不会成为下一个案例。8.2 一套能直接抄的加固清单我每部署一个 Redis 实例无论环境多“临时”都会按下面这套清单检查一遍改绑定地址。bind 127.0.0.1或者绑定到内网 IP。如果客户端和服务在不同网段必须通过网络策略明确放行范围而不是无脑 0.0.0.0。开启 protected-mode。配置protected-mode yes这是 Redis 在没有密码且绑定内网/公网时的兜底保护机制。设置强密码。requirepass必须配密码不低于 16 位且避免与业务代码里的默认口令混用。别忘了主从模式的从节点也要配置 masterauth。重命名危险命令。在 redis.conf 里对 FLUSHALL、FLUSHDB、CONFIG、KEYS 这类高危险命令做 renamerename-command FLUSHALL rename-command FLUSHDB rename-command CONFIG rename-command KEYS 网络层封堵。云服务器安全组和本地防火墙都要限制 6379 端口的访问来源基本原则是“只有需要的机器能连”。Docker 部署时不要直接映射公网端口。-p 6379:6379在开发环境很方便但在服务器上等于把服务裸奔。如果一定要对外暴露至少要配合密码、防火墙和 ACL。启用 Redis ACL 做账号分权。新版 Redis 支持 ACL给业务账号只分配需要的最小命令权限比一把 requirepass 通打所有命令细粒度得多。例如ACL SETUSER appuser on apppassword ~cache:* read set get expire定期自检。用外部机器执行redis-cli -h your_ip -p 6379 ping如果不需要密码就能返回 PONG立即整改。顺便说一句很多团队把安全加固拖到“等系统上线稳定之后”再做结果一上线就被扫描器盯上。我的习惯是把安全项和安装步骤写在同一张清单里装完 Redis 立刻做而不是放在 TODO 列表里。最后再分享一点实操体验。我这些年经手的 Redis 事故绝大多数不是 Redis 本身的 bug而是使用姿势问题。如果让我给新人一条最重要的建议那就是安装完成后第一件事设置 requirepass 和 maxmemory第二件事想清楚持久化策略第三件事在写任何业务代码前确认序列化器是统一的。这三件事做完你后续遇到的大部分诡异问题其实已经提前被规避了。至于主从哨兵、集群这些高可用方案建议先在 Docker 环境里完整演练一遍故障转移再上生产——纸上谈兵的主从切换到了真出事那天你大概率还是会手忙脚乱。