在先前的文章中,我首先做了FunTester框架Redis壓測預備,然后分享了- FunTester測試框架Redis性能測試實踐,對普通的key-value類型的Redis操作進行了測試。
今天分享一下FunTester測試框架對Redis數據庫key-list數據操作的性能測試,分為添加、刪除和組合測試。
場景
線上分成了三個測試場景:
- 往Redis添加一批
key-list
數據,然后並發去往每個key-list
中添加元素。 - 基於1中的數據,並發去從
key-valu
中,獲取並刪除元素。 - 同時想Redis的
key-list
數據中添加和刪除元素。(其中包含從列表頭和列表尾添加和刪除元素),思路中詳細說明。
思路
由於測試Redis服務性能比較差,之前文章實測也就100 ~ 150的QPS,本次暫不對同一個key
進行並發測試,每個線程擁有唯一的key
。這樣測試方案稍微復雜一點,用到了java.util.concurrent.atomic.AtomicInteger
線程安全類。在每個多線程任務com.funtester.base.constaint.FixedThread
實現類中多一個屬性com.funtest.redis.RedisList01.FunTester#listName
。
總體思路就是多線程+Redis連接池,每個線程擁有唯一不重復的key
,然后不斷執行三個測試場景中的操作。
對於第三個測試的場景,這里有必要說明一下測試用例:首先往數據庫里面存在一些key和對應的List類型的value。然后我會從list的的頭部添加一個元素a,然后我從list的尾部添加一個元素b,然后我從list的頭部獲取並刪除一個元素c,從list尾部獲取並刪除一個元素d,最后我驗證ac並且bd。
以下是三個測試場景的具體實現。由於功能重復性比較多,我會着重的講第三個測試場景的實現和測試結果。前兩個場景只分享一下測試的腳本和數據即可。
場景1
用例
class RedisList01 extends RedisBase {
static AtomicInteger num = new AtomicInteger(0)
static RedisBase drive
public static void main(String[] args) {
String host = "FunTester"
int port = 6379
drive = new RedisBase(host, port)
int times = 400
int thread = 20
Constant.RUNUP_TIME = 0
def tester = new FunTester(times)
def task = new Concurrent(tester, thread, "redis測試實踐,list添加測試")
task.start()
drive.close()
}
private static class FunTester extends FixedThread {
String listName = DEFAULT_STRING + num.getAndIncrement()
FunTester(int limit) {
super(null, limit, true)
}
@Override
protected void doing() throws Exception {
drive.lpush(listName, StringUtil.getString(10), StringUtil.getString(10), StringUtil.getString(10), StringUtil.getString(10), StringUtil.getString(10))
}
@Override
ThreadBase clone() {
return new FunTester(this.limit)
}
}
}
測試結果
此處省略進度條展示和圖形化統計測試數據,只分享測試結果。table使用base64解碼之后就是圖形化測試結果,有興趣的可以轉一下看看分布圖。
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
> {
> ① . "rt":165,
> ① . "failRate":0.0,
> ① . "threads":20,
> ① . "deviation":"0.44%",
> ① . "errorRate":0.0,
> ① . "executeTotal":7899,
> ① . "qps2":120.67464136761538,
> ① . "total":7899,
> ① . "qps":121.21212121212122,
> ① . "startTime":"2021-09-16 16:05:22",
> ① . "endTime":"2021-09-16 16:06:28",
> ① . "mark":"redis測試實踐,list添加測試161605",
> ① . "table":"eJztkzsKwkAQhnshd5gDRIjBKscQLxBwwQU3SlZBS18oWptSPIFWYuFtAorHcESND8SNRt0gs/wwIcX830c2RgaUx2clLrfL8W4x2cynu9XSrHBZ367Wm9Hs+BpsC+pln7kl9TYjYzzvLDBZq3qSQZEL5kAzK5nP3Qp4DWFCKyuQxvVUHWoOwT047nJyeQuENIXbdGzLxscPWCQ9YTDExDJJd0sYdDDfbQqDNgZHH3Ou+lRjtLyLwdHDXKoGmMu4Ln44XqSJ1a0sfXmoKCOs06f9Edab5BHtLeaDu5KmcTa5vwIpx45l9ux//YdxMEwBBhmSIRnqxyBDMiRD/RhkSIZkqB+DDMmQDPVjkCEZkqF+DDJMZLgHx9E+BA=="
> }
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
場景2
用例
class RedisList02 extends RedisBase {
static AtomicInteger num = new AtomicInteger(0)
static RedisBase drive
public static void main(String[] args) {
String host = "FunTester"
int port = 6379
drive = new RedisBase(host, port)
int times = 400
int thread = 20
Constant.RUNUP_TIME = 0
def tester = new FunTester(times)
def task = new Concurrent(tester, thread, "redis測試實踐,list從頭獲取並刪除測試")
task.start()
drive.close()
}
private static class FunTester extends FixedThread {
String listName = DEFAULT_STRING + num.getAndIncrement()
FunTester(int limit) {
super(null, limit, true)
}
@Override
protected void doing() throws Exception {
drive.lpop(listName)
}
@Override
ThreadBase clone() {
return new FunTester(this.limit)
}
}
}
測試結果
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
> {
> ① . "rt":172,
> ① . "failRate":0.0,
> ① . "threads":20,
> ① . "deviation":"0.25%",
> ① . "errorRate":0.0,
> ① . "executeTotal":7912,
> ① . "qps2":115.98962074677847,
> ① . "total":7912,
> ① . "qps":116.27906976744185,
> ① . "startTime":"2021-09-16 16:08:43",
> ① . "endTime":"2021-09-16 16:09:51",
> ① . "mark":"redis測試實踐,list從頭獲取並刪除測試161608",
> ① . "table":"eJzj5VLAD4pSUzKLn23tfrF+6tN1815s36qTk1lc8mR339MlW170bX/aP+3pzm1POxa8nLkEokrByEChJKMoNTGFgMkKvFy8+G0PSi0uyM8rTlUIycxNtVKo0C1OLcpMzFHIK83VUajUzQU6LTGPkB2EXKGgkJuZpwAxy8rQxFght1gnN7HCysjMAsgkrJugLygFj6Z1ABFRPhm1ZdSWkW3Lo2ntQERzmx5NawYimDVUt+3RtEYgAlJNQISgWoAIzYcUuQDdGqifoNa0AhGCglqK227iKbyuhLsJn2Mosp56rh/0biXRV0SksKFJ8XINCmeM+nDUh6M+HHhnjPpw1IejPhx4Z4z6cNSHoz4ceGeM+nDUh6M+HHhn0NSHAKoNkl0="
> }
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
場景3
class RedisList03 extends RedisBase {
static AtomicInteger num = new AtomicInteger(0)
static RedisBase drive
public static void main(String[] args) {
String host = "FunTester"
int port = 6379
drive = new RedisBase(host, port)
int times = 100
int thread = 20
Constant.RUNUP_TIME = 0
def tester = new FunTester(times)
def task = new Concurrent(tester, thread, "redis測試實踐,list從尾獲取並刪除測試")
task.start()
drive.close()
}
private static class FunTester extends FixedThread {
String listName = DEFAULT_STRING + num.getAndIncrement()
FunTester(int limit) {
super(null, limit, true)
}
@Override
protected void doing() throws Exception {
def a = Time.getTimeStamp() + StringUtil.getString(10)
def b = Time.getTimeStamp() + StringUtil.getString(10)
drive.lpush(listName, a)
drive.rpush(listName, b)
def c = drive.lpop(listName)
def d = drive.rpop(listName)
if (a != c || b != d) com.funtester.base.exception.FailException.fail(this.threadName + "驗證失敗!")
}
@Override
ThreadBase clone() {
return new FunTester(this.limit)
}
}
}
測試結果
進度條截取:
INFO-> redis測試實踐,list從尾獲取並刪除測試進度:▍▍▍ 5% ,當前QPS: 33
INFO-> redis測試實踐,list從尾獲取並刪除測試進度:▍▍▍▍▍▍ 10% ,當前QPS: 30
INFO-> redis測試實踐,list從尾獲取並刪除測試進度:▍▍▍▍▍▍▍▍▍▍ 15% ,當前QPS: 32
INFO-> redis測試實踐,list從尾獲取並刪除測試進度:▍▍▍▍▍▍▍▍▍▍▍▍ 19% ,當前QPS: 31
INFO-> redis測試實踐,list從尾獲取並刪除測試進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 24% ,當前QPS: 31
INFO-> redis測試實踐,list從尾獲取並刪除測試進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 28% ,當前QPS: 30
可以看出QPS大概是單獨操作測試結果的1/4,比較符合預期。
QPS變化曲線圖:
測試結果:
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
> {
> ① . "rt":673,
> ① . "failRate":0.0,
> ① . "threads":20,
> ① . "deviation":"0.55%",
> ① . "errorRate":0.0,
> ① . "executeTotal":1981,
> ① . "qps2":29.55481291400609,
> ① . "total":1981,
> ① . "qps":29.71768202080238,
> ① . "startTime":"2021-09-16 16:23:31",
> ① . "endTime":"2021-09-16 16:24:38",
> ① . "mark":"redis測試實踐,list從尾獲取並刪除測試161623",
> ① . "table":"eJzj5VLAD4pSUzKLn23tfrF+6tN1815s36qTk1lc8mR339MN+170bX/aP+3pzm1POxa8nLkEokrByEChJKMoNTGFgMkKvFy8+G0PSi0uyM8rTlUIycxNtVKo0C1OLcpMzFHIK83VUajUzQU6LTGPkB2EXKGgkJuZpwAxy8rU3Ewht1gnN7HCysLUGMgkrJugLygFj6Z1ABFRPhm1ZYjb8mhaIxABqSYgQlDNQASkWoAIZi0ltqPbAjW3FYgQVDsQoVlGMkXIdXCHoHqQ+g6h0OX43TkgDiTPR3CPoPpgEDiPYt9hzR7DiAL5cBA4Y9SHoz4c9eHAO2PUh6M+HPXhwDtj1IejPhz14cA7Y9SHoz4c9eHAO2PUh6M+HPXhwDuDpj4EAOQLudM="
> }
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
響應時間分布圖:
總結
在寫FunTester測試框架對Redis進行性能測試這個系列的過程中。有一些粉絲。跟我聊就是說問這個實際的應用場景是什么?因為在測試的過程中,很少有遇到Redis性能出現平靜,或者說Redis性能需要調優的這樣的情況。一般認為ready是性能非常快的,只有向cpu,內存,帶寬會成為ready的平靜。但是有些比較極端的情況下,像Redis的key分布以及Redis數據存儲的設計,都會成為系統性能平靜。我個人對ready的這類調油也沒有什么經驗。寫這個教程呢,主要是因為開發對Redis存儲設計的有了幾種替代(解決)方案,需要性能測試工程師協助驗證這幾種方案的在不同場景下的性能指標。