Python/Redis 多进程处理

6
我正在使用multiprocessing库中的Pool.map函数来遍历一个大型XML文件,并将单词和ngram计数保存到三个redis服务器中(完全位于内存中)。但是由于某种原因,所有4个CPU核心在整个过程中都保持着60%左右的空闲状态。该服务器有足够的RAM,并且iotop显示没有进行磁盘IO操作。
我有4个Python线程和3个redis服务器作为守护进程在三个不同的端口上运行。每个Python线程连接到所有三个服务器。
每个服务器上的redis操作数量都远低于其基准能力。
我找不到此程序中的瓶颈?可能的候选者是什么?

从磁盘访问XML文件?与服务器进行进程间通信?(您使用什么进行通信?) - Thomas K
2
chunksize 可能太小了。 - jfs
我正在使用cElementTree解析XML文件,并且它全部适合内存。然后,我从中提取文本并将单词传递到Redis。 - albertsun
传递每个块时有很多开销吗?我没有手动设置块大小,但似乎默认为1。 - albertsun
1
chunksize 不是指传递的块的大小(您的代码控制该部分),而是指在映射的可迭代对象中同时处理多少个项目。也就是说,chunksize > 1将允许主进程空闲一段时间,从可迭代对象中获取更多项并将它们缓存,以便下一个工作进程准备好其中的一个时使用。缺点是,如果每个项目相当大,您可能会遇到内存问题和/或需要长时间启动线程才能开始工作。尝试将其增加到10、100、1000,直至len(iterable)。 - Joe Kington
上下文切换的开销?在一个盒子上完成所有操作可能会受到影响。 - Paul
1个回答

7
网络延迟可能会导致您的Python客户端应用程序空闲CPU时间增加。如果客户端到服务器之间的网络延迟仅有2毫秒,且您执行了10,000个Redis命令,则无论其他组件的速度如何,您的应用程序都必须空闲至少20秒钟。
使用多个Python线程可以帮助,但每个线程在发送阻塞命令到服务器时仍会变为空闲状态。除非您有非常多的线程,否则它们经常会同步并全部阻塞等待响应。由于每个线程都连接到所有三个服务器,因此发生这种情况的几率会降低,除非所有线程都被阻塞等待同一个服务器。
假设您在服务器上具有均匀随机分布的访问来服务您的请求(通过哈希键名实现分片或分区),那么三个随机请求哈希到同一Redis服务器的可能性与服务器数量成反比。对于1台服务器,您100%的时间将哈希到同一台服务器,对于2台服务器,它是50%的时间,对于3台服务器,它是33%的时间。可能发生的情况是,有1/3的时间,所有线程都被阻塞等待同一台服务器。Redis在处理数据操作时是单线程的,因此必须逐个处理每个请求。您观察到CPU仅达到60%利用率,这与您的请求全部被阻塞在等待相同服务器的网络延迟上的概率相符。
继续假设您正在通过哈希键名实现客户端分片,您可以通过为每个线程分配单个服务器连接并在传递请求给工作线程之前评估分区哈希来消除线程之间的争用。这将确保所有线程都在等待不同的网络延迟。但是,使用流水线可能会有更好的改进。
如果您不需要立即从服务器获得结果,可以使用redis-py模块的管道功能来减少网络延迟的影响。这对您可能是可行的,因为似乎您正在将数据处理结果存储到redis中。要使用redis-py实现此操作,请使用.pipeline()方法定期获取现有redis连接对象的管道句柄,并针对该新句柄调用多个存储命令,就像针对主redis.Redis连接对象一样。然后调用.execute()以阻塞回复。通过使用流水线技术将数十个或数百个命令批处理在一起,您可以获得数量级的改进。您的客户端线程直到在管道句柄上发出最终的.execute()方法之前都不会被阻塞。
如果您应用了两种更改,并且每个工作线程只与一个服务器通信,并将多个命令(至少5-10个以获得显着结果)合并到一个管道中,您可能会看到客户端CPU使用率更高(接近100%)。 CPython GIL仍将限制客户端使用一个核心,但是听起来您已经使用多进程模块使用其他核心进行XML解析。

在redis.io网站上有一篇关于流水线的好文章


是的,尤其是如果你在担心网络延迟的话,在运行期间可以测量网络利用率来查看它有多重。如果你正在进行IO操作,那么管道化可能会帮助减轻大部分时间和操作上的压力。 - jheld

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接