天天看點

當我遇到10億參數組合

最早提到接口測試的優點時,有一個就是執行效率提升,可能是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))
                            }
                        }
                    }
                }
            }
        }
    }
}
           

但是方案的缺陷顯而易見。

  1. 數量太大,導緻後面的異步任務直接被線程池拒絕
  2. 無法控制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看看性能如何,敬請期待。