滑动时间窗口实现重试限流,有效避免在短时间内大量请求失败导致的重试风暴问题。
class SlidingWindow {
private List<RequestRecord> window = new ArrayList<>();
private int threshold; // 超时阈值
private int windowSize; // 窗口大小
private long windowDuration; // 窗口时间范围(毫秒)
public SlidingWindow(int threshold, int windowSize, long windowDuration) {
this.threshold = threshold;
this.windowSize = windowSize;
this.windowDuration = windowDuration;
}
synchronized void add(RequestRecord req) {
// 移除过期的请求记录
long currentTime = System.currentTimeMillis();
window.removeIf(r -> (currentTime - r.getTimestamp()) > windowDuration);
// 如果窗口已满,移除最早的记录
if (window.size() >= windowSize) {
window.remove(0);
}
window.add(req); // 添加请求记录
}
boolean allowRetry() {
int timeoutCount = 0;
long currentTime = System.currentTimeMillis();
// 统计窗口内超时的失败请求
for (RequestRecord r : window) {
if (r.isTimeout() && !r.isSuccess() && (currentTime - r.getTimestamp()) <= windowDuration) {
timeoutCount++;
}
}
return timeoutCount < threshold; // 判断是否允许重试
}
}
// 请求记录类
class RequestRecord {
private long timestamp; // 请求时间戳
private int timeoutThreshold; // 超时阈值(毫秒)
private boolean success; // 请求是否成功
public RequestRecord(long timestamp, int timeoutThreshold, boolean success) {
this.timestamp = timestamp;
this.timeoutThreshold = timeoutThreshold;
this.success = success;
}
public boolean isTimeout() {
long currentTime = System.currentTimeMillis();
return (currentTime - timestamp) > timeoutThreshold; // 判断是否超时的逻辑
}
public long getTimestamp() {
return timestamp;
}
public boolean isSuccess() {
return success;
}
}
public class OrderController {
public static void main(String[] args) {
// 初始化滑动窗口,设置超时阈值为3,窗口大小为10,时间窗口为1分钟
SlidingWindow slidingWindow = new SlidingWindow(3, 10, 60000);
// 模拟请求和重试逻辑
for (int i = 0; i < 15; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
boolean success = Math.random() > 0.5; // 随机模拟请求成功或失败
RequestRecord record = new RequestRecord(System.currentTimeMillis(), 150, success);
slidingWindow.add(record);
if (!success && slidingWindow.allowRetry()) {
System.out.println("请求失败,触发重试");
} else if (!success) {
System.out.println("请求失败,达到重试阈值,不再重试");
} else {
System.out.println("请求成功");
}
}
}
}
局限性:
• 内存爆炸:每秒数万次请求需存储数万条记录,内存占用高达40MB(10万QPS场景)。
• 遍历开销:每次判定需遍历全部请求,时间复杂度为O(n),导致CPU峰值负载达70-90%。
• 锁竞争严重:多线程更新窗口需加锁,吞吐量仅5,000 QPS。