关于KeyDB 和 Redis 的性能测试与分析

据传言,KeyDB的时延比redis小,吞吐量比redis高,那么我就想测测keyDB;

首先对于KeyDB,有几点其实我没太弄清

  1. KeyDB的并发控制方案: 对比了很多资料,我推测的模式是这样的:KeyDB整体使用MVCC机制,在多线程并发写时,使用key-level-lock给每个k-v结构加锁;而这个key-value-lock就在redisObject结构之中,MVCC机制的核心trx_id,在这里应该是用时间戳代替了,也可以在redisObject中;

    由于网上有很多说不全面的局部资料,导致我也只能部分听取后自己尝试梳理出来;当然更合理的办法是直接看源码,但是这种架构层面的逻辑从源码里感觉会很抽象

  2. redis我们都知道是单线程执行指令,单线程仍然快的原因之一是,限制速度的瓶颈在于内存大小和网络带宽 ------ 八股文是这样说的;按照这种思路,KeyDB想要基于redis提速应当首先改善内存布局或者优化网络IO,而不是通过引入多线程就能够有效降低时延。

    关于这个想法其实第一句话就不太能站得住脚,内存大小和网络带宽本身确实会影响redis的响应时间,但是就时延方面来说 影响应该是很有限的

    内存占用太多可能导致fork时主线程被占用,也可能由于key太多导致在全局散列表上查询变慢,还可能由于回收的k-v太多导致一直在以某个速度回收...但是影响都不足以称为瓶颈,最主要很少会让redis内存占用太高吧,据说为了防止写时复制产生的备份太多,部署时一般会预留50%左右的空余内存。

    所以在正常情况下的指令,其时延和CPU、内存、带宽都没关系

    这一点在这位大哥的压测说明中也体现了

    从一次压测看redis的瓶颈问题 - 知乎 (zhihu.com)

    另外借用官方的一句话: 翻译:从访问数据结构和生成回复的角度来看,处理每个命令的成本非常低廉,但从执行套接字 I/O 的角度来看,成本却非常高。这涉及调用 sendrecv 系统调用,这意味着需要从用户态切换到内核态。这样的上下文切换带来了巨大的速度损失

    所以影响时延的关键在于:是否使用了pipelining,来减少redis的系统调用。


性能测试

使用单台设备进行性能测试有好多坑,都会导致最终的结论和想调查的内容无关,在此简单记录一些犯过的错误

  1. 刚开始用python脚本写多线程测试,结果python有一套GLI机制,想实现并发似乎需要多进程,有点难搞

    深入理解Python中的GIL(全局解释器锁)。 - 知乎 (zhihu.com)

  2. 之后在Java中用16个线程分别循环六千多次,每次循环调用jedis的send方法,但是切记16个线程需要有各自的连接,并且建立连接的工作是提前做好的,不计入计时范围

  3. 后面怀疑调用jedis.send()是同步阻塞的,有没有可能整个测试的时间瓶颈在send后等待响应?所以后续使用redisson的异步调用方案;但是无论是同步还是异步,时间都差不多,原因在于:即使是转化成异步,在一条redis连接上也会被同步 ------ redis的RESP协议本身实现很简单,客户端和服务器的通信也是交互式的,一条收到返回确认后再发一条,如果是异步的话还需要实现滑动窗口,通信协议里也要带id,但实际上RESP中都没有

    【高阶篇】3.1 Redis协议(RESP )详解_redis resp-CSDN博客

    但是在使用异步请求时,观察到这样一个现象:所有请求全部发出去只用了0.3s(验证了之前一个连接上的请求会被串行),所有请求都收到响应的时间是3s(和同步一致),所以推测的结论是这样的:由于通信协议限制,一条连接上只能一个一个发,这才是本次测试真正的瓶颈;如果有一种方法能模拟出多台设备 - 多个连接 - 并发请求,这才是真正的压测;

  4. 后续使用redis的pipelining,在一条连接的一次请求中发起二百个指令;这两百个指令到达redis后,被一个IO线程读取,就和其他连接的指令一起被主线程执行了;所以感觉可以模拟出并发的场景(存在干扰项:使用pipelining减少了context-switched的次数,而这个是时延的主要瓶颈)

测试代码:

JAVA 复制代码
package KeyDB_Redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PipeLineTest {
    private static final String REDIS_HOST = "192.168.56.10";
    private static final int REDIS_PORT = 6379;
    private static final int NUM_THREADS = 16;
    private static final int NUM_ITEMS = 1000000;
    static List<Jedis> jedisPool = new ArrayList<>();

    static {
        for (int i = 0; i < NUM_THREADS; i++) {
            jedisPool.add(new Jedis(REDIS_HOST, REDIS_PORT));
        }
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
        Instant startTime = Instant.now();

        int itemsPerThread = NUM_ITEMS / NUM_THREADS;

        for (int i = 0; i < NUM_THREADS; i++) {
            int startIndex = i * itemsPerThread;
            int endIndex = startIndex + itemsPerThread;
            int finalI = i;
            executor.submit(() -> insertData(startIndex, endIndex, finalI));
        }

        executor.shutdown();
        while (!executor.isTerminated()) {
            // Wait for all threads to finish
        }

        Instant endTime = Instant.now();
        System.out.println("插入完毕 " + Duration.between(startTime, endTime).toMillis() + " 毫秒");
    }

    private static void insertData(int start, int end, int index) {
        Jedis jedis = jedisPool.get(index);
        for (int i = start; i < end; i+=200) {
            Pipeline pipeline = jedis.pipelined();
            try {
                for (int ii = 0; ii < 200; ii++) {
                    pipeline.get("testKey");
                    // pipeline.set("test" + i * 200 + ii,i + "+val");


                }
                List<Object> responses = pipeline.syncAndReturnAll();

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

测试结果: 很尴尬的是:redis是1135ms,keyDB是1554ms;其他指令的测试也是keyDB稍慢

二者在同样有1000000条k-v时,内存占用分别是:

  • Keydb

  • redis

    内存占用keyDB更大我是可以理解的,毕竟有MVCC的多个版本共存,而且在数据结构上也很难比redis更优化,内存分配上也没有变,所以一定会更大;

    但是KeyDB的时延比redis更长,这个我觉得问题还是在我测试方法上,但一时也难以发现;希望看到的大佬可以指点一下

    完整测试代码在github中:github.com/13038032626...

相关推荐
小坏讲微服务12 小时前
Spring Cloud Alibaba 整合 Scala 教程完整使用
java·开发语言·分布式·spring cloud·sentinel·scala·后端开发
一水鉴天13 小时前
整体设计 定稿 之1 devOps 中台的 结论性表述(豆包助手)
服务器·数据库·人工智能
过客随尘13 小时前
Spring AOP以及事务详解(一)
spring boot·后端
老鼠只爱大米13 小时前
Java设计模式之外观模式(Facade)详解
java·设计模式·外观模式·facade·java设计模式
vx_dmxq21113 小时前
【微信小程序学习交流平台】(免费领源码+演示录像)|可做计算机毕设Java、Python、PHP、小程序APP、C#、爬虫大数据、单片机、文案
java·spring boot·python·mysql·微信小程序·小程序·idea
武子康13 小时前
大数据-167 ELK Elastic Stack(ELK) 实战:架构要点、索引与排错清单
大数据·后端·elasticsearch
9号达人13 小时前
优惠系统演进:从"实时结算"到"所见即所得",前端传参真的鸡肋吗?
java·后端·面试
q***071413 小时前
Spring Boot 中使用 @Transactional 注解配置事务管理
数据库·spring boot·sql
wei_shuo13 小时前
openEuler 底座赋能:openGauss 数据库部署与性能实战评测
后端
用户40981702151013 小时前
Python 的基本类型
后端