如何从命名管道读取恒定大小的缓冲区,并为每个缓冲区运行命令

如何从命名管道读取恒定大小的缓冲区,并为每个缓冲区运行命令

终端中有一个功能:redis-cli -x PUBLISH myChannel。它从中读取数据stdin并完成其工作。

FIFO 本身不提供分块数据。所以我需要从流中创建块,并redis-cli -x PUBLISH myChannel为每个块运行。

我已经尝试过这个: { while :; do dd iflag=fullblock iflag=nonblock bs=65536 count=1 2> /dev/null | redis-cli -x PUBLISH myChannel ; done } < myFifo

它正在发挥作用。但CPU消耗很高。而且对于扩展来说非常糟糕(50 个并发命令导致 40% 的 CPU 使用率)。

我也尝试了@terdon提供的解决方案[这里]:

{ 
  while :; do 
    dd iflag=fullblock iflag=nonblock bs=65536 count=1 2> /dev/null | 
      redis-cli -x PUBLISH myChannel 
    sleep 0.1
  done 
} < myFifo

它使用更少的 CPU(50 个并发命令使用 20%)。但仍会造成更大规模的问题。

我也尝试使用tail -F从 FiFo 读取正如@raffa所建议的:

tail -c +1 -F myFifo | 
  dd iflag=fullblock bs=65536 2> /dev/null | 
    redis-cli -x PUBLISH myChannel

不幸的是,这不起作用,因为redis-cli -x PUBLISH myChannel必须为每个块运行。它无法接收数据流。

有没有一种方法可以完成该命令的功能,但 CPU 效率高?我可能会补充一点,该解决方案需要在Ubuntu 16.04 及以上版本

非常感谢。

答案1

我已经尝试过这个:{ while :; do dd iflag=fullblock iflag=nonblock bs=65536 count=1 2> /dev/null | redis-cli -x PUBLISH myChannel ; done } < myFifo

它正在发挥作用。但CPU消耗很高。而且对于扩展来说非常糟糕(50 个并发命令导致 40% 的 CPU 使用率)。

iflag=nonblock要求 GNU coreutils dd 使用非阻塞读取,这意味着如果没有数据可供读取,控制权会立即返回到进程并返回错误代码 EAGAIN 或 EWOULDBLOCK,而不是让操作系统句柄等待更多数据。看起来 dd 处理这个问题就像处理任何错误一样,打印错误消息并退出。因为循环只是重新启动 dd,所以您会一次又一次地快速连续地陷入相同的情况。当然,这会导致CPU使用率高。

如果您没有将错误重定向到/dev/null,您会看到 dd 的输出,如下所示,清楚地显示它如何一次重复运行很短的一段时间,而不执行任何操作:

dd: error reading 'standard input': Resource temporarily unavailable
0+0 records in
0+0 records out
0 bytes copied, 4.3734e-05 s, 0.0 kB/s
[...]

建议:

1)如果你正在测试某些东西,或者尝试做某事的最佳方法,隐藏错误,它们非常有用。

2)如果你正在读取数据并且没有其他事情要做,就让系统调用阻塞。

删除iflag=nonblock将大大有助于解决这个问题。

另外,我不确定你是否需要 dd ,head -c 65536应该做类似的事情。


但这仍然留下了当您正在读取的管道关闭时会发生什么。此时,任何读取都将(成功)返回零字节,指示数据流的结尾。此时,dd 将退出并读取零字节,并且循环将一次又一次地重新启动它......可悲的是,检测到这一点更难做到,因为就 dd 而言,它仍然是成功的运行。

可以选择在传递数据之前将数据读取到临时文件中,这样您就可以在决定继续之前检查文件的大小;或者读取 dd 的 stderr 输出以获取复制的数据量。 (或者将数据读取到 shell 变量,但这在大多数 shell 中都不是 8 位干净的,zsh 是一个例外(我认为)。)

通常,您还应该检查 dd 的退出状态,以便能够在出现任何错误时中断循环,但也应该检查零字节运行。

或者,您可以在读写模式下打开 FIFO,因此如果实际的写入器关闭,dd或者head在您的循环中最终会阻塞自身,可以这么说。循环不会退出,但至少不会陷入繁忙循环。

所以也许是这样的:

while true; do head -c 65536| redis ... ; done 0<> fifo

head或者如果or失败也打破循环redis

set -o pipefail
while head -c 65536 | redis ... ; do true; done 0<> fifo

不管怎样,类似的东西可能会在 shell 之外的其他东西中更好地实现,例如在 Perl 中,或者使用一些现有的工具。 (喜欢split)。

相关内容