前段时间我关注了一篇文章 ,多少分享如果使用并发压测发现 BUG。才出突然想起一个话题 :
线程不安全需要多少 QPS 压测才能发现 BUG ?
我接触到的多少并发缺陷绝大部分是因为线程安全问题导致的 ,还有一些数据库锁的才出问题(这个不擅长)这里就不分享了 。
关于 Java 的多少一些线程安全的问题,可以参考旧文:
下面我们来聊聊上面提到的问题,因为这涉及到不同类型的才出 BUG 需要多少 QPS 才能测出来 BUG,今天来分享一下最简单的多少线程不安全操作i++需要多少 QPS 才能测出来BUG。
首先 ,才出我使用的多少同一个 JVM 来测试i++,发现极容易出现 BUG ,才出后来放弃了这种方式。多少经过思考发现如果放在一个 JVM 里面,才出本身已经创建了很多线程去执行i++ ,多少这种跟实际接口测试差异比我想象的大很多 。 其次 ,我创建了一个简单的 Springboot 项目,写一个简单的接口来实现 。
总提测下来 ,上面的问题需要修正,因为能不能测出来不是一个 Boolean 值 ,而是一个概率值 ,后面我也会用发现比例值来表示是测出 BUG 的难易程度。
之前一直用moco_funtester框架来构建服务端不行了,无法动态接口返回。所以只能简单弄一个 Springboot 项目 。其他的就不分享了,只分享一下 controller 的部分 。这里模拟盘了一个接口平均响应时间 10ms,然后执行一个非线程安全的操作。
int i;nn @GetMapping(value = "/funtest")n public Result test1() { n Thread.sleep(SourceCode.getRandomInt(20));n return Result.success(i++);n }nn @GetMapping(value = "/geti")n public Result test() { n return Result.success(i);n }n @GetMapping(value = "/zero")n public Result te2st() { n i = 0;n return Result.success(i);n }n
这里没有使用正经的测试框架 ,只用了异步线程池和粗略的sleep休眠的方法控制 QPS,所以这里会有一个实际 QPS 统计 。
测试用例模拟盘两个模型 :线程模型和 QPS 模型。
public static void main(String[] args) { n def test = { getHttpResponse(getHttpGet("http://localhost:8080/user/funtest"))}n def get = { getHttpResponse(getHttpGet("http://localhost:8080/user/geti"))}n def init = { getHttpResponse(getHttpGet("http://localhost:8080/user/zero"))}n AtomicInteger index = new AtomicInteger()n FunHttp.LOG_KEY = falsen def t = 1000n def size = 1n setPoolMax(500)n init()n fun { n output(DEFAULT_STRING)n }n sleep(1.0)n def start = Time.getTimeStamp()n size.times { n fun { n t.times { n test()n index.getAndIncrement()n }n }n }n ThreadPoolUtil.waitFunIdle()n def value = get().getIntValue("data")n def end = Time.getTimeStamp()n output("当前 QPS: ${ index / (end - start) * 1000}")n output(index.get(), value)n output(getPercent(index.get(), index.get() - value))n }nn
public static void main(String[] args) { n def test = { getHttpResponse(getHttpGet("http://localhost:8080/user/funtest"))}n def get = { getHttpResponse(getHttpGet("http://localhost:8080/user/geti"))}n def init = { getHttpResponse(getHttpGet("http://localhost:8080/user/zero"))}n AtomicInteger index = new AtomicInteger()n FunHttp.LOG_KEY = falsen def qps = 100n def t = qps * 10n setPoolMax(1000)n init()n def decimal = 1_000_000_000 / qpsn fun { n output(decimal)n }n sleep(1.0)n def start = Time.getTimeStamp()n t.times { n sleepNano(decimal as long)n fun { n test()n index.getAndIncrement()n }n }n ThreadPoolUtil.waitFunIdle()n def value = get().getIntValue("data")n def end = Time.getTimeStamp()n output("当前 QPS: ${ index / (end - start) * 1000}")n output(index.get(), value)n output(getPercent(index.get(), index.get() - value))n }n
线程模型模仿的固定线程数去不断请求接口 ,这里由于接口平均响应时间 10ms,每个线程执行次数设计为 2000 次,差不多 20s 执行完。
设计 QPS实际 QPS误差数量误差比(百分比)10 9.6 0 0 20 18.7 0 0 50 42 0 0 100 87 2 0.1 200 174 9 0.22 300 280 18 0.3 400 285 23 0.28 500 417 2 0.02
由于对实际结果测试并不能很好预期,这里就先从较小的 QPS 开始了 。全程没有触发性能瓶颈 ,误差部分,测 5 次,取误差最大的一次记录 。这里模拟的线程模型的
线程数实际 QPS误差数量误差比(百分比)1 76 0 0 2 144 1 0.05 4 305 33 0.41 8 617 111 0.69 12 927 224 0.93
经过上面测试,对于需要多少压力才能发现可能存在的缺陷,希望本文能投提供参考 。
(作者:汽车电瓶)