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
相关产品推荐
相关产品推荐

