我有一项对文件进行长时间处理的服务,它需要特定的资源来进行一次只能使用一次的处理。
用户可能在白天需要该资源,但晚上不需要。白天,他可以用它来做同样的处理或做其他事情。
用户还有一个要在晚上处理的文件列表,他可以随时将这些文件添加到队列文件夹中,然后将在晚上处理它们。
为了实现这一目标,我有:
文件夹结构:
.
├── IN # user add files to be processed here
├── QUEUE # files that will be processed
├── PROCESSING # queue of size <= 1, contains the file being processed
├── OUT # when files have been processed, the service move them here
服务:
# sync QUEUE with IN (possibly with --delete or not)
sync.service
# pick the oldest file from QUEUE and copy it to PROCESSING
pick_one.service
# process the file in PROCESSING,
# remove it from IN, QUEUE and PROCESSING when completed and move it to OUT
processing.service
路径单位:
sync.path # should trigger on each modification of IN folder
pick_one.path # should trigger while QUEUE is not empty and only when PROCESSING is
processing.path # should trigger whem a file is present in PROCESSING
定时器:
start.timer # trigger sync.path and pick_one.path using a target ommitted here
stop.timer # stop sync.path and pick_one.path using a target ommitted here
通过这个系统,我希望用户能够随时处理文件IN
,这些文件将在晚上处理。我可以灵活地选择用户是否可以从中删除文件IN
,并且仍然可以使用中介和 来处理这些QUEUE
文件sync.service
。processing.path
白天继续运行,以便用户只需将其放入PROCESSING
文件夹中即可处理他想要的文件。
问题是我无法在路径单元中找到实现它所需的选项。看来我无法监视修改或避免通过等待完成IN
的循环对内容进行批处理(我猜使用 a )。我对吗?QUEUE
while QUEUE not empty
tune.service
while PROCESSING not empty
我喜欢该解决方案的灵活性,但是我可能会使其过于复杂,并且这可能无法像我尝试做的那样使用纯 systemd 来实现。
有人有比我更好的求婚方法吗?
谢谢
PS:如果您认为需要,我可以发布单元文件的内容,但我试图使该帖子尽可能清晰,而又不会使其太长。
答案1
我想我在这里的第一句话是,我看到您使用术语“复制”、“同步”(rsync?)和“--delete”(这进一步让我想到了 rsync),而排队的正确实现您所描述的服务应该担心原子性每个队列中的文件的数量。
假设您有IN
、PROCESSING
和OUT
,由队列处理器管理,您应该使用原子系统调用,例如重命名(2)和链接(2)(如“硬链接”)在多个队列之间移动或复制/复制文件。
另一个问题是将文件摄取到队列中。您应该让将进程写入队列的进程仅IN
在完成后将它们放入目录中,否则您会再次遇到原子性问题(如果您的队列处理器比写入项目的进程更快,它可能会找到一个在作者有时间填充内容之前为空文件。)
解决这个问题的方法是有一个额外的目录,比如说TMP
,它不被排队系统监视,但被写入者用来归档新项目。作者将在那里创建一个新文件,用内容填充它,关闭该文件,然后才使用重命名(2)或者链接(2)将它们移动到IN
队列中,并让队列处理器可以拾取它们。
我不太明白您想要两个单独的队列IN
,QUEUE
也许这与我提到的想法类似TMP
,但是您谈到了在这两个队列之间移动项目的后台服务,而重点TMP
是与进程同步将一个项目提交到队列中,因为这是您必须解决的原子性问题。
关于 systemd 服务,在我看来,您可以使用运行整个队列的单个 systemd 服务来凑合,在 中拾取项目IN
,暂时将它们移动到,最后在完成PROCESSING
后将它们转储进去。OUT
根据队列作业的处理时间以及启动它们的速度(如果您完全关心延迟),也许从一些非常简单的事情开始,例如IN
在队列处理器空闲时定期轮询目录可能就足够了。当队列为空时,您可以每 5 秒甚至 60 秒左右轮询一次,然后在作业完成后立即再次开始轮询(因此,如果队列繁忙,作业将连续运行。)
是的,使用类似的东西inotify可以提高效率,但实际上只有当队列为空时,因为当队列已满时,您将连续运行作业,因为在完成作业后,您将重新扫描目录以从中选择新作业那里(inotify 不能真正帮助你。)
如果你决定使用inotify,你可以在你的守护进程本身中实现它,它可以保持运行状态,但是当队列为空时,它会在其上注册一个inotify并进入睡眠状态,一旦放置一个项目就会被唤醒在队列中。或者你可以利用系统路径单元,在这种情况下,您可以在队列为空时关闭服务,并让 systemd 仅在有项目需要处理时才启动它。
无论哪种方式使用 inotify 都有潜在的竞争条件。如果您扫描队列并发现它是空的,因此您决定去睡觉怎么办?但就在您执行此操作之前,一个新作业会添加到队列中,从而触发通知。但是通知在您决定睡觉之前到达,因此由于您仍处于遍历队列的模式,因此您认为不需要通知,因此忽略它?这最终导致您的进程在队列中有一个项目时处于休眠状态,并且直到(或者如果有)另一个项目排队时它才会真正被唤醒。
因此,在将 inotify 引入到图片中时请三思而后行(无论是直接引入还是通过 systemd 路径单元引入),因为它会带来相当多的额外复杂性,并且如果您可以以相当大的间隔进行轮询,则不一定需要处理它。
我希望这些指示有用!
答案2
以下是我如何解决这个问题的概述。我将创建一个服务程序,该程序是使用 IN 目录中的路径激活以及计时器(在晚上)启动的。该服务程序的任务是在IN目录中查找文件并将所有找到的文件移动到QUEUE目录中,和(如果当天时间合适)开始逐一处理文件。程序可以在这两个任务之间交替,或者可以在单独的线程中并行完成它们。
该系统的关键原则应该是程序执行所有可用的工作并退出,当有更多工作(新文件)时由 systemd 启动。 systemd 的作用只是起到闹钟的作用,当有工作要做时唤醒服务。服务本身应该检查有多少工作要做,并在退出之前重新检查。
我需要提到这个基于文件的系统的一个复杂之处:当文件被复制到 IN 目录中时,文件可能仍处于打开状态以供写入,甚至很有可能。当文件出现在目录中,但复制过程尚未完成写入时,路径单元会触发。这可能会导致数据损坏。应该将文件移动或原子链接到 IN 目录,或者应该使用其他机制进行轮询,直到复制过程关闭文件。