怎样在Spring Boot中通过AOP记录所有Redis操作耗时日志

轻辰同学_7261

轻辰同学_7261

2026-10-01

174人浏览

原创

RedisTemplate 的 public 方法不能直接切,因其大量操作最终委托给 execute 方法(尤其是带 RedisCallback 的重载),而 opsForValue().get() 等链式调用的耗时也集中在 execute 或 executePipelined;只切 get/set 会遗漏回调、事务、Pipeline 场景,且 Spring Data Redis 5.x+ 默认 Lettuce 是异步非阻塞的,execute 可能返回 CompletionStage/Mono,同步 AOP 无法准确测真实耗时。

怎样在spring boot中通过aop记录所有redis操作耗时日志

为什么不能直接切 RedisTemplate 的 public 方法

因为 RedisTemplate 大量操作最终会委托给 execute 方法(尤其是带 RedisCallback 的重载),而像 opsForValue().get() 这类链式调用,实际耗时集中在底层 execute 或 executePipelined。只切 get()、set() 等方法会漏掉回调式调用、事务、Pipeline 场景,日志不全。

更关键的是:Spring Data Redis 5.x 起默认使用 Lettuce,其连接是异步非阻塞的,execute 返回的可能是 CompletionStage 或响应式类型(如 Mono),同步 AOP 拦截无法准确测出真实耗时。

  • 优先切 execute 和 executePipelined 这两个核心模板方法
  • 若项目已升级到 Spring Boot 3 + Redis 6+ 响应式栈,需改用 ReactiveRedisTemplate 配合 Mono.usingWhen 或自定义 ExchangeFilterFunction,AOP 不适用
  • 避免切 JedisConnectionFactory 或 LettuceClientConfigurationBuilder —— 它们不承载业务逻辑,切了也记不到操作级耗时

如何写一个能覆盖 sync/async 场景的 AOP 切面

核心思路:对 RedisTemplate.execute* 方法做环绕通知,但要区分返回值类型做适配。同步场景直接记录;Lettuce 异步场景下,execute 可能返回 Future 或 CompletionStage,需在回调中补录耗时。

@Around("execution(* org.springframework.data.redis.core.RedisTemplate.execute*(..))")
public Object logRedisExecution(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.nanoTime();
    String methodName = joinPoint.getSignature().getName();
    try {
        Object result = joinPoint.proceed();
<pre class="brush:php;toolbar:false;">    // 同步结果:直接记录
    if (!(result instanceof CompletionStage) && !(result instanceof Future)) {
        logTiming(methodName, System.nanoTime() - start);
        return result;
    }

    // 异步结果:包装为新 CompletableFuture,确保回调里记录耗时
    if (result instanceof CompletionStage) {
        return ((CompletionStage>) result)
            .whenComplete((r, ex) -> logTiming(methodName, System.nanoTime() - start));
    }
    if (result instanceof Future) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                Object r = ((Future>) result).get();
                logTiming(methodName, System.nanoTime() - start);
                return r;
            } catch (Exception e) {
                logTiming(methodName, System.nanoTime() - start);
                throw new RuntimeException(e);
            }
        });
    }
    return result;
} catch (Throwable t) {
    logTiming(methodName, System.nanoTime() - start, t);
    throw t;
}

}

  • 不要用 @Order 或高优先级增强——可能干扰 TransactionInterceptor,导致事务内 Redis 耗时统计失真
  • logTiming 方法建议用 org.slf4j.Logger.atDebug() + lazy 字符串拼接,避免日志级别关闭时仍构造消息
  • 若项目混用 StringRedisTemplate,需额外加切点:execution(* org.springframework.data.redis.core.StringRedisTemplate.execute*(..))

怎样避免日志爆炸和线程上下文污染

高频 Redis 操作(如秒杀查库存)每秒数百次,全量打日志会拖垮磁盘 IO 和 GC。必须分级控制:

Redis Skill - 高性能缓存管理
Redis Skill - 高性能缓存管理

Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。

下载
  • 默认只记录 >100ms 的慢操作,阈值用配置项驱动:spring.redis.aop.slow-threshold-ms=100
  • 对 scan、keys、slowlog 等高危命令强制记录(无论快慢),防止误用
  • 禁止在日志中打印 RedisCallback 的入参(尤其是 byte[] key/value),容易 OOM;可记录 key.toString() 或哈希摘要
  • 若用了 MDC,确保 ThreadLocal 在异步回调中能透传(Lettuce 默认不继承,需显式 CompletableFuture.delayedExecutor 或 ThreadPoolTaskExecutor 配置 taskDecorator)

为什么 @EnableAspectJAutoProxy(proxyTargetClass = true) 是必须的

RedisTemplate 是普通 Java 类,不是接口。Spring 默认用 JDK 动态代理(基于接口),而 RedisTemplate 没实现公共接口(它实现的是 InitializingBean 等内部标记接口),JDK 代理会失效,切面完全不触发。

必须启用 CGLIB 代理,否则你写的 @Around 看似生效,实则一概拦截不到。

  • 在主启动类或配置类上加 @EnableAspectJAutoProxy(proxyTargetClass = true)
  • 确认 classpath 有 spring-boot-starter-aop 且版本与 Spring Boot 匹配(Boot 3.x 需 AOP 6.x)
  • 如果项目用了 Lombok 的 @AllArgsConstructor 或 @RequiredArgsConstructor,注意 CGLIB 代理会绕过构造器注入,导致 RedisTemplate 字段为 null —— 改用 @Setter(onMethod_ = @Autowired) 或字段注入

最常被忽略的一点:Lettuce 的 StatefulRedisConnection 是共享的,AOP 记录的“单次操作耗时”其实包含连接复用、命令排队、网络往返等复合延迟,它反映的是业务视角的感知延迟,不是纯 Redis 执行时间。要定位真正瓶颈,得配合 redis-cli --latency 和 Lettuce 的 CommandLatencyCollector。

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

2025.08.06

2311

22

Java Spring Security 与认证授权
Java Spring Security 与认证授权

本专题系统讲解 Java Spring Security 框架在认证与授权中的应用,涵盖用户身份验证、权限控制、JWT与OAuth2实现、跨站请求伪造(CSRF)防护、会话管理与安全漏洞防范。通过实际项目案例,帮助学习者掌握如何 使用 Spring Security 实现高安全性认证与授权机制,提升 Web 应用的安全性与用户数据保护。

2026.01.26

437

25

常用的数据库软件
常用的数据库软件

常用的数据库软件有MySQL、Oracle、SQL Server、PostgreSQL、MongoDB、Redis、Cassandra、Hadoop、Spark和Amazon DynamoDB。更多关于数据库软件的内容详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.02

4249

19

内存数据库有哪些
内存数据库有哪些

内存数据库有Redis、Memcached、Apache Ignite、VoltDB、TimesTen、H2 Database、Aerospike、Oracle TimesTen In-Memory Database、SAP HANA和ache Cassandra。更多关于内存数据库相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.14

3735

11

mongodb和redis哪个读取速度快
mongodb和redis哪个读取速度快

redis 的读取速度比 mongodb 更快。原因包括:1. redis 使用简单的键值存储,而 mongodb 存储 json 格式的数据,需要解析和反序列化。2. redis 使用哈希表快速查找数据,而 mongodb 使用 b-tree 索引。因此,redis 在需要高性能读取操作的应用程序中是一个更好的选择。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.02

6732

6

redis怎么做缓存服务器
redis怎么做缓存服务器

redis 作为缓存服务器的答案:redis 是一款开源、高性能、分布式的键值存储,可作为缓存服务器使用。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

623

6

redis怎么解决数据一致性
redis怎么解决数据一致性

redis 提供了两种一致性模型,以维护副本数据一致性:强一致性 (sync) 确保写操作仅在复制到所有从节点后才完成;最终一致性 (async) 则在主节点上写操作后认为已完成,牺牲一致性换取性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

736

6

mysql和redis怎么保证双写一致性
mysql和redis怎么保证双写一致性

确保 mysql 和 redis 双写一致性的技术包括:1、事务性更新:同时更新 mysql 和 redis,保证一致性;2、主从复制:mysql 主服务器更改同步到 redis 从服务器;3、基于事件的更新:mysql 记录更改并发送到 redis等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

6282

6

redis缓存一般存些什么数据
redis缓存一般存些什么数据

redis缓存中存储的数据类型包括:字符串、哈希、列表、集合、有序集合、位图、地理空间数据和hyperloglog。这些数据类型适用于存储各种数据,从简单信息到复杂对象和地理位置。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

1140

6

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
phpEnv手册
phpEnv手册

共0课时 | 0人学习

进程与SOCKET
进程与SOCKET

共6课时 | 0.5万人学习