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

FunTester · 2021年09月22日 · 956 次阅读

在先前的文章中,我首先做了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 变化曲线图:

redis测试实践,list从尾获取并删除测试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 ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~

响应时间分布图:

redis测试实践,list从尾获取并删除测试

总结

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

Have Fun ~ Tester !

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册