You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel同步模式下TestSyncJob任务不重叠的实现问题

解决同步队列任务执行重叠问题

问题原因

dispatchSync() 是同步执行任务,Laravel 原生的 WithoutOverlapping 中间件针对队列异步任务设计,在同步模式下检测到锁存在时会直接跳过任务,不会等待锁释放,因此无法满足你需要等待的需求。

解决方案

方案一:手动实现分布式阻塞锁(推荐)

利用 Laravel 的缓存锁实现分布式阻塞等待,确保同一时间只有一个 TestSyncJob 执行,其他请求会等待锁释放后再运行。

修改 TestSyncJob 的 handle 方法:

class TestSyncJob implements ShouldQueue
{
    public function handle(): void
    {
        // 定义全局唯一的锁名称
        $lockName = 'test-sync-job-lock';
        // 设置锁超时时间(需大于任务最长执行时间,避免死锁)
        $lockTimeout = 20;

        // 获取锁并阻塞等待,直到拿到锁或超时(传 null 表示一直等待)
        $lock = \Illuminate\Support\Facades\Cache::lock($lockName, $lockTimeout);
        if ($lock->get()) {
            try {
                \Log::info('Sync job started!');
                sleep(10);
                \Log::info('Sync job finished!');
            } finally {
                // 无论任务成功失败,都必须释放锁
                $lock->release();
            }
        }
    }
}

说明:

  • 缓存锁基于 Redis/Memcached 实现,支持多进程、多服务器场景下的分布式锁
  • finally 块确保锁一定会被释放,避免死锁
  • 若需要限制最长等待时间,可将 $lock->get() 改为 $lock->block(60)(表示最多等待60秒)

方案二:使用单进程专属队列(异步场景适用)

如果不需要 TestQueueJob 等待 TestSyncJob 执行完成,可将 TestSyncJob 改为异步任务,并分配到一个仅启动单进程的专属队列,天然保证任务串行执行。

  1. 配置 Supervisor 专属队列进程:
[program:laravel-queue-sync]
process_name=%(program_name)s
command=php /path/to/your/laravel/artisan queue:work database --queue=sync-jobs --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/path/to/laravel/storage/logs/queue-sync.log
  1. 修改任务调度方式:
    在 TestQueueJob 的 handle 方法中,将 dispatchSync() 改为异步调度到专属队列:
class TestQueueJob implements ShouldQueue
{
    public function handle(): void
    {
        // some code
        TestSyncJob::dispatch()->onQueue('sync-jobs');
        // some code
    }
}

说明:此方案下 TestQueueJob 会继续执行后续代码,不会等待 TestSyncJob 完成,适合不需要同步等待的场景。

方案三:自定义阻塞式无重叠中间件

基于原生 WithoutOverlapping 中间件改造,让同步任务也能阻塞等待锁释放。

  1. 创建自定义中间件:
<?php

namespace App\Queue\Middleware;

use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Queue\Middleware\WithoutOverlapping as BaseMiddleware;

class BlockingWithoutOverlapping extends BaseMiddleware
{
    protected $cache;
    protected $waitTimeout;

    public function __construct($key, $waitTimeout = 60)
    {
        parent::__construct($key);
        $this->waitTimeout = $waitTimeout;
        $this->cache = app(Cache::class);
    }

    public function handle($job, $next)
    {
        $lockKey = $this->getLockKey($job);

        // 尝试获取锁,若失败则阻塞等待指定时长
        $lock = $this->cache->lock($lockKey, $this->expireAfter);
        $lock->block($this->waitTimeout);

        try {
            return $next($job);
        } finally {
            $lock->release();
        }
    }
}
  1. 在 TestSyncJob 中使用自定义中间件:
use App\Queue\Middleware\BlockingWithoutOverlapping;

class TestSyncJob implements ShouldQueue
{
    public function handle(): void
    {
        \Log::info('Sync job started!');
        sleep(10);
        \Log::info('Sync job finished!');
    }

    public function middleware()
    {
        return [new BlockingWithoutOverlapping(1)];
    }
}

说明:此方案保留了中间件的使用方式,同时适配了同步任务的阻塞需求,适合习惯用中间件管理任务约束的场景。


内容的提问来源于stack exchange,提问作者hehoboy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 05:05:21