之前写过一篇如何 mock 固定 QPS 的接口,中间用到了流量控制类Semaphore
和线程安全的知识。当时在测试过程中,多线程并发请求,QPS
误差率在5%以内,觉得算是在可接受范围内的,所以并没有做进一步优化。
最近看了一篇有关开源代码文章,有点感触,就在原来的基础上做了优化。主要思路是新建一个线程,通过计算理论值和实际值的差距,通过一个线程安全的对象完成这个补偿修正。
核心代码如下:
package com.fun.moco.support
import com.fun.frame.SourceCode
import com.github.dreamhead.moco.ResponseHandler
import com.github.dreamhead.moco.handler.AbstractResponseHandler
import com.github.dreamhead.moco.internal.SessionContext
import com.github.dreamhead.moco.util.Idles
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import static com.google.common.base.Preconditions.checkArgument
/**
* 固定QPS的接口实现类
*/
class QPSHandler extends AbstractResponseHandler {
private static final Semaphore semaphore = new Semaphore(1, true);
/**
* 访问间隔,使用微秒控制
*/
private final int gap
private final ResponseHandler handler
/**
* 用于记录收到第一个请求的时间
*/
private long start
/**
* 用于统计已处理请求的总次数,因为用了流量控制,所以不适用安全类
*/
private int times = 0
/**
* 用于统计实际的请求数和预期请求数直接的差距,由于在真实场景下预期的QPS总是大于实际QPS,所以只处理diff为正值的情况
*/
private AtomicInteger diff = new AtomicInteger(0)
private QPSHandler(ResponseHandler handler, int gap) {
this.gap = gap * 1000
this.handler = handler
}
public static ResponseHandler newSeq(final ResponseHandler handler, int gap) {
checkArgument(handler != null, "responsehandler 不能为空!");
def handler1 = new QPSHandler(handler, gap)
handler1.thread.start()
return handler1;
}
/**
* 具体实现,这里采用微秒,实验证明微秒更准确
* @param context
*/
@Override
void writeToResponse(SessionContext context) {
if (start == 0) start = SourceCode.getNanoMark()
semaphore.acquire()
if (diff.getAndIncrement() <= 0) Idles.idle(gap, TimeUnit.MICROSECONDS)
times++
semaphore.release()
handler.writeToResponse(context)
}
/**
* 用于定时计算实际处理请求与预期处理请求数量差距,补偿缺失部分的多线程
*/
private Thread thread = new Thread(new Runnable() {
@Override
void run() {
while (true) {
SourceCode.sleep(30_000)
long present = SourceCode.getNanoMark()
def t0 = present - start
def t1 = times * gap
if (t0 - t1 > gap) diff.getAndSet((t0 - t1) / gap)
}
}
})
}
times
属性我并没有使用线程安全类,因为执行times++
的时候已经是单线程执行了,过多使用线程安全类会使QPS
误差更大。diff
属性的正负值问题。在实际使用时发现diff
的值总是正值,也就是期望QPS
总是大于实际的QPS
,这个跟响应的代码执行和Idles.idle()
方法的误差有关系。公众号FunTester首发,原创分享爱好者,腾讯云和掘金社区首页推荐,知乎七级原创作者,欢迎关注、交流,禁止第三方擅自转载。