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

Laravel十万级Firebase批量推送通知优化方案问询

大规模Firebase推送优化方案(Laravel环境)

问题背景

我在Laravel中使用kutia-software-company/larafirebase包发送Firebase通知,当前需要从支持1万条FCM Token扩展到10万条。现有实现在开发环境模拟1万条推送时,因循环创建任务耗时过长触发最大执行时间错误,已配置非sync队列但问题未解决,寻求高效优化方案。

现有实现代码

1. 通知类

<?php

namespace Vendor\Core\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Kutia\Larafirebase\Messages\FirebaseMessage;

class FirebasePushNotification extends Notification
{
    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct(protected $title, protected $message, protected array $fcmTokens = [])
    {
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param mixed $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['firebase'];
    }

    public function toFirebase($notifiable)
    {
        return (new FirebaseMessage)
            ->withTitle($this->title)
            ->withBody($this->message)
            ->withPriority('high')
            ->asNotification($this->fcmTokens);
    }
}

2. 任务类

<?php

namespace Vendor\PushNotification\Jobs;

use Vendor\Core\Notifications\FirebasePushNotification;
use Vendor\Customer\Contracts\UserDeviceInfo;
use Vendor\Customer\Contracts\Customer;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Notification;


class SendPushNotificationJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Batchable;

    public function __construct(public string $title, public string $body, public Customer $customer, public UserDeviceInfo $userDeviceInfo)
    {
    }

    public function handle()
    {
        Notification::send($this->customer, new FirebasePushNotification($this->title, $this->body, [$this->userDeviceInfo->fcm]));
    }

}

3. 任务分发代码

$usersDeviceInfos = $usersDeviceInfos->with('customer')->get();

$bus = [];
for ($i = 0; $i < 100000; $i++){
    foreach ($usersDeviceInfos as $usersDeviceInfo) {

        $bus [] = new SendPushNotificationJob($data['notification_title'], $data['notification_body'], $usersDeviceInfo->customer, $usersDeviceInfo);
    }
}

Bus::batch($bus)->name('push notification' . now()->toString())->dispatch();

问题分析

  • 单任务单Token浪费资源:每个任务仅推送1个Token,10万条需创建10万个任务,内存占用高,队列分发耗时极长
  • 同步循环创建任务超时:在请求周期内一次性生成大量任务实例,阻塞进程触发执行时间限制
  • 不必要的模型传递:任务携带Customer和UserDeviceInfo模型,序列化/反序列化增加额外开销,实际仅需FCM Token

优化方案

1. 批量推送Token,减少任务数量

FCM支持最多500个Token/请求的批量推送,修改通知类和任务类,让单个任务处理一批Token:

修改通知类(简化后)

<?php

namespace Vendor\Core\Notifications;

use Illuminate\Notifications\Notification;
use Kutia\Larafirebase\Messages\FirebaseMessage;

class FirebasePushNotification extends Notification
{
    public function __construct(protected string $title, protected string $message, protected array $fcmTokens = [])
    {
    }

    public function via($notifiable)
    {
        return ['firebase'];
    }

    public function toFirebase($notifiable)
    {
        return (new FirebaseMessage)
            ->withTitle($this->title)
            ->withBody($this->message)
            ->withPriority('high')
            ->asNotification($this->fcmTokens);
    }
}

新批量任务类

<?php

namespace Vendor\PushNotification\Jobs;

use Vendor\Core\Notifications\FirebasePushNotification;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Notification;

class BatchFirebasePushJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Batchable;

    // 仅传递必要数据,避免模型序列化开销
    public function __construct(public string $title, public string $body, public array $fcmTokens)
    {
    }

    public function handle()
    {
        // 使用空匿名类作为notifiable,无需关联用户模型
        Notification::send(new class{}, new FirebasePushNotification($this->title, $this->body, $this->fcmTokens));
    }
}

2. 异步分块生成任务,避免请求超时

使用Laravel的chunk方法分批获取Token,异步分发任务,不在请求周期内一次性生成所有任务:

// 基础分块分发方案
UserDeviceInfo::query()->chunk(500, function ($deviceInfos) use ($data) {
    $tokens = $deviceInfos->pluck('fcm')->toArray();
    
    Bus::dispatch(new BatchFirebasePushJob(
        $data['notification_title'],
        $data['notification_body'],
        $tokens
    ));
});

// 如需批量管理任务状态(进度/失败回调),改用Bus::batch分块添加
$batch = Bus::batch([])->name('push notification ' . now())->dispatch();

UserDeviceInfo::query()->chunk(500, function ($deviceInfos) use ($data, $batch) {
    $tokens = $deviceInfos->pluck('fcm')->toArray();
    
    $batch->add(new BatchFirebasePushJob(
        $data['notification_title'],
        $data['notification_body'],
        $tokens
    ));
});

3. 额外优化建议

  • 移除不必要的模型关联:任务中仅传递FCM Token字符串,避免序列化整个模型
  • 调整队列配置:增加队列worker数量,设置合理的--timeout和--sleep参数,避免worker超时
  • 启用任务重试机制:处理FCM请求失败的情况
class BatchFirebasePushJob implements ShouldQueue
{
    // 最多重试3次
    public $tries = 3;
    // 重试间隔(秒)
    public $backoff = [10, 30, 60];
    
    // ... rest of the code
}
  • 使用FCM主题订阅:如果用户可按分组管理,直接推送给FCM主题,无需维护大量Token
  • 监控任务执行:利用Laravel Horizon(Redis队列)监控队列状态,及时排查失败任务

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 21:15:34