【问题标题】:How to execute a long task in background?如何在后台执行长时间的任务?
【发布时间】:2020-02-06 00:34:06
【问题描述】:

我有一个使用 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在它结束之前就终止并在上传完成之前返回“504 Gateway Timeout” .

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

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

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

如何做到这一点?

【问题讨论】:

  • 这种情况的最佳方法是使用 symfony messenger。

标签: php symfony decoupling


【解决方案1】:

有很多方法可以做到这一点,但您基本上想要的是将动作触发器及其执行解耦。

简单地说:

  • 从控制器中移除所有繁重的工作。您的控制器最多应该检查客户端提供的视频 ID 是否存在于您的 VideoRepository 中。
  • 存在吗?好的,那么您需要将这个“工作订单”存储在某个地方。

    对此有很多解决方案,具体取决于您已安装的内容、您觉得更舒适的技术等。

    为简单起见,假设您有一个PendingUploads 表,其中包含videoIdstatuscreatedAtuserId。因此,您的控制器唯一要做的就是在此表中创建一条新记录(也许检查该作业是否尚未“排队”,这种细节取决于您的实现)。

  • 然后返回200(或202,其中could be more appropriate in the circumstances

然后你需要编写一个单独的进程。

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

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

具体实施细节由您决定。这个问题过于宽泛和固执己见。选择你已经了解的东西,让你行动得更快。如果您将作业存储在 Rabbit、Redis、数据库或平面文件中并不是特别重要。如果您以cronsupervisor 开头“消费者”。

Symfony 有一个现成的组件,可以让您异步解耦这种消息传递 (Symfony Messenger),它非常棒。调查它是否是您的一杯茶,但如果您不打算在您的应用程序中将其用于其他任何事情,我会保持简单。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-10
    • 2017-12-14
    • 2017-11-12
    • 2014-10-26
    • 2018-01-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多