http://blog.csdn.net/tlq1988/article/details/9612237

首先说明一下,本文只是介绍一些容易被开发者忽视,而导致性能低下问题。并不是介绍如何向苹果设备成功发送一条消息,这里假设所有阅读者已经能够向苹果服务器发送消息,并且成功接收,只是发送效率比较低,并且丢失率很高。如果你不是此类情况,那么绕道吧。PS:伸手党可以直接看标红部分(结论)

最近参与并且完成了公司1000W级的消息推送服务平台重建。此次重构级别解决了消息丢失,并且大幅度提升了推送效率。有些东西我想很多开发者也会碰到,并且难以被开发者所意识到。

先先扫下盲哈。如果你发送消息是一次连接发送一条,那么请你先改成长连接发送--一次连接发送多条数据。粘下PHP代码吧:)

  1. $pass = ''; // $pass是你在建立证书的时候输入的密码
  2. $ctx = stream_context_create();
  3. // apns.pem就是你的证书的路径了,最好写绝对路径
  4. stream_context_set_option($ctx, 'ssl', 'local_cert', 'apns.pem');
  5. stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
  6. $fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
  7. if(!$fp) {
  8. print "Failed to connect $err $errstr";
  9. exit();
  10. } else {
  11. print "Connection OK\n";
  12. }
  13. $body = array('aps' => array('badge' => 1));
  14. for($i = 0; $i <= 10000; $i++) {
  15. $deviceToken = md5(time() . rand(0, 9999999)) . md5(time() . rand(0, 9999999)); // 模拟一个Device Token
  16. $body['aps']['alert'] = md5(time() . rand(0, 9999999)); // 随便模拟点数据
  17. $payload = json_encode($body);
  18. // 这里是简单的消息结构,如果想多发几个但是不要返回错误,可以用这个
  19. /*
  20. $msg = chr(0) . pack("n", 32)
  21. . pack('H*', str_replace(' ', '', $deviceToken))
  22. . pack("n", strlen($payload)) . $payload;
  23. */
  24. // 这个是增强型消息格式,$i就是Identifier,864000就是Expiry了
  25. $msg = pack('CNNnH*', self::COMMAND_PUSH, $i, 864000, 32, $deviceToken)
  26. . pack('n', strlen($payload))
  27. . $payload;
  28. print "sending message :" . $payload . "\n";
  29. fwrite($fp, $msg);
  30. // 这里是读取错误信息,不要没发一条就读取一次,这样苹果会认为攻击而终止连接
  31. //fread($fp, 6);
  32. }

要往下面说我先解释一下这个东东--Broken Pipe,如果你有过大量的数据推送,并且看下你的错误日志那么Writen Broken Pipe你一定不陌生。这个错误产生的原因通常是当管道读端没有在读,而管道的写端继续有线程在写,就会造成管道中断。可以简单的理解为你在向一个已经关闭的连接写数据就会抛出这个错误。

由于Broken Pipe的关系,我们不得不重新和苹果服务器建立连接,这个连接耗时在国内.....(你们懂的3sec+),这个应该是我们推送速度最大的瓶颈了。有很多开发者也许会认为这个是由于国内的网络环境导致,因为他们习惯的“traceroute>你就错了。

我们用大量(10W左右)能保证基本正确的Device Token来做测试,平均一次连接的能写入3W左右的数据,好的情况下能一次写完这10W数据!!!这个测试也就证明平凡出现Broken Pipe不是由于网络原因。既然不是由于网络原因,那么我做个大胆的假设:这个连接是由APNs主动断开的

那么假设这个猜想是正确的,那苹果什么时候会断开连接了?解释这个问题,我们又做了一个测试:往这10W的Device Token里面均匀插入1000个错误的Device Token。神奇的事情发生了,发送期间平均断开连接900次+。这个实验正好验证了我之前的猜测:产生Broken Pipe是因为APNs服务器主动断开了连接,并且是由于错误的Device Token引起的(或者其他的错误)。苹果的错误类型和代码编号:

Status code

Description

0

No errors encountered

1

Processing error

2

Missing device token

3

Missing topic

4

Missing payload

5

Invalid token size

6

Invalid topic size

7

Invalid payload size

8

Invalid token

10

Shutdown

255

None (unknown)

我们进一步跟进测试,我们发现一个奇怪的现象,断开连接的时的前一个Token并不是我们所特意设置的错误Token。同时我们也发现消息送达率也变得非常的低(偶尔有设备能收到)。这个很好解释,之前我就有文章提到过(官方也有相应说明)当一次连接先发送一个错误的Token,之后的有效Token的消息是无法送达的(http://blog.csdn.net/hjq_tlq/article/details/8131115),这就导致了错误的Token后面的正确的Token全部没有收到,从而送达率也就明显下降了。

经过上面的测试,当APNs接收到错误的Token的时候会主动断开连接,但是断开连接之前会有1sec左右的延迟。那么你可以有下面这个例子理解:

你要发送1000条数据并且第20个Token是错误的

当此次连接发到第20个Token的时候苹果认为此次连接终止(但是连接并没有断开,只是APNs将抛弃之后的内容),并且不处理此次连接之后的消息

1sec左右的时间之后苹果主动断开SSL连接,如果你继续忘此连接写数据,你将可以捕捉到Broken Pipe错误

此时由于1sec左右的延迟,你已经发送到了第123个消息

此时从20以后直至123的消息将全部没有送达

太可怕了.....你竟然不知道是从哪一个错了!!!苹果是SB啊!先不要做这样的结论,我们先看一下苹果官方文档所给出的东东:

Figure 5-1  Notification format

The first byte in the notification format is a command value of 1. The remaining fields are as follows:

  • Identifier—An arbitrary value that identifies this notification. This same identifier is returned in a error-response packet if APNs cannot interpret a notification.

  • Expiry—A fixed UNIX epoch date expressed in seconds (UTC) that identifies when the notification is no longer valid and can be discarded. The expiry value uses network byte order (big endian). If the expiry value is positive, APNs tries to deliver
    the notification at least once. Specify zero (or a value less than zero) to request that APNs not store the notification at all.

  • Token length—The length of the device token in network order (that is, big endian)

  • Device token—The device token in binary form.

  • Payload length—The length of the payload in network order (that is, big endian). The payload must not exceed 256 bytes and must not be null-terminated.

  • Payload—The>    PS:这里苹果到是做了件好事,这个消息结构在早些的文档中显示的是5-2 Enhanced Notification Format,而之前的5-1是Notification Format。区别在于之前的5-1中的消息结构为简单消息结构,没有Identifier和Expiry字段并且Command为0。现在直接把简单的消息体结构给去掉了,这样可以强制开发者加上Identifier,从而得到返回值。

    为了方便我直接把官方文档粘过来了哈:)我们需要注意的是Identifier这个东东。没错,这个就是苹果用来提供的给第三方的4唯一标示,如果鸟语不是很好的话他后面的那个注释大致就是说:一个消息的唯一标识。如果苹果服务器不能解释这个消息,那么将在错误中返回这个唯一标示。

    可恶的苹果并没有说明这个会有延迟,以及怎么确保我们能收到这个错误。我们现在采用的是每发送100条消息,就检查一下(read)是否有失败的。如果你抓到这个错误,那么果断断开连接,并且重新发送这条错误以后的Token,这样就能保证消息基本能送达。

    哦,顺便说一下如何得到错误反馈,如果你发送的时候加上了Identifier,那么此时你一定有一个和APNs的连接吧(废话,没连接怎么write),那么你只要read就好了,如果有就能读到一个二进制数据:)

    有一个也需要提一下,就是APNs的FeedBack功能也一定要用上,这个能帮助你更好的剔除错误的Token。

    当你的Token基本为正确的时候,如果还有大量的Broken Pipe出现,你可以给我留言,我们一起研究到底哪里出问题了:)

    附录:苹果推送官方文档

APNS导致消息丢失和发送效率原因的更多相关文章

  1. rabbitmq 重复ACK导致消息丢失

    rabbitmq 重复确认导致消息丢失 背景 rabbitmq 在应用场景中,大多采用工作队列 work-queue的模式. 在一个常见的工作队列模式中,消费者 worker 将不断的轮询从队列中拉取 ...

  2. RabbitMQ,RocketMQ,Kafka 事务性,消息丢失和消息重复发送的处理策略

    消息队列常见问题处理 分布式事务 什么是分布式事务 常见的分布式事务解决方案 基于 MQ 实现的分布式事务 本地消息表-最终一致性 MQ事务-最终一致性 RocketMQ中如何处理事务 Kafka中如 ...

  3. 解决RabbitMQ消息丢失问题和保证消息可靠性(一)

    原文链接(作者一个人):https://juejin.im/post/5d468591f265da03b810427e 工作中经常用到消息中间件来解决系统间的解耦问题或者高并发消峰问题,但是消息的可靠 ...

  4. RocketMQ消息丢失解决方案:事务消息

    前言 上篇文章,王子通过一个小案例和小伙伴们一起分析了一下消息是如何丢失的,但没有提出具体的解决方案. 我们已经知道发生消息丢失的原因大体上分为三个部分: 1.生产者发送消息到MQ这一过程导致消息丢失 ...

  5. 如何处理RabbitMQ 消息堆积和消息丢失问题

    消息堆积 解决方案: 增加消费者或后台相关组件的吞吐能力 增加消费的多线程处理 根据不同的业务实现不同的丢弃任务,选择不同的策略淘汰任务 默认情况下,RabbitMQ消费者为单线程串行消费,设置并行消 ...

  6. RabbitMQ-从基础到实战(2)— 防止消息丢失

    转载请注明出处 1.简介 RabbitMQ中,消息丢失可以简单的分为两种:客户端丢失和服务端丢失.针对这两种消息丢失,RabbitMQ都给出了相应的解决方案. 2.防止客户端丢失消息 如图,生产者P向 ...

  7. RabbitMQ防止消息丢失

    转载请注明出处 0.目录 RabbitMQ-从基础到实战(1)— Hello RabbitMQ RabbitMQ-从基础到实战(3)— 消息的交换 1.简介 RabbitMQ中,消息丢失可以简单的分为 ...

  8. RocketMQ消息丢失解决方案:同步刷盘+手动提交

    前言 之前我们一起了解了使用RocketMQ事务消息解决生产者发送消息时消息丢失的问题,但使用了事务消息后消息就一定不会丢失了吗,肯定是不能保证的. 因为虽然我们解决了生产者发送消息时候的消息丢失问题 ...

  9. RocketMQ 消息丢失场景分析及如何解决

    生产者产生消息发送给RocketMQ RocketMQ接收到了消息之后,必然需要存到磁盘中,否则断电或宕机之后会造成数据的丢失 消费者从RocketMQ中获取消息消费,消费成功之后,整个流程结束 1. ...

随机推荐

  1. java基础Haep(堆)和Stack(栈)区别

    简单的可以理解为: heap:是由malloc之类函数分配的空间所在地.地址是由低向高增长的.  stack:是自动分配变量,以及函数调用的时候所使用的一些空间.地址是由高向低减少的. 注:何为高地址 ...

  2. DB查询分析器访问EXCEL时,要在表名前后加上中括弧或双引号

    1     引言    中国本土程序员马根峰推出的个人作品----万能数据库查询分析器,中文版本<DB 查询分析器>.英文版本<DB Query Analyzer>. 万能数据 ...

  3. rubygem若干常用选项参数

    可以用gem help commands看所有支持的参数,这个比gem -h显示的全: wisy@wisy-ThinkPad-X61:~/src/ruby_src$ gem help commands ...

  4. 【图片版】学习CSS网格布局

    简言 CSS网格布局(Grid)是一套二维的页面布局系统,它的出现将完全颠覆页面布局的传统方式.传统的CSS页面布局 一直不够理想.包括table布局.浮动.定位及内联块等方式,从本质上都是Hack的 ...

  5. python MultiProcessing标准库使用Queue通信的注意要点

    今天原本想研究下MultiProcessing标准库下的进程间通信,根据 MultiProcessing官网 给的提示,有两种方法能够来实现进程间的通信,分别是pipe和queue.因为看queue顺 ...

  6. MySQL 菜鸟入门“秘籍”

    一.MySQL简介 1.什么是数据库 ? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,它产生于距今六十多年前,随着信息技术和市场的发展,特别是二十世纪九十年代以后,数据管理不 ...

  7. spiral matrix 螺旋矩阵

    Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral or ...

  8. DDD中直接引用和ID关联的关系

    聚合根到聚合根:通过ID关联: 聚合根到其内部的实体,直接引用: 聚合根到值对象,直接引用: 实体到聚合根: 通过ID关联 : 实体到其聚合的聚合根:1对1ID关联,1对多可直接引用 : 实体到其聚合 ...

  9. JBOSS的启动和停止

    本实例使用的JBOSS版本是jboss-4.2.3.GA 假设条件 1.  已设置好JAVA_HOME环境变量 2.  已下载JBoss并且安装目录为:D:\Java\jboss-4.2.3.GA 启 ...

  10. 接口文档神器之apidoc

    //@desn:apidoc linux环境  windows环境使用 //@desn:码字不宜,转载请注明出处 //@author:张慧源  <turing_zhy@163.com> / ...