网络延迟可能会导致您的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网站上有一篇关于流水线的好文章。
chunksize
可能太小了。 - jfschunksize
不是指传递的块的大小(您的代码控制该部分),而是指在映射的可迭代对象中同时处理多少个项目。也就是说,chunksize
> 1将允许主进程空闲一段时间,从可迭代对象中获取更多项并将它们缓存,以便下一个工作进程准备好其中的一个时使用。缺点是,如果每个项目相当大,您可能会遇到内存问题和/或需要长时间启动线程才能开始工作。尝试将其增加到10、100、1000,直至len(iterable)。 - Joe Kington