java统计qps_统计接⼝QPS
现在记录话单的时候想加⼀个参数:每秒接⼝调⽤的并发量,也就是所谓的QPS(Queries per cond)。QPS即每秒请求数,是对⼀个特
定的接⼝在规定时间内请求流量的衡量标准。那么如何实现QPS的计算呢?我想到的是两种⽅案:
1、⼀定时间内(⽐如⼀分钟)的请求总量/统计时间段(⽐如⼀分钟),最终得出就是每秒的并发量,它是基于某⼀段时间来统计的
2、直接统计⼀秒钟内的请求总量,就是按每秒的时间段来统计,简单粗暴
⽅案⼀的适⽤场景应该是报表、运维统计之类的,只关⼼QPS曲线;如果⽤来做并发量校验,明显只能⽤⽅案⼆,需要实时获取QPS。那
么如何统计⼀秒内的并发量?假设某⼀个时间点有接⼝到来,那么就开始统计该接⼝,在⼀秒之内,来多少个累加多少次。⼀秒之后,统计
备考
数清零。之后的某⼀个时间点,⼜有接⼝到来,⼜开始统计⼀秒之内的接⼝调⽤量,如此循环往复。
音响没有声音
那么如何维护⼀个⼀秒之内的接⼝计数器呢?我觉得失效缓存是⼀个合适的选择,缓存的键即为接⼝名,值就是接⼝统计数,过期时间⼀
秒。为了避免引⼊第三⽅中间件,我们⾃⼰实现该过期缓存,需要维护⼀个定时器和⼀个优先级队列,每秒清理⼀次队列中已过期的缓存。
废话说完了,看代码:
1、缓存的值
importlombok.Getter;importlombok.Setter;urrent.atomic.AtomicLong;/*** 内部类,缓存对象,按失效时间排
序,越早失效越前
*@authorwulf
*@since20200422*/@Getter
@Setterpublic class CacheNode implements Comparable{privateString key;privateAtomicLong callQ
uantity;private longexpireTime;public CacheNode(String key, AtomicLong callQuantity, longexpireTime) {this.key =key;this.callQuantity
=pireTime =expireTime;
}
@Overridepublic intcompareTo(CacheNode o) {long dif = pireTime -o.expireTime;if (dif > 0) {return 1;
}el if (dif < 0) {return -1;
}return 0;
}
}
2、过期缓存:
importcom.wlf.bean.CacheNode;importjava.util.Map;importjava.util.PriorityQueue;urrent.ConcurrentHashMap
小手牵大手手抄报
带过期时间的缓存
*
*@authorwulf
*@since2020/04/21*/
public classExpiredCache {//缓存key=接⼝名,value=接⼝调⽤量、过期时间戳
private Map cache = new ConcurrentHashMap<>();//qps
最美的情话
private AtomicLong qps = null;//重⼊锁
private ReentrantLock lock = newReentrantLock();//失效队列
private PriorityQueue queue = new PriorityQueue<>();//启动定时任务,每秒清理⼀次过期缓存
private final static ScheduledExecutorService scheduleExe = new ScheduledThreadPoolExecutor(1
0);//构造函数中启动定时任务,执⾏对已过期缓存的清理⼯作,每秒执⾏⼀次
publicExpiredCache() {
scheduleExe.scheduleAtFixedRate(new CleanExpireCacheTask(), 1L, 1L, TimeUnit.SECONDS);
}/*** 内部类,清理过期缓存对象*/
private class CleanExpireCacheTask implementsRunnable {
@Overridepublic voidrun() {long currentTime =System.currentTimeMillis();//取出队列中的队头元素,对已过期的元素执⾏清除计划,剩下没有过期则退出
while (true) {
三个字的lock.lock();try{
CacheNode cacheNode=queue.peek();//已经把队列清空了,或者所有过期元素已清空了,退出
if (cacheNode == null || ExpireTime() >currentTime) {return;
}//开始⼤清理了
福楼拜作品
queue.poll();
}finally{
lock.unlock();
}
}
}
}/*** 根据缓存key获取values
*
*@paramcacheKey
*@return
*/
publicCacheNode getCacheNode(String cacheKey) {(cacheKey);
}/*** 加⼊缓存,设置存活时间
*
*@paramcacheKey
*@paramttl 缓存的存活时间
* return*/
public AtomicLong t(String cacheKey, longttl) {//若缓存中已存在缓存节点,不需要更新过期时间,仅更新QPS值
CacheNode oldNode =(cacheKey);if (oldNode != null) {
AtomicLong CallQuantity();
oldQps.incrementAndGet();
cache.put(cacheKey, oldNode);
}el{//否则新创建CacheNode对象,失效时间=当前时间+缓存存活时间
AtomicLong qps = new AtomicLong(1);
CacheNode newNode= new CacheNode(cacheKey, qps, System.currentTimeMillis() + ttl * 1000);//放⼊缓存,加⼊过期队列cache.put(cacheKey, newNode);
queue.add(newNode);
}(cacheKey).getCallQuantity();
}
}
3、在切⾯中统计接⼝QPS:
packagecom.wlf.cdr;importcom.s.TranslateCdr;importcom.wlf.utils.ExpiredCache;importcom.wlf.utils.IPUtil;impor
@Slf4j
@Aspect
@Componentpublic classCdrApt {private final static SimpleDateFormat SF = new
SimpleDateFormat("yyyyMMddHHmmss");//话单格式:接⼝名称|话单记录时间|接⼝时延|调⽤⽅IP|本地IP|⽤户ID|⽤户名|源语⾔|⽬
标语⾔|结果码|QPS
private final static String CDR_FORMAT = "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}";//过期缓存
private ExpiredCache expiredCache = newExpiredCache();
@Around("execution(* ller.TranslateController.*(..))")public Object
recordCdr(ProceedingJoinPoint joinPoint) throwsThrowable {long startTime =System.currentTimeMillis();
String startDate= SF.format(newDate(startTime));//⽩名单校验
ServletRequestAttributes attributes =(ServletRequestAttributes) RequestAttributes(); HttpServletRequest Request();
String LocalIp();炒油茶
String RemoteIp(httpServletRequest);
TranslateCdr cdr= newTranslateCdr();
cdr.tRemoteIp(remoteIp);
CdrThreadLocal.tTranslateCdr(cdr);//获取接⼝名
String requestPath =RequestURI();
String cacheKey= requestPath.substring(requestPath.lastIndexOf("/") + 1, requestPath.length());//设置过期时间为1秒
long qps = expiredCache.t(cacheKey, 1).get();
Object result=joinPoint.proceed();long endTime =System.currentTimeMillis();
TranslateCdr();if (cdr != null) {
<(CDR_FORMAT, cacheKey, startDate, endTime-startTime, remoteIp, localIp, UrId(),
}
CdrThreadLocal.delThreadLocal();returnresult;
}
}
在切⾯中只需t⼀下,如果这时缓存有数据,就累加统计数,没有就设置统计数为1,再get出来的得到QPS。但这⾥为了兼顾吞吐量,让接⼝的调⽤不受QPS统计的影响,并没有在切⾯或者过期缓存的t⽅法加锁,因此对两个并发时间很短的接⼝,统计数会相同。壳怎么组词