package com.dianping.pigeon.remoting.invoker.route.statistics;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.dianping.pigeon.log.Logger;
import com.dianping.pigeon.log.LoggerLoader;
import com.dianping.pigeon.remoting.common.domain.InvocationRequest;
import com.dianping.pigeon.remoting.common.util.Constants;
@SuppressWarnings("serial")
public class CapacityBucket implements Serializable {
private static final Logger logger = LoggerLoader.getLogger(CapacityBucket.class);
private String address;
private volatile float capacity = 0f;
private Set<Long> requestSeqs = Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
// 桶中某些容量因某些意外因素导致没有释放, 可以用这个进行Check
private transient Map<Long, Object[]> requestSeqDetails = new ConcurrentHashMap<Long, Object[]>();
private AtomicLong totalRequest = new AtomicLong(); // total request
// send
private AtomicLong onewayRequest = new AtomicLong(); // total oneway
// request send
private Map<Integer, AtomicInteger> totalRequestInSecond = new ConcurrentHashMap<Integer, AtomicInteger>();
private transient Lock capacityLock = new ReentrantLock();
public CapacityBucket(String address) {
this.address = address;
preFillData(); // 为了更优地计算每秒请求数, 使用预填数据代替同步数据结构
}
public void flowIn(InvocationRequest request) {
Calendar now = Calendar.getInstance();
totalRequest.incrementAndGet();
if (request.getCallType() == Constants.CALLTYPE_NOREPLY) {
onewayRequest.incrementAndGet();
}
incrementTotalRequestInSecond(now.get(Calendar.SECOND));
if (request.getCallType() == Constants.CALLTYPE_REPLY) {
refreshCapacity(1f);
this.requestSeqs.add(request.getSequence());
this.requestSeqDetails.put(request.getSequence(),
new Object[] { now.getTimeInMillis(), request.getTimeout(), 1f });
}
}
public void flowOut(InvocationRequest request) {
if (request.getCallType() == Constants.CALLTYPE_REPLY) {
flowOut(request.getSequence(), 1f);
}
}
public void flowOut(long requestSeq, Float flow) {
if (requestSeqs.remove(requestSeq) && flow != null) {
refreshCapacity(-1 * flow);
}
requestSeqDetails.remove(requestSeq);
}
public int getLastSecondRequest() {
int lastSecond = Calendar.getInstance().get(Calendar.SECOND) - 1;
lastSecond = lastSecond >= 0 ? lastSecond : lastSecond + 60;
AtomicInteger counter = totalRequestInSecond.get(lastSecond);
return counter != null ? counter.intValue() : 0;
}
private void incrementTotalRequestInSecond(int second) {
AtomicInteger counter = totalRequestInSecond.get(second);
if (counter != null) {
counter.incrementAndGet();
} else {
logger.error("Impossible case happended, second[" + second + "]'s request counter is null.");
}
}
public void refreshCapacity(float addition) {
capacityLock.lock();
try {
this.capacity += addition;
} finally {
capacityLock.unlock();
}
}
/**
* 重置过期的每秒请求数计数器
*/
public void resetRequestInSecondCounter() {
int second = Calendar.getInstance().get(Calendar.SECOND);
int prev3Sec = second - 10;
for (int i = 1; i <= 30; i++) {
int prevSec = prev3Sec - i;
prevSec = prevSec >= 0 ? prevSec : prevSec + 60;
AtomicInteger counter = totalRequestInSecond.get(prevSec);
if (counter != null) {
counter.set(0);
}
}
}
private void preFillData() {
for (int sec = 0; sec < 60; sec++) {
totalRequestInSecond.put(sec, new AtomicInteger());
}
}
public String getAddress() {
return address;
}
public float getCapacity() {
return capacity;
}
public AtomicLong getTotalRequest() {
return totalRequest;
}
public AtomicLong getOnewayRequest() {
return onewayRequest;
}
public Map<Long, Object[]> getRequestSeqDetails() {
return requestSeqDetails;
}
}