如何在分布式环境中处理文件路径

How to handle file paths in distributed environment

我正在努力建立一个分布式芹菜环境来对 PDF 文件进行 OCR。我有大约 3M 的 PDF 并且 OCR 是 CPU-bound 所以我的想法是创建一个服务器集群来处理 OCR。

在我写我的任务时,我得到了这样的东西:

@app.task
def do_ocr(pk, file_path):
    content = run_tesseract_command(file_path)
    item = Document.objects.get(pk=pk)
    item.content = ocr_content
    item.save()

我的问题是让 file_path 在分布式环境中工作的最佳方法是什么。人们通常如何处理这种情况?现在,我所有的文件都保存在我们其中一台服务器上的一个简单目录中。

好吧,有多种方法可以处理它,但让我们坚持使用最简单的一种:

  • 既然你想使用多台服务器处理大量文件,我的第一个建议是在每台服务器上使用相同的 OS,这样你就不必担心跨平台问题兼容性
  • 使用 'cluster' 这个词表示所有这些服务器都应该知道它们的相互状态 - 它增加了复杂性,尝试切换到无状态工人的农场('stateless' 我的意思是 "not knowing about other's" 因为他们至少应该知道自己的状态,例如:空闲、IN_PROGRESS、QUEUE_FULL 或更多(如果需要)
  • 对于文件列表处理部分,您可以使用拉取或推送模型:
    • 推送模型 可以通过一个简单的应用程序轻松实现,该应用程序会抓取文件并将它们分派(例如:通过 SCP、FTP 等)到一组可用的服务器;服务器可以监视其本地目录的更改并选择新文件进行处理;它还 非常容易扩展 - 只需启动更多服务器并更新推送客户端(即使在运行时);唯一的限制是您的推送客户端的性能
    • 拉模型有点棘手,因为你必须处理更多的复杂性;拥有一组服务器意味着每个节点和偏移量都有一个正确的起始索引 - 这将使 错误处理更加困难,另外,它不容易扩展(想象一下,添加两倍的服务器以加速处理并在每个节点上正确更新索引和偏移量……似乎是一个容易出错的解决方案)
  • 我认为网络流量不是一个大问题 - 有 3M 的文件要处理会在某处以某种方式生成它..
  • collecting/storing 结果大相径庭,但这里列出的可能解决方案是无限的

由于我遗漏了您的很多体系结构细节和应用程序细节,您可以将此答案作为指导性答案,而不是严格的答案。 您可以按照以下顺序采用此方法:

1- 部署一个内部文件服务器,将所有文件存储在一个地方并为它们提供服务

示例:

http://interanal-ip-address/storage/filenameA.pdf

http://interanal-ip-address/storage/filenameB.pdf

http://interanal-ip-address/storage/filenameC.pdf

等等...

2-Install/DeployRedis

3- 创建上传 client/service/process 以获取您要上传的文件并将它们传递到上述存储位置 (/storage/),这样您的文件在上传后就可用,同时将完整文件路径 URL 推送到预定义的 Redis List/Queue(建立在链表数据结构上),如下所示:http://internal-ip-address/storage/filenameA.pdf

您可以在此处获得有关 Redis Lists 下的 LPUSH 和 RPOP 的更多详细信息:http://redis.io/topics/data-types-intro

示例:

  1. 文件上传表单,将文件直接存储到存储区
  2. 文件上传utility/command-line/background-process,您可以自己创建或使用一些现有工具将文件上传到存储位置,从特定位置获取文件,无论是网址还是其他服务器有你的文件

4- 现在我们来到你的 celery worker,你的每个 worker 都应该从 Redis 队列中拉取 (RPOP) 一个文件 URLs,从你的内部文件服务器下载文件(我们构建在第一步中),然后按照您希望的方式进行所需的处理。

Redis 文档中需要注意的重要事项:

Lists have a special feature that make them suitable to implement queues, and in general as a building block for inter process communication systems: blocking operations.

However it is possible that sometimes the list is empty and there is nothing to process, so RPOP just returns NULL. In this case a consumer is forced to wait some time and retry again with RPOP. This is called polling, and is not a good idea in this context because it has several drawbacks

So Redis implements commands called BRPOP and BLPOP which are versions of RPOP and LPOP able to block if the list is empty: they'll return to the caller only when a new element is added to the list, or when a user-specified timeout is reached.

如果这回答了您的问题,请告诉我。

注意事项

  • 您可以根据需要添加任意数量的工人,因为此解决方案非常 可扩展,你唯一的瓶颈是 Redis 服务器,你可以在断电或服务器崩溃的情况下创建集群并持久化你的队列

  • 您可以用 RabbitMQ、Beanstalk、Kafka 或任何其他 queuing/messaging 系统替换 Redis,但由于 Redis 的简单性和从盒子.

如果您在 linux 环境中,最简单的方法是使用 sshfs 在集群中每个节点的 /mnt 文件夹中安装远程文件系统。然后你可以将节点名称传递给 do_ocr 函数并工作,因为所有数据都是当前节点的本地数据

例如,您的集群有 N 个节点,名称为:node1, ... ,nodeN
让我们配置node1,为每个节点挂载远程文件系统。这是节点 1 的示例 /etc/fstab 文件

sshfs#user@node2:/var/your/app/pdfs    /mnt/node2 fuse    port=<port>,defaults,user,noauto,uid=1000,gid=1000        0       0
....
sshfs#user@nodeN:/var/your/app/pdfs    /mnt/nodeN fuse    port=<port>,defaults,user,noauto,uid=1000,gid=1000        0       0

在当前节点 (node1) 中创建一个名为当前服务器的符号链接指向 pdf 的路径

ln -s /var/your/app/pdfs node1

您的 mnt 文件夹应该包含远程的文件系统和一个符号链接

user@node1:/mnt$ ls -lsa
0 lrwxrwxrwx  1 user user      16 apr 12  2016 node1 -> /var/your/app/pdfs
0 lrwxrwxrwx  1 user user      16 apr 12  2016 node2
...
0 lrwxrwxrwx  1 user user      16 apr 12  2016 nodeN

那么你的函数应该是这样的:

import os
MOUNT_POINT = '/mtn'
@app.task
def do_ocr(pk, node_name, file_path):
    content = run_tesseract_command(os.path.join(MOUNT_POINT,node_name,file_path))
    item = Document.objects.get(pk=pk)
    item.content = ocr_content
    item.save()

它的工作方式就像所有文件都在当前机器上一样,但是有远程逻辑透明地为您工作