这种在APP处于后台或关闭情形下的推送能力,常日在以了局景下非常有用:
1)IM即时通讯谈天运用:谈天关照、音视频谈天呼叫等,范例代表有:微信、QQ、易信、米聊、钉钉、Whatsup、Line;
2)新闻资讯运用:最新资讯关照等,范例代码有:网易新闻客户端、腾讯新闻客户端;
3)SNS社走运用:转发/关注/赞等关照,范例代表有:微博、知乎;
4)邮箱客户端:新邮件关照等,范例代表有:QQ邮箱客户端、Foxmail客户端、网易邮箱大师;
5)金融支付运用:收款关照、转账关照等,范例代表有:支付宝、各大银行的手机银行等;
.... ....
除了以上范例场景下,推送这种能力已经被越来越多的APP作为根本能力之一,由于移动互联网时期下,用户的“全时在线”能力非常诱人和强大,能随时随地即时地将各种主要信息推送给用户,无疑是非常故意义的。
众所周之,iOS真个这项推送能力便是利用苹果供应的APNs做事来实现(有些iOS小白开拓者可能看到各种第3方的iOS端推送SDK,总会习气性地认为这是完备由第3方供应的能力,实际上同样是利用APNs,只是封装了一下而已)。目前先容APNs推送的文章多谈论的是手机真个实现,而做事真个要怎么“推”出来这样的文章,要么太老,要么只是先容如何调用第3方的做事端SDK接口而已(如极光推广、友盟推送、腾讯信鸽推送等)。以是本文趁着最近对项目组的老苹果iOS推送进行升级修正机会,详细查阅了最新苹果的APNs接口文档,同时为了避免重复造轮子(
补充解释:网上目前能查到的有关iOS端APNs推送的Java做事端代码实现,多是先容如何利用Java-APNS这个工程,但这个工程以及类似的其它工程都良久没有掩护了,跟最新的苹果APNs做事已经很难匹配了。相较而言puhsy这个工程一贯比较生动,也对苹果的最新APNs跟进的比较及时,因而本文作者在公司的项目进行升级和重构过程中,绝不犹豫的利用了pushy。
学习互换:
- 即时通讯开拓互换3群:185926912 [推举]
- 移动端IM开拓入门文章:《新手入门一篇就够:从零开拓移动端IM》
(本文同步发布于:http://www.52im.net/thread-1820-1-1.html)
2、干系文章有关iOS客户端APNs推送技能的先容文章:
《iOS的推送做事APNs详解:设计思路、技能事理及毛病等》
《信鸽团队原创:一起走过 iOS10 上推送(APNS)的坑》
《理解iOS推送一文就够:史上最全iOS Push技能详解》
《移动端实时推送技能浅析》
《扫盲贴:浅谈iOS和Android后台实时推送的事理和差异》
有关推送技能做事端架构方面的文章:
《绝对干货:基于Netty实现海量接入的推送做事技能要点》
《极光推送系统大规模高并发架构的技能实践分享》
《魅族2500万长连接的实时推送架构的技能实践分享》
《专访魅族架构师:海量长连接的实时推送系统的心得体会》
《一个基于长连接的安全可扩展的订阅/推送做事实现思路》
《实践分享:如何构建一套高可用的移动端推送系统?》
《Go措辞构建千万级在线的高并发推送系统实践(来自360公司)》
《腾讯信鸽技能分享:百亿级实时推送的实战履历》
《百万在线的美拍直播弹幕系统的实时推送技能实践之路》
《京东京麦商家开放平台的推送架构演进之路》
>> 更多同类文章 ……
3、提一下Android真个推送论坛里做IM或推送做事的朋友都很清楚,相对付苹果为iOS包办好的APNs技能,Android上的推送技能乱七八糟、一塌糊涂,缘故原由是海内的Android厂商将Android原生的GCM(现在叫FCM,跟iOS的APNs是类似的技能)进行了阉割,加上各厂商的省电策略、这全策略各不相同,导致为了实现IM和其它各种运用中的后台推送,不得不为了进程保活、网络保活搞出各种黑科技(当然,自从Android 6.0发布往后,谷歌为了打击这种不道德的行为,进行了越来越严格的限定,保活黑科技越来越难搞了)。
海内的厂商为了跟进新版本Android的GCM(现在叫FCM),也都在搞自已的推送通道:小米手机有小米推送、魅族手机有魅族推送、华为手机有华为推送等等,开拓者在放弃保活黑科技往后,只能一家一家接入各厂商的推送通道,而这这又涉到同一厂商的手机版本、不同厂商通道的自动识别等,麻烦事乱到你无法想象,就连第3方推送做事也只能就范——一家一家接入(比如信鸽的《[资讯] 信鸽新版上线:号称Android首家统一推送做事》)。
为理解决上述乱象,好是去年有政府背景的“统一推送同盟”成立了(详见《[资讯] 统一推送同盟在京成立:结束海内安卓生态混乱》),广大Android开拓者真是翘首以盼,但坏是好进展并不顺利(大家心知肚明啊,各厂商的利益不好均衡嘛),最近一次跟推送做事有关的活动还是3个月前的《[资讯] 统一推送同盟2018成员大会准期召开》。虽然进展不大,但总算还是有希望,Android同行们再等等,总有Android端推送一统江湖的方案涌现的那天。
当然,本文紧张是谈论iOS真个推送,本节笔墨只是写给Android端推送感兴趣的同行看的,更多Android推送技能的文章,请前往:http://www.52im.net/forum.php?mod=collection&action=view&ctid=11
4、说一说为什么不该用第3方推送做事SDK?目前主流的iOS第3方推送SDK有:友盟推送、极光推送、信鸽推送等。
利用第3方推送的优点紧张是:
1)大略:开箱即用,无需关注技能细节;
2)统计:供应了推送数据的统计能力等;
3)性能:无需关注性能负载,由于第3方都帮你实现好了,你只要调用它的接口即可。
利用第3方推送的缺陷也很明显:
1)到达率:虽然第3方移动端推送产品都宣扬到达率能够达到 90%及以上,但是实际利用起来,创造远远达不到;
2)实时性:第3方移动端推送产品的推送通道是共用的,会面向多个推送客户,如果某一个客户PUSH推送量特殊大,那么其他的移动端推送实时性可能就会受到影响;
3)不可控:虽然各种技能细节无需你关注是个优点,但它也同时是个缺陷——由于你不可控的东西太多了,想要做一个定制化的需求就力不从心了;
4)被限流:由于第3方的推送做事多是免费供应,以是接口调用等都是有限定哀求的(纵然纸面上没有说出来),限流是一定要做的,不然这些本钱谁抗的住?
针对以上问题,58同城团队在《58同城高性能移动端推送技能架构演进之路》也有谈论。
更为关键的是,如果是实现iOS的推送,苹果官方供应的APNs做事已经足够大略,如果不是为了项目赶进度或偷
好了,言归正传,连续聊回利用pushy实现iOS高性能推送这个话题。
5、APNs和Pushy苹果设备的推送是依赖苹果的APNs(Apple Push Notification service)做事的,APNs的官方简介如下:
Apple Push Notification service (APNs) is the centerpiece of the remote notifications feature. It is a robust, secure, and highly efficient service for app developers to propagate information to iOS (and, indirectly, watchOS), tvOS, and macOS devices.
(如果英文看起来未便利,可以看看《iOS的推送做事APNs详解:设计思路、技能事理及毛病等》)
IOS设备(tvOS、macOS)上的所有推送都须要经由APNs,APNs做事确实非常厉害,每天须要推送上百亿的,可靠、安全、高效。就算是微信和QQ这种用户级别的即时通讯app在程序没有启动或者后台运行过程中也是须要利用APNs的(当程序启动时,利用自己建立的长连接),只不过腾讯优化了整条从他们做事器到苹果做事器的线路而已,以是以为推送要快(参考知乎)。
项目组老的苹果推送做事利用的是苹果以前的基于二进制socket的APNs,同时利用的是一个javapns的开源库,这个javapns貌似效果不是很好,在网上也有人有过谈论。javapns现在也停滞掩护DEPRECATED掉了。作者建议转向基于苹果新APNs做事的库。
苹果新APNs基于HTTP/2,通过连接复用,更加高效,当然还有其它方面的优化和改进,可以参考APNs的一篇先容,讲解的比较清楚。
再说一下我们利用的Pushy,官方简介如下:
Pushy is a Java library for sending APNs (iOS, macOS, and Safari) push notifications. It is written and maintained by the engineers at Turo......We believe that Pushy is already the best tool for sending APNs push notifications from Java applications, and we hope you'll help us make it even better via bug reports and pull requests.
Pushy的文档息争释很全,谈论也很生动,作者基本有问必答,大部分疑问都可以找到答案,利用难度也不大。
6、在Java端利用Pushy进行APNs推送6.1 首先加入包
6.2 身份认证
苹果APNs供应了两种认证的办法:基于JWT的身份信息token认证和基于证书的身份认证。Pushy也同样支持这两种认证办法,这里我们利用证书认证办法,关于token认证办法可以查看Pushy的文档。
如何获取苹果APNs身份认证证书可以查考官方文档。
6.3 Pushy利用
ps:这里的setClientCredentials函数也可以支持传入一个InputStream和证书密码。
同时也可以通过setApnsServer函数来指定是开拓环境还是生产环境:
Pushy是基于Netty的,通过ApnsClientBuilder我们可以根据须要来修正ApnsClient的连接数和EventLoopGroups的线程数:
关于连接数和EventLoopGroup线程数官网有如下的解释,大略来说,不要配置EventLoopGroups的线程数超过APNs连接数:
Because connections are bound to a single event loop (which is bound to a single thread), it never makes sense to give an ApnsClient more threads in an event loop than concurrent connections. A client with an eight-thread EventLoopGroup that is configured to maintain only one connection will use one thread from the group, but the other seven will remain idle. Opening a large number of connections on a small number of threads will likely reduce overall efficiency by increasing competition for CPU time.
关于的推送,把稳一定要利用异步操作,Pushy发送会返回一个Netty Future工具,通过它可以拿到发送的情形:
APNs做事器可以担保同时发送1500条,当超过这个限定时,Pushy会缓存,以是我们不必担心异步操作发送的过多。
当我们的非常多,达到上亿时,我们也得做一些掌握,避免缓存过大,内存不敷,Pushy给出了利用Semaphore的办理方法:
The APNs server allows for (at the time of this writing) 1,500 notifications in flight at any time. If we hit that limit, Pushy will buffer notifications automatically behind the scenes and send them to the server as in-flight notifications are resolved.
In short, asynchronous operation allows Pushy to make the most of local resources (especially CPU time) by sending notifications as quickly as possible.
以上仅是Pushy的基本用法,在我们的生产环境中情形可能会更加繁芜,我们可能须要知道什么时候所有推送都完成了,可能须要对推送成功进行计数,可能须要防止内存不敷,也可能须要对不同的发送结果进行不同处理....
不多说,上代码(请看下节...)。
7、Pushy的最佳实践参考Pushy的官方最佳实践,我们加入了如下操作:
通过Semaphore来进行流控,防止缓存过大,内存不敷;
通过CountDownLatch来标记是否发送完成;
利用AtomicLong完成匿名内部类operationComplete方法中的计数;
利用Netty的Future工具进行推送结果的判断。
详细用法参考如下代码:
publicclassIOSPush {
privatestaticfinalLogger logger = LoggerFactory.getLogger(IOSPush.class);
privatestaticfinalApnsClient apnsClient = null;
privatestaticfinalSemaphore semaphore = newSemaphore(10000);
publicvoidpush(finalList deviceTokens, String alertTitle, String alertBody) {
longstartTime = System.currentTimeMillis();
if(apnsClient == null) {
try{
EventLoopGroup eventLoopGroup = newNioEventLoopGroup(4);
apnsClient = newApnsClientBuilder().setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
.setClientCredentials(newFile(\"大众/path/to/certificate.p12\"大众), \公众p12-file-password\公众)
.setConcurrentConnections(4).setEventLoopGroup(eventLoopGroup).build();
} catch(Exception e) {
logger.error(\公众ios get pushy apns client failed!\"大众);
e.printStackTrace();
}
}
longtotal = deviceTokens.size();
finalCountDownLatch latch = newCountDownLatch(deviceTokens.size());
finalAtomicLong successCnt = newAtomicLong(0);
longstartPushTime = System.currentTimeMillis();
for(String deviceToken : deviceTokens) {
ApnsPayloadBuilder payloadBuilder = newApnsPayloadBuilder();
payloadBuilder.setAlertBody(alertBody);
payloadBuilder.setAlertTitle(alertTitle);
String payload = payloadBuilder.buildWithDefaultMaximumLength();
finalString token = TokenUtil.sanitizeTokenString(deviceToken);
SimpleApnsPushNotification pushNotification = newSimpleApnsPushNotification(token, \"大众com.example.myApp\公众, payload);
try{
semaphore.acquire();
} catch(InterruptedException e) {
logger.error(\"大众ios push get semaphore failed, deviceToken:{}\公众, deviceToken);
e.printStackTrace();
}
finalFuture> future = apnsClient.sendNotification(pushNotification);
future.addListener(newGenericFutureListener>() {
@Override
publicvoidoperationComplete(Future pushNotificationResponseFuture) throwsException {
if(future.isSuccess()) {
finalPushNotificationResponse response = future.getNow();
if(response.isAccepted()) {
successCnt.incrementAndGet();
} else{
Date invalidTime = response.getTokenInvalidationTimestamp();
logger.error(\"大众Notification rejected by the APNs gateway: \公众+ response.getRejectionReason());
if(invalidTime != null) {
logger.error(\"大众\t…and the token is invalid as of \"大众+ response.getTokenInvalidationTimestamp());
}
}
} else{
logger.error(\"大众send notification device token={} is failed {} \公众, token, future.cause().getMessage());
}
latch.countDown();
semaphore.release();
}
});
}
try{
latch.await(20, TimeUnit.SECONDS);
} catch(InterruptedException e) {
logger.error(\"大众ios push latch await failed!\"大众);
e.printStackTrace();
}
longendPushTime = System.currentTimeMillis();
logger.info(\"大众test pushMessage success. [共推送\"大众+ total + \"大众个][成功\公众+ (successCnt.get()) + \"大众个],
totalcost= \"大众 + (endPushTime - startTime) + \公众, pushCost=\公众 + (endPushTime - startPushTime));
}
}
关于多线程调用client:
Pushy ApnsClient是线程安全的,可以利用多线程来调用。
关于创建多个client:
创建多个client是可以加快发送速率的,但是提升并不大,作者建议:
ApnsClient instances are designed to stick around for a long time. They're thread-safe and can be shared between many threads in a large application. We recommend creating a single client (per APNs certificate/key), then keeping that client around for the lifetime of your application.
关于APNs相应信息(缺点信息):
可以查看APNs官网的error code表格,理解出错情形,及时调度。
8、来看看Pushy的性能作者在Google谈论组中说Pushy推送可以单核单线程达到10k/s-20k/s,如下图所示:
▲ 作者关于创建多client的建议及Pushy性能描述
但是可能是网络或其他缘故原由,我的测试结果没有这么好,把测试结果贴出来,仅供参考(韶光ms)。
ps:由于是测试,没有大量的设备可以用于群发推送测试,以是以往一个设备发送多条推送替代。这里短韶光往一个设备发送大量的推送,APNs会报TooManyRequests缺点,Too many requests were made consecutively to the same device token。以是会有少量无法发出。
ps:这里的推送韶光,没有加上client初始化的韶光。
ps:推送韶光与被推的大小有关系,这里我在测试时没有掌握变量(都是我瞎填的,都是很短的)以是数据仅供参考。
关于Pushy性能优化也可以看看官网作者的建议:Threads, concurrent connections, and performance
大家有测试的数据也可以分享出来一起谈论一下。
8、思考和小结
苹果APNs一贯在更新优化,一贯在拥抱新技能(HTTP/2,JWT等),是一个非常了不起的做事。
自己来直接调用APNs做事来达到天生环境哀求还是有点困难。Turo给我们供应了一个很好的Java库:Pushy。Pushy还有一些其他的功能与用法(Metrics、proxy、Logging...),总体来说还是非常不错的。
同时觉得我们利用Pushy还可以调优...
(本文同步发布于:http://www.52im.net/thread-1820-1-1.html)