FCM单条消息仅支持1000个注册ID,PHP如何推送至500000台设备?
如何在PHP中向50万台设备推送FCM通知?
兄弟,这个场景我太熟了!FCM明确规定单条消息最多包含1000个设备注册ID,要搞定50万的推送量,核心思路就是分批处理,再配合一些优化技巧保证推送效率和成功率。下面给你一步步拆解:
1. 第一步:拆分设备ID为1000个一组的批次
首先得把50万的token数组拆分成每1000个一组的子数组,PHP自带的array_chunk()函数完美适配这个需求,一行代码就能搞定:
// 假设你从数据库或其他存储拿到了50万个设备token $allDeviceTokens = [...]; // 你的50万token集合 // 拆分每1000个token为一个批次 $tokenBatches = array_chunk($allDeviceTokens, 1000); // 拆分后会得到500个批次(500000 / 1000 = 500)
2. 批量发送FCM请求
接下来要给每个批次发送FCM请求,这里有几个关键要点要注意:
基础同步发送示例(原生PHP + Guzzle)
如果是小批量测试或者没有框架支持,用Guzzle发送同步请求是最直接的方式,同时要处理响应里的错误信息:
use GuzzleHttp\Client; // 初始化Guzzle客户端 $client = new Client(); // 替换成你的FCM服务器密钥 $fcmServerKey = 'YOUR_FCM_SERVER_KEY'; $fcmApiEndpoint = 'https://fcm.googleapis.com/fcm/send'; // 遍历每个批次发送 foreach ($tokenBatches as $batchIndex => $batch) { // 构造FCM请求 payload $payload = [ 'registration_ids' => $batch, 'notification' => [ 'title' => '你的通知标题', 'body' => '你的通知内容' ], // 可选:添加自定义数据字段 'data' => [ 'custom_key' => 'custom_value' ] ]; try { $response = $client->post($fcmApiEndpoint, [ 'headers' => [ 'Authorization' => 'key=' . $fcmServerKey, 'Content-Type' => 'application/json' ], 'json' => $payload ]); // 解析响应,处理失败的token $responseData = json_decode($response->getBody(), true); if (isset($responseData['results'])) { foreach ($responseData['results'] as $tokenIndex => $result) { if (isset($result['error'])) { $invalidToken = $batch[$tokenIndex]; // 这里要把无效token从你的存储中移除(比如数据库) // 常见错误:invalid_registration、not_registered都是无效token error_log("批次{$batchIndex}中无效token: {$invalidToken},错误原因:{$result['error']}"); } } } } catch (\Exception $e) { // 捕获请求异常,比如网络错误、限流等,可添加重试逻辑 error_log("批次{$batchIndex}发送失败:{$e->getMessage()}"); } // 可选:添加小延迟,避免触发FCM频率限制 usleep(100000); // 暂停0.1秒 }
进阶:用队列异步处理(推荐)
如果是在Laravel、Symfony这类框架中,强烈建议把每个批次的推送任务放到队列中处理,这样不会阻塞前端请求,也能避免长时间运行导致的超时问题。
以Laravel为例:
- 创建一个队列任务:
php artisan make:job SendFcmBatch - 在任务类中编写推送逻辑:
namespace App\Jobs; use GuzzleHttp\Client; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class SendFcmBatch implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $tokenBatch; public function __construct(array $tokenBatch) { $this->tokenBatch = $tokenBatch; } public function handle() { $client = new Client(); $fcmServerKey = config('services.fcm.server_key'); $payload = [ 'registration_ids' => $this->tokenBatch, 'notification' => [ 'title' => '你的通知标题', 'body' => '你的通知内容' ] ]; try { $response = $client->post('https://fcm.googleapis.com/fcm/send', [ 'headers' => [ 'Authorization' => 'key=' . $fcmServerKey, 'Content-Type' => 'application/json' ], 'json' => $payload ]); // 同样处理无效token逻辑... } catch (\Exception $e) { $this->fail($e); // 标记任务失败,方便重试 } } }
- 分发任务:
// 拆分批次后,循环分发任务 foreach ($tokenBatches as $batch) { SendFcmBatch::dispatch($batch); }
3. 关键优化技巧
- 及时清理无效token:FCM返回的
invalid_registration、not_registered错误对应的token是无效的,一定要从你的存储中移除,避免后续重复浪费请求。 - 控制请求频率:FCM有隐性的频率限制,不要一次性发送所有批次,加个小延迟(比如0.1秒)或者控制队列并发数,避免被限流。
- 使用FCM主题订阅(可选):如果你的设备可以按主题分组(比如同一类用户、同一业务场景),可以让设备订阅主题,直接给主题发消息,FCM会自动推送给所有订阅设备,省去自己拆分token的麻烦。
- 监控与日志:一定要记录每个批次的发送结果,成功数、失败数、错误类型,方便后续排查问题。
4. 百万级以上的扩展方案
如果后续设备量增长到百万级,可以考虑:
- 多进程并发发送:用PHP的
pcntl_fork()创建子进程,每个进程处理部分批次(注意进程管理,避免资源耗尽)。 - 使用FCM v1 API:相比传统的服务器密钥认证,v1 API用OAuth2认证,权限更精细,适合大规模推送场景。
内容的提问来源于stack exchange,提问作者krishna
相关产品推荐
相关产品推荐

