Spring AOP防抖与节流器实现原理

小豆丁 10小时前 ⋅ 10 阅读
聊天记录
头像

ebouncer 抖动去除器 对容器内某个bean的方法按指定时间去除抖动,抖动去除完成后肯定会得到执行。

me.insidezhou.southernquiet.debounce.* Throttle 节流器 对容器内某个bean的方法按指定时间或次数节流,节流期间方法不会得到执行。

这两个原理

头像

这两个组件本质上都是基于 Spring AOP(面向切面编程)的时间/频次控制器,核心目的是解决高频触发的方法调用对系统的冲击。

它们的实现原理并不神秘,主要是利用了 代理模式 + 定时任务 + 内存状态存储。以下是详细拆解:


1. Debouncer(抖动去除器 / 防抖)

行为定义:在指定的时间窗口内,如果该方法被连续调用,计时器会不断重置;只有当停止调用超过设定时间后,方法才会真正执行一次。

📐 核心原理

  1. AOP 拦截:通过 @Debounce 注解标记方法,Spring 会为该 Bean 创建一个代理(Proxy)。所有对该方法的调用都会被代理拦截,而不是直接执行。
  2. Key 映射:为每个被拦截的方法生成一个唯一的 Key(通常是 类名 + 方法名 + 参数哈希,或者你自定义的 Key)。
  3. 延时任务(Delay Task)
    • 当方法第一次被调用时,AOP 拦截器不会立刻执行原方法,而是向一个**调度线程池(ScheduledExecutorService)**提交一个延时任务(例如延时 500ms)。
    • 如果在 500ms 内再次调用该方法,旧的延时任务会被取消(cancel),并重新提交一个新的延时任务。
  4. 最终执行:当最后一次调用后的 500ms 内没有新调用,延时任务到期,代理才会去执行真正的业务逻辑。

🧠 形象比喻

> 就像电梯门:你一直按“开门”按钮(抖动),门就不会关;只有当你停手 3 秒后,门才会关上(执行)。

🔑 关键技术点

  • 状态存储:通常使用 ConcurrentHashMap<MethodKey, ScheduledFuture> 来存储待执行的任务。
  • 幂等性:因为最终肯定会执行一次,所以它适用于保存最终状态的场景(如自动保存草稿、配置更新)。

2. Throttle(节流器)

行为定义:在指定的时间窗口内,无论调用多少次,该方法只允许执行一次(或 N 次),其余的调用直接丢弃或忽略。

📐 核心原理

  1. AOP 拦截:同样通过 @Throttle 注解进行拦截。
  2. 原子计数器 / 时间戳
    • 时间阈值模式:记录上一次执行的时间戳。每次调用时检查 (当前时间 - 上次执行时间) < 设定间隔,如果是则直接返回(不执行),否则放行并更新时间戳。
    • 次数限制模式:维护一个计数器(如 AtomicInteger),在时间窗口内递增,达到上限则拒绝,窗口结束后重置。
  3. 无阻塞丢弃:与 Debouncer 不同,Throttler 发现不满足条件时,通常不会等待,而是直接结束流程(可能返回一个默认值或抛出一个特定异常)。

🧠 形象比喻

> 就像 FPS 游戏的射击频率:不管你鼠标点得多快,AK47 一秒钟只能射出 10 发子弹(节流),点得快只会导致点击无效。

🔑 关键技术点

  • Guava RateLimiter:很多此类组件底层直接使用或参考了 Guava 的限流算法。
  • 本地锁:通常使用 synchronizedReentrantLock 配合时间戳判断,确保在并发环境下计数的准确性。

3. 两者对比总结

特性 Debouncer (防抖) Throttle (节流)
核心目标 合并多次操作为一次 限制操作频率
执行时机 最后一次调用后的冷却期 固定时间间隔的开始/期间
中间调用 重置计时器 直接丢弃
最终结果 必定执行一次 可能一次都不执行
典型场景 搜索框联想输入、窗口Resize、配置热更新 按钮防止重复提交、API 限流、日志打印

4. SouthernQuiet 中的具体实现推测

me.insidezhou.southernquiet 中,这两个组件的实现通常遵循以下代码结构:

  • 注解定义@Debounce@Throttle 定义参数(如 value = 500ms, key = "#userId")。
  • MethodInterceptor:实现 org.aopalliance.intercept.MethodInterceptor
    • invoke 方法中读取注解参数。
    • ConcurrentHashMap 中获取该方法的“锁”或“任务”。
    • 决定是否调用 methodInvocation.proceed()(执行原方法)。
  • 线程池:依赖 Spring 的 TaskScheduler 来处理延时任务。

注意:由于它们是基于 JVM 内存的,这意味着它们是单机版的。如果你的应用部署在多台服务器上,每台机器上的 Debounce/Throttle 是互不感知的。如果需要在分布式环境下生效,通常需要引入 Redis 或 ZooKeeper 来实现分布式锁或令牌桶。

全部评论: 0

    我有话说: