feat(1.5.0-beta1): 优化滑动窗口
This commit is contained in:
parent
15e341fcac
commit
34e826cd97
@ -5,6 +5,7 @@ import com.aizuda.snailjob.common.core.util.JsonUtil;
|
|||||||
import com.aizuda.snailjob.common.core.window.Listener;
|
import com.aizuda.snailjob.common.core.window.Listener;
|
||||||
import com.aizuda.snailjob.common.log.SnailJobLog;
|
import com.aizuda.snailjob.common.log.SnailJobLog;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
@ -108,9 +109,6 @@ public class SlidingWindow<T> {
|
|||||||
// 扫描n-1个窗口,是否过期,过期则删除
|
// 扫描n-1个窗口,是否过期,过期则删除
|
||||||
removeInvalidWindow();
|
removeInvalidWindow();
|
||||||
|
|
||||||
// 超过窗口阈值预警
|
|
||||||
alarmWindowTotal();
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
oldWindowAdd(data);
|
oldWindowAdd(data);
|
||||||
}
|
}
|
||||||
@ -135,11 +133,11 @@ public class SlidingWindow<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扫描n-1个窗口,是否过期,过期则删除 过期条件为窗口期内无数据
|
* 扫描n-2个窗口,是否过期,过期则删除 过期条件为窗口期内无数据
|
||||||
*/
|
*/
|
||||||
private void removeInvalidWindow() {
|
private void removeInvalidWindow() {
|
||||||
|
|
||||||
for (int i = 0; i < saveData.size() - 1; i++) {
|
for (int i = 0; i < saveData.size() - 2; i++) {
|
||||||
Map.Entry<LocalDateTime, ConcurrentLinkedQueue<T>> firstEntry = saveData.firstEntry();
|
Map.Entry<LocalDateTime, ConcurrentLinkedQueue<T>> firstEntry = saveData.firstEntry();
|
||||||
if (CollUtil.isEmpty(firstEntry.getValue())) {
|
if (CollUtil.isEmpty(firstEntry.getValue())) {
|
||||||
saveData.remove(firstEntry.getKey());
|
saveData.remove(firstEntry.getKey());
|
||||||
@ -159,8 +157,24 @@ public class SlidingWindow<T> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 高并发下情况下出现刚刚获取的最后一个窗口被删除的情况
|
||||||
|
int count = 10;
|
||||||
ConcurrentLinkedQueue<T> list = saveData.get(windowPeriod);
|
ConcurrentLinkedQueue<T> list = saveData.get(windowPeriod);
|
||||||
|
while (Objects.isNull(list) && count > 0) {
|
||||||
|
count--;
|
||||||
|
windowPeriod = getNewWindowPeriod();
|
||||||
|
if (Objects.isNull(windowPeriod)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
list = saveData.get(windowPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Objects.nonNull(list)) {
|
||||||
list.add(data);
|
list.add(data);
|
||||||
|
} else {
|
||||||
|
// 这里一般走不到,作为兜底
|
||||||
|
SnailJobLog.LOCAL.error("Data loss. [{}]", JsonUtil.toJsonString(data));
|
||||||
|
}
|
||||||
|
|
||||||
if (list.size() >= totalThreshold) {
|
if (list.size() >= totalThreshold) {
|
||||||
doHandlerListener(windowPeriod);
|
doHandlerListener(windowPeriod);
|
||||||
@ -176,7 +190,8 @@ public class SlidingWindow<T> {
|
|||||||
private void doHandlerListener(LocalDateTime windowPeriod) {
|
private void doHandlerListener(LocalDateTime windowPeriod) {
|
||||||
|
|
||||||
NOTICE_LOCK.lock();
|
NOTICE_LOCK.lock();
|
||||||
|
// 深拷贝
|
||||||
|
ConcurrentLinkedQueue<T> deepCopy = null;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
ConcurrentLinkedQueue<T> list = saveData.get(windowPeriod);
|
ConcurrentLinkedQueue<T> list = saveData.get(windowPeriod);
|
||||||
@ -185,38 +200,28 @@ public class SlidingWindow<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 深拷贝
|
// 深拷贝
|
||||||
ConcurrentLinkedQueue<T> deepCopy = new ConcurrentLinkedQueue<>(list);
|
deepCopy = new ConcurrentLinkedQueue<>(list);
|
||||||
clear(windowPeriod, deepCopy);
|
clear(windowPeriod, deepCopy);
|
||||||
|
|
||||||
if (CollUtil.isEmpty(deepCopy)) {
|
if (CollUtil.isEmpty(deepCopy)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Listener<T> listener : listeners) {
|
|
||||||
listener.handler(new ArrayList<>(deepCopy));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
SnailJobLog.LOCAL.error("到达总量窗口期通知异常", e);
|
SnailJobLog.LOCAL.error("deep copy task queue is error", e);
|
||||||
} finally {
|
} finally {
|
||||||
NOTICE_LOCK.unlock();
|
NOTICE_LOCK.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CollectionUtils.isEmpty(deepCopy)) {
|
||||||
|
try {
|
||||||
|
for (Listener<T> listener : listeners) {
|
||||||
|
listener.handler(new ArrayList<>(deepCopy));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
SnailJobLog.LOCAL.error("notice is error", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除2倍窗口期之前无效窗口
|
|
||||||
*
|
|
||||||
* @param windowPeriod 当前最老窗口期
|
|
||||||
*/
|
|
||||||
private void removeInvalidWindow(LocalDateTime windowPeriod) {
|
|
||||||
|
|
||||||
LocalDateTime currentTime = LocalDateTime.now().minus(duration * 2, chronoUnit);
|
|
||||||
if (windowPeriod.isBefore(currentTime)) {
|
|
||||||
SnailJobLog.LOCAL.debug("删除过期窗口 windowPeriod:[{}] currentTime:[{}]", windowPeriod, currentTime);
|
|
||||||
saveData.remove(windowPeriod);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -248,6 +253,19 @@ public class SlidingWindow<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除2倍窗口期之前无效窗口
|
||||||
|
*
|
||||||
|
* @param windowPeriod 当前最老窗口期
|
||||||
|
*/
|
||||||
|
private void removeInvalidWindow(LocalDateTime windowPeriod) {
|
||||||
|
|
||||||
|
LocalDateTime currentTime = LocalDateTime.now().minus(duration * 2, chronoUnit);
|
||||||
|
if (windowPeriod.isBefore(currentTime) && CollUtil.isEmpty(saveData.get(windowPeriod))) {
|
||||||
|
saveData.remove(windowPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否开启新窗口期
|
* 是否开启新窗口期
|
||||||
*
|
*
|
||||||
@ -286,6 +304,9 @@ public class SlidingWindow<T> {
|
|||||||
// 删除过期窗口期数据
|
// 删除过期窗口期数据
|
||||||
removeInvalidWindow(windowPeriod);
|
removeInvalidWindow(windowPeriod);
|
||||||
|
|
||||||
|
// 超过窗口阈值预警
|
||||||
|
alarmWindowTotal();
|
||||||
|
|
||||||
if (windowPeriod.isBefore(condition)) {
|
if (windowPeriod.isBefore(condition)) {
|
||||||
SnailJobLog.LOCAL.debug("到达时间窗口期 [{}] [{}]", windowPeriod, JsonUtil.toJsonString(saveData));
|
SnailJobLog.LOCAL.debug("到达时间窗口期 [{}] [{}]", windowPeriod, JsonUtil.toJsonString(saveData));
|
||||||
doHandlerListener(windowPeriod);
|
doHandlerListener(windowPeriod);
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
package com.aizuda.snailjob.server.support.generator.id;
|
|
||||||
|
|
||||||
import com.aizuda.snailjob.common.core.constant.SystemConstants;
|
|
||||||
import com.aizuda.snailjob.common.log.SnailJobLog;
|
|
||||||
import com.aizuda.snailjob.server.common.exception.SnailJobServerException;
|
|
||||||
import com.aizuda.snailjob.server.common.generator.id.IdGenerator;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试多线程情况下号段模式的运行情况
|
|
||||||
*
|
|
||||||
* @author: opensnail
|
|
||||||
* @date : 2023-05-06 09:16
|
|
||||||
* @since 1.2.0
|
|
||||||
*/
|
|
||||||
@SpringBootTest
|
|
||||||
@Slf4j
|
|
||||||
public class SegmentIdGeneratorTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("segmentIdGenerator")
|
|
||||||
private IdGenerator idGenerator;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void idGeneratorTest() throws InterruptedException {
|
|
||||||
|
|
||||||
// step: 100, cpu: 4 memory: 16G, 线程1-150之间出现异常id:-3的情况较少。
|
|
||||||
// 如果有特殊需求可以提高step, 可通过系统配置: SystemProperties#step属性进行配置
|
|
||||||
Set<String> idSet = new HashSet<>();
|
|
||||||
int size = 500;
|
|
||||||
CountDownLatch count = new CountDownLatch(size);
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
count.countDown();
|
|
||||||
|
|
||||||
String id = idGenerator.idGenerator("example_group", SystemConstants.DEFAULT_NAMESPACE);
|
|
||||||
SnailJobLog.LOCAL.info("id:[{}]", id);
|
|
||||||
if (Long.parseLong(id) < 0) {
|
|
||||||
throw new SnailJobServerException("exception id");
|
|
||||||
} else if (idSet.contains(id)) {
|
|
||||||
throw new SnailJobServerException("duplicate id [{}]", id);
|
|
||||||
} else {
|
|
||||||
idSet.add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
count.await();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user