使用管道技术加速查询
#
请求/响应协议与 RTTKeyDB 是一个使用客户端-服务器模型和所谓的*请求/响应*协议的 TCP 服务器。
这意味着一个请求通常通过以下步骤完成:
- 客户端向服务器发送一个查询,并从套接字读取服务器的响应,通常是以阻塞方式进行。
- 服务器处理该命令并将响应发送回客户端。
因此,一个包含四个命令的序列大致如下:
- 客户端: INCR X
- 服务器 1
- 客户端: INCR X
- 服务器 2
- 客户端: INCR X
- 服务器 3
- 客户端: INCR X
- 服务器 4
客户端和服务器通过网络链接相连。这种链接可以非常快(环回接口),也可以非常慢(通过互联网建立的连接,两台主机之间有许多跳)。无论网络延迟如何,数据包从客户端传输到服务器,再从服务器返回客户端携带应答都需要时间。
这个时间被称为 RTT(Round Trip Time,往返时间)。当客户端需要连续执行许多请求时(例如,向同一个列表添加许多元素,或用许多键填充数据库),很容易看出这将如何影响性能。例如,如果 RTT 时间是 250 毫秒(在通过互联网的非常慢的链接情况下),即使服务器每秒能处理 10 万个请求,我们每秒最多也只能处理四个请求。
如果使用的是环回接口,RTT 会短得多(例如,我的主机 ping 127.0.0.1 的报告是 0.044 毫秒),但如果需要连续执行许多写操作,这仍然是一个很长的时间。
幸运的是,有一种方法可以改善这种用例。
#
KeyDB 管道技术请求/响应服务器可以被实现为即使客户端还没有读取旧的响应,也能处理新的请求。这样,就可以向服务器发送*多个命令*而完全无需等待回复,最后在一个步骤中读取所有回复。
这被称为管道技术(pipelining),是几十年来广泛使用的一种技术。例如,许多 POP3 协议的实现已经支持此功能,极大地加快了从服务器下载新邮件的过程。
KeyDB 从早期版本开始就支持管道技术,所以无论你运行哪个版本,都可以使用 KeyDB 的管道技术。以下是使用原生 netcat 工具的示例:
这次我们不是为每次调用支付 RTT 的成本,而只是为三个命令支付一次。
更明确地说,使用管道技术,我们最初示例的操作顺序将如下所示:
- 客户端: INCR X
- 客户端: INCR X
- 客户端: INCR X
- 客户端: INCR X
- 服务器 1
- 服务器 2
- 服务器 3
- 服务器 4
重要提示:当客户端使用管道技术发送命令时,服务器将被迫将回复排队,这会占用内存。因此,如果需要通过管道发送大量命令,最好将它们分批发送,每批包含一个合理的数量,例如 1 万个命令,读取回复,然后再发送下一个 1 万个命令,依此类推。速度几乎相同,但额外使用的内存最多只需要存储这 1 万个命令的回复所需的量。
#
这不仅仅是 RTT 的问题管道技术不仅是减少与往返时间相关的延迟成本的一种方法,它实际上还极大地提高了在给定的 KeyDB 服务器上每秒可以执行的操作数量。这是因为,在不使用管道技术的情况下,从访问数据结构和生成回复的角度来看,服务每个命令的成本非常低,但从进行套接字 I/O 的角度来看成本非常高。这涉及到调用 read()
和 write()
系统调用,意味着从用户态切换到内核态。上下文切换是一个巨大的速度损失。
当使用管道技术时,通常通过单个 read()
系统调用读取多个命令,并通过单个 write()
系统调用传递多个回复。因此,随着管道变长,每秒执行的总查询数最初几乎呈线性增长,最终达到不使用管道技术时基准的 10 倍。
#
管道技术与脚本通过使用 KeyDB 脚本(eval),许多管道技术的用例可以通过在服务器端执行大量工作的脚本来更有效地解决。脚本的一大优势是它能够以最小的延迟读取和写入数据,使得像*读取、计算、写入*这样的操作非常快(管道技术在这种场景下无法提供帮助,因为客户端需要读取命令的回复后才能调用写入命令)。
有时,应用程序可能也想在管道中发送 EVAL
或 EVALSHA
命令。这完全是可行的,KeyDB 通过 SCRIPT LOAD 命令明确支持这一点(它保证了 EVALSHA
可以被调用而不会有失败的风险)。
#
附录:为什么即使在环回接口上,忙碌循环也很慢?即使了解了本页所涵盖的所有背景知识,你可能仍然想知道为什么像下面这样的 KeyDB 基准测试(伪代码),即使在环回接口上执行,当服务器和客户端在同一台物理机器上运行时,速度也很慢:
毕竟,如果 KeyDB 进程和基准测试都在同一台机器上运行,这不就只是在内存中将消息从一个地方复制到另一个地方,而没有实际的延迟或网络参与吗?
原因是系统中的进程并非总是在运行,实际上是内核调度器让进程运行。因此,例如,当基准测试被允许运行时,它会从 KeyDB 服务器读取回复(与上一个执行的命令相关),并写入一个新命令。该命令现在位于环回接口的缓冲区中,但为了让服务器读取它,内核需要调度服务器进程(当前在系统调用中被阻塞)来运行,依此类推。因此,实际上,由于内核调度器的工作方式,环回接口仍然涉及类似网络的延迟。
基本上,在测量网络服务器性能时,忙碌循环基准测试是能做的最愚蠢的事情。明智的做法是避免以这种方式进行基准测试。