最早提到接口測試的優點時,有一個就是執行效率提升,可能是UI層面執行的N倍。但是今天我要分享的這個案例這個優點的更新版本。
某個接口參數倒是不多,但是每個參數的範圍略大,最大的将近500個枚舉範圍,小的也是20個。如果把所有參數組合窮舉完,粗略估計可能10億級别的。
需求就是要把這部分所有參數組合都周遊進行測試,然後我就開始了踩坑了。
初版方案
一開始的想法就是多個循環嵌套,然後并發發起請求,實作起來非常簡單友善。如下:
@Log4j2
class TT extends MonitorRT {
static void main(String[] args) {
["types參數集合"].each {
def type = it
["id類型集合"].each {
def id = it
2.upto(99) {
def a = it
2.upto(99) {
def b = it
2.upto(99) {
def c = it
def params = new JSONObject()
params.id = id
params.endTime = 0
params.type = type
params.paramMap = parse("{\"a\":\"${a}\",\"b\":\"$b\",\"c\":\"$c\"}")
fun {
getHttpResponse(getHttpGet(url,params))
}
}
}
}
}
}
}
}
但是方案的缺陷顯而易見。
- 數量太大,導緻後面的異步任務直接被線程池拒絕
- 無法控制QPS和并發數
針對這第一個問題,我是增加了異步線程池等待隊列的長度,可以我發現了新的問題,就是記憶體壓力太大,這個會在後面的中也遇到。
更新版
針對存在第二個問題,我回歸到性能測試架構中,通過動态調整QPS的功能來調整QPS或者并發數,這裡我選擇了QPS,這個更容易更可控。我的思路是,先把所有參數周遊一遍,存在一個List當中,然後在去周遊這個List,通過動态QPS壓測模型把所有請求發出去。
static void main(String[] args) {
def list = []
["types參數集合"].each {
def type = it
["id類型集合"].each {
def id = it
2.upto(99) {
def a = it
2.upto(99) {
def b = it
2.upto(99) {
def c = it
def params = new JSONObject()
params.id = id
params.endTime = 0
params.type = type
params.paramMap = parse("{\"a\":\"${a}\",\"b\":\"$b\",\"c\":\"$c\"}")
}
}
}
}
}
AtomicInteger index = new AtomicInteger()
def test = {
def increment = index.getAndIncrement()
if (increment >= list.size()) FunQpsConcurrent.stop()
else getHttpResponse(getHttpGet(url, list.get(increment)))
}
new FunQpsConcurrent(test,"周遊10億參數組合").start()
}
但是新的問題立馬就來了,當我運作改代碼的時候,發現本機的CPU瘋狂飙升,仔細看了一下,原來是GC導緻的。存放這麼多的資料,記憶體撐不住了。下面就着手解決記憶體的問題,這裡參考10 億條日志回放chronicle性能測試中的思路。
終版
這裡用到了線程安全的隊列java.util.concurrent.LinkedBlockingQueue以及對應長度的等待功能,再配合異步生成請求參數,基本上完美解決需求。這裡依舊使用休眠1s來進行緩沖,避免隊列長度過大,隻有隊列長度足夠1s的2倍消費即可。
static void main(String[] args) {
def ps = new LinkedBlockingQueue()
fun {
["types參數集合"].each {
def type = it
["id類型集合"].each {
def id = it
2.upto(99) {
def a = it
2.upto(99) {
def b = it
2.upto(99) {
def c = it
def params = new JSONObject()
params.id = id
params.endTime = 0
params.type = type
params.paramMap = parse("{\"a\":\"${a}\",\"b\":\"$b\",\"c\":\"$c\"}")
if (ps.size() > 10_0000) sleep(1.0)
ps.put(params)
}
}
}
}
}
}
AtomicInteger index = new AtomicInteger()
def test = {
def params = ps.poll(100, TimeUnit.MILLISECONDS)
if (params == null) FunQpsConcurrent.stop()
else getHttpResponse(getHttpGet(url, params))
}
new FunQpsConcurrent(test, "周遊10億參數組合").start()
}
随着對隊列的學習和使用,最近自己也想寫一個10億級别的日志回放功能,到時候對比chronicle看看性能如何,敬請期待。