如何在后台执行一个长任务?

How to execute a long task in background?

我有一个使用 Symfony 5 制作的应用程序,我有一个脚本可以将位于服务器上的视频上传到已登录的用户频道。

这里基本上是我的控制器的代码:

    /**
     * Upload a video to YouTube.
     *
     * @Route("/upload_youtube/{id}", name="api_admin_video_upload_youtube", methods={"POST"}, requirements={"id" = "\d+"})
     */
    public function upload_youtube(int $id, Request $request, VideoRepository $repository, \Google_Client $googleClient): JsonResponse
    {
        $video = $repository->find($id);

        if (!$video) {
            return $this->json([], Response::HTTP_NOT_FOUND);
        }

        $data = json_decode(
            $request->getContent(),
            true
        );

        $googleClient->setRedirectUri($_SERVER['CLIENT_URL'] . '/admin/videos/youtube');
        $googleClient->fetchAccessTokenWithAuthCode($data['code']);

        $videoPath = $this->getParameter('videos_directory') . '/' . $video->getFilename();

        $service = new \Google_Service_YouTube($googleClient);
        $ytVideo = new \Google_Service_YouTube_Video();

        $ytVideoSnippet = new \Google_Service_YouTube_VideoSnippet();
        $ytVideoSnippet->setTitle($video->getTitle());
        $ytVideo->setSnippet($ytVideoSnippet);

        $ytVideoStatus = new \Google_Service_YouTube_VideoStatus();
        $ytVideoStatus->setPrivacyStatus('private');
        $ytVideo->setStatus($ytVideoStatus);

        $chunkSizeBytes = 1 * 1024 * 1024;

        $googleClient->setDefer(true);

        $insertRequest = $service->videos->insert(
            'snippet,status',
            $ytVideo
        );

        $media = new \Google_Http_MediaFileUpload($googleClient, $insertRequest, 'video/*', null, true, $chunkSizeBytes);
        $media->setFileSize(filesize($videoPath));

        $uploadStatus = false;
        $handle = fopen($videoPath, "rb");

        while (!$uploadStatus && !feof($handle)) {
            $chunk = fread($handle, $chunkSizeBytes);
            $uploadStatus = $media->nextChunk($chunk);
        }

        fclose($handle);
    }

这基本上可行,但问题是视频可能非常大(10G+),所以需要很长时间,基本上 Nginx 在结束之前终止并且 returns 一个“504 网关超时" 在上传完成之前。

无论如何,我不希望用户在上传页面时必须等待页面加载。

所以,我正在寻找一种方法,而不是立即 运行 该脚本,而是在某种后台线程中或以异步方式执行该脚本。

控制器 returns 向用户发送 200,我可以告诉他正在上传,稍后再回来检查进度。

如何操作?

有很多方法可以实现这一点,但您基本上想要的是将动作触发器与其执行分离。

简单地:

  • 从您的控制器中删除所有繁重的工作。您的控制器最多应该检查客户端提供的视频 ID 是否存在于您的 VideoRepository.
  • 存在吗?好,那么你需要把这个 "work order" 存储在某个地方。

    针对此问题有很多解决方案,具体取决于您已经安装的内容、您觉得table使用哪种技术更舒服等等

    为了简单起见,假设您有一个 PendingUploads table,其中 videoIdstatuscreatedAt 可能还有 userId.所以你的控制器唯一会做的就是在这个 table 中创建一个新记录(也许检查作业不是 "queued" ,这种细节取决于你的实现)。

  • 然后return200(或202,即could be more appropriate in the circumstances

然后您需要编写一个单独的过程。

很可能是您会定期执行的控制台命令(使用 cron 将是最简单的方法)

在每次执行时,该进程(将具有所有 Google_Client 逻辑,可能还有一个 PendingUploadsRepository)将检查哪些作业正在等待上传,按顺序处理它们,然后设置 status 到您表示要完成的任何内容。例如,您可以将 status 设置为 0(待处理)、1(处理中)和 2(已处理),并相应地设置每个步骤的状态脚本。

具体实现细节由您决定。这个问题太宽泛和自以为是了。选择你已经理解的东西,让你行动得更快。如果您将作业存储在 Rabbit、Redis、数据库或平面文件中,并不是特别重要。如果您的 "consumer" 以 cronsupervisor 开头,则两者都可以。

Symfony 有一个现成的组件,可以让您异步地解耦这种消息传递 (Symfony Messenger),它非常好。调查一下它是否适合您,但如果您不打算将它用于您的应用程序中的任何其他内容,我会在开始时保持简单。