序
本文主要研究一下sentinel的ArrayMetric
ArrayMetric
com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java
public class ArrayMetric implements Metric {
private final WindowLeapArray data;
public ArrayMetric(int windowLength, int interval) {
this.data = new WindowLeapArray(windowLength, interval);
}
/**
* For unit test.
*/
public ArrayMetric(WindowLeapArray array) {
this.data = array;
}
@Override
public long success() {
data.currentWindow();
long success = 0;
List list = data.values();
for (Window window : list) {
success += window.success();
}
return success;
}
@Override
public long maxSuccess() {
data.currentWindow();
long success = 0;
List list = data.values();
for (Window window : list) {
if (window.success() > success) {
success = window.success();
}
}
return Math.max(success, 1);
}
@Override
public long exception() {
data.currentWindow();
long exception = 0;
List list = data.values();
for (Window window : list) {
exception += window.exception();
}
return exception;
}
@Override
public long block() {
data.currentWindow();
long block = 0;
List list = data.values();
for (Window window : list) {
block += window.block();
}
return block;
}
@Override
public long pass() {
data.currentWindow();
long pass = 0;
List list = data.values();
for (Window window : list) {
pass += window.pass();
}
return pass;
}
@Override
public long rt() {
data.currentWindow();
long rt = 0;
List list = data.values();
for (Window window : list) {
rt += window.rt();
}
return rt;
}
@Override
public long minRt() {
data.currentWindow();
long rt = Constants.TIME_DROP_VALVE;
List list = data.values();
for (Window window : list) {
if (window.minRt() < rt) {
rt = window.minRt();
}
}
return Math.max(1, rt);
}
@Override
public List details() {
List details = new ArrayList();
data.currentWindow();
for (WindowWrap window : data.list()) {
if (window == null) {
continue;
}
MetricNode node = new MetricNode();
node.setBlockedQps(window.value().block());
node.setException(window.value().exception());
node.setPassedQps(window.value().pass());
long passQps = window.value().success();
node.setSuccessQps(passQps);
if (passQps != 0) {
node.setRt(window.value().rt() / passQps);
} else {
node.setRt(window.value().rt());
}
node.setTimestamp(window.windowStart());
details.add(node);
}
return details;
}
@Override
public Window[] windows() {
data.currentWindow();
return data.values().toArray(new Window[data.values().size()]);
}
@Override
public void addException() {
WindowWrap wrap = data.currentWindow();
wrap.value().addException();
}
@Override
public void addBlock() {
WindowWrap wrap = data.currentWindow();
wrap.value().addBlock();
}
@Override
public void addSuccess() {
WindowWrap wrap = data.currentWindow();
wrap.value().addSuccess();
}
@Override
public void addPass() {
WindowWrap wrap = data.currentWindow();
wrap.value().addPass();
}
@Override
public void addRT(long rt) {
WindowWrap wrap = data.currentWindow();
wrap.value().addRT(rt);
}
@Override
public void debugQps() {
data.currentWindow();
StringBuilder sb = new StringBuilder();
sb.append(Thread.currentThread().getId()).append("_");
for (WindowWrap windowWrap : data.list()) {
sb.append(windowWrap.windowStart()).append(":").append(windowWrap.value().pass()).append(":")
.append(windowWrap.value().block());
sb.append(",");
}
System.out.println(sb);
}
@Override
public long previousWindowBlock() {
WindowWrap wrap = data.currentWindow();
wrap = data.getPreviousWindow();
if (wrap == null) {
return 0;
}
return wrap.value().block();
}
@Override
public long previousWindowPass() {
WindowWrap wrap = data.currentWindow();
wrap = data.getPreviousWindow();
if (wrap == null) {
return 0;
}
return wrap.value().pass();
}
}
- ArrayMetric底层使用WindowLeapArray作为数据存取
- 该类实现了指标的获取以及指标的新增
WindowLeapArray
com/alibaba/csp/sentinel/slots/statistic/metric/WindowLeapArray.java
public class WindowLeapArray extends LeapArray {
public WindowLeapArray(int windowLengthInMs, int intervalInSec) {
super(windowLengthInMs, intervalInSec);
}
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 resetWindowTo(WindowWrap w, long startTime) {
w.resetTo(startTime);
w.value().reset();
return w;
}
@Override
public WindowWrap currentWindow(long time) {
long timeId = time / windowLength;
// Calculate current index.
int idx = (int)(timeId % array.length());
// Cut the time to current window start.
time = time - time % windowLength;
while (true) {
WindowWrap old = array.get(idx);
if (old == null) {
WindowWrap window = new WindowWrap(windowLength, time, new Window());
if (array.compareAndSet(idx, null, window)) {
return window;
} else {
Thread.yield();
}
} else if (time == old.windowStart()) {
return old;
} else if (time > old.windowStart()) {
if (addLock.tryLock()) {
try {
// if (old is deprecated) then [LOCK] resetTo currentTime.
return resetWindowTo(old, time);
} finally {
addLock.unlock();
}
} else {
Thread.yield();
}
} else if (time < old.windowStart()) {
// Cannot go through here.
return new WindowWrap(windowLength, time, new Window());
}
}
}
}
- 继承了LeapArray,统计值为Window类型,WindowLeapArray重写了currentWindow方法
- currentWindow方法使用ReentrantLock在resetWindowTo的时候进行加锁
LeapArray
com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java
public abstract class LeapArray {
protected int windowLength;
protected int sampleCount;
protected int intervalInMs;
protected AtomicReferenceArray> array;
public LeapArray(int windowLength, int intervalInSec) {
this.windowLength = windowLength;
this.sampleCount = intervalInSec * 1000 / windowLength;
this.intervalInMs = intervalInSec * 1000;
this.array = new AtomicReferenceArray>(sampleCount);
}
public WindowWrap currentWindow() {
return currentWindow(TimeUtil.currentTimeMillis());
}
/**
* Get window at provided timestamp.
*
* @param time a valid timestamp
* @return the window at provided timestamp
*/
abstract public WindowWrap currentWindow(long time);
public WindowWrap getPreviousWindow(long time) {
long timeId = (time - windowLength) / windowLength;
int idx = (int)(timeId % array.length());
time = time - windowLength;
WindowWrap wrap = array.get(idx);
if (wrap == null || isWindowDeprecated(wrap)) {
return null;
}
if (wrap.windowStart() + windowLength < (time)) {
return null;
}
return wrap;
}
public WindowWrap getPreviousWindow() {
return getPreviousWindow(System.currentTimeMillis());
}
public T getWindowValue(long time) {
long timeId = time / windowLength;
int idx = (int)(timeId % array.length());
WindowWrap old = array.get(idx);
if (old == null || isWindowDeprecated(old)) {
return null;
}
return old.value();
}
AtomicReferenceArray> array() {
return array;
}
private boolean isWindowDeprecated(WindowWrap windowWrap) {
return TimeUtil.currentTimeMillis() - windowWrap.windowStart() >= intervalInMs;
}
public List> list() {
ArrayList> result = new ArrayList>();
for (int i = 0; i < array.length(); i++) {
WindowWrap windowWrap = array.get(i);
if (windowWrap == null || isWindowDeprecated(windowWrap)) {
continue;
}
result.add(windowWrap);
}
return result;
}
public List values() {
ArrayList result = new ArrayList();
for (int i = 0; i < array.length(); i++) {
WindowWrap windowWrap = array.get(i);
if (windowWrap == null || isWindowDeprecated(windowWrap)) {
continue;
}
result.add(windowWrap.value());
}
return result;
}
}
- 这里使用AtomicReferenceArray包装了WindowWrap
- 当前窗口的timeId为time / windowLength,对应的数组index为 (int)(timeId % array.length()),这里通过取余数来循环覆盖
- 通过index取出来的数据,还需要判断该窗口是否过期
WindowWrap
com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java
public class WindowWrap {
/**
* The length of the window.
*/
private final long windowLength;
/**
* Start time of the window in milliseconds.
*/
private long windowStart;
/**
* Statistic value.
*/
private T value;
/**
* @param windowLength the time length of the window
* @param windowStart the start timestamp of the window
* @param value window data
*/
public WindowWrap(long windowLength, long windowStart, T value) {
this.windowLength = windowLength;
this.windowStart = windowStart;
this.value = value;
}
public long windowLength() {
return windowLength;
}
public long windowStart() {
return windowStart;
}
public T value() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public WindowWrap resetTo(long startTime) {
this.windowStart = startTime;
return this;
}
@Override
public String toString() {
return "WindowWrap{" +
"windowLength=" + windowLength +
", windowStart=" + windowStart +
", value=" + value +
'}';
}
}
- 这里定义了开始时间,窗口长度,以及统计值
Window
com/alibaba/csp/sentinel/slots/statistic/base/Window.java
public class Window {
private final LongAdder pass = new LongAdder();
private final LongAdder block = new LongAdder();
private final LongAdder exception = new LongAdder();
private final LongAdder rt = new LongAdder();
private final LongAdder success = new LongAdder();
private volatile long minRt;
public Window() {
initMinRt();
}
private void initMinRt() {
this.minRt = Constants.TIME_DROP_VALVE;
}
/**
* Clean the adders and reset window to provided start time.
*
* @return new clean window
*/
public Window reset() {
pass.reset();
block.reset();
exception.reset();
rt.reset();
success.reset();
initMinRt();
return this;
}
public long pass() {
return pass.sum();
}
public long block() {
return block.sum();
}
public long exception() {
return exception.sum();
}
public long rt() {
return rt.sum();
}
public long minRt() {
return minRt;
}
public long success() {
return success.sum();
}
public void addPass() {
pass.add(1L);
}
public void addException() {
exception.add(1L);
}
public void addBlock() {
block.add(1L);
}
public void addSuccess() {
success.add(1L);
}
public void addRT(long rt) {
this.rt.add(rt);
// Not thread-safe, but it's okay.
if (rt < minRt) {
minRt = rt;
}
}
}
- Window对象记录了sentinel要统计的各个指标值
小结
sentinel使用ArrayMetric进行指标统计,底层使用的是WindowLeapArray,而WindowLeapArray使用的是WindowWrap
doc
- statistic/metric