ebouncer 抖动去除器 对容器内某个bean的方法按指定时间去除抖动,抖动去除完成后肯定会得到执行。
me.insidezhou.southernquiet.debounce.* Throttle 节流器 对容器内某个bean的方法按指定时间或次数节流,节流期间方法不会得到执行。
这两个原理
这两个组件本质上都是基于 Spring AOP(面向切面编程)的时间/频次控制器,核心目的是解决高频触发的方法调用对系统的冲击。
它们的实现原理并不神秘,主要是利用了 代理模式 + 定时任务 + 内存状态存储。以下是详细拆解:
1. Debouncer(抖动去除器 / 防抖)
行为定义:在指定的时间窗口内,如果该方法被连续调用,计时器会不断重置;只有当停止调用超过设定时间后,方法才会真正执行一次。
📐 核心原理
- AOP 拦截:通过
@Debounce注解标记方法,Spring 会为该 Bean 创建一个代理(Proxy)。所有对该方法的调用都会被代理拦截,而不是直接执行。 - Key 映射:为每个被拦截的方法生成一个唯一的 Key(通常是
类名 + 方法名 + 参数哈希,或者你自定义的 Key)。 - 延时任务(Delay Task):
- 当方法第一次被调用时,AOP 拦截器不会立刻执行原方法,而是向一个**调度线程池(ScheduledExecutorService)**提交一个延时任务(例如延时 500ms)。
- 如果在 500ms 内再次调用该方法,旧的延时任务会被取消(cancel),并重新提交一个新的延时任务。
- 最终执行:当最后一次调用后的 500ms 内没有新调用,延时任务到期,代理才会去执行真正的业务逻辑。
🧠 形象比喻
> 就像电梯门:你一直按“开门”按钮(抖动),门就不会关;只有当你停手 3 秒后,门才会关上(执行)。
🔑 关键技术点
- 状态存储:通常使用
ConcurrentHashMap<MethodKey, ScheduledFuture>来存储待执行的任务。 - 幂等性:因为最终肯定会执行一次,所以它适用于保存最终状态的场景(如自动保存草稿、配置更新)。
2. Throttle(节流器)
行为定义:在指定的时间窗口内,无论调用多少次,该方法只允许执行一次(或 N 次),其余的调用直接丢弃或忽略。
📐 核心原理
- AOP 拦截:同样通过
@Throttle注解进行拦截。 - 原子计数器 / 时间戳:
- 时间阈值模式:记录上一次执行的时间戳。每次调用时检查
(当前时间 - 上次执行时间) < 设定间隔,如果是则直接返回(不执行),否则放行并更新时间戳。 - 次数限制模式:维护一个计数器(如
AtomicInteger),在时间窗口内递增,达到上限则拒绝,窗口结束后重置。
- 时间阈值模式:记录上一次执行的时间戳。每次调用时检查
- 无阻塞丢弃:与 Debouncer 不同,Throttler 发现不满足条件时,通常不会等待,而是直接结束流程(可能返回一个默认值或抛出一个特定异常)。
🧠 形象比喻
> 就像 FPS 游戏的射击频率:不管你鼠标点得多快,AK47 一秒钟只能射出 10 发子弹(节流),点得快只会导致点击无效。
🔑 关键技术点
- Guava RateLimiter:很多此类组件底层直接使用或参考了 Guava 的限流算法。
- 本地锁:通常使用
synchronized或ReentrantLock配合时间戳判断,确保在并发环境下计数的准确性。
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 来实现分布式锁或令牌桶。