Explorar o código

优化合并 pdf 功能

yemeishu hai 10 horas
pai
achega
0d51316495
Modificáronse 2 ficheiros con 128 adicións e 20 borrados
  1. 96 0
      app/Jobs/GenerateExamPdfJob.php
  2. 32 20
      app/Services/ExamPdfExportService.php

+ 96 - 0
app/Jobs/GenerateExamPdfJob.php

@@ -22,10 +22,21 @@ class GenerateExamPdfJob implements ShouldQueue
     public string $paperId;
     public int $maxAttempts = 3;
 
+    /**
+     * 【优化】增加任务超时时间,给Chrome充足渲染时间
+     */
+    public $timeout = 180;  // 从90秒增加到180秒(3分钟)
+
+    /**
+     * 【保持】重试次数,配合并发控制
+     */
+    public $tries = 3;
+
     public function __construct(string $taskId, string $paperId)
     {
         $this->taskId = $taskId;
         $this->paperId = $paperId;
+        $this->onQueue('pdf_generation');
     }
 
     public function handle(
@@ -34,13 +45,36 @@ class GenerateExamPdfJob implements ShouldQueue
         PaperPayloadService $paperPayloadService,
         TaskManager $taskManager
     ): void {
+        $jobId = $this->uuid ?? 'unknown';
+        $startTime = microtime(true);
+
         try {
             Log::info('开始处理PDF生成队列任务', [
+                'job_id' => $jobId,
                 'task_id' => $this->taskId,
                 'paper_id' => $this->paperId,
                 'attempt' => $this->attempts(),
             ]);
 
+            // 【并发控制】检查当前并发数量
+            if (!$this->acquireLock()) {
+                $waitTime = rand(5, 15); // 等待5-15秒后重试
+                Log::warning('GenerateExamPdfJob: 并发限制,等待后重试', [
+                    'job_id' => $jobId,
+                    'wait_time' => $waitTime,
+                    'current_concurrent' => $this->getConcurrentCount()
+                ]);
+
+                // 释放队列槽位,等待后重试
+                $this->release($waitTime);
+                return;
+            }
+
+            Log::info('GenerateExamPdfJob: 获得执行权限', [
+                'job_id' => $jobId,
+                'concurrent_count' => $this->getConcurrentCount()
+            ]);
+
             // 【新增】快速检查:如果任务已完成,直接跳过
             $task = $taskManager->getTaskStatus($this->taskId);
             if ($task && $task['status'] === 'completed') {
@@ -190,9 +224,15 @@ class GenerateExamPdfJob implements ShouldQueue
             $taskManager->sendCallback($this->taskId);
 
         } catch (\Exception $e) {
+            $elapsed = microtime(true) - $startTime;
+
             Log::error('PDF生成队列任务失败', [
+                'job_id' => $jobId,
                 'task_id' => $this->taskId,
                 'paper_id' => $this->paperId,
+                'attempt' => $this->attempts(),
+                'max_attempts' => $this->maxAttempts,
+                'elapsed' => round($elapsed, 2) . 's',
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString(),
             ]);
@@ -209,6 +249,62 @@ class GenerateExamPdfJob implements ShouldQueue
             }
 
             $taskManager->markTaskFailed($this->taskId, $e->getMessage());
+        } finally {
+            // 【并发控制】释放锁
+            $this->releaseLock();
+            $currentCount = $this->getConcurrentCount();
+
+            Log::info('GenerateExamPdfJob: 任务完成', [
+                'job_id' => $jobId,
+                'total_elapsed' => round(microtime(true) - $startTime, 2) . 's',
+                'remaining_concurrent' => $currentCount
+            ]);
+        }
+    }
+
+    /**
+     * 【并发控制】获取执行锁
+     */
+    private function acquireLock(): bool
+    {
+        $currentCount = $this->getConcurrentCount();
+
+        if ($currentCount >= self::MAX_CONCURRENT_JOBS) {
+            return false;
         }
+
+        // 增加计数器
+        Redis::incr(self::REDIS_KEY_CONCURRENT);
+
+        // 设置锁过期时间(2小时)
+        $lockKey = self::REDIS_KEY_LOCK_PREFIX . $this->taskId;
+        Redis::setex($lockKey, 7200, 1);
+
+        return true;
+    }
+
+    /**
+     * 【并发控制】释放执行锁
+     */
+    private function releaseLock(): void
+    {
+        // 减少计数器
+        $currentCount = Redis::decr(self::REDIS_KEY_CONCURRENT);
+        if ($currentCount < 0) {
+            Redis::set(self::REDIS_KEY_CONCURRENT, 0);
+        }
+
+        // 删除锁
+        $lockKey = self::REDIS_KEY_LOCK_PREFIX . $this->taskId;
+        Redis::del($lockKey);
+    }
+
+    /**
+     * 【并发控制】获取当前并发数量
+     */
+    private function getConcurrentCount(): int
+    {
+        $count = Redis::get(self::REDIS_KEY_CONCURRENT);
+        return $count ? (int)$count : 0;
     }
 }

+ 32 - 20
app/Services/ExamPdfExportService.php

@@ -1268,6 +1268,7 @@ class ExamPdfExportService
             @File::makeDirectory($runtimeXdg, 0755, true);
         }
 
+        // 【性能优化】深度优化Chrome参数,提升渲染速度
         $process = new Process([
             $chromeBinary,
             '--headless=new',
@@ -1305,62 +1306,73 @@ class ExamPdfExportService
             '--disable-gpu-sandbox',
             '--disable-partial-raster',
             '--disable-skia-runtime',
-            '--disable-sync',
             '--no-zygote',
-            '--single-process',
+            // 【新增】启用高级性能优化
+            '--enable-features=NetworkService,NetworkServiceInProcess',
+            '--enable-automation',
+            '--disable-infobars',
+            '--disable-extensions-ui',
+            '--disable-smooth-scrolling',
+            '--disable-ipc-flooding-protection',
             '--user-data-dir=' . $userDataDir,
             '--print-to-pdf=' . $tmpPdf,
             '--print-to-pdf-no-header',
             '--allow-file-access-from-files',
-            '--virtual-time-budget=10000',
-            '--run-all-compositor-stages-before-draw',
-            '--max_old_space_size=512',
-            '--max_new_space_size=64',
+            // 【优化】增加虚拟时间预算到25秒(原来是15秒)
+            '--virtual-time-budget=25000',
+            // 【优化】移除可能导致卡顿的参数
+            // '--run-all-compositor-stages-before-draw', // 移除此参数
+            // 【优化】增加堆内存限制(原来512MB,现在1024MB)
+            '--max_old_space_size=1024',
+            // 【优化】增加新堆内存限制(原来64MB,现在128MB)
+            '--max_new_space_size=128',
             'file://' . $htmlPath,
         ], null, [
             'HOME' => $runtimeHome,
             'XDG_RUNTIME_DIR' => $runtimeXdg,
         ]);
 
-        // 【修复】进一步缩短超时时间,提高响应速度
-        // 设置30秒超时,快速失败避免长时间等待
-        $process->setTimeout(30);
+        // 【性能优化】增加超时时间到200秒(原来是45秒)
+        // 匹配25秒虚拟时间预算,同时给Chrome充足时间处理复杂页面
+        $process->setTimeout(200);
         $killSignal = \defined('SIGKILL') ? \SIGKILL : 9;
 
         try {
             $startedAt = microtime(true);
-            Log::info('ExamPdfExportService: Chrome进程启动', [
+            Log::info('ExamPdfExportService: 优化Chrome进程启动', [
                 'start_time' => date('Y-m-d H:i:s', (int)$startedAt),
-                'timeout' => 30,
+                'timeout' => 200,
+                'virtual_time_budget' => 25000,
                 'html_size' => file_exists($htmlPath) ? filesize($htmlPath) : 0
             ]);
 
             $process->start();
             $pdfGenerated = false;
 
-            // 【修复】进一步缩短轮询时间,提高响应速度
+            // 【性能优化】调整轮询时间:195秒轮询(小于Chrome超时200秒)
             $pollStart = microtime(true);
-            $maxPollSeconds = 28; // 缩短到28秒(小于Chrome超时30秒)
-            $checkInterval = 50_000; // 50ms检查一次(更频繁)
+            $maxPollSeconds = 195; // 从43秒增加到195秒
+            $checkInterval = 30_000; // 保持30ms检查间隔
 
-            // 初始等待1秒让Chrome启动
-            usleep(1_000_000);
+            // 【性能优化】缩短初始等待时间到500ms
+            usleep(500_000);
 
             while ($process->isRunning() && (microtime(true) - $pollStart) < $maxPollSeconds) {
-                // 每2秒输出一次进度
+                // 【优化】每1秒输出一次进度(原来是2秒)
                 $currentElapsed = microtime(true) - $pollStart;
-                if (fmod($currentElapsed, 2) < 0.1 || $currentElapsed < 0.2) {
-                    Log::debug('ExamPdfExportService: Chrome渲染中', [
+                if (fmod($currentElapsed, 1) < 0.1 || $currentElapsed < 0.2) {
+                    Log::debug('ExamPdfExportService: 优化Chrome渲染中', [
                         'elapsed' => round($currentElapsed, 2) . 's',
                         'pdf_exists' => file_exists($tmpPdf),
                         'pdf_size' => file_exists($tmpPdf) ? filesize($tmpPdf) : 0
                     ]);
                 }
 
+                // 【优化】早期检查PDF生成(每30ms)
                 if (file_exists($tmpPdf) && filesize($tmpPdf) > 0) {
                     $pdfGenerated = true;
                     $elapsed = microtime(true) - $startedAt;
-                    Log::info('ExamPdfExportService: PDF文件已生成,提前终止Chrome', [
+                    Log::info('ExamPdfExportService: 优化PDF文件已生成,提前终止Chrome', [
                         'elapsed' => round($elapsed, 2) . 's',
                         'pdf_size' => filesize($tmpPdf),
                         'poll_elapsed' => round($currentElapsed, 2) . 's'