前因:
公司的新版ios app已经成功上架了、运营们跑过来就说啦:“我们这周五要推送push消息,你们的push后台什么时候能弄好啊”
困难:
其实松林也没有做过苹果push、原来公司有个push后台,看了下源代码,用的是原始的推送(数据库取出所有deviceToken,然后用foreach循环一个苹果的socket连接推送),这种做法是有一个很大的弊端的、
1:苹果的socket连接一次只能推送100~1000左右(原来测试过,忘了是在多少条的时候断开链接的,不会超过1000)
2:一个socket链接循环多条,如果deviceToken有一条失效的,那么在这条deviceToken之后的都不会推送成功
公司原来的这种做法排除php超时的情况下、deviceToken肯定不会都推送完毕的。当时公司的deviceToken获取到3000左右~。问了下公司的同事,他们也是foreach循环推送。
既然知道一个socket对象推送有什么弊端之后就很容易做了,以下是松林的解决方案
公司app上架没两个星期,deviceToken已经保存了近16w的数据。肯定是需要php进程执行了。
涉及到进行,那么放到队列里面是最合适的,松林用的是Redis的list类型进行存储推送消息内容
当运营人员在后台编辑完推送内容之后,php会将所有deviceToken在mysql查询出来foreach存入Redis中(包括推送内容),然后编写一个php从Redis中取数据并发送的脚本(每次都建立苹果连接),php脚本每次开启运行5分钟、5分钟以后自动退出。然后编辑个shell脚本,循环执行php脚本20次(次数根据deviceToken多少进行增加),最后将shell脚本编辑到crontab中运行、至于运行时间设置为php脚本执行时间最好、这样就相当于不间断取数据推送。这样做的好处就是预防php超时
代码如下
php后台编辑内容入redis的代码我就不发了,发一下php脚本代码
$message, 'sound' => 'default', 'openType' => $info['iType'], 'openId' => strval($info['iPushid']), 'link' => $info['iPushid'], ); $payload = json_encode($body); $msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $info['token'])) . pack("n",strlen($payload)) . $payload; fwrite($fp, $msg); fclose($fp); } /** *获取IOS推送对象 */ public static function getIosObj($pass='123456'){ $ctx = stream_context_create(); $pem = dirname(__FILE__) .'/'.'push-dev2.pem'; stream_context_set_option($ctx, 'ssl', 'local_cert', $pem); stream_context_set_option($ctx, 'ssl', 'passphrase', $pass); $fp = stream_socket_client('ssl://gateway.push.apple.com:2195',$err,$errstr, 60, STREAM_CLIENT_CONNECT, $ctx); if(!$fp) return false; else return $fp; } } $pushKey = 'ios_push_user'; //Redis Key $time = 300; //执行时间 $obj = new push; MQTaskHelper::consume($pushKey,array($obj,'sendPush'),$time,['host'=>'127.0.0.1']);
MQTaskHelper.php 类
listPush($strKey,$mixMessage); } public static function consume($strKey,$strCallback,$iRunTime = 60,$arrOptions=array()){ $oRedis = self::_getRedisObj($arrOptions); //开始时间 $iStartTime = time(); while(true){ $iCurrentTime = time(); if($iCurrentTime - $iStartTime >= $iRunTime){ print_r("[".date('Y-m-d H:i:s')."] I worked from[" .date('Y-m-d H:i:s',$iStartTime)."] to [" .date('Y-m-d H:i:s',$iCurrentTime)."]. Quiting!\n"); exit; } $mixMessage = $oRedis->lPop($strKey); if(!$mixMessage){ continue; }else{ try{ call_user_func($strCallback, $mixMessage); }catch(Exception $ex){ print_r("mq_consume_exception:".$ex->getMessage()."\n"); } } } } private static function _getRedisObj($arrOptions) { if(empty(self::$redisObj)){ self::$redisObj = new Redis(); self::$redisObj->connect($arrOptions['host'],6379); } return self::$redisObj; } }
服务器push脚本设置
*/5 * * * * /bin/bash /data/xxx/push/push.sh
push.sh
for i in {1..10} do /data/xxx/php/bin/php -f /data/xxx/push/push.php & done
基本推送了15w条的时间在1小时左右吧、如果觉得慢的话,多开几个进程、或者多弄几台服务器都是可以的。
至此结束