在先前的文章中,我首先做了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,最后我验证 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 变化曲线图:
测试结果:
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ 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 存储设计的有了几种替代(解决)方案,需要性能测试工程师协助验证这几种方案的在不同场景下的性能指标。
Have Fun ~ Tester !
- FunTester 测试框架架构图初探
- 颇具年代感的《JMeter 中文操作手册》
- 140 道面试题目(UI、Linux、MySQL、API、安全)
- 环境基础【FunTester 框架教程】
- HTTP 接口测试基础【FunTester 框架教程】
- 一起吐槽接口文档
- 基于 WebSocket 的 client 封装
- 利用微基准测试修正压测结果
- Selenium4 Alpha-7 升级体验
- 压测中测量异步写入接口的延迟
- LT 浏览器——响应式网站测试利器
- 将 json 数据格式化输出到控制台
- Java NIO 在接口自动化中应用
- 安卓 APP 测试知识大全【面试储备】
- 单元测试框架 spock 和 Mockito 应用