package com.taobao.tddl.atom.utils;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 将时间片分为多个槽,每个槽一个计数器。游标按时间循环遍历每个槽。游标移动时才清零并且只清零当前的槽; 因为采用mod计算(cursor =
* currentTime % timeslice/aSlotTime),游标到头会自动折回来,事实上是一个环
*
* <pre>
* cursor
* |
* +---------------------+-------------------------+
* | | | | | | C | | | | | | |
* +-----------------------------------------------+
* | |
* \-----------------timeslice-------------------/
*
* </pre>
*
* @author linxuan
*/
public class TimesliceFlowControl {
private final static int MAX_SLOT = 20; // 最多20片
private final static int MIN_SLOT_TIME = 500; // slot时间最少500毫秒
private final String name;
private final AtomicInteger[] slots; // 槽数组,最小的时间粒度数组
private final int aSlotTimeMillis; // 一个槽的时间,最小的时间单位
private final int timesliceMillis; // 总的时间窗口(时间片)大小
private final int timesliceMaxIns; // 时间片内允许的最大访问次数(进入个数)
private final AtomicInteger total = new AtomicInteger(); // 总的计数
private final AtomicInteger totalReject = new AtomicInteger(); // 总的拒绝/超限计数
private volatile int cursor = 0; // 游标
private volatile long cursorTimeMillis = System.currentTimeMillis(); // 当前slot的开始时间
/**
* @param name 流控的名称
* @param slotTimeMillis //一个槽的时间
* @param slotCount //槽的数目
* @param limit //时间窗口内最多允许执行的次数,设为0则不限制
*/
public TimesliceFlowControl(String name, int aSlotTimeMillis, int slotCount, int timesliceMaxIns){
if (slotCount < 2) {
throw new IllegalArgumentException("slot至少要有两个");
}
this.name = name;
this.aSlotTimeMillis = aSlotTimeMillis;
this.timesliceMillis = aSlotTimeMillis * slotCount;
this.timesliceMaxIns = timesliceMaxIns;
slots = new AtomicInteger[slotCount];
for (int i = 0; i < slotCount; i++) {
slots[i] = new AtomicInteger(0);
}
}
/**
* 最小的时间单位取默认的500毫秒
*
* @param name 流控的名称
* @param timesliceMillis 时间片; 传0表示使用默认值1分钟
* @param limit 时间片内最多允许执行多少次,设为0则不限制
*/
public TimesliceFlowControl(String name, int timesliceMillis, int timesliceMaxIns){
if (timesliceMillis == 0) {
timesliceMillis = 60 * 1000; // 时间片默认1分钟
}
if (timesliceMillis < 2 * MIN_SLOT_TIME) {
throw new IllegalArgumentException("时间片最少" + (2 * MIN_SLOT_TIME));
}
// this(name, 500, timesliceMillis / 500, limit);
int slotCount = MAX_SLOT; // 默认分20个slot
int slotTime = timesliceMillis / slotCount;
if (slotTime < MIN_SLOT_TIME) {
slotTime = MIN_SLOT_TIME; // 如果slot时间小于MIN_SLOT_TIME,则最小半秒
slotCount = timesliceMillis / slotTime;
}
this.name = name;
this.aSlotTimeMillis = slotTime;
// this.timesliceMillis = timesliceMillis; //直接赋值因为截余的关系,会数组越界
this.timesliceMillis = aSlotTimeMillis * slotCount;
this.timesliceMaxIns = timesliceMaxIns;
slots = new AtomicInteger[slotCount];
for (int i = 0; i < slotCount; i++) {
slots[i] = new AtomicInteger(0);
}
}
public void check() {
if (!allow()) {
throw new IllegalStateException(reportExceed());
}
}
public String reportExceed() {
return name + " exceed the limit " + timesliceMaxIns + " in timeslice " + timesliceMillis;
}
public boolean allow() {
final long current = System.currentTimeMillis();
final int index = (int) ((current % timesliceMillis) / aSlotTimeMillis);
if (index != cursor) {
int oldCursor = cursor;
cursor = index; // 尽快赋新值
final long oldCursorTimeMillis = cursorTimeMillis;
cursorTimeMillis = current; // 尽快赋新值
// 多个线程会进入下面这里,但是每个线程计算的total会大致相同
if (current - oldCursorTimeMillis > timesliceMillis) {
// 时间差大于timesliceMillis,则整个时间片都应该清零了
for (int i = 0; i < slots.length; i++) {
slots[i].set(0); // 清零,忽略并发造成的计数出入
}
this.total.set(0);
} else {
do {
// 吃尾(尾清零),考虑跳跃的情况
oldCursor++;
if (oldCursor >= slots.length) {
oldCursor = 0;
}
slots[oldCursor].set(0); // 清零,忽略并发造成的计数出入
} while (oldCursor != index);
// int clearCount = slots[index].get();
// slots[index].set(0); //清零,忽略并发造成的计数出入
int newtotal = 0;
for (int i = 0; i < slots.length; i++) {
newtotal += slots[i].get(); // 包括了新的当前槽
}
this.total.set(newtotal); // 设置总数,忽略并发造成的计数出入
}
} else {
if (current - cursorTimeMillis > aSlotTimeMillis) {
// index相同但是时间差大于一个slot的时间,说明整个时间片都需要清零了
cursorTimeMillis = current; // 尽快赋新值
for (int i = 0; i < slots.length; i++) {
slots[i].set(0); // 清零,忽略并发造成的计数出入
}
this.total.set(0);
}
// 是否为了避免开销,不做上面的判断?
}
if (timesliceMaxIns == 0) {
return true; // 0为不限制
}
if (this.total.get() < timesliceMaxIns) {
// 放来的才计数,拒绝的不计数
slots[index].incrementAndGet();
total.incrementAndGet();
return true;
} else {
totalReject.incrementAndGet();
return false;
}
}
/**
* @return 当前时间片内的总执行次数
*/
public int getCurrentCount() {
return total.get();
}
/**
* @return 返回有史以来(对象创建以来)被拒绝/超限的次数
*/
public int getTotalRejectCount() {
return totalReject.get();
}
}