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.
我的简单测试设置:
- 首先,我在 Ubuntu (WSL2) 上安装 KeyDB 并获取它 运行ning
- 我注意到启动 KeyDb 时,有 2 个线程处于活动状态:
Thread 0 alive.
Thread 1 alive.
- 我修改了 keydb.conf 以禁用某些 saving/persisting,但最重要的是,我将
server-threads
选项更改为 2:server-threads 2
。注意:我也尝试过不使用配置文件,只是添加 cmd 标志 --server-threads 2
并将线程设置为 4,没有区别。
- 然后我运行一个简单的脚本:
- 使用一些简单的 JSON 对象在散列中创建 1M 个条目
- 创建一个使用两个线程的简单控制台应用程序;一个线程开始在循环中执行非常简单的 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也会并行读取和协议解析。
我正在试验 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.
我的简单测试设置:
- 首先,我在 Ubuntu (WSL2) 上安装 KeyDB 并获取它 运行ning
- 我注意到启动 KeyDb 时,有 2 个线程处于活动状态:
Thread 0 alive.
Thread 1 alive.
- 我注意到启动 KeyDb 时,有 2 个线程处于活动状态:
- 我修改了 keydb.conf 以禁用某些 saving/persisting,但最重要的是,我将
server-threads
选项更改为 2:server-threads 2
。注意:我也尝试过不使用配置文件,只是添加 cmd 标志--server-threads 2
并将线程设置为 4,没有区别。 - 然后我运行一个简单的脚本:
- 使用一些简单的 JSON 对象在散列中创建 1M 个条目
- 创建一个使用两个线程的简单控制台应用程序;一个线程开始在循环中执行非常简单的 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也会并行读取和协议解析。