首页 > 编程语言 >Java简单实现滑动窗口

Java简单实现滑动窗口

时间:2023-02-05 21:32:31浏览次数:58  
标签:Java int windowSize private System 滑动 now 窗口


由于最近有一个统计单位时间内某key的访问次数的需求,譬如每5秒访问了redis的某key超过100次,就取出该key单独处理。

这样的单位时间统计,很明显我们都知道有个边界问题,譬如5秒内100次的限制。刚好前4.99秒访问都是0,最后0.01秒来了100次,5.01秒又来了100次。也就是访问有明显的毛刺情况出现,为了弱化这个毛刺情况,我们可以采用滑动窗口。

滑动窗口

滑动窗口的主要原理比较简单,就是将这个单位时间进行拆分,譬如5秒的统计范围,我们将它划分成5个1秒。

当请求进来时,先判断当前请求属于这5个1秒的时间片中的哪个,然后将对应的时间片对应的统计值加1,再判断当前加上前4个时间片的次数总和是否已经超过了设置的阈值。

当时间已经到达第6个时间片时,就把第一个时间片给干掉,因为无论第一片是多少个统计值,它都不会再参与后续的计算了。

就这样,随着时间的推移,统计值就随着各个时间片的滚动,不断地进行统计。

Java简单实现滑动窗口_java

具体要将单位时间拆分为多少片,要根据实际情况来决定。当然,毫无疑问的是切分的越小,毛刺现象也越少。系统统计也越准确,随之就是内存占用会越大,因为你的这个窗口的数组会更大。

代码实现思路就是定义好分片数量,每个分片都有一个独立的计数器,所有的分片合计为一个数组。当请求来时,按照分片规则,判断请求应该划分到哪个分片中去。要判断是否超过阈值,就将前N个统计值相加,对比定义的阈值即可。

代码我直接引用别人写好的了,源代码在​​https://www.iteye.com/blog/go12345-1744728​

import java.util.concurrent.atomic.AtomicInteger;

/**
* 滑动窗口。该窗口同样的key,都是单线程计算。
*
* @author wuweifeng wrote on 2019-12-04.
*/
public class SlidingWindow {
/**
* 循环队列,就是装多个窗口用,该数量是windowSize的2倍
*/
private AtomicInteger[] timeSlices;
/**
* 队列的总长度
*/
private int timeSliceSize;
/**
* 每个时间片的时长,以毫秒为单位
*/
private int timeMillisPerSlice;
/**
* 共有多少个时间片(即窗口长度)
*/
private int windowSize;
/**
* 在一个完整窗口期内允许通过的最大阈值
*/
private int threshold;
/**
* 该滑窗的起始创建时间,也就是第一个数据
*/
private long beginTimestamp;
/**
* 最后一个数据的时间戳
*/
private long lastAddTimestamp;

public static void main(String[] args) {
//1秒一个时间片,窗口共5个
SlidingWindow window = new SlidingWindow(100, 4, 8);
for (int i = 0; i < 100; i++) {
System.out.println(window.addCount(2));

window.print();
System.out.println("--------------------------");
try {
Thread.sleep(102);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public SlidingWindow(int duration, int threshold) {
//超过10分钟的按10分钟
if (duration > 600) {
duration = 600;
}
//要求5秒内探测出来的,
if (duration <= 5) {
this.windowSize = 5;
this.timeMillisPerSlice = duration * 200;
} else {
this.windowSize = 10;
this.timeMillisPerSlice = duration * 100;
}
this.threshold = threshold;
// 保证存储在至少两个window
this.timeSliceSize = windowSize * 2;

reset();
}

public SlidingWindow(int timeMillisPerSlice, int windowSize, int threshold) {
this.timeMillisPerSlice = timeMillisPerSlice;
this.windowSize = windowSize;
this.threshold = threshold;
// 保证存储在至少两个window
this.timeSliceSize = windowSize * 2;

reset();
}

/**
* 初始化
*/
private void reset() {
beginTimestamp = SystemClock.now();
//窗口个数
AtomicInteger[] localTimeSlices = new AtomicInteger[timeSliceSize];
for (int i = 0; i < timeSliceSize; i++) {
localTimeSlices[i] = new AtomicInteger(0);
}
timeSlices = localTimeSlices;
}

private void print() {
for (AtomicInteger integer : timeSlices) {
System.out.print(integer + "-");
}
}

/**
* 计算当前所在的时间片的位置
*/
private int locationIndex() {
long now = SystemClock.now();
//如果当前的key已经超出一整个时间片了,那么就直接初始化就行了,不用去计算了
if (now - lastAddTimestamp > timeMillisPerSlice * windowSize) {
reset();
}

return (int) (((now - beginTimestamp) / timeMillisPerSlice) % timeSliceSize);
}

/**
* 增加count个数量
*/
public boolean addCount(int count) {
//当前自己所在的位置,是哪个小时间窗
int index = locationIndex();
// System.out.println("index:" + index);
//然后清空自己前面windowSize到2*windowSize之间的数据格的数据
//譬如1秒分4个窗口,那么数组共计8个窗口
//当前index为5时,就清空6、7、8、1。然后把2、3、4、5的加起来就是该窗口内的总和
clearFromIndex(index);

int sum = 0;
// 在当前时间片里继续+1
sum += timeSlices[index].addAndGet(count);
//加上前面几个时间片
for (int i = 1; i < windowSize; i++) {
sum += timeSlices[(index - i + timeSliceSize) % timeSliceSize].get();
}
System.out.println(sum + "---" + threshold);

lastAddTimestamp = SystemClock.now();

return sum >= threshold;
}

private void clearFromIndex(int index) {
for (int i = 1; i <= windowSize; i++) {
int j = index + i;
if (j >= windowSize * 2) {
j -= windowSize * 2;
}
timeSlices[j].set(0);
}
}

}
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
* 用于解决高并发下System.currentTimeMillis卡顿
* @author lry
*/
public class SystemClock {

private final int period;

private final AtomicLong now;

private static class InstanceHolder {
private static final SystemClock INSTANCE = new SystemClock(1);
}

private SystemClock(int period) {
this.period = period;
this.now = new AtomicLong(System.currentTimeMillis());
scheduleClockUpdating();
}

private static SystemClock instance() {
return InstanceHolder.INSTANCE;
}

private void scheduleClockUpdating() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable, "System Clock");
thread.setDaemon(true);
return thread;
}
});
scheduler.scheduleAtFixedRate(() -> now.set(System.currentTimeMillis()), period, period, TimeUnit.MILLISECONDS);
}

private long currentTimeMillis() {
return now.get();
}

/**
* 用来替换原来的System.currentTimeMillis()
*/
public static long now() {
return instance().currentTimeMillis();
}
}

 

参照代码main方法,通过修改每个时间片的时间,窗口数量,阈值,来进行测试。

这就是简单实现了。

 

 

标签:Java,int,windowSize,private,System,滑动,now,窗口
From: https://blog.51cto.com/u_13706148/6038473

相关文章

  • JavaScript学习笔记—DOM:操作class
    element.classList是一个对象,对象中提供了对当前元素的类的各种操作方法element.classList.add()向元素中添加一个或多个classelement.classList.remove()移除元素中......
  • 我的Java设计模式-责任链模式
    今天来说说程序员小猿和产品就关于需求发生的故事。前不久,小猿收到了产品的需求。产品经理:小猿,为了迎合大众屌丝用户的口味,我们要放一张图,要露点的。小猿:......露点?你大......
  • 【JavaScript】3_深挖数据类型
    4、其他的数据类型布尔值(Boolean)-布尔值主要用来进行逻辑判断-布尔值只有两个true和false-使用typeof检查......
  • 面试题-java
    目录基础概念什么是java?什么是面向对象?java三大特性this,super关键字抽象类、接口抽象类abstract接口共同点区别抽象类能使用final修饰吗?==和equalsfinal关键字数据类型字......
  • Java两大工具库:Commons和Guava(6)
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~   除了操作集合、限流和缓存,Guava还有另一个隐秘的功能:事件总线EventBus机制——是发布-订阅模式的实现,不需要显式......
  • 1.Java基础
    Java基础Java接口和抽象类有什么区别?共同点:都不能被实例化。都可以包含抽象方法。都可以有默认实现的方法(Java8可以用default关键字在接口中定义默认方法)。区别......
  • Java多线程并发06—CAS、AQS
    CAS(CompareAndSwap/Set)概念CAS函数,是比较并交换函数,它是原子操作函数。原理CAS是基于乐观锁的原理进行操作的。它总是认为自己可以成功完成操作。当多个线程同时使用CAS......
  • java——spring boot集成MongoDB——数据库安装和登录、简单使用
    参考文档,菜鸟教程:https://www.runoob.com/mongodb/mongodb-tutorial.html  参考文档、黑马教程:https://www.bilibili.com/video/BV1bJ411x7mq?p=1&vd_source=79bbd5b7......
  • Java异常
    Java异常异常指程序运行中出现的不期而至的各种状况。Java把异常当做对象来处理,并定义了一个基类java.lang.Throwable作为所有异常的超类。通常分为Error和Exception两......
  • JAVA 双亲委派与类加载器
    JAVA双亲委派与类加载器双亲委派虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当JVM想要加载一个.class文件的时候,到底应该由哪个类加......