KeyDB 和多线程:看起来没有进行多线程?

KeyDB and multithreading: Looks like no multithreading is going on?

我正在试验 KeyDB 以查看是否可以获得性能提升以及性能提升多少,因为 Redis 单线程查询模型肯定存在瓶颈。所以我找到了 KeyDB,并且 they say they use "real" multithreading 对数据库进行并行查询,不像 Redis 只有 IO 多线程而不是实际查询。

来自上面的文档 link:

Unlike Redis6 and Elasticache, KeyDB multithreads several aspects including placing the event loop on multiple threads, with network IO, and query parsing done concurrently.

我的简单测试设置:

  1. 首先,我在 Ubuntu (WSL2) 上安装 KeyDB 并获取它 运行ning
    1. 我注意到启动 KeyDb 时,有 2 个线程处于活动状态: Thread 0 alive.Thread 1 alive.
  2. 我修改了 keydb.conf 以禁用某些 saving/persisting,但最重要的是,我将 server-threads 选项更改为 2:server-threads 2。注意:我也尝试过不使用配置文件,只是添加 cmd 标志 --server-threads 2 并将线程设置为 4,没有区别。
  3. 然后我运行一个简单的脚本:
    1. 使用一些简单的 JSON 对象在散列中创建 1M 个条目
    2. 创建一个使用两个线程的简单控制台应用程序;一个线程开始在循环中执行非常简单的 SET (SET key1 1) 或 GET (GET key1 1),另一个线程从哈希 (HGETALL testhash) 中执行“全部获取”。第二个线程在开始其“长查询”之前等待 1 秒。

GitHub 存储库(使用 StackExchange.Redis 库)可以在 here.

中找到

我的期望:

我希望简单的快速 SET/GETs 每次都花费大约相同的时间,而不会因 KeyDB 中的块而导致任何延迟或节流,而长查询是 运行ning。

会发生什么:

简单的快速 SET/GETs 是 blocked/delayed 大约 500-700 毫秒,而长查询是 运行ning,表明只有一个线程正在使用,因此阻塞了其他操作.这符合 Redis 的工作方式,以及我想用 KeyDB 避免的情况。

日志:

“开始长查询”是当我们执行 HGETALL 并且几乎紧接着,简单的 SET 被限制并占用了 500 多毫秒,当它应该花费 0-1 毫秒时,如前所示和之后。

使用 ServiceStack Redis 客户端:

10:50:55.336    GetValueFromHashAsync took 1
10:50:55.367    GetValueFromHashAsync took 1
10:50:55.397    GetValueFromHashAsync took 0
10:50:55.416    Starting long query
10:50:56.191    GetValueFromHashAsync took 766 <-- THROTTLED! Delayed with what I think is the actual query time, not the IO part, so at this point, the line fetching data has not completed yet
10:50:56.228    GetValueFromHashAsync took 0
10:50:56.261    GetValueFromHashAsync took 1
....
....
10:51:00.592    GetValueFromHashAsync took 1
10:51:00.620    GetValueFromHashAsync took 1
10:51:00.651    GetValueFromHashAsync took 1
10:51:00.663    Long query done in 5244        <-- The long query returns here, line is completed, total time was about 5 seconds, while the block was about 0.7 seconds

我也测试过从散列中获取而不是 SET,同样的事情。

使用StackExchange.Redis: 在 GitHub 可重现项目 found here 中,我使用 StackExchange.Redis 而不是 ServiceStack,我得到了不同的(更糟糕的!)行为:

11:27:12.084    HashGetAsync took 0
11:27:12.115    HashGetAsync took 0
11:27:12.146    HashGetAsync took 0
11:27:12.177    HashGetAsync took 1
11:27:12.183    Starting long query
11:27:14.877    Long query done in 2692
11:27:14.893    HashGetAsync took 2686      <-- THROTTLED! This time the other thread is delayed the entire time, query + IO.
11:27:14.929    HashGetAsync took 0
11:27:14.960    HashGetAsync took 0
11:27:14.992    HashGetAsync took 0
11:27:15.023    HashGetAsync took 0
11:27:15.053    HashGetAsync took 0

结论

无论我使用什么客户端库,KeyDB 都在限制 requests/queries 而“长查询”是 运行ning,即使我有 2 个线程。如果我用 4 个线程启动 KeyDB 也没关系,同样的行为。

我不知道为什么 StackExchange 的行为与 ServiceStack 不同,但这不是现在的主要问题。

KeyDB,其实只是运行s并行的IO操作和Redis协议解析操作。它以串行方式处理命令,即一个一个地处理命令,工作线程与自旋锁同步。

这就是为什么那些简单的 set/get 命令会被慢速命令阻止的原因。因此,即使使用 KeyDB,您也不应该 运行 慢速命令,而且多线程也无济于事。

更新

KeyDB 可以让多个线程监听同一个IP:port,这样它就可以并行接受多个连接,即SO_REUSEPORT。它还读取(包括将接收到的数据解析为使用 redis 协议的命令,即 RESP)并并行写入套接字。

虽然 Redis 只有一个线程,即主线程,但在 IP:port 上监听。默认情况下,Redis 在单线程中读取和写入套接字。从 Redis 6.0 开始,您可以启用 io-threads 以使其并行写入套接字。另外,如果开启io-threads-do-reads,Redis也会并行读取和协议解析。