如何用PHP高效向20万用户同步发送Firebase推送通知
解决FCM批量推送延迟问题的方案
当前用户基数20万,直播活动正在进行,需要通过PHP向所有用户同步发送FCM推送通知,但FCM单次请求最多支持1000个设备ID,导致最后一批用户在直播开始10分钟后才收到通知。以下是可行的解决措施:
核心优化方案
1. 改用FCM主题推送(最优解)
让所有用户订阅同一个直播相关主题(比如live_event_xxx),直接向该主题发送推送。FCM会自动负责将消息分发给所有订阅用户,无需手动分批处理,完全避开单次1000个设备的限制,且分发效率由FCM后台保障,能确保所有用户几乎同时收到通知。
2. 并行处理批量请求
如果必须使用设备ID列表推送,不要串行逐个发送每批请求,改成并行发送多批请求:
- 使用
curl_multi扩展同时发起多个HTTP请求,一次性处理多批(比如10-20批)1000个设备的推送,大幅压缩总耗时。 - 或者借助异步任务队列(如Redis队列+PHP Worker进程),将200批推送任务放入队列,由多个Worker同时处理,避免单进程阻塞等待。
3. 修正原代码的批量推送逻辑
你的现有代码存在一个关键问题:当$regIds是数组时,没有设置FCM要求的registration_ids字段,导致无法正确批量推送。先修正这个基础问题:
function sendMsg($regIds, $data, $device_type=''){ $apiKey = $this->data['api_key']; // 修正apiKey获取方式,原代码未正确使用 $fields = array( 'priority' => 'high', 'data' => array( 'title' => $this->data['title'], "body" => $this->data['message'], 'sound' => 'default', 'message' => $this->data['message'], 'screen' => $this->data['screen'], 'screen_id' => $this->data['screen_id'] ) ); if($this->data['screen_data']) { $fields['data']['screen_data'] = $this->data['screen_data']; } if($this->data['notification_id']) { $fields['data']['notification_id'] = $this->data['notification_id']; } // 修正批量推送逻辑 if(is_array($regIds)) { // 确保每批不超过1000个设备ID if(count($regIds) > 1000) { throw new Exception("每批设备ID数量不能超过1000"); } $fields['registration_ids'] = $regIds; } else { $fields['to'] = $regIds; } if($device_type != 'android') { $fields['notification'] = array( 'title' => $this->data['title'], 'body' => $this->data['message'], 'sound' => 'default' ); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'https://fcm.googleapis.com/fcm/send'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: key='.$apiKey, 'Content-Type: application/json')); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // 建议开启SSL验证,提升安全性 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields)); $result = curl_exec($ch); curl_close($ch); return $result; }
4. 并行请求示例(使用curl_multi)
以下是一个简单的并行处理多批设备ID的示例,可大幅减少总推送时间:
function sendBulkMsg($allRegIds, $data, $device_type=''){ $batchSize = 1000; $batches = array_chunk($allRegIds, $batchSize); $mh = curl_multi_init(); $handles = []; foreach($batches as $batch) { $ch = curl_init(); $apiKey = $data['api_key']; $fields = array( 'priority' => 'high', 'registration_ids' => $batch, 'data' => array( 'title' => $data['title'], "body" => $data['message'], 'sound' => 'default', 'message' => $data['message'], 'screen' => $data['screen'], 'screen_id' => $data['screen_id'] ) ); if($data['screen_data']) { $fields['data']['screen_data'] = $data['screen_data']; } if($data['notification_id']) { $fields['data']['notification_id'] = $data['notification_id']; } if($device_type != 'android') { $fields['notification'] = array( 'title' => $data['title'], 'body' => $data['message'], 'sound' => 'default' ); } curl_setopt($ch, CURLOPT_URL, 'https://fcm.googleapis.com/fcm/send'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: key='.$apiKey, 'Content-Type: application/json')); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields)); curl_multi_add_handle($mh, $ch); $handles[] = $ch; } // 执行所有请求 $running = null; do { curl_multi_exec($mh, $running); curl_multi_select($mh); } while($running > 0); // 获取结果并关闭句柄 $results = []; foreach($handles as $ch) { $results[] = curl_multi_getcontent($ch); curl_multi_remove_handle($mh, $ch); curl_close($ch); } curl_multi_close($mh); return $results; }
总结
优先选择主题推送,这是最省心高效的方案;如果必须用设备ID列表,就用并行请求或异步队列处理批次,避免串行阻塞导致的延迟。同时修正原代码中未处理批量设备ID的bug,确保请求格式符合FCM要求。
内容的提问来源于stack exchange,提问作者Pankaj Dhameliya
相关产品推荐
相关产品推荐

