moco固定QPS接口升级补偿机制

时间:2022-07-24
本文章向大家介绍moco固定QPS接口升级补偿机制,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

之前写过一篇?如何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()方法的误差有关系。