Python/Redis 多进程处理
我正在使用多进程库中的Pool.map来处理一个很大的XML文件,并把单词和n-gram的计数保存到三台Redis服务器中(这些服务器完全在内存中运行)。但是不知为什么,四个CPU核心在整个过程中都有大约60%的空闲时间。服务器有足够的内存,iotop显示没有磁盘IO的活动。
我有四个Python线程和三台Redis服务器,它们作为守护进程在三个不同的端口上运行。每个Python线程都连接到这三台服务器。
每台Redis服务器的操作数量远低于它们的性能基准。
我找不到这个程序的瓶颈在哪里?可能的原因是什么?
1 个回答
网络延迟可能是你在 Python 客户端应用中 CPU 空闲时间的一个原因。如果客户端和服务器之间的网络延迟即使只有 2 毫秒,当你执行 10,000 个 Redis 命令时,你的应用程序至少要闲置 20 秒,无论其他组件的速度有多快。
使用多个 Python 线程可以有所帮助,但每个线程在发送阻塞命令到服务器时仍然会闲置。除非你有很多线程,否则它们通常会同步,全部等待响应。因为每个线程都连接到所有三个服务器,所以这种情况发生的几率会降低,除非所有线程都在等待同一个服务器的响应。
假设你在服务器之间均匀随机分配请求(通过对键名进行哈希来实现分片或分区),那么三条随机请求哈希到同一个 Redis 服务器的几率与服务器的数量成反比。对于 1 个服务器,100% 的时间你都会哈希到同一个服务器;对于 2 个服务器,50% 的时间;对于 3 个服务器,33% 的时间。可能发生的情况是,三分之一的时间,你的所有线程都在等待同一个服务器的响应。Redis 在处理数据操作时是单线程的,所以它必须一个接一个地处理每个请求。你观察到 CPU 只达到 60% 的利用率,这与请求都因网络延迟而被阻塞在同一服务器上的概率是一致的。
继续假设你通过对键名进行哈希来实现客户端的分片,你可以通过为每个线程分配一个单独的服务器连接来消除线程之间的竞争,并在将请求传递给工作线程之前评估分区哈希。这将确保所有线程在等待不同的网络延迟。但使用管道功能可能会有更好的改进。
如果你不需要立即从服务器获取结果,可以通过使用 redis-py 模块 的管道功能来减少网络延迟的影响。看起来你是在将数据处理的结果存储到 Redis 中,这样做是可行的。要使用 redis-py 实现这一点,定期通过 .pipeline()
方法获取现有 Redis 连接对象的管道句柄,并像对主 redis.Redis 连接对象那样对这个新句柄调用多个存储命令。然后调用 .execute()
来等待回复。通过使用管道将数十或数百个命令批量处理,你可以获得数量级的提升。你的客户端线程不会阻塞,直到你在管道句柄上发出最后的 .execute()
方法。
如果你同时应用这两个改动,并且每个工作线程只与一个服务器通信,将多个命令一起管道化(至少 5-10 个命令才能看到显著效果),你可能会看到客户端的 CPU 使用率更高(接近 100%)。虽然 CPython 的全局解释器锁(GIL)仍然会限制客户端使用一个核心,但听起来你已经通过使用多进程模块在使用其他核心进行 XML 解析。
关于管道功能,redis.io 网站上有一篇很好的介绍。