在先前的文章中,我首先做了FunTester框架Redis压测预备,然后分享了- ​FunTester测试框架Redis性能测试实践​​,对普通的key-value类型的Redis操作进行了测试。

今天分享一下FunTester测试框架对Redis数据库key-list数据操作的性能测试,分为添加、删除和组合测试。

场景

线上分成了三个测试场景:


  1. 往Redis添加一批​​key-list​​数据,然后并发去往每个​​key-list​​中添加元素。
  2. 基于1中的数据,并发去从​​key-valu​​中,获取并删除元素。
  3. 同时想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,最后我验证a==c并且b==d。

以下是三个测试场景的具体实现。由于功能重复性比较多,我会着重的讲第三个测试场景的实现和测试结果。前两个场景只分享一下测试的脚本和数据即可。

场景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变化曲线图:

FunTester框架Redis性能测试之list操作_ide

测试结果:

~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ 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性能测试之list操作_json_02

总结

在写FunTester测试框架对Redis进行性能测试这个系列的过程中。有一些粉丝。跟我聊就是说问这个实际的应用场景是什么?因为在测试的过程中,很少有遇到Redis性能出现平静,或者说Redis性能需要调优的这样的情况。一般认为ready是性能非常快的,只有向cpu,内存,带宽会成为ready的平静。但是有些比较极端的情况下,像Redis的key分布以及Redis数据存储的设计,都会成为系统性能平静。我个人对ready的这类调油也没有什么经验。写这个教程呢,主要是因为开发对Redis存储设计的有了几种替代(解决)方案,需要性能测试工程师协助验证这几种方案的在不同场景下的性能指标。