* Optimize for leap array - Fix a bug: old position is not cleaned when inserting into a new (empty) position - Reuse buckets for optimization - The strategy is now changed: deprecated buckets will not be reset until newer time triggered. LeapArray is responsible for filtering the deprecated buckets (e.g. in `list` or `values`) - Update test cases Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -24,6 +24,7 @@ import com.alibaba.csp.sentinel.util.TimeUtil; | |||||
/** | /** | ||||
* @param <T> type of data wrapper | * @param <T> type of data wrapper | ||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
* @author Eric Zhao | |||||
*/ | */ | ||||
public abstract class LeapArray<T> { | public abstract class LeapArray<T> { | ||||
@@ -45,6 +46,12 @@ public abstract class LeapArray<T> { | |||||
return currentWindow(TimeUtil.currentTimeMillis()); | return currentWindow(TimeUtil.currentTimeMillis()); | ||||
} | } | ||||
/** | |||||
* Get window at provided timestamp. | |||||
* | |||||
* @param time a valid timestamp | |||||
* @return the window at provided timestamp | |||||
*/ | |||||
abstract public WindowWrap<T> currentWindow(long time); | abstract public WindowWrap<T> currentWindow(long time); | ||||
public WindowWrap<T> getPreviousWindow(long time) { | public WindowWrap<T> getPreviousWindow(long time) { | ||||
@@ -53,8 +60,8 @@ public abstract class LeapArray<T> { | |||||
time = time - windowLength; | time = time - windowLength; | ||||
WindowWrap<T> wrap = array.get(idx); | WindowWrap<T> wrap = array.get(idx); | ||||
if (wrap == null) { | |||||
return wrap; | |||||
if (wrap == null || isWindowDeprecated(wrap)) { | |||||
return null; | |||||
} | } | ||||
if (wrap.windowStart() + windowLength < (time)) { | if (wrap.windowStart() + windowLength < (time)) { | ||||
@@ -73,23 +80,27 @@ public abstract class LeapArray<T> { | |||||
int idx = (int)(timeId % array.length()); | int idx = (int)(timeId % array.length()); | ||||
WindowWrap<T> old = array.get(idx); | WindowWrap<T> old = array.get(idx); | ||||
if (old == null) { | |||||
if (old == null || isWindowDeprecated(old)) { | |||||
return null; | return null; | ||||
} | } | ||||
return old.value(); | return old.value(); | ||||
} | } | ||||
public AtomicReferenceArray<WindowWrap<T>> array() { | |||||
AtomicReferenceArray<WindowWrap<T>> array() { | |||||
return array; | return array; | ||||
} | } | ||||
private boolean isWindowDeprecated(WindowWrap<T> windowWrap) { | |||||
return TimeUtil.currentTimeMillis() - windowWrap.windowStart() >= intervalInMs; | |||||
} | |||||
public List<WindowWrap<T>> list() { | public List<WindowWrap<T>> list() { | ||||
ArrayList<WindowWrap<T>> result = new ArrayList<WindowWrap<T>>(); | ArrayList<WindowWrap<T>> result = new ArrayList<WindowWrap<T>>(); | ||||
for (int i = 0; i < array.length(); i++) { | for (int i = 0; i < array.length(); i++) { | ||||
WindowWrap<T> windowWrap = array.get(i); | WindowWrap<T> windowWrap = array.get(i); | ||||
if (windowWrap == null) { | |||||
if (windowWrap == null || isWindowDeprecated(windowWrap)) { | |||||
continue; | continue; | ||||
} | } | ||||
result.add(windowWrap); | result.add(windowWrap); | ||||
@@ -103,7 +114,7 @@ public abstract class LeapArray<T> { | |||||
for (int i = 0; i < array.length(); i++) { | for (int i = 0; i < array.length(); i++) { | ||||
WindowWrap<T> windowWrap = array.get(i); | WindowWrap<T> windowWrap = array.get(i); | ||||
if (windowWrap == null) { | |||||
if (windowWrap == null || isWindowDeprecated(windowWrap)) { | |||||
continue; | continue; | ||||
} | } | ||||
result.add(windowWrap.value()); | result.add(windowWrap.value()); | ||||
@@ -31,22 +31,26 @@ public class Window { | |||||
private final LongAdder minRt = new LongAdder(); | private final LongAdder minRt = new LongAdder(); | ||||
public Window() { | public Window() { | ||||
initMinRt(); | |||||
} | |||||
private void initMinRt() { | |||||
minRt.add(4900); | minRt.add(4900); | ||||
} | } | ||||
/** | /** | ||||
* Clean the adders and reset window to provided start time. | * Clean the adders and reset window to provided start time. | ||||
* | * | ||||
* @param startTime the start time of the window | |||||
* @return new clean window | * @return new clean window | ||||
*/ | */ | ||||
Window resetTo(long startTime) { | |||||
public Window reset() { | |||||
pass.reset(); | pass.reset(); | ||||
block.reset(); | block.reset(); | ||||
exception.reset(); | exception.reset(); | ||||
rt.reset(); | rt.reset(); | ||||
success.reset(); | success.reset(); | ||||
minRt.reset(); | minRt.reset(); | ||||
initMinRt(); | |||||
return this; | return this; | ||||
} | } | ||||
@@ -65,4 +65,18 @@ public class WindowWrap<T> { | |||||
public void setValue(T value) { | public void setValue(T value) { | ||||
this.value = value; | this.value = value; | ||||
} | } | ||||
public WindowWrap<T> resetTo(long startTime) { | |||||
this.windowStart = startTime; | |||||
return this; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "WindowWrap{" + | |||||
"windowLength=" + windowLength + | |||||
", windowStart=" + windowStart + | |||||
", value=" + value + | |||||
'}'; | |||||
} | |||||
} | } |
@@ -29,18 +29,26 @@ import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | |||||
*/ | */ | ||||
public class WindowLeapArray extends LeapArray<Window> { | public class WindowLeapArray extends LeapArray<Window> { | ||||
private final int timeLength; | |||||
public WindowLeapArray(int windowLengthInMs, int intervalInSec) { | public WindowLeapArray(int windowLengthInMs, int intervalInSec) { | ||||
super(windowLengthInMs, intervalInSec); | super(windowLengthInMs, intervalInSec); | ||||
timeLength = intervalInSec * 1000; | |||||
} | } | ||||
private ReentrantLock addLock = new ReentrantLock(); | private ReentrantLock addLock = new ReentrantLock(); | ||||
/** | |||||
* Reset current window to provided start time and reset all counters. | |||||
* | |||||
* @param startTime the start time of the window | |||||
* @return new clean window wrap | |||||
*/ | |||||
private WindowWrap<Window> resetWindowTo(WindowWrap<Window> w, long startTime) { | |||||
w.resetTo(startTime); | |||||
w.value().reset(); | |||||
return w; | |||||
} | |||||
@Override | @Override | ||||
public WindowWrap<Window> currentWindow(long time) { | public WindowWrap<Window> currentWindow(long time) { | ||||
long timeId = time / windowLength; | long timeId = time / windowLength; | ||||
// Calculate current index. | // Calculate current index. | ||||
int idx = (int)(timeId % array.length()); | int idx = (int)(timeId % array.length()); | ||||
@@ -62,29 +70,17 @@ public class WindowLeapArray extends LeapArray<Window> { | |||||
} else if (time > old.windowStart()) { | } else if (time > old.windowStart()) { | ||||
if (addLock.tryLock()) { | if (addLock.tryLock()) { | ||||
try { | try { | ||||
WindowWrap<Window> window = new WindowWrap<Window>(windowLength, time, new Window()); | |||||
if (array.compareAndSet(idx, old, window)) { | |||||
for (int i = 0; i < array.length(); i++) { | |||||
WindowWrap<Window> tmp = array.get(i); | |||||
if (tmp == null) { | |||||
continue; | |||||
} else { | |||||
if (tmp.windowStart() < time - timeLength) { | |||||
array.set(i, null); | |||||
} | |||||
} | |||||
} | |||||
return window; | |||||
} | |||||
// if (old is deprecated) then [LOCK] resetTo currentTime. | |||||
return resetWindowTo(old, time); | |||||
} finally { | } finally { | ||||
addLock.unlock(); | addLock.unlock(); | ||||
} | } | ||||
} else { | } else { | ||||
Thread.yield(); | Thread.yield(); | ||||
} | } | ||||
} else if (time < old.windowStart()) { | } else if (time < old.windowStart()) { | ||||
// Cannot go through here. | |||||
return new WindowWrap<Window>(windowLength, time, new Window()); | return new WindowWrap<Window>(windowLength, time, new Window()); | ||||
} | } | ||||
} | } | ||||
@@ -104,7 +104,7 @@ public class WindowLeapArrayTest { | |||||
assertEquals(0L, currentWindow.block()); | assertEquals(0L, currentWindow.block()); | ||||
} | } | ||||
@Test | |||||
@Deprecated | |||||
public void testWindowDeprecatedRefresh() { | public void testWindowDeprecatedRefresh() { | ||||
WindowLeapArray leapArray = new WindowLeapArray(windowLengthInMs, intervalInSec); | WindowLeapArray leapArray = new WindowLeapArray(windowLengthInMs, intervalInSec); | ||||
final int len = intervalInSec * 1000 / windowLengthInMs; | final int len = intervalInSec * 1000 / windowLengthInMs; | ||||
@@ -160,7 +160,34 @@ public class WindowLeapArrayTest { | |||||
} | } | ||||
@Test | @Test | ||||
public void testListWindows() { | |||||
public void testListWindowsResetOld() throws Exception { | |||||
final int windowLengthInMs = 100; | |||||
final int intervalInSec = 1; | |||||
final int intervalInMs = intervalInSec * 1000; | |||||
WindowLeapArray leapArray = new WindowLeapArray(windowLengthInMs, intervalInSec); | |||||
long time = TimeUtil.currentTimeMillis(); | |||||
Set<WindowWrap<Window>> windowWraps = new HashSet<WindowWrap<Window>>(); | |||||
windowWraps.add(leapArray.currentWindow(time)); | |||||
windowWraps.add(leapArray.currentWindow(time + windowLengthInMs)); | |||||
List<WindowWrap<Window>> list = leapArray.list(); | |||||
for (WindowWrap<Window> wrap : list) { | |||||
assertTrue(windowWraps.contains(wrap)); | |||||
} | |||||
Thread.sleep(windowLengthInMs + intervalInMs); | |||||
// This will replace the deprecated bucket, so all deprecated buckets will be reset. | |||||
leapArray.currentWindow(time + windowLengthInMs + intervalInMs).value().addPass(); | |||||
assertEquals(1, leapArray.list().size()); | |||||
} | |||||
@Test | |||||
public void testListWindowsNewBucket() throws Exception { | |||||
final int windowLengthInMs = 100; | final int windowLengthInMs = 100; | ||||
final int intervalInSec = 1; | final int intervalInSec = 1; | ||||
@@ -172,12 +199,16 @@ public class WindowLeapArrayTest { | |||||
windowWraps.add(leapArray.currentWindow(time)); | windowWraps.add(leapArray.currentWindow(time)); | ||||
windowWraps.add(leapArray.currentWindow(time + windowLengthInMs)); | windowWraps.add(leapArray.currentWindow(time + windowLengthInMs)); | ||||
Thread.sleep(intervalInSec * 1000 + windowLengthInMs * 3); | |||||
List<WindowWrap<Window>> list = leapArray.list(); | List<WindowWrap<Window>> list = leapArray.list(); | ||||
for (WindowWrap<Window> wrap : list) { | for (WindowWrap<Window> wrap : list) { | ||||
assertTrue(windowWraps.contains(wrap)); | assertTrue(windowWraps.contains(wrap)); | ||||
} | } | ||||
leapArray.currentWindow(time + windowLengthInMs * 20 + intervalInSec * 1000).value().addPass(); | |||||
// This won't hit deprecated bucket, so no deprecated buckets will be reset. | |||||
// But deprecated buckets can be filtered when collecting list. | |||||
leapArray.currentWindow(TimeUtil.currentTimeMillis()).value().addPass(); | |||||
assertEquals(1, leapArray.list().size()); | assertEquals(1, leapArray.list().size()); | ||||
} | } |