package com.taobao.tddl.atom.utils;
import java.io.ByteArrayInputStream;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import com.taobao.tddl.common.utils.logger.Logger;
import com.taobao.tddl.common.utils.logger.LoggerFactory;
/**
* 平滑阀门。用于可用性状态切换。 解决不可用到可用时,瞬间的流量、连接等的暴增冲击问题
* 第一批只允许执行1次,第二批允许执行2次,。。。最后一批之后不再限制(batchLimits)
* 每一批的名额用尽,要间隔timeDelay毫秒后,才开始下一批名额的发放。 在这个间隔时间期间的请求直接拒绝。
* 允许负数的limit,含义为1/n的概率去执行。具体见构造函数
*
* <pre>
* 使用列子:
* SmoothValve smoothValve = new SmoothValve(0);
* try {
* if (valve.isNotAvailable()) {
* // try-lock trying
* }else {
* if (valve.smoothThroughOnInitial()) {
* // do smooth trying
*
* if(sucessed){
* valve.setAvailable();
* } else {
* // throw not available
* }
*
* }else {
* // throw not available
* }
* }
*
*
* </pre>
*
* @author linxuan
*/
public class SmoothValve {
private static final Logger log = LoggerFactory.getLogger(SmoothValve.class);
private volatile boolean available = true;
private volatile boolean isInSmooth = false; // 是否在平滑期
private final AtomicInteger count = new AtomicInteger();
private final AtomicInteger batchNo = new AtomicInteger();
private final int[] batchLimits;
private final AtomicInteger rejectCount = new AtomicInteger();
private final long timeDelay; // ms
private volatile long timeBegin;
public SmoothValve(long timeDelay){
this.timeDelay = timeDelay;
this.batchLimits = new int[] { 1, 2, 4, 8, 16, 32, 64 };
}
/**
* @param timeDelay
* @param batchLimits 允许负数的limit,为了解决cliet数量相当多时,每个client放一个请求进来都会冲垮服务器的场景:
* >1,表示允许通过的次数 <br/>
* -1和0作用相同,表示放弃当前尝试,只是多延迟了一些时间。所以一般不设 <br/>
* -2表示这批尝试中,只随机放入所有client的1/2(让当前操作1/2的概率通过) <br/>
* -3表示这批尝试中,只随机放入所有client的1/3(让当前操作1/3的概率通过) ... <br/>
* 例如:batchLimits = new int[] { -4,-3,-2, 1, 2, 4, 8, 16, 32 };
*/
public SmoothValve(long timeDelay, int[] batchLimits){
this.timeDelay = timeDelay;
this.batchLimits = batchLimits;
}
/**
* @return 返回平滑控制后的可用不可用信息 true: 可用; false:不可用,或者可用但没通过限流
*/
public boolean smoothThroughOnInitial() {
if (timeDelay == 0) { // 相当于完全关闭这个功能
return true;
}
while (isInSmooth && available) { // 已可用,且在平滑期
int batch = batchNo.get();
if (batch >= batchLimits.length) {
isInSmooth = false;
count.set(0);
batchNo.set(0);
rejectCount.set(0);
return available; // 返回可用
}
int limit = batchLimits[batch];
if (limit < -1) { // 0和-1不管,后面会直接放弃
int randomInt = new Random().nextInt(-limit);
if (randomInt == 0) {
return available; // 返回可用
} else {
logReject(rejectCount, limit);
return false; // 返回不可用
}
}
int current = count.get();
while (current < limit) {
if (count.compareAndSet(current, current + 1)) {
timeBegin = System.currentTimeMillis();
return available; // 返回可用
}
current = count.get();
}
// current >= limit
if (System.currentTimeMillis() - timeBegin > timeDelay) {
// timeDelay * (batch + 1) 这样做可能会人为造成聚集暴增
// 超过时间,才设置下一个批次
batchNo.compareAndSet(batch, batch + 1);
// 继续循环
} else {
logReject(rejectCount, limit);
return false; // 返回不可用
}
}
return available;
}
private void logReject(AtomicInteger rejectCount, int limit) {
int rc = rejectCount.incrementAndGet();
log.warn("A request reject in available switch. limit=" + limit + ",rejectCount=" + rc);
}
/**
* @return 返回实际可用不可用的信息
*/
public boolean isAvailable() {
return available;
}
public boolean isNotAvailable() {
return !available;
}
/**
* 设置为可用
*/
public void setAvailable() {
if (available) {
return;
}
count.set(0);
batchNo.set(0);
this.isInSmooth = true;
this.available = true;
}
/**
* 设置为不可用
*/
public void setNotAvailable() {
if (available) {
rejectCount.set(0);
this.available = false;
}
}
private static enum CreateProperties {
timeDelay, batchLimits;
}
public static SmoothValve parse(String str) {
try {
Properties p = new Properties();
p.load(new ByteArrayInputStream((str).getBytes()));
long td = 0;
String[] limits = null;
for (Map.Entry<Object, Object> entry : p.entrySet()) {
String key = ((String) entry.getKey()).trim();
String value = ((String) entry.getValue()).trim();
switch (CreateProperties.valueOf(key)) {
case timeDelay:
td = Integer.parseInt(value);
break;
case batchLimits:
limits = value.split("\\|");
break;
default:
break;
}
}
if (td == 0) {
log.error("SmoothValve Properties incomplete");
return null;
}
if (limits != null) {
int[] limitArray = new int[limits.length];
for (int i = 0; i < limits.length; i++) {
limitArray[i] = Integer.parseInt(limits[i].trim());
}
return new SmoothValve(td, limitArray);
} else {
return new SmoothValve(td);
}
} catch (Exception e) {
log.error("parse SmoothValve Properties failed", e);
return null;
}
}
public String toString() {
return new StringBuilder("timeDelay=").append(timeDelay)
.append(",batchLimits=")
.append(Arrays.toString(batchLimits))
.toString();
}
}