终端中有一个功能: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
)。