缓存策略、分布式锁与 Spring 集成

共 74 题
📑 题目列表 74 题
#
★★★

1. @CachePut/@CacheEvict 与一致性

Spring 的 @CachePut 与 @CacheEvict 注解如何与缓存一致性结合使用?

  • @CachePut 主动更新缓存 vs @CacheEvict 删除缓存
  • 与数据库更新的时序
  • 一致性取舍

@CachePut 在方法执行后把返回值写入缓存,@CacheEvict 在方法执行后删除指定缓存。两者配合数据库更新时,典型做法是"先更新数据库再删除缓存"(Cache-Aside),用 @CacheEvict 保证下次读取重建缓存,避免更新缓存与并发写造成的不一致。@CachePut 适用于"读多写少且写后立即读"的场景,但双写缓存与数据库存在竞态(先写缓存后写 DB 失败导致缓存脏数据)。一致性上,@CacheEvict 删除缓存更安全(即使删除失败也可靠过期兜底),@CachePut 直接写值需更谨慎地处理失败。

缓存一致性本质是"数据库与缓存谁先谁后"。删除缓存比更新缓存更不易出错,配合 TTL 与消息通知(如 Canal/延迟双删)可进一步收敛。

#
★★★

2. @Cacheable/@CacheEvict 与 Redis 缓存管理器

@Cacheable/@CacheEvict 注解如何与 Redis 缓存管理器(RedisCacheManager)协同工作?

  • @Cacheable 读缓存、@CacheEvict 失效缓存
  • RedisCacheManager 的配置(序列化、TTL)
  • Key 生成与缓存名

@Cacheable 命中缓存则直接返回,未命中则执行方法并把结果写入缓存;@CacheEvict 在方法执行后删除缓存。两者都依赖 CacheManager 抽象,Spring Boot 集成 Redis 时使用 RedisCacheManager,它通过 RedisCacheConfiguration 配置序列化器、默认 TTL、key 前缀等。@Cacheable(cacheNames = "user", key = "#id") 会生成 user::id 这样的缓存键。使用 Redis 缓存管理器时,需注意序列化策略(避免默认 JDK 二进制)与 TTL 设置,避免缓存永不失效。

注解只是声明式入口,真正的缓存读写由 CacheManager 落到 Redis。正确配置 RedisCacheManager 的序列化与 TTL 是缓存生效与一致性管理的关键。

@Bean
RedisCacheManager cacheManager(RedisConnectionFactory cf) {
    RedisCacheConfiguration conf = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(5))
        .serializeValuesWith(RedisSerializationContext.SerializationPair
            .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    return RedisCacheManager.builder(cf).cacheDefaults(conf).build();
}
#
★★★

3. Cache-Aside(旁路缓存)的工程实现

Cache-Aside(旁路缓存)模式在工程中如何实现?

  • 读路径(先查缓存,未命中查库回填)
  • 写路径(先更新 DB,再删缓存)
  • 一致性维护与并发

Cache-Aside 读路径:先查缓存,命中直接返回;未命中则查数据库,回填缓存并设置 TTL 后返回。写路径:先更新数据库,再删除缓存(而不是更新缓存),下次读时重建。这样避免并发下"更新缓存"与"更新数据库"交错导致脏数据。工程实现要点:回填用"缓存预热"或"读时回填",删缓存用 @CacheEvict 或显式 delete;删除失败可用延迟双删或消息补偿;对并发可用加锁/合并请求缓解击穿。它是业界最常用的缓存模式,逻辑简单、一致性较好。

Cache-Aside 的正确性依赖"更新 DB 后删缓存"的时序,删除缓存比更新缓存更不易产生脏数据,配合 TTL 兜底。实现上要处理删除失败与并发重建。

public User getUser(Long id) {
    User u = redisTemplate.opsForValue().get("user:" + id);
    if (u == null) {
        u = userMapper.selectById(id); // 查库
        if (u != null) redisTemplate.opsForValue().set("user:" + id, u, 5, TimeUnit.MINUTES);
    }
    return u;
}
public void updateUser(User u) {
    userMapper.update(u);                 // 先更新 DB
    redisTemplate.delete("user:" + u.id); // 再删缓存
}
#
★★★

4. GETSET/GETEX(Redis 6.2)的工程价值

Redis 的 GETSET 与 GETEX(6.2 引入)命令有哪些工程价值?

  • GETSET:原子地设置并返回旧值
  • GETEX:读取并设置过期时间
  • 在计数/锁/自增等场景的应用

GETSET key value 原子地把 key 设为新值并返回旧值,用于需要"读取并更新旧值"的场景(如计数器重置、状态机切换、分布式锁的过期值替换)。GETEX key [EX|PX|EXAT|PXAT] [PERSIST] 读取值并同时设置/清除过期时间,是 6.2 新增命令,用于"读取并刷新 TTL"(如活跃会话续期、滑动窗口续期),避免先 GET 再 EXPIRE 的两步非原子操作。工程价值:GETSET 在计数器清零、锁竞争检测中常用;GETEX 让"读+续期"原子化,减少竞态。

两者都把"读 + 写/续期"合并为原子命令,避免多命令竞态。GETSET 面向"取旧值换新值",GETEX 面向"取当前值并维护 TTL"。

#
★★★

5. INCR/DECR 的原子计数应用

Redis 的 INCR/DECR 原子计数命令有哪些应用场景?

  • INCR/DECR 的原子性
  • 计数、限流、秒杀、ID 生成
  • 与 Lua 的配合

INCR/DECR 对 key 存储的整数做原子自增/自减,由 Redis 单线程保证原子性,并发下不会丢失更新。应用场景:计数器(访问量、点赞数)、限流(固定窗口计数)、秒杀库存扣减(配合判断库存大于 0)、全局自增 ID 的生成等。注意 INCR 返回的是新值,可用于判断阈值;秒杀场景可配合 INCR 后判断是否超卖,或封装 Lua 脚本保证"判断+扣减"原子。自增 ID 在应用崩溃重启后可能跳号,需结合业务容忍度。

INCR/DECR 的原子性来自 Redis 单线程命令执行,适合高并发计数。正确使用要结合结果判断(如返回值是否超限)与 Lua 脚本做复合操作。

#
★★★

6. JDK 25 虚拟线程在 Lettuce 长连接场景的栈复用与连接泄漏防范

JDK 25 虚拟线程在 Lettuce 长连接场景下如何实现栈复用,如何防范连接泄漏?

  • 虚拟线程的栈复用与挂载
  • 长连接下的资源管理
  • 连接泄漏防范(超时、关闭、池)

虚拟线程由 JVM 调度,在阻塞点(如等待 IO)时挂起并把载体线程让给其他虚拟线程,栈在挂起时复制/续用,从而用很少的载体线程支撑海量虚拟线程。在 Lettuce 长连接场景,虚拟线程调用同步 API 时阻塞挂起,共享的 Lettuce 连接被持续复用,不阻塞载体线程。连接泄漏防范:一是设置命令超时(commandTimeout),避免阻塞挂起无限等待;二是连接用完 close、客户端 shutdown 释放共享资源;三是使用连接池限制并发连接并配合空闲回收;四是避免在虚拟线程中无限创建连接对象。核心是限定并发与超时,避免资源随请求无限增长。

虚拟线程解决"阻塞线程数"问题而非"连接数"问题,长连接必须有超时与关闭兜底,否则虚拟线程的"廉价"反而会掩盖连接或后续资源泄漏。

#
★★★

7. Lua 脚本在分布式锁的应用(CAS 重入)

Lua 脚本在分布式锁中如何实现 CAS 校验与可重入?

  • Lua 原子脚本实现加锁/解锁
  • CAS 校验持有者(compare-and-set)
  • 可重入计数

分布式锁用 Lua 脚本把"校验 + 操作"原子化。加锁:SETNX key value,value 为 UUID:threadId 标识持有者;若 key 已存在且 value 等于当前持有者,则计数加一(可重入)。解锁:Lua 脚本校验 value 是否为本持有者,是才删除/计数减一,避免误删他人锁(CAS 语义)。可重入通过记录"持有者 + 计数"实现:同一线程重复加锁计数 +1,解锁计数 -1 归零才删除。全部用 Lua 保证多步操作原子,避免并发下的竞态。

Lua 的核心价值是"读-判断-写"在 Redis 单线程内原子执行,杜绝了"先判断后删除"之间的竞态窗口。CAS 校验持有者防止误删,计数实现可重入。

// 解锁 Lua:校验持有者再删除(CAS)
String unlockScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
    "then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>(unlockScript, Long.class);
Long r = redisTemplate.execute(script, List.of("lock:key"), "uuid:thread");
#
★★★

8. Lua 脚本在原子操作的应用

Lua 脚本在 Redis 原子操作中有哪些应用场景?

  • Lua 的原子性保证
  • 复合原子操作(扣减、限流、锁)
  • 与多命令的对比

Lua 脚本在 Redis 中以原子方式执行,执行期间不会被其他命令/脚本穿插,适合把"读-判断-写"多步操作封装为单个原子单元。典型应用:库存扣减(判断库存>0 再 DECR)、限流(滑动窗口计数+过期)、分布式锁(加锁/释放)、批量更新的业务规则、防止超卖、原子实现"取值-计算-写回"。相比 client 侧多命令(存在竞态窗口),Lua 保证原子;相比事务(MULTI/EXEC),Lua 可以包含复杂逻辑(if/循环),且不会因中间命令失败而整体回滚(Lua 更灵活但需自行处理错误)。

Lua 是"在 Redis 端执行的一段原子程序",把并发安全内聚到服务端。选型时:需要简单原子用单个命令,需要多步逻辑用 Lua,需要可回滚的批量"事务"语义用 MULTI/EXEC。

#
★★★

9. Redis 7 的 FUNCTION 与分布式锁

Redis 7 的 FUNCTION(函数)如何用于分布式锁?

  • FUNCTION 与 Lua 脚本的关系
  • 函数库管理锁逻辑
  • 集群一致性

Redis 7 的 FUNCTION 把分布式锁的 Lua 逻辑封装成命名函数库,通过 FUNCTION LOAD 加载到所有节点,用 FCALL 调用。相比 EVAL 脚本,FUNCTION 解决脚本管理的痛点:无需管理脚本缓存哈希(避免 EVALSHA 的 NOSCRIPT)、集群下自动一致加载、支持版本管理(FUNCTION LIST/FLUSH)。分布式锁的加锁/解锁/续期逻辑可写成函数库中的一个函数,配以 ARGS 传入持有者与 TTL,从而把锁逻辑统一管理、复用。FCALL 同样原子执行,保留与 EVAL 相同的正确性。

FUNCTION 是 EVAL 的下一代封装,把锁的 Lua 逻辑代码化、可版本化、可跨节点一致部署,工程上更易维护,但运行语义(原子性)与 Lua 一致。

#
★★★

10. Redis 7 的 只读函数调用(FCALL_RO) 性能

Redis 7 的 只读函数调用(FCALL_RO)(分片函数)在性能上的特点是什么?

  • 只读函数调用(FCALL_RO) 与 FUNCTIONS 的区别
  • 分片函数在集群中的执行
  • 性能动机

Redis 7 引入 只读函数调用(FCALL_RO)(FCALL_RO 等带 _RO 后缀的只读变体),用于在 Cluster 中把函数按槽位路由执行。普通 Functions 在集群中要求所有 key 命中同一槽,否则报 CROSSSLOT;只读函数调用(FCALL_RO) 通过 fcall_ro 限制只读命令,并支持在多个分片上并行执行(如对多个 key 分布在多个槽的场景),从而提升跨分片操作的吞吐。性能上,只读函数调用(FCALL_RO) 减少跨槽限制与串行化,配合只读语义可在多个分片并行处理,适合需要对多分片做只读聚合/计算的场景。

只读函数调用(FCALL_RO) 是"函数 + 集群分片路由"的结合,核心是通过只读变体与分片并行提升集群场景性能,同时保持函数的原子执行语义。

#
★★★

11. Redis 7 的 latency-tracking 与 INFO LATENCY

Redis 7 的 latency-tracking 与 INFO LATENCY 如何帮助定位延迟问题?

  • latency-tracking(命令级延迟追踪)
  • INFO LATENCYSTATS / LATENCY HISTOGRAM
  • 定位慢命令与延迟源

Redis 7 引入命令级延迟追踪:latency-tracking 配置开启后,Redis 记录每个命令的延迟直方图(latency histogram),并通过 INFO LATENCYSTATSLATENCY HISTOGRAM 查看。它比传统 LATENCY LATEST(仅记录事件)更细粒度,能反映命令执行的延迟分布(如 p50/p99),帮助定位慢命令、资源竞争与阻塞。INFO commandstats 提供命令调用次数与耗时统计,LATENCY EVENT 记录慢事件(如 fork、aof fsync)。工程上结合 slowlog--latency 工具,可系统定位延迟瓶颈。

latency-tracking 提供"命令级延迟直方图",是从"有无延迟"到"延迟分布"的精细化,配合 slowlog 与 INFO 多维度定位延迟源。

#
★★★

12. Redis 7.x 的 Client-Side Caching(客户端缓存,TRACKING)与 RESP3 协议

Redis 7.x 的 Client-Side Caching(客户端缓存,TRACKING)与 RESP3 协议如何配合?

  • CLIENT TRACKING 缓存失效推送
  • RESP3 的 Push 消息与失效通知
  • 本地缓存与 Redis 的一致性

Client-Side Caching 允许客户端在本地缓存数据,通过 CLIENT TRACKING ON 开启,Redis 在数据被修改时向客户端推送失效通知(invalidation message),客户端据此删除本地缓存,实现本地缓存与 Redis 的一致性。该功能依赖 RESP3 协议的 Push 类型(> 前缀)来承载服务端主动推送的失效事件。开启 TRACKING 后,客户端维护 "global cache" 或 "redirect" 模式,收到失效通知即失效对应本地 key。Redis 7.x 增强了 TRACKING 的广播模式与确定性,降低网络开销。工程上可与 Lettuce 的 RESP3 客户端缓存(ClientCache)结合,实现"本地缓存 + Redis 推送失效"的低延迟读。

客户端缓存 = 本地缓存 + 服务端主动失效通知,RESP3 的 Push 是传送失效通知的协议基础。它解决"本地缓存与 Redis 数据不一致"的问题,同时保留本地缓存低延迟。

#
★★★

13. Redis 7.x 的 RedLock(红锁)分布式锁在 Spring Boot 3.5+ 的 Redisson 集成

Redis 7.x 的 RedLock(红锁)分布式锁如何在 Spring Boot 3.5+ 中与 Redisson 集成?

  • RedLock 算法(多节点多数派)
  • Redisson 的 RedissonRedLock 实现
  • Spring Boot 集成与配置

RedLock 算法要求写锁到多个(奇数个)独立 Redis 节点,只有获得多数派(N/2+1)节点成功才算持锁,从而降低单点故障导致锁失效的风险。Redisson 提供 RedissonRedLock,把多个 RLock 组合成红锁,lock() 时向各节点加锁并统计成功率。Spring Boot 3.5+ 中通过 RedissonClient 配置多个 Config.useSingleServer()/useClusterServers() 或使用 RedissonMultiLock,把 Redisson 作为 Bean 注入,业务代码用 RedissonRedLock 获取分布式锁。RedLock 有争议(依赖时钟、网络分区),但仍是工程上多副本场景的常用实现。

RedLock 的价值是"多副本多数派"降低单点锁失效风险,但依赖时钟与网络,存在理论争议。工程落地多用 Redisson 现成 API,并清醒认识其边界。

RLock lock1 = redisson1.getLock("lockKey");
RLock lock2 = redisson2.getLock("lockKey");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2);
redLock.lock(10, TimeUnit.SECONDS);
try { /* 业务 */ } finally { redLock.unlock(); }
#
★★★

14. Redis Cluster 的 Spring 集成

Redis Cluster 在 Spring(Spring Data Redis)中如何集成?

  • RedisConnectionFactory 的集群配置
  • Spring Boot 的 spring.data.redis.cluster 配置
  • 槽路由与故障转移

Spring Data Redis 通过 LettuceConnectionFactoryJedisConnectionFactory 支持 Cluster,配置 RedisClusterConfiguration(节点列表 + 密码等)。Spring Boot 中通过 spring.data.redis.cluster.nodes 配置集群节点,自动创建 LettuceClusterConnectionFactory。底层 Lettuce 处理槽路由、MOVED/ASK 重定向与拓扑刷新,Spring 透明使用集群。开发时注意:cluster 模式下多 key 操作需落在同一槽(用 hash tag {});@Transactional 缓存与 Redis 事务在 cluster 下受限。故障转移时 Lettuce 感知节点切换并重试。

Spring 集成 Cluster 的核心是 ConnectionFactory 抽象,真正路由由 Lettuce 完成。开发者只需配置节点,但要遵守槽位约束(hash tag、单 key 操作)。

spring:
  data:
    redis:
      cluster:
        nodes: 127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002
#
★★★

15. Redis Pub/Sub 在 Spring 的 MessageListenerAdapter

Redis Pub/Sub 在 Spring 中如何通过 MessageListenerAdapter 使用?

  • Pub/Sub 的发布/订阅模型
  • MessageListenerAdapter 与消息处理
  • 序列化与消息映射

Redis Pub/Sub 是"发布-订阅"模式,发布者向频道发消息,订阅者收到后处理,消息不持久化(订阅者离线则丢失)。Spring 通过 RedisMessageListenerContainer 管理订阅,用 MessageListenerAdapter 把消息路由到普通 Java 方法:它用 MessageConverter 反序列化消息体,并通过 onMessage 或按方法名(如 handleMessage)分发。RedisMessageListenerContainer 有独立线程消费,支持多个监听器订阅不同频道。配置时需指定 Topic(如 PatternTopic 支持通配符)与监听者。

MessageListenerAdapter 把"回调接口"适配为"业务方法",屏蔽了 MessageListener 的底层 Message 细节。注意 Pub/Sub 不持久化,适合通知/广播类场景而非可靠消息队列。

@Configuration
public class PubSubConfig {
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory cf, MessageListenerAdapter adapter) {
        var c = new RedisMessageListenerContainer();
        c.setConnectionFactory(cf);
        c.addMessageListener(adapter, new PatternTopic("orders:*"));
        return c;
    }
    @Bean
    MessageListenerAdapter adapter(OrderHandler handler) {
        return new MessageListenerAdapter(handler, "handleOrder");
    }
}
#
★★★

16. Redis Sentinel 的 Spring 集成

Redis Sentinel 在 Spring 中如何集成?

  • Sentinel 的高可用与故障转移
  • Spring 的哨兵配置(master + nodes)
  • 读写分离与故障感知

Redis Sentinel 负责监控主从,主节点故障时自动提升从节点为新主,客户端通过 Sentinel 获取当前 master 地址。Spring 通过 RedisSentinelConfiguration 配置 master 名称与 Sentinel 节点列表,LettuceConnectionFactory/JedisConnectionFactory 会向 Sentinel 查询 master 并在故障转移后自动切换。Spring Boot 中用 spring.data.redis.sentinel.masternodes 配置。读写分离可配置 setReadFrom(如 REPLICA),但需注意从节点数据延迟。故障转移期间客户端可能短暂报错,Lettuce 会自动重连新主。

Sentinel 集成让客户端对 master 故障透明,核心是"通过 Sentinel 发现 master 并随切换更新"。读写分离到从库需自行权衡一致性。

spring:
  data:
    redis:
      sentinel:
        master: mymaster
        nodes: 127.0.0.1:26379,127.0.0.1:26380
#
★★★

17. Redisson 分布式锁的看门狗(Watchdog)续期机制

Redisson 分布式锁的看门狗(Watchdog)续期机制是如何工作的?

  • 看门狗定时续期
  • 续期的 Lua 脚本与持有者校验
  • 续期失效与超时

Redisson 的看门狗默认在加锁成功后,为锁设置 30 秒超时,并启动一个后台调度任务每 10 秒执行一次续期 Lua 脚本:校验锁持有者(value == 当前线程标识)未变,则把过期时间重置为 30 秒。这样只要业务持有锁且持有者未变,锁就持续有效,避免业务未完成时锁被自动过期。若持有者崩溃(线程结束或 JVM 停止),续期任务停止,锁会在 30 秒后自动释放,避免死锁。lockWatchdogTimeout 可自定义,关闭看门狗可用 lock(leaseTime, unit) 指定固定租期。

看门狗解决"锁过期 vs 业务未完成"的矛盾,通过持有者校验续期保证只有持有者能续期。崩溃时续期停止,锁自动过期释放,兼顾安全与可用。

#
★★★

18. 分布式锁与 Redisson 同步器(Semaphore/CountDownLatch)

Redisson 的同步器(Semaphore/CountDownLatch)与分布式锁有何关系?

  • Redisson 的 RSemaphore/RCountDownLatch
  • 与分布式锁的异同
  • 跨实例同步场景

Redisson 提供 RSemaphore(分布式信号量)与 RCountDownLatch(分布式倒计时门闩),用于跨多个 JVM 实例的同步协调,与分布式锁同属"跨进程互斥/同步"工具。RSemaphore.tryAcquire 基于 Redis 原子计数,可限制并发访问数量;RCountDownLatchcountDown/await 实现跨实例"等待所有任务完成"。区别:分布式锁是"互斥"(同一时刻只有一人持有),信号量是"限流"(最多 N 人),门闩是"栅栏"(所有人都到达才放行)。两者都基于 Redis 原子操作与 Lua 脚本。

分布式锁、信号量、门闩覆盖不同同步语义:互斥、限流、聚合等待。Redisson 把它们统一抽象为分布式同步原语,底层都是 Redis 原子操作。

#
★★

19. Redis Stream 在 Spring Data Redis(2.6+)

Redis Stream 在 Spring Data Redis 2.6+ 中如何消费?

  • StreamMessageListenerContainer
  • @StreamListener 注解
  • 消费者组与 ack

Spring Data Redis 2.6+ 提供完整的 Stream 支持:StreamMessageListenerContainer 封装阻塞式 XREADGROUP 消费循环,自动管理消费者组与偏移;@StreamListener(target = "stream", condition = "...") 注解标注消费方法,声明式消费。StreamOperations 提供 add/read/ack/delete 底层操作。容器支持 AcknowledgmentHandler 手动 ack(RECEIVE 模式)或自动 ack(AUTO 模式)。消费者组需先 XGROUP CREATE 创建,消费后 ack 移出 PEL。配合 StreamMessageListenerContainerOptions 配置轮询超时与并发。

@StreamListener 让开发者声明式消费 Stream,容器负责阻塞拉取与 ack 管理。适用于 Redis 作为轻量消息队列的场景,需自行管理消费者组与重试。

#
★★

20. Redis 与本地缓存(Caffeine/Guava Cache)

Redis 与本地缓存(Caffeine/Guava Cache)如何配合使用?

  • 多级缓存(L1 本地 + L2 Redis)
  • 本地缓存的一致性
  • 容量与命中率

本地缓存(Caffeine/Guava)作为一级缓存(L1),Redis 作为二级缓存(L2),数据库为最终源。读路径先查 L1(Caffeine,低延迟、进程内),未命中查 L2(Redis),再未命中查库并回填。优点:Caffeine 命中可获得极低延迟,减少 Redis 与 DB 压力。缺点:本地缓存是进程内副本,多实例间数据不一致,写操作需主动失效或依赖消息(如 Redis Pub/Sub 广播失效)同步。Caffeine 用 expireAfterWrite/maximumSize 控制容量与 TTL,命中率高时收益大,但需权衡一致性与内存占用。

多级缓存的核心是"Local 快 + Redis 共享",收益是低延迟与降负载,代价是一致性维护。强一致场景慎用本地缓存,弱一致可用 TTL + 广播失效收敛。

#
★★

21. Redis 关键性能参数(maxmemory/maxmemory-policy)

Redis 的 maxmemory 与 maxmemory-policy 参数如何影响性能与淘汰?

  • maxmemory 内存上限
  • 淘汰策略(volatile-lru/allkeys-lru 等)
  • 淘汰对性能的影响

maxmemory 设置 Redis 最大可用内存,超过后按 maxmemory-policy 进行淘汰。策略包括:noeviction(不淘汰,写报错)、volatile-lru(仅淘汰设置了 TTL 的键中最久未用)、allkeys-lru(所有键 LRU)、volatile-lfu/allkeys-lfu(LFU 频率)、volatile-random/allkeys-randomvolatile-ttl(快过期先淘汰)。生产上常设 allkeys-lruallkeys-lfu 保证缓存可用内淘汰。淘汰在 maxmemory 触发时进行,可能影响主线程性能(大量淘汰会增加延迟),需监控 evicted_keys 与内存。正确规划容量避免频繁淘汰。

maxmemory-policy 决定"内存满了怎么办"。缓存场景用 LRU/LFU 淘汰冷数据,数据场景用 noeviction 防误删。淘汰是异步分批(Redis 6+ 有 lazy 淘汰)但仍需关注。

#
★★

22. Redis 慢查询日志(slowlog-log-slower-than)

Redis 的慢查询日志(slowlog-log-slower-than)如何配置与使用?

  • slowlog 配置与阈值
  • SLOWLOG GET 查看
  • 慢查询与定位

slowlog-log-slower-than 设置慢查询阈值(微秒),执行时间超过该值的命令会被记录到慢查询日志;slowlog-max-len 限制日志条数(环形队列)。SLOWLOG GET 查看记录,含命令、参数、耗时、时间戳。它不记录网络/排队时间,只记录命令执行耗时,用于定位服务端慢命令(如大 KEYS、SMEMBERS、SORT、大范围操作)。注意:慢查询日志本身占内存,及时消费;结合 --latencyMONITOR 定位。工程上把阈值设为业务可接受值(如 10ms),并监控慢查询趋势。

slowlog 是定位 Redis 服务端慢命令的首要工具,阈值设置需平衡"捕获率"与"日志噪音",配合命令改造(减量、分页、Lua)优化。

#
★★

23. Redis 的 BZPOPMIN/BLMPOP(阻塞操作)

Redis 的 BZPOPMIN/BLMPOP 等阻塞操作如何使用?

  • 阻塞列表/ZSet 弹出命令
  • BLPOP/BZPOPMIN/BLMPOP
  • 阻塞与超时语义

BLPOP/BRPOP 阻塞弹出列表头/尾元素;BZPOPMIN/BZPOPMAX 阻塞弹出有序集合的最小/最大元素;BLMPOP/BZMPOP(7.0+)支持从多个 key 中弹出。它们作为"阻塞式消费者"实现简单任务队列/优先级队列:没有元素时命令阻塞至超时(可设 0 表示无限),有元素立即返回。使用注意:阻塞命令会占用连接,需设置合理超时;多客户端竞争时用并行或阻塞队列避免长连接占用;BLPOP 原生支持多 key 与超时。阻塞命令在虚拟线程下阻塞虚拟线程不占平台线程,但连接仍被占用。

阻塞命令把"轮询"变成"阻塞等待",降低空转,用于任务队列、优先级队列。要控制连接占用与超时,避免阻塞过多连接。

#
★★

24. Redis 的 Lua 脚本与 Spring 的 RedisScript

Redis 的 Lua 脚本如何与 Spring 的 RedisScript 配合使用?

  • RedisScript 封装 Lua 脚本
  • DefaultRedisScript 与 ResultType
  • 参数传递(KEYS/ARGV)

Spring 用 DefaultRedisScript 封装 Lua 脚本,指定脚本文本与返回类型(ResultType),RedisTemplate.execute(script, keys, args) 执行。Spring 会自动把脚本传给 Redis(EVAL)并缓存脚本哈希(EVALSHA),减少传输。KEYS 与 ARGV 分别传入:第一个参数数组传给脚本的 KEYS,其余传给 ARGV。脚本用 redis.call 调用命令。Spring 的 RedisScript 支持返回 LongList、自定义对象(需序列化)。注意:DefaultRedisScript 建议作为单例 Bean,避免每次创建脚本对象。

RedisScript 把 Lua 脚本声明式封装,让 Spring 管理脚本哈希与参数序列化,减少手写 EVAL 细节。返回类型需与服务端 Lua 返回一致。

@Bean
DefaultRedisScript<Long> incrScript() {
    DefaultRedisScript<Long> s = new DefaultRedisScript<>();
    s.setScriptText("return redis.call('INCR', KEYS[1])");
    s.setResultType(Long.class);
    return s;
}
// 执行
Long n = redisTemplate.execute(incrScript(), List.of("counter"));
#
★★

25. Redis 的 OBJECT 子命令

Redis 的 OBJECT 子命令有哪些用途?

  • OBJECT ENCODING/REFCOUNT/IDLETIME
  • 查看键的内部编码与值大小
  • 内存与编码优化

OBJECT 子命令用于查看键的内部元数据:OBJECT ENCODING key 返回内部编码(如 int、embstr、raw、hashtable、ziplist、listpack),帮助判断数据结构是否被压缩;OBJECT REFCOUNT 返回引用计数;OBJECT IDLETIME 返回空闲时间(配合 LRU 判断访问热度);OBJECT FREQ(LFU 下)返回访问频率。这些命令辅助诊断内存占用与编码优化(如把小 List 用 listpack 压缩、大字符串用 raw)。OBJECT 是只读诊断命令,不改变数据。

OBJECT 用于理解 Redis 内部编码与内存布局,是内存调优与编码诊断的工具,配合 --bigkeysMEMORY USAGE 全面分析。

#
★★

26. Redis 的 Pipeline 与 Spring Data Redis

Redis 的 Pipeline 在 Spring Data Redis 中如何使用?

  • executePipelined 批量执行
  • 减少网络往返
  • 无原子性

Spring Data Redis 用 executePipelined(RedisCallback) 执行管道操作:回调内提交的命令不会立即返回,而是打包后批量发送到服务端,减少网络往返(RTT),显著提升批量吞吐。返回值通过 executePipelined 的返回 List<Object> 获取(按顺序)。注意:pipeline 不保证原子性(命令间可被其他请求穿插),且命令结果在全部执行后才返回。适合批量写入、批量读取场景;配合 executePipelined(conn -> {...}, serializer) 指定序列化。Lettuce 的 execute 也支持 flushCommands

Pipeline 优化的是"网络往返"而非"原子性",批量操作收益大,但需注意内存(积压命令)与错误定位。

List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) conn -> {
    for (int i = 0; i < 1000; i++) {
        conn.stringCommands().set("k" + i, "v" + i);
    }
    return null;
});
#
★★

27. Redis 的 Pub/Sub 在缓存失效的应用

Redis 的 Pub/Sub 如何应用于缓存失效?

  • 通过 Pub/Sub 广播失效通知
  • 多实例缓存一致性
  • 与 TRACKING 的对比

在本地缓存 + Redis 多级缓存场景,写操作更新 DB 后,通过 Redis Pub/Sub 向所有实例广播"key 失效"消息,各实例收到后删除本地缓存,从而在多实例间同步缓存失效,保持一致性。相比轮询/短 TTL,Pub/Sub 广播是实时、低成本的失效通知方式。实现上:每个实例订阅同一频道,写操作 publish 失效消息。注意 Pub/Sub 不持久化,若实例在消息发送时离线会漏收(需 TTL 兜底);也可用 Redis 7 的 CLIENT TRACKING 的广播模式替代。工程上常把"删除 Redis + 广播失效"结合。

Pub/Sub 做缓存失效广播是"多实例本地缓存一致性"的常用手段,实时但非可靠(离线漏收),需 TTL 兜底,避免过度依赖。

#
★★

28. Redis 的 hotkey 检测(redis-cli --hotkeys)

Redis 的 hotkey 检测(redis-cli --hotkeys)如何操作?

  • --hotkeys 扫描热键
  • 依赖 maxmemory-policy
  • 识别与治理

redis-cli --hotkeys 通过扫描所有 key 的访问频率(需要 maxmemory-policy 为 LFU 或 object freq 可用)来识别热点 key,输出按访问频率排序的 key 列表。它依赖 OBJECT FREQ,要求用 LFU 淘汰策略(allkeys-lfu/volatile-lfu)才能统计。注意:--hotkeys 会全量扫描(类似 KEYS),可能阻塞主线程,生产需低峰期执行或使用 SCAN 逐步统计。识别 hotkey 后可通过 key 分片(加后缀拆分)、本地缓存、副本读等缓解。Redis 7.2+ 对 LFU 的统计更精确。

hotkey 检测是"先定位后治理",LFU 统计提供访问频率依据。扫描成本高(全量),需在低峰执行,治理靠拆分与本地缓存兜底。

#
★★

29. Redis 的 io-threads-do-reads 配置

Redis 的 io-threads-do-reads 配置有什么作用?

  • io-threads 多线程 IO
  • io-threads-do-reads 的读写线程
  • 适用场景与边界

Redis 默认单线程处理命令,但 IO 读写可用多线程。io-threads 开启多线程处理网络 IO(写响应),io-threads-do-reads yes 额外让读请求也分摊到 IO 线程。这能提升多核机器上高吞吐/高并发网络场景的性能,因为命令解析与响应发送不再全在主线程。注意:命令执行(逻辑)仍是单线程,多线程只并行化 IO 解析与读写;且启用后需 io-threads 配置合理(建议 4-8),过小无收益、过大上下文切换。该配置对 CPU 密集逻辑收益有限,主要缓解网络 IO 瓶颈。

io-threads 是"单线程执行 + 多线程 IO"模型,针对网络 IO 瓶颈优化,不改变命令执行的单线程语义。

#
★★

30. Redis 的 jemalloc 与内存分配器

Redis 的 jemalloc 与内存分配器有何关系?

  • Redis 默认使用 jemalloc
  • 内存分配与碎片
  • jemalloc 的优化(tcmalloc 等)

Redis 默认使用 jemalloc 作为内存分配器(编译时指定),相比 glibc malloc,jemalloc 在减少碎片、多线程扩展性、缓存友好性上更优,支持多种分配粒度(size classes)与线程本地缓存,适合 Redis 频繁小对象分配的场景。使用 jemalloc 时可用 MEMORY STATS/MEMORY DOCTOR 查看分配器统计与碎片。若需替换分配器(如 tcmalloc),需在编译时指定并重新基准测试。jemalloc 的 arena 与线程缓存也影响内存峰值,碎片率高时用 active-defrag 或重启整理。

内存分配器决定内存使用效率与碎片率。jemalloc 是 Redis 默认选择,理解其 size class 与线程缓存有助于解释内存占用与碎片。

#
★★

31. Redis 的 latency 子命令的工程价值

Redis 的 latency 子命令有什么工程价值?

  • LATENCY LATEST/HISTORY/DOCTOR
  • 定位延迟事件
  • 监控与告警

LATENCY 子命令用于监控延迟事件:LATENCY LATEST 返回最近发生延迟事件的类型与时间;LATENCY HISTORY event 查看某事件历史;LATENCY RESET 重置;LATENCY DOCTOR 给出诊断建议;LATENCY GRAPH 图形化。Redis 会记录慢事件(如命令阻塞、fork、AOF fsync、eviction)作为延迟事件,帮助定位"间歇性延迟尖刺"的根源。工程上配合 latency-monitor-threshold 配置阈值,及时告警并定位是 CPU 竞争、磁盘 fsync、还是 fork 等导致。

LATENCY 子命令是针对"延迟事件"的诊断工具,区别于客户端命令延迟,它定位服务端内部慢事件,是排查延迟尖刺的关键。

#
★★

32. Redis 的事务(MULTI/EXEC)与 Lua 的取舍

Redis 的事务(MULTI/EXEC)与 Lua 脚本如何取舍?

  • MULTI/EXEC 的原子性与回滚
  • Lua 的原子性与逻辑
  • 选型

MULTI/EXEC 事务把命令排入队列,EXEC 时顺序执行,保证原子性(期间不被其他命令插入),但出现命令错误时不会回滚已执行的命令(MULTI 事务只保证"一次性执行",无回滚语义);且事务内不能包含条件逻辑(所有命令预先排好)。Lua 脚本同样原子执行,但可包含 if/循环等逻辑,能依据运行状态决定执行哪些命令,更灵活;但 Lua 出错时同样不自动回滚(需自行处理)。取舍:需要"原子 + 简单命令序列"用 MULTI/EXEC;需要"判断逻辑 + 原子"用 Lua;需要"可回滚"两者都不满足(需外部补偿)。

MULTI/EXEC 与 Lua 都是原子执行,区别在"能否包含逻辑"与"预排 vs 运行时决策"。Lua 更强大,工程上复杂原子操作优先 Lua。

#
★★

33. Redis 的内存碎片率(mem_fragmentation_ratio)

Redis 的内存碎片率(mem_fragmentation_ratio)如何解读与治理?

  • 碎片率计算与含义
  • 碎片成因
  • active-defrag 与治理

mem_fragmentation_ratio = used_memory_rss / used_memory,反映物理内存(RSS)与逻辑内存的比值。碎片率 >1 表示存在碎片(分配器内部碎片),<1 表示虚拟内存/swap 或内存不足。正常范围约 1~1.5;持续偏高(>1.5)说明碎片严重,可用 active-defrag 自动整理(碎片整理线程),或重启/迁移。过高碎片率浪费内存,可通过 MEMORY DOCTOR 诊断。碎片率高常见于频繁增删小对象、不同 size class 混用。治理:合理设置 active-defrag-* 参数,或规划内存与对象大小。

碎片率是内存健康指标,>1.5 需关注。active-defrag 在后台整理碎片,避免频繁重启,但整理本身有 CPU/内存开销。

#
★★

34. RedisBloom/RedisSearch 等模块的性能影响

RedisBloom/RedisSearch 等 Redis 模块对性能有何影响?

  • 模块的扩展能力
  • Bloom Filter、全文搜索的计算开销
  • 内存与 CPU 影响

Redis 模块(RedisBloom、RedisSearch、RedisJSON、RedisTimeSeries 等)在 Redis 内部以 C 实现扩展命令,把复杂数据结构(布隆过滤器、倒排索引、JSON、时序)下沉到服务端。性能影响:一方面把计算放到内存、减少客户端往返,能显著提升特定场景(如布隆过滤器判断、全文搜索)效率;另一方面,模块命令在单线程执行,复杂计算(如大索引查询、正则)会阻塞主线程,增加延迟;模块也占用额外内存。需评估命令复杂度与内存开销,避免把重计算放入主线程。RedisBloom 的 BF 查询 O(k) 快,RedisSearch 的复杂查询可能成慢命令。

模块提升功能密度但引入主线程计算与内存开销。选型要评估是"内存加速"还是"主线程阻塞",复杂查询用副本或降级。

#
★★

35. RedisTemplate 的事务支持(SessionCallback)

RedisTemplate 的事务支持(SessionCallback)如何使用?

  • SessionCallback 绑定连接
  • multi/exec 事务
  • 与 RedisConnection 的关系

RedisTemplate.execute(new SessionCallback<Object>() {...}) 让回调在同一个 RedisConnection 上执行,保证命令绑定到同一连接,从而支持 multi/exec 事务(事务命令必须在同一连接)。SessionCallback 内执行 conn.multi()、命令、conn.exec(),它们在同一连接排队执行,保证原子性。相比直接调用 execute 多次(可能不同连接),SessionCallback 确保连接一致。Spring 还有 @Transactional 对 Redis 的同步(RedisTemplate 参与本地事务),但跨数据库需外部事务管理器。

SessionCallback 的核心是"同一连接多次操作",是 Redis 事务/管道的前提。它让开发者手动控制事务边界,适合需要精确控制批次与事务的场景。

redisTemplate.execute(new SessionCallback<Object>() {
    public Object execute(RedisOperations ops) {
        ops.multi();
        ops.opsForValue().set("a", "1");
        ops.opsForValue().set("b", "2");
        return ops.exec(); // 原子提交
    }
});
#
★★

36. RedisTemplate.opsForHash() 的批量 HSET

RedisTemplate.opsForHash() 的批量 HSET 如何执行?

  • opsForHash().putAll()
  • 批量写入 Hash
  • put 与 putAll 的差异

opsForHash() 提供 Hash 操作:put(key, hashKey, value) 单字段写入,putAll(key, Map) 一次性写入多个字段(底层 HSET key field value ...),putIfAbsent 仅在字段不存在时写入。批量用 putAll 减少命令往返,提高效率。entries(key) 读取全部,multiGet 批量读取多个字段。注意:HSET 对字段存在与否不校验,重复写入覆盖;putAll 在 Redis 7+ 一次 HSET 多字段更高效。配合 increment 做字段计数。

批量操作(putAll/multiGet)能减少 RTT,opsForHash 的 HSET 是"对象存储"的常用形态,适合存实体字段。

Map<String, Object> map = new HashMap<>();
map.put("name", "zhang");
map.put("age", 30);
redisTemplate.opsForHash().putAll("user:1", map);
#
★★

37. RedisTemplate.opsForValue() 的常用 API

RedisTemplate.opsForValue() 有哪些常用 API?

  • set/get/setIfAbsent/increment
  • 过期时间
  • 字符串操作

opsForValue() 封装 String 类型操作,常用 API:set(key,value) 写入、set(key,value,timeout,unit) 带过期写入、get(key) 读取、getAndSet 取旧值写新值、setIfAbsent(SETNX,分布式锁/防重复)、increment/decrement 原子自增自减、append 追加、multiSet/multiGet 批量、getExpire 查看 TTL。它对应 Redis 的 GET/SET/INCR/APPEND 等命令,是使用最频繁的 ops。setIfAbsent 常用于分布式锁与幂等,increment 用于计数。

opsForValue 是 RedisTemplate 最常用的操作入口,覆盖字符串与数值的读写、原子操作、过期管理,是缓存与计数的基础。

redisTemplate.opsForValue().set("key", "value", 10, TimeUnit.MINUTES);
Boolean ok = redisTemplate.opsForValue().setIfAbsent("lock", "1", 5, TimeUnit.SECONDS);
Long n = redisTemplate.opsForValue().increment("counter");
#
★★

38. RedisTemplate/StringRedisTemplate 的序列化器

RedisTemplate 与 StringRedisTemplate 的序列化器如何配置?

  • 默认序列化器
  • StringRedisTemplate 的 String 序列化
  • 自定义序列化器(Jackson/JSON)

RedisTemplate 默认使用 JdkSerializationRedisSerializer,key 和 value 都按 Java 序列化(二进制、不可读、需类可序列化);StringRedisTemplate 默认使用 StringRedisSerializer(UTF-8 字符串,可读)。自定义序列化:setKeySerializer/setValueSerializer/setHashKeySerializer/setHashValueSerializer,常用 GenericJackson2JsonRedisSerializer(value 存 JSON,可读、跨语言)、StringRedisSerializer(key 存字符串)。配置后需 afterPropertiesSet() 生效。注意 key 与 value 序列化器分开设置,避免不一致。

序列化器决定数据形态,生产应统一为"key 用 String、value 用 JSON/String",避免默认 JDK 序列化的不可读与耦合。

RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(cf);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
#
★★

39. Redisson 的 LockPubSub 与解锁通知

Redisson 的 LockPubSub 与解锁通知机制是什么?

  • 锁等待的订阅机制
  • LockPubSub 订阅解锁事件
  • 优化锁竞争

Redisson 的分布式锁在加锁失败(锁被他人持有)时,不会一直忙轮询,而是通过 LockPubSub 订阅锁的频道(lock channel),等待锁持有者解锁时发布解锁事件,等待线程收到通知后再次尝试加锁。这样把"轮询等待"变为"事件驱动等待",减少无效的 Redis 请求与 CPU 消耗。当锁被释放时,unlockpublish 到频道,所有等待者收到后重新竞争。LockPubSub 是 Redisson 内部基于 Pub/Sub 的锁等待通知机制,配合可重入与看门狗构成完整锁实现。

LockPubSub 用 Pub/Sub 实现"解锁即唤醒",避免自旋轮询,是分布式锁高效等待的关键设计。注意 Pub/Sub 消息不持久,等待者需配合超时兜底。

#
★★

40. Redisson 的 RLock/RFairLock/RReadWriteLock

Redisson 的 RLock/RFairLock/RReadWriteLock 有何区别?

  • RLock 普通锁(可重入)
  • RFairLock 公平锁
  • RReadWriteLock 读写锁

RLock 是普通可重入分布式锁(非公平,抢占式);RFairLock 是公平锁,保证按加锁请求顺序获取锁,通过维护一个等待队列实现,避免"饿死"但加锁开销更大;RReadWriteLock 是读写锁——读锁可多个并发持有,写锁互斥,且写锁优先(有写锁等待时新的读锁不授予),适合"读多写少"但需写互斥的场景。三者的选择:默认用 RLock,需公平性用 RFairLock,需读写分离用 RReadWriteLock。实现都基于 Redis 原子操作与 Lua。

三类锁对应不同同步语义:通用互斥、公平排队、读写分离。公平锁开销大、读写锁在"读写冲突"场景需谨慎(写锁互斥)。

#
★★

41. RedissonClient 的 tryLock(timeout, unit) 与阻塞

RedissonClient 的 tryLock(timeout, unit) 的阻塞语义是什么?

  • tryLock 等待超时
  • 阻塞与返回
  • 租期与看门狗

tryLock(waitTime, unit) 表示最多等待 waitTime(如 3 秒)尝试获取锁,若在超时前获取成功返回 true,超时未获取返回 false(不阻塞)。tryLock(waitTime, leaseTime, unit) 额外指定租期(leaseTime,锁的固定有效时间,此时不开启看门狗,到期自动释放)。lock() 则无限阻塞直到获取。tryLock() 无参则立即尝试不等待。设计上 tryLock 的等待超时避免业务无限阻塞,常用于"获取不到锁就放弃/降级"的场景。注意 waitTime 与锁的租期、业务耗时的关系。

tryLock 的 waitTime 是"获取锁的等待容忍度",配合租期控制锁生命周期。避免在乐观竞争场景用无限阻塞的 lock()。

RLock lock = redisson.getLock("order");
if (lock.tryLock(3, TimeUnit.SECONDS)) {   // 最多等 3 秒
    try { /* 业务 */ } finally { lock.unlock(); }
} else {
    // 获取失败,降级/重试
}
#
★★

42. Redlock(Redis 分布式锁算法)的争议与边界

Redlock(Redis 分布式锁算法)存在哪些争议与适用边界?

  • Redlock 的多数派思想
  • 时钟与网络分区争议
  • 适用边界

Redlock 要求向多个独立 Redis 节点加锁,多数派成功才持锁,旨在降低单点故障导致锁失效的风险。争议来自 Martin Kleppmann 与 Redis 作者的辩论:Redlock 依赖各节点时钟同步(锁过期时间基于本地时钟),若某节点时钟跳变会导致锁提前过期;网络分区下,"多数派"与"持有者"可能不一致,导致两个客户端同时持锁(安全属性被破坏)。因此 Redlock 适用于"容忍小概率并发"的弱一致场景(如限流、幂等),不适用于需要强安全保证的强一致场景(如资金扣减)。工程上多数场景用单节点 Redisson + 看门狗已足够,Redlock 需谨慎评估。

Redlock 的争议核心是"时钟与网络分区下无法保证强互斥"。适用边界是"可用性优先、可容忍极小概率并发"的场景,强一致需用 ZK/etcd 提供的一致性算法。

#
★★

43. Spring 7 中 @TransactionalEventListener 与 Redis Pub/Sub 的事件驱动架构

Spring 7 中 @TransactionalEventListener 如何与 Redis Pub/Sub 结合实现事件驱动架构?

  • @TransactionalEventListener 的事务边界
  • 事务提交后发事件
  • 与 Pub/Sub 的异步解耦

@TransactionalEventListener(phase = AFTER_COMMIT) 标注的事件监听器在事务提交后触发,避免"事务未提交就发事件导致监听方读到旧数据"。结合 Redis Pub/Sub:在事务内发布一个"事件已就绪"标记,事务提交后监听器再把事件 publish 到 Redis 频道,其他实例订阅消费。这样既保证"事件在事务提交后发出"(一致性),又实现跨实例异步解耦。典型场景:订单创建成功(事务提交)后发消息通知。注意:Pub/Sub 不持久,事务提交后发布失败需补偿(可用本地消息表/Outbox)。

@TransactionalEventListener 解决了"事务与事件时序"问题,保证事件在数据可见后发出;配合 Pub/Sub 实现跨实例事件驱动,但需考虑可靠性(Outbox 兜底)。

#
★★

44. Spring Data Redis 的 RedisConnectionFactory 配置

Spring Data Redis 的 RedisConnectionFactory 如何配置?

  • LettuceConnectionFactory 配置
  • 连接池/超时/序列化
  • 配置项

Spring Data Redis 用 RedisConnectionFactory 抽象连接,默认 LettuceConnectionFactory。配置核心:LettuceClientConfiguration 设置 commandTimeoutshutdownTimeoutClientOptions(协议 RESP3、自动重连);ConsumerClientOptions/池配置(若用连接池)。RedisStandaloneConfiguration/RedisClusterConfiguration/RedisSentinelConfiguration 设置地址、密码、数据库。Spring Boot 通过 spring.data.redis.* 自动配置,也可手动定义 Bean 覆盖。还需配置 ClientResources 共享与序列化器。合理的超时与连接池是稳定性的关键。

ConnectionFactory 是 Spring Data Redis 的入口,正确配置地址、超时、序列化与连接管理决定可用性与性能。Lettuce 默认共享连接,池可选。

#
★★

45. redis-cli --bigkeys 的内存分析

redis-cli --bigkeys 如何做内存分析?

  • --bigkeys 扫描大 key
  • 各类数据结构的大 value 检测
  • 与 --memkeys 的区别

redis-cli --bigkeys 扫描所有 key,用类型判断与长度命令(STRLEN/LLEN/HLEN/SCARD/ZCARD 等)统计各类数据结构(String、List、Set、Hash、ZSet)中最大的 key,输出"最大 key 及其大小、类型"。它基于 SCAN 迭代抽样(非全量精确),用于发现大 key,避免大 key 导致的阻塞、内存倾斜与迁移问题。注意:--bigkeys 会执行采样命令,可能产生一定开销,建议低峰执行;它只显示"最大"而非全部大 key。--memkeys 则用 MEMORY USAGE 精确统计每个 key 的内存。

--bigkeys 是发现大 key 的常用工具,基于 SCAN 抽样,用于定位"内存/阻塞倾斜"的 key,配合拆分与优化。

#
★★

46. redis-cli --latency-history 的实时延迟采样

redis-cli --latency-history 如何做实时延迟采样?

  • --latency 实时延迟监控
  • --latency-history 历史采样
  • 定位延迟波动

redis-cli --latency 持续向 Redis 发送 PING 并统计往返延迟(min/max/avg),--latency-history 在采样窗口内统计并分段时间输出历史延迟(每段一个 min/max/avg),用于观察延迟随时间波动。这些工具反映客户端到服务端的网络+处理延迟,排查网络抖动、慢查询、大 key 导致的延迟尖刺。--latency-dist 显示延迟分布直方图。分析时结合服务端 LATENCY 事件与 slowlog 区分是网络还是服务端问题。

--latency-history 是"客户端视角的延迟采样",可观察延迟随时间的变化趋势,定位间歇性延迟尖刺与其时段。

#
★★

47. redis-cli --memkeys 的内存细分

redis-cli --memkeys 如何做内存细分分析?

  • --memkeys 用 MEMORY USAGE 统计
  • 精确内存占比
  • 与 --bigkeys 的区别

redis-cli --memkeys 使用 MEMORY USAGE 命令逐个 key 精确统计内存占用,并汇总输出各类型与 key 的内存分布,比 --bigkeys(抽样找最大)更精确地反映"每个 key 占多少内存、哪些 key 占内存多"。它遍历所有 key(依赖 SCAN),对每个 key 执行 MEMORY USAGE,开销较大(每个 key 一次命令),适合小到中型数据集或低峰期使用。输出包括各数据类型的 key 数与总内存、内存最大的 key。适合内存治理与容量规划。

--memkeys 通过 MEMORY USAGE 精确测量内存,区别于 --bigkeys 的"找最大",用于理解内存分布与占比,是容量规划工具。

#
★★

48. volatile-lru/allkeys-lfu 的取舍

Redis 的 volatile-lru 与 allkeys-lfu 淘汰策略如何取舍?

  • volatile-lru 只淘汰有 TTL 的键
  • allkeys-lfu 全键按频率淘汰
  • 场景选型

volatile-lru 仅在设置了 TTL 的键中按 LRU(最近最少使用)淘汰,未设 TTL 的键(如长期数据)不被淘汰;allkeys-lfu 在所有键中按 LFU(最少访问频率)淘汰,即使未设 TTL 也会被淘汰。取舍:volatile-lru 适合"有些键需长期保留、有些是缓存"的场景,保护长期数据;allkeys-lfu 适合"纯缓存、无明显需保留数据"的场景,用频率淘汰更贴合"热数据保留"。LFU 比 LRU 更能抵御"偶发访问"导致的误淘汰。选型取决于数据是否分"可淘汰/不可淘汰"。

volatile-lru 保护无 TTL 数据,allkeys-lfu 全量按频率淘汰。缓存场景常用 allkeys-lfu 或 allkeys-lru;混合场景用 volatile-lru 保护长期数据。

#
★★

49. 分布式锁的 GC 停顿与 Redis 假死问题

分布式锁的 GC 停顿与 Redis 假死问题如何理解与应对?

  • GC 停顿导致锁过期
  • 锁过期误删与假死
  • 看门狗/续期与防误删

分布式锁的经典问题:客户端获取锁后发生长 GC 停顿(STW),导致锁记录过期,其他客户端获取锁并发执行,造成"两个持有者"(锁失效)。同理"Redis 假死"(网络分区/服务端暂停)会让锁在客户端不知情时过期。应对:一是锁值用唯一标识(UUID:threadId),解锁时校验(Lua CAS)防止误删他人锁;二是用看门狗自动续期覆盖 GC 停顿(Redisson 每 10s 续期,GC 停顿< 续期间隔则安全);三是合理设置租期与看门狗,让锁失效时间大于最坏 GC 停顿;四是业务侧保证幂等,容忍极小概率并发。GC 停顿无法完全消除,只能尽量缩小窗口。

锁过期时机受 GC/网络影响,安全靠"唯一标识 + 续期 + 幂等"组合。没有绝对安全的分布式锁,工程上平衡"可用性"与"安全性"。

#
★★

50. 分布式锁的公平性(FairLock)与读写锁(R/W Lock)

分布式锁的公平性(FairLock)与读写锁(R/W Lock)如何实现?

  • FairLock 公平排队
  • RReadWriteLock 读共享写互斥
  • 实现与开销

公平锁(Redisson RFairLock)通过维护一个全局等待队列/信号量,保证按请求顺序获取锁,避免非公平锁的"饿死";实现基于 Redis 的 ZSet 或计数+Pub/Sub 通知,加锁开销更大。读写锁(RReadWriteLock)提供 readLock()(可多个并发持有)与 writeLock()(互斥),且写锁优先(有写等待时新读锁不授予),实现基于"读锁计数 + 写锁标记"的 Lua 原子操作。适用场景:读多写少、读可并发读需互斥写。注意读写锁的"写饥饿"与锁粒度,权衡一致性。

公平锁解决"饥饿",读写锁提升"读并发度"。两者都基于 Redis 原子操作,但公平锁与读写锁的元数据更复杂、开销更高。

#
★★

51. 分布式锁的可重入性(Reentrant Lock)

分布式锁的可重入性(Reentrant Lock)如何实现?

  • 可重入计数
  • 持有者标识
  • Redisson 实现

可重入锁允许同一线程(持有者)重复获取锁而不自锁。Redisson 实现:锁值记录 UUID:threadId,并维护一个计数器(Hash 或值中的计数)。加锁时 Lua 脚本判断:若锁不存在,设置持有者并计数 1;若锁存在且持有者是当前线程,计数 +1(重入);否则返回失败。解锁时校验持有者,计数 -1,归零才删除锁。这样同一线程嵌套加锁不会死锁,释放时逐层递减。可重入需在多线程模型下区分"每个线程"(threadId),避免不同线程误判为持有者。

可重入的关键是"持有者标识 + 计数器",用 Lua 原子保证判断与计数的一致性。它解决嵌套调用与递归加锁场景。

#
★★

52. 分布式锁的超时与释放语义

分布式锁的超时与释放语义是什么?

  • 锁租期(超时)
  • 释放的持有者校验
  • 看门狗与租期

分布式锁的超时(租期)指锁在指定时间后自动过期释放,防止持有者崩溃时锁永久占用(死锁)。释放语义:解锁必须校验持有者(Lua),只有锁的持有者能释放,防止误删他人锁(如 A 超时后 B 获得锁,A 业务结束误删 B 的锁)。配合看门狗:设置租期时若开启看门狗,锁会被续期,过期时间持续重置;若指定固定租期(leaseTime),则不续期,到期自动释放。释放语义的核心是"持有者身份校验"保证安全性。超时太短会误删/提前释放,太长会造成锁长时间占用。

超时解决"崩溃死锁",释放校验解决"误删他人锁"。两者结合保证锁的安全与可用,超时需匹配业务时长并配合续期。

#
★★

53. 基于 Binlog + Canal 的缓存一致性

基于 Binlog + Canal 的缓存一致性方案如何实现?

  • Canal 订阅 MySQL Binlog
  • 数据库变更 -> 缓存失效
  • 与业务代码解耦

Binlog + Canal 方案:Canal 伪装成 MySQL 从库,订阅主库的 Binlog,把数据库变更(增删改)解析成事件;业务侧监听这些事件,删除或更新对应 Redis 缓存。这样缓存失效逻辑与业务代码解耦,不侵入业务,且能保证"数据库变更 -> 缓存失效"的顺序(以 Binlog 为准)。优点:实现 Cache-Aside 的"先更新 DB 再删缓存"更可靠,且天然异步。缺点:引入额外组件(Canal),需处理 Binlog 延迟与重复事件(幂等)。适合"数据库是唯一事实源、缓存异步失效"的场景。

Binlog+Canal 把"缓存失效"从业务代码中剥离,以数据库日志为准,避免业务侧先后顺序错误,是可靠的缓存一致性方案之一。

#
★★

54. 热点 key 如何被发现(客户端统计/代理层探测),拆分(key 分片)与本地缓存兜底各如何缓解单分片压力

热点 key 如何被发现,拆分(key 分片)与本地缓存兜底各如何缓解单分片压力?

  • 热点 key 发现(客户端统计/代理探测)
  • key 分片拆分
  • 本地缓存兜底

热点 key 发现:客户端侧统计访问频率(本地计数,超阈值上报);代理层(如 Codis、Redis 代理)探测;redis-cli --hotkeys 用 LFU 统计;或监控 Redis 的 CLIENT LIST 与命令数。治理:key 分片是把同一个热点 key 拆成多个副本 key(如 hotkey:0~hotkey:N),请求随机路由到某一副本,从而把单 key 的读写压力分散到多个分片/节点,缓解单分片热点;本地缓存兜底是在应用进程内缓存热点 value(Caffeine),命中本地直接返回,减少对 Redis 的请求,从而缓解单分片压力。两者结合:分片解决"单 key 压力",本地缓存解决"请求量"。

热点 key 治理的三步:发现(统计/探测)-> 拆分(分片分散)-> 本地缓存(本地兜底)。分片需维护副本一致性,本地缓存需处理失效。

#
★★

55. 热点 key 突发流量下,多级缓存(Caffeine + Redis)与请求合并(singleflight)如何避免缓存击穿

热点 key 突发流量下,多级缓存(Caffeine + Redis)与请求合并(singleflight)如何避免缓存击穿?

  • 缓存击穿(热点 key 过期同时打穿)
  • 多级缓存(本地兜底)
  • singleflight 请求合并

缓存击穿指热点 key 过期瞬间大量请求同时打到数据库。应对:多级缓存(Caffeine L1 + Redis L2)让本地缓存兜底,减少对 Redis 与 DB 的并发;singleflight(请求合并)把同一 key 的大量并发请求合并为一个,只有一个请求真正去查库回填,其余请求等待该结果,避免击穿数据库。singleflight 可用 LoadingCache 的同步加载或 Guava 的 get(key, loader) 实现"并发只执行一次"。工程上:多级缓存降低到 DB 的流量,singleflight 保证同一时刻只有一次 DB 查询。

防击穿的关键是"合并并发查询"(singleflight)与"本地兜底"(多级缓存),避免同一热点 key 的并发查询同时打到 DB。

#
★★

56. 缓存与数据库的最终一致性(先更新 DB,再失效缓存)

缓存与数据库的最终一致性(先更新 DB,再失效缓存)如何实现?

  • 先写 DB 再删缓存
  • 删除失败与补偿
  • 最终一致

"先更新 DB,再失效缓存"是 Cache-Aside 的写路径,保证以数据库为准,最终一致。时序:更新 DB 成功后删除缓存,下次读缓存未命中重建。为确保最终一致,删除失败需补偿:延迟双删(更新后延迟再一次删除)、消息队列删除、Binlog/Canal 异步删除。最终一致性指短暂时间内缓存可能为旧值,但最终收敛到数据库值。并发风险:读未命中查库回填与写删缓存交错可能短暂脏数据,靠 TTL 兜底收敛。相比"先删缓存再更新 DB"(窗口期多一次读库),"先更新 DB 再删缓存"更优。

先更新 DB 再删缓存保证了"数据库为最终事实",删除失败用补偿与 TTL 兜底收敛到一致,是最终一致性工程实践。

#
★★

57. 缓存容量规划与 maxmemory-policy 选型

缓存容量规划与 maxmemory-policy 选型如何做?

  • 容量评估(数据量、TTL、QPS)
  • maxmemory-policy 选型
  • 监控与扩容

容量规划需评估:数据总量(key 数 × 平均大小)、TTL 分布(活跃数据量)、QPS 与命中率、内存预算(配置 maxmemory 与系统内存)。maxmemory-policy 选型:纯缓存场景用 allkeys-lru/allkeys-lfu(淘汰冷数据);有不可淘汰数据用 volatile-lru(只淘汰有 TTL 的);数据不允许丢失用 noeviction(写报错)。规划时留出余量(如 70% 内存上限),避免内存满触发频繁淘汰影响性能。监控 used_memoryevicted_keys、命中率,容量不足时扩容或优化 value 大小/压缩。

容量规划是"数据量 + 策略 + 监控"的组合,策略决定内存满时行为,监控决定何时扩容与调优。

#
★★

58. 缓存层整体不可用时如何降级与熔断,直连数据库的保护性预案(限流、降级开关、静态兜底)应如何设计

缓存层整体不可用时如何降级与熔断,直连数据库的保护性预案如何设计?

  • 缓存降级与熔断
  • 直连数据库的限流保护
  • 降级开关与静态兜底

缓存整体不可用时:一是熔断——检测到缓存故障率超过阈值,快速失败并切换降级,避免雪崩;二是降级——走降级链路(如直连 DB 但需限流、或返回静态兜底数据)。直连数据库的保护预案:设置限流(DB 请求量阈值,超过则返回降级/静态数据)、降级开关(人工/自动切换只读缓存或静态页)、静态兜底(配置热数据或默认值)。设计上:缓存失败时按"降级开关 -> 限流 -> 静态兜底"分层,DB 端加熔断与并发控制(如线程池、信号量),防止 DB 被打垮。核心是"快速失败、保护下游、保留可用性"。

降级预案是"缓存不可用时不拖垮 DB"的工程防线,靠熔断、限流、降级开关、静态兜底分层,本质是"有损可用"。

#
★★

59. 缓存穿透(Cache Penetration)的布隆过滤器(Bloom Filter)

布隆过滤器(Bloom Filter)如何解决缓存穿透?

  • 布隆过滤器的原理
  • 缓存穿透(查不存在的数据)
  • 误判率与内存权衡

缓存穿透指查询数据库中不存在的数据,导致每次请求都穿透到 DB(缓存和 DB 都无值)。布隆过滤器:先把所有可能存在的数据 key 加入位图(多哈希函数映射),查询时先过布隆过滤器,若判断"不存在"则直接返回,避免打 DB;若判断"可能存在"仍需查 DB(存在误判,但误判只把"不存在"误判为"存在",不会漏掉真实存在的数据)。优点:占用内存小、查询 O(k)。缺点:有误判率(可能多打 DB)、不能删除元素(需计数型或重建)。Redis 用 RedisBloom 模块的 BF.ADD/BF.EXISTS 实现。

布隆过滤器把"不存在"的查询挡在 DB 之前,用"空间换穿透防护"。误判只会多查 DB,不会漏数据,适合 key 集合相对固定的场景。

#
★★

60. 缓存穿透(Cache Penetration)的成因与解决方案

缓存穿透的成因是什么,有哪些解决方案?

  • 穿透成因(查询不存在数据)
  • 空值缓存、布隆过滤器、参数校验
  • 防恶意攻击

缓存穿透成因:查询的 key 在缓存和数据库都不存在,导致每次请求都打到 DB,常见于恶意请求(构造不存在的 id)或数据异常。解决方案:一是空值缓存——把"不存在"的结果也缓存(设短 TTL),避免重复打 DB;二是布隆过滤器——预先加入真实 key,不存在的直接拦截;三是参数校验——非法参数(如 id 为负、超范围)直接拒绝;四是限流/黑名单——对恶意请求限流。结合使用:参数校验 + 空值缓存 + 布隆过滤器多层防护。注意空值缓存要短 TTL 且避免缓存漏洞。

穿透的根源是"无效 key 反复查询"。空值缓存兜底、布隆过滤器拦截、参数校验前置,层层减少无效 DB 查询。

#
★★

61. 缓存雪崩(Cache Avalanche)的成因与防护

缓存雪崩的成因有哪些,如何防护?

  • 雪崩成因(大量 key 同时过期 / 缓存宕机)
  • 过期时间随机化
  • 缓存集群高可用

缓存雪崩指大量缓存 key 同时失效(或缓存服务整体宕机),导致大量请求同时打到数据库,压垮 DB。成因:一是大量 key 设了相同过期时间,同时过期;二是缓存集群宕机(Redis 不可用)。防护:一是过期时间随机化(加随机偏移,避免同时过期);二是多级缓存(本地缓存兜底);三是缓存集群高可用(Redis Sentinel/Cluster 主从、故障转移);四是缓存预热与限流、熔断(DB 侧保护);五是设置永不失效的"永不过期"核心数据+后台刷新。核心是"避免瞬时并发打库"。

雪崩是"同时失效/宕机"导致的并发打库,防护靠随机化分散过期、多级兜底、高可用与限流熔断。

#
★★

62. 缓存雪崩(Cache Avalanche)的过期时间随机化与多级缓存(Redis + Caffeine)

缓存雪崩的过期时间随机化与多级缓存(Redis + Caffeine)如何协同防护?

  • 过期时间随机化
  • 多级缓存兜底
  • 协同防护

过期时间随机化:给缓存 TTL 加一个随机偏移(如 base ± 随机),使大量 key 不同时过期,错峰刷新,避免同时失效打库。多级缓存(Caffeine L1 + Redis L2):即使 Redis 层面部分 key 过期,本地 Caffeine 仍可命中兜底,减少直打 DB;Redis 不可用时,本地缓存仍能支撑部分读。两者协同:随机化分散"瞬时过期",多级缓存提供"本地兜底",再叠加预热与限流。注意:多级缓存需处理本地缓存失效的一致性(广播/短 TTL)。

随机化解决"同时过期",多级缓存解决"过期/宕机时的本地兜底",两者叠加降低雪崩对 DB 的冲击。

#
★★

63. 缓存预热(Warm-up)与冷启动策略

缓存预热(Warm-up)与冷启动策略如何设计?

  • 预热时机(启动/发布前)
  • 预热数据源
  • 冷启动避免打库

缓存预热指在缓存刚启动(冷启动)或发布前,把热点数据预先加载到缓存,避免冷启动时大量请求未命中打到 DB。策略:一是启动时把热点数据(如商品、配置)批量写入缓存;二是发布前预热(切流前填充);三是按访问频率/历史数据挑选热点 key 预热;四是读时回填 + 限流兜底冷启动。冷启动的防护:预热期间 DB 限流、启动时先加载热点再接受流量、配合 singleflight 合并冷启动查询。预热能显著提升首屏命中率,减少 DB 冲击。

预热解决"冷启动命中率低"问题,通过预加载热点数据 + 限流/singleflight 兜底,避免冷启动瞬间打库。

#
★★

64. @Cacheable 注解的 Spring 集成

@Cacheable 注解在 Spring 中如何集成?

  • @Cacheable 的缓存读写
  • 缓存管理器与配置
  • 条件/unless/key

@Cacheable 标注方法,Spring 通过 AOP 代理拦截:先查缓存(按 cacheNames + key),命中直接返回;未命中执行方法并把结果写入缓存。可配置 key(SpEL 表达式指定缓存键)、condition(满足条件才缓存)、unless(满足条件不缓存结果)、keyGenerator。需要开启 @EnableCaching 并配置 CacheManager(Spring Boot 默认 RedisCacheManager)。@CachePut 更新缓存、@CacheEvict 失效缓存可组合使用。注意:@Cacheable 对方法内部调用(this 调用)不生效(AOP 代理限制),需自注入或拆分。

@Cacheable 是声明式缓存,底层 AOP + CacheManager。使用注意代理自调用失效、key 序列化一致、TTL 配置。

@Cacheable(cacheNames = "user", key = "#id", unless = "#result == null")
public User getUser(Long id) {
    return userMapper.selectById(id);
}
#
★★

65. CLIENT LIST 与客户端连接分析

Redis 的 CLIENT LIST 命令如何用于客户端连接分析?

  • CLIENT LIST 查看连接
  • 连接属性(addr/name/idle)
  • 僵尸连接与健康检查

CLIENT LIST 输出所有客户端连接的信息,包括地址(addr)、名称、连接时间、空闲时间(idle)、上次命令、缓冲(qbuf/obl)、是否阻塞(flag)等。用于分析:连接数是否过多、空闲僵尸连接(idle 大)是否占据资源、是否存在阻塞连接(BLPOP 等)、连接缓冲是否占用过大。CLIENT KILL 可断开异常连接(如非法地址、空闲超时)。工程上监控客户端连接数,配合 maxclients 设置上限,避免连接耗尽。Lettuce 共享连接会减少连接数,Jedsi 连接池连接数可控。

CLIENT LIST 是连接诊断的入口,分析连接数、空闲、阻塞与缓冲,配合 CLIENT KILL 治理异常连接。

#
★★

66. CONFIG GET/CONFIG SET 的动态调参

Redis 的 CONFIG GET/CONFIG SET 如何动态调参?

  • CONFIG GET 查看配置
  • CONFIG SET 动态修改
  • 持久化与生效

CONFIG GET pattern 查看配置(支持通配符),CONFIG SET param value 动态修改运行期配置(无需重启),如 maxmemory、maxmemory-policy、slowlog 阈值、appendonly 等。动态修改后可用 CONFIG REWRITE 把当前配置写入配置文件(启动时生效),否则重启后恢复。注意:CONFIG SET 修改的是运行期参数,部分参数重启后不保留(需 REWRITE);错误参数会被拒绝。工程上用于在线调优(如调整淘汰策略、日志阈值),但要谨慎并记录。Redis 7 的部分参数支持 CONFIG SET

CONFIG SET 让运行期调优无需重启,配合 CONFIG REWRITE 持久化。动态调参需谨慎并验证,避免生产配置错误。

#
★★

67. INFO 命令的工程价值(memory/commandstats/stats)

INFO 命令的 memory/commandstats/stats 等部分有什么工程价值?

  • INFO memory 内存指标
  • INFO commandstats 命令统计
  • INFO stats 运行统计

INFO 返回 Redis 的全面运行信息,分节:memory(used_memory、maxmemory、碎片率、内存峰值)、stats(总连接、命令处理数、命中率、evicted_keys、expired_keys)、commandstats(每个命令的调用次数、总耗时、均耗时,用于定位慢命令与热点命令)、replication(主从状态、延迟)、clients(连接数)、server(版本、运行时间)。工程价值:监控与告警(内存、命中率、连接)、性能分析(commandstats 找热点/慢命令)、容量规划与故障排查。配合第三方监控(Prometheus)持续采集。

INFO 是 Redis 监控的数据源,memory/stats/commandstats 分别覆盖内存、运行、命令三个维度,是运维与性能分析的基础。

#

68. Multi-Level Cache(L1+L2)的设计

Multi-Level Cache(L1+L2)如何设计?

  • L1 本地缓存 + L2 Redis
  • 命中率与延迟
  • 一致性维护

多级缓存设计:L1 为进程内本地缓存(Caffeine/Guava,低延迟、进程内),L2 为 Redis(共享、跨实例),数据库为最终源。读路径 L1 -> L2 -> DB 逐级查找并回填;写路径更新 DB 后同步失效 L1/L2。设计要点:L1 容量与 TTL 控制(避免内存膨胀)、L2 统一序列化、命中率与延迟权衡、一致性维护(L1 失效靠短 TTL/广播,L2 失效靠删缓存)。收益:L1 命中延迟极低,减少 Redis 与 DB 压力;代价:L1 多实例不一致,需失效机制。适用于读多写少、热点集中的场景。

L1+L2 是"快 + 共享"的折中,收益是低延迟与降负载,核心设计和难点是 L1 的一致性维护与容量控制。

#

69. PIPELINE 与 MGET/MSET 的批量优化

PIPELINE 与 MGET/MSET 如何做批量优化?

  • MGET/MSET 单命令批量
  • PIPELINE 批量命令
  • 选择与取舍

MGET/MSET 是单条命令一次处理多个 key,最省网络往返;PIPELINE 是批量发送多条命令(可不同操作),减少 RTT 但命令各自执行。批量优化:多个 key 的读取用 MGET(一次往返);不同操作的批量用 PIPELINE(打包发送)。MGET 在 client 侧一次拿到多个值,简单高效;PIPELINE 更灵活(可混合读/写/计数)。取舍:同类型批量(全读)用 MGET,混合操作用 PIPELINE;注意 MGET 的 key 需在同一 Redis(cluster 下需同槽)。整体目标都是减少网络往返。

MGET 是"同类批量读"的最佳实践,PIPELINE 是"混合批量操作"的通用手段,两者都减少 RTT 提升吞吐。

#

70. Read-Through/Write-Through/Write-Behind 的差异

Read-Through/Write-Through/Write-Behind 缓存模式有何差异?

  • Read-Through 读透
  • Write-Through 写透
  • Write-Behind 写后

Read-Through:应用只访问缓存,缓存未命中时由缓存层自动加载数据库并回填(应用无感知);Write-Through:写操作先写缓存再写数据库,同步写双份,保证一致但写延迟高;Write-Behind(写后):写操作只写缓存,缓存异步批量刷到数据库,读快写快但可能丢失(崩溃时未刷盘数据)且不一致窗口大。三者都属于"缓存层承载访问"的模式(区别于 Cache-Aside 应用侧管理)。取舍:Read-Through 简化读、Write-Through 保证即时一致、Write-Behind 优化写吞吐但牺牲可靠性与一致性。

三种模式围绕"谁管理缓存与 DB 的一致性":Read-Through 管读、Write-Through 管同步写、Write-Behind 管异步写。各有权衡。

#

71. SCAN/KEYS 的安全差异

Redis 的 SCAN 与 KEYS 命令在安全性上有何差异?

  • KEYS 全量扫描阻塞
  • SCAN 游标增量扫描
  • 生产安全

KEYS pattern 会一次性遍历所有 key 匹配,大库上会阻塞主线程(阻塞其他命令),生产禁止使用;SCAN cursor 用游标增量遍历,每次返回少量 key 与下一个游标,不阻塞主线程,可安全用于大库扫描。SCAN 的缺点:遍历过程中可能重复或遗漏(key 在扫描期间变化),但用于批量删除/统计可接受。工程上:批量删除用 SCAN + DEL(分片),避免 KEYS 阻塞;--bigkeys 等工具也基于 SCAN。SCAN 配合 MATCH/COUNT 控制。

SCAN 是"增量游标"安全遍历,KEYS 是"全量阻塞"危险遍历。生产扫描必须用 SCAN 避免阻塞主线程。

#

72. active-defrag 的自动碎片整理

Redis 的 active-defrag 自动碎片整理如何工作?

  • active-defrag 功能
  • 碎片整理参数
  • 与手动整理对比

active-defragactive-defrag yes)让 Redis 在后台自动整理内存碎片,把分散的小块内存合并,降低碎片率(mem_fragmentation_ratio)。参数:active-defrag-ignore-bytes(小于该字节数的碎片不整理)、active-defrag-threshold-lower(碎片率低于该值不整理)、active-defrag-upper(高于该值加大整理力度)、active-defrag-cycle-min/max(整理 CPU 占比)。它在线整理、不阻塞主线程,但占用一定 CPU。相比重启整理(短暂停机),active-defrag 无停机但持续有 CPU 开销。适用于碎片率持续偏高的场景。

active-defrag 在线整理碎片,避免重启停机,通过参数控制整理的触发阈值与 CPU 消耗,权衡碎片与性能。

#

73. redis-benchmark 的性能压测

redis-benchmark 如何做性能压测?

  • 压测参数(-c 并发、-n 请求数、-d 数据大小)
  • 测试命令与场景
  • 结果解读

redis-benchmark 是 Redis 官方压测工具,常用参数:-c 并发连接数、-n 总请求数、-d 数据大小、-t 指定命令(如 SET/GET)、-P 管道数、-r 随机 key。输出每秒请求数(req/s)与延迟。用于基准测试吞吐与延迟,评估运维调整(如多线程 IO、淘汰策略)的影响。注意:压测结果受网络、机器、数据规模影响,需在受控环境;-n 建议足够大(如 100000),-c-P 组合看吞吐上限。压测应与生产负载匹配,避免压测干扰线上。

redis-benchmark 测吞吐与延迟,帮助评估 Redis 性能与配置影响。合理参数(并发、请求数、命令)保证结果可信。

#

74. 延迟双删(Delay Double Delete)模式

延迟双删(Delay Double Delete)模式如何实现?

  • 双删时序
  • 解决并发不一致
  • 延迟时间选择

延迟双删:更新 DB 后先删除缓存,延迟一段时间(如几百毫秒)再删除一次缓存。目的是解决"读未命中回填旧值"与"写更新 DB"交错的窗口:第一次删除让旧缓存失效,第二次删除清掉"在第一次删除后、回填旧值"产生的脏缓存。时序:更新 DB -> 删缓存 -> 延迟 -> 再删缓存。延迟时间需大于"读回填"的耗时(最坏情况),通常几百毫秒到 1 秒。缺点:延迟期间仍可能读到旧值(最终一致),且两次删除增加复杂度。用于缓存一致性要求较高的场景,配合 TTL 兜底。

延迟双删通过"二次删除"清理回填竞态产生的脏数据,是"先更新 DB 再删缓存"的加强版,用延迟牺牲短暂一致换取最终收敛。