场景:
Price alert的功能存在性能问题,price alert是类似于价格变动提醒的功能,比如股票上涨了10%,我们就给所有订阅了某只股票alert的用户推送push通知,一个用户可以订阅多个股票。我们现在最热门的股票有接近1000万的订阅,然后现在的问题是推送耗时太长,需要接近20分钟。可以做哪些优化?
上行消息限额
我们将每个项目的上行消息限制为 1500000 条/分钟,以避免上行目标服务器过载。
我们将每台设备的上行消息限制为 1000 条/分钟,以防止因不良应用行为导致电池电量耗尽。
主题消息限额
主题订阅添加/移除率限制为每个项目 3000 QPS。
有关消息发送速率的信息。
FCM 消息简介
Firebase Cloud Messaging (FCM) 提供了众多消息传递选项和功能。本页中的内容旨在帮助您了解不同类型的 FCM 消息以及您可以将其用于哪些目的。
消息类型
使用 FCM,您可以向客户端发送两种类型的消息:
- 通知消息,有时被称为“显示消息”。此类消息由 FCM SDK 自动处理。
- 数据消息,由客户端应用处理。
通知消息包含一组预定义的用户可见的键。 与其相对,数据消息只包含用户定义的自定义键值对。通知消息可以包含可选的数据载荷。两种消息类型的载荷上限均为 4KB,但从 Firebase 控制台发送消息时会强制执行 1024 个字符的限制。
使用情景 | 如何发送 | |
---|---|---|
通知消息 | FCM 代表客户端应用自动向最终用户设备显示消息。通知消息包含一组预定义的用户可见键以及自定义键值对的可选数据载荷。 |
|
数据消息 | 客户端应用负责处理数据消息。数据消息仅包含自定义键值对,没有保留键名(请参阅下文)。 | 在可信环境(例如 Cloud Functions 或应用服务器中),使用 Admin SDK 或者 FCM 服务器协议:仅设置 data 键。 |
如果您需要 FCM 代表您的客户端应用显示某个通知,可使用通知消息。如果您需要在自己的客户端应用处理消息,可使用数据消息。
FCM 可以发送包含可选的数据载荷的通知消息。在此类情况下,FCM 负责显示通知载荷,而客户端应用负责处理数据载荷。
通知消息
如果要进行测试,或者要开展营销、重新吸引用户,您可以使用 Firebase 控制台发送通知消息。 Firebase 控制台提供基于分析的 A/B testing,可帮助您优化和改进营销消息。
如需使用 Admin SDK 或 FCM 协议以编程方式发送通知消息,可使用通知消息中用户可见部分所必需的预定义键值选项集来设置 notification
键。例如,以下是 IM 应用中的 JSON 格式的通知消息。用户可能会在设备上看到标题为“Portugal vs. Denmark”、文本为“great match!”的消息:
{ "message":{ "token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...", "notification":{ "title":"Portugal vs. Denmark", "body":"great match!" } } }
应用在后台运行时,通知消息将被传递至通知面板。应用在前台运行时,消息由回调函数处理。
如需可用于构建通知消息的预定义键的完整列表,请参阅参考文档:
数据消息
使用自定义键值对设置适当的键以将数据载荷发送至客户端应用。
请确保未在自定义键值对中使用任何保留字词。保留字词包括 “from”、“notification”、“message_type” 或以 “google” 或 “gcm” 开头的任何字词。
例如,下方所示为上述同一即时通讯应用中的一条 JSON 格式的消息,在此消息中,信息封装在常用的 data
键中,且客户端应用需要解读相应内容:
{ "message":{ "token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...", "data":{ "Nick" : "Mario", "body" : "great match!", "Room" : "PortugalVSDenmark" } } }
上述示例显示了顶级或通用 data
字段的用法,该字段由接收消息的所有平台上的客户端解释。 在每个平台上,客户端应用都会在回调函数中收到数据载荷。
数据消息加密
Android 传输层(请参阅 FCM 架构)使用点到加密。您可以根据需要决定为数据消息使用端到端加密。FCM 不提供端到端解决方案。但是,您可以使用外部解决方案,例如 Capillary 或 DTLS。
包含可选数据载荷的通知消息
无论是通过编程方式还是通过 Firebase 控制台发送通知消息,您都可以在消息中包含由自定义键值对组成的可选载荷。在 Notifications Composer 中,使用高级选项中的自定义数据字段。
接收同时包含通知和数据载荷的消息时,应用的行为取决于应用是在后台还是前台运行 - 特别是在接收时应用是否处于活跃状态。
- 在后台运行时,应用会在通知面板中接收通知载荷,且仅在用户点按通知时处理数据载荷。
- 在前台运行时,您的应用将会接收一个消息对象,且两种载荷都可用。
以下是包含 notification
密钥和 data
密钥的 JSON 格式的消息:
{ "message":{ "token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...", "notification":{ "title":"Portugal vs. Denmark", "body":"great match!" }, "data" : { "Nick" : "Mario", "Room" : "PortugalVSDenmark" } } }
跨平台自定义消息
Firebase Admin SDK 和 FCM v1 HTTP 协议都允许您的消息请求设置 message
对象中的所有可用字段。包括:
- 由接收消息的所有应用实例解读的一组通用字段。
- 特定于平台的字段集,如
AndroidConfig
和WebpushConfig
,仅由在指定平台上运行的应用实例进行解释。
针对具体平台的字段块可让您灵活地针对不同平台自定义消息,以确保它们在被收到后得到正确处理。FCM 后端会考虑所有指定参数并针对每个平台自定义消息。
何时使用通用字段
在以下情况下使用通用字段:
- 面向所有平台(iOS、Android 和 Web)上的应用实例
- 向主题发送消息
无论平台如何,所有应用实例都可以解读以下通用字段:
何时使用针对具体平台的字段
如果需要执行下列操作,请使用针对具体平台的字段:
- 仅向特定平台发送字段
- 发送通用字段以及针对具体平台的字段
当您仅希望向特定平台发送值时,不要使用通用字段,而应使用针对具体平台的字段。例如,仅向 iOS 和 Web 发送通知而不向 Android 发送通知,您必须针对 iOS 和 Web 各使用一组字段。
当您发送包含特定递送选项的消息时,请使用针对具体平台的字段进行设置。 您可以根据需要为每个平台指定不同的值。但是,即使您想为各种平台设置相同的值,也必须使用针对具体平台的字段。这是因为每种平台对值的解读方式可能会略有不同 - 例如,存留时间在 Android 上设置为以秒为单位的到期时间,而在 iOS 上则设置为到期日期。
示例:包含针对具体平台的递送选项的通知消息
以下 v1 发送请求会向所有平台发送通用的通知标题和内容,但也会发送一些针对具体平台的覆盖内容。 具体而言,该请求会:
- 为 Android 和 Web 平台设置较长的存留时间,同时将 APNs (iOS) 消息设置为低优先级
- 设置相应的键来定义 Android 和 iOS 上的用户点按通知的结果,分别为
click_action
和category
。
{ "message":{ "token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...", "notification":{ "title":"Match update", "body":"Arsenal goal in added time, score is now 3-0" }, "android":{ "ttl":"86400s", "notification"{ "click_action":"OPEN_ACTIVITY_1" } }, "apns": { "headers": { "apns-priority": "5", }, "payload": { "aps": { "category": "NEW_MESSAGE_CATEGORY" } } }, "webpush":{ "headers":{ "TTL":"86400" } } } }
如需全面、详细地了解消息正文中针对具体平台的块提供的键,请参阅 HTTP v1 参考文档。如需详细了解如何构建包含消息正文的发送请求,请参阅构建发送请求。
递送选项
FCM 为发送到 Android 设备的消息提供了一组特定的递送选项,并允许在 iOS 和 Web 上使用类似的选项。例如,Android 可通过 FCM 的 collapse_key
,iOS 上通过 apns-collapse-id
和 JavaScript/Web 通过 Topic
支持“可折叠”消息行为。如需了解详情,请参阅本节和相关参考文档中的说明。
不可折叠消息和可折叠消息
“不可折叠”消息表示每一条消息都将被传递至设备。与无内容的“ping”之类的可折叠消息不同,不可折叠消息能传递一些有用内容至移动应用,从而联系服务器以获取数据。
FCM 不保证传递顺序。
聊天消息或关键消息都是典型的不可折叠消息。 例如,在即时通讯应用中,您会要传递每一条消息,因为它们的内容各不相同。
对于 Android,在不折叠的情况下,最多可存储 100 条消息。达到此限值后,所有存储的消息都将被舍弃。设备在重新联网后将收到一条特殊消息,提示已达到此上限。 之后,应用可以正常处理该状况,一般情况下会请求与应用服务器进行一次完全同步。
“可折叠消息”在还未被传递至设备的情况下可能会被新消息替代。
可折叠消息的常见使用情形包括告知移动应用从服务器同步数据。为用户更新最新比分的体育应用就属于这种消息。 只有最新的消息是相关的。
要在 Android 上将一条消息标记为可折叠,请在消息载荷中加入 collapse_key
参数。默认情况下,折叠键是在 Firebase 控制台中注册的应用软件包名称。FCM 服务器可以为每台设备同时存储 4 条不同的可折叠消息,每一条都含有不同的折叠键。如果超出此限值,FCM 将仅保留 4 个折叠键,具体保留哪几个不一定。
默认情况下,没有载荷的主题消息是可折叠消息。
我应该使用哪种消息?
从性能的角度来看,可折叠消息是更好的选择,但前提是您的应用不需要使用不可折叠消息。但是,如果您使用可折叠消息,则须切记:FCM 仅允许 FCM 连接服务器在任意指定时间内为每个注册令牌使用最多四个不同的折叠键。您不得超出此限值,否则可能导致无法预测的后果。
使用情景 | 如何发送 | |
---|---|---|
不可折叠 | 每一条消息对客户端应用都很重要,都需要传递。 | 默认情况下,除了通知消息外的所有消息都是不可折叠消息。 |
可折叠 | 当新消息使得相关的旧消息对客户端应用不再有用时,FCM 将替换掉旧消息。 例如:用于从服务器启动数据同步的消息或过期的通知消息。 | 在消息请求中设置适当的参数:
|
设置消息的优先级
Android 中的下行消息传递优先级有两种:普通优先级和高优先级。普通优先级和高优先级的消息传递方式如下:
-
普通优先级。这是数据消息的默认优先级。应用在前台运行时,普通优先级消息会被立即传递。当设备处于低电耗模式时,此类消息可能会被延迟传递以节省电量。如果是对时间不太敏感的消息(例如新电子邮件通知、使界面保持同步或在后台同步应用数据),建议您选择普通传递优先级。
如果在 Android 上收到请求同步应用的后台数据的普通优先级消息,则您可以使用 WorkManager 安排一项任务,以便在网络可用时处理该请求。
-
高优先级。FCM 会立即尝试传递高优先级消息,允许 FCM 服务在必要时唤醒休眠设备并运行一些有限的处理(包括非常有限的网络访问)。高优先级消息通常应该会导致用户与您的应用或其通知进行互动。如果 FCM 检测到未产生此行为,系统可能会降低您的消息的优先级。Android P 推出了应用备用存储分区,如果消息未导致用户使用应用或查看通知,这些分区会限制您向自己的应用发送的 FCM 高优先级消息的数量。如果在响应高优先级消息时以用户可见的方式显示了通知,则该消息不会占用您的应用备用存储分区配额。
在向 iOS 设备发送数据消息时,优先级必须设置为 5(即普通优先级)。具有高优先级的消息会被 FCM 后端拒绝,并显示错误
INVALID_ARGUMENT
。由于一小部分 Android 移动用户使用的是高延迟网络,因此请避免在显示通知之前打开与服务器的连接。在允许的处理时间结束之前回调服务器可能会让使用高延迟网络的用户面临风险。 请在 FCM 消息中包含通知内容并立即显示。如果您需要同步 Android 上的其他应用内内容,可以使用 WorkManager 安排一项任务,在后台进行同步。
以下是一条普通优先级消息的示例,该消息通过 FCM HTTP v1 协议发送,用于通知杂志订阅者有新内容可以下载:
{ "message":{ "topic":"subscriber-updates", "notification":{ "body" : "This week's edition is now available.", "title" : "NewsMagazine.com", }, "data" : { "volume" : "3.21.15", "contents" : "http://www.news-magazine.com/world-week/21659772" }, "android":{ "priority":"normal" }, "apns":{ "headers":{ "apns-priority":"5" } }, "webpush": { "headers": { "Urgency": "high" } } } }
如需了解有关设置消息优先级的针对具体平台的详细信息,请参阅以下文章:
- APNs 文档
- 针对低电耗模式和应用待机模式进行优化 (Android)
- Web 推送消息的紧急程度
设置消息的有效期
FCM 通常会在消息发出之后马上进行传递。 但是,也有一些例外情况。例如,如果在 Android 平台上,设备有可能处于关闭、离线或不可用状态。 FCM 可能会有意延迟消息传递,以防止应用消耗过多资源和对电池续航时间产生不良影响。
在这种情况下,FCM 会存储消息,等到可行时立即发送。尽管大多数情况下这样做都没什么问题,但有些应用可能永远不会传递延迟消息。举例来说,如果消息是来电或视频聊天通知,则它仅在通话终止之前的这段较短时间内有意义。或者,如果消息是活动邀请,那么如果在活动结束后才收到消息,它将毫无用处。
在 Android 和 Web/JavaScript 上,您可以指定消息的最长有效期。此值必须是介于 0 至 2419200 秒(28 天)之间的一段持续时间,其对应于 FCM 存储并尝试传递消息的最长时间期限。不含此字段的请求默认为最长期限(四周)。
以下是此功能的一些可能用途:
- 视频聊天来电
- 即将到期的邀请事件
- 日历活动
指定消息有效期的另一个好处是 FCM 绝不会限制存留时间值为 0 秒的消息。 换句话说,FCM 将尽全力保证必须现在传递、过时无效的消息得到传递。请留意,time_to_live
值为 0 表示不能立即递送的邮件会被舍弃。不过,由于此类消息不会被存储,因此可以为发送通知消息提供最佳延时。
以下是包含 TTL 的请求示例:
{ "message":{ "token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...", "data":{ "Nick" : "Mario", "body" : "great match!", "Room" : "PortugalVSDenmark" }, "apns":{ "headers":{ "apns-expiration":"1604750400" } }, "android":{ "ttl":"4500s" }, "webpush":{ "headers":{ "TTL":"4500" } } } }
接收多个发送者发送的消息
FCM 允许多方向同一客户端应用发送消息。例如,假设客户端应用是包含多个贡献者的文章聚合器,并且每个应用都能够在发布新文章时发送消息。 此消息可能包含一个网址,供客户端应用下载该文章。 利用 FCM,您可以让每一位撰稿者都能发送自己的消息,而不必将所有发送活动集中在一个位置。
如需启用此功能,请确保您有每个发送者的发送者 ID。在申请注册时,客户端应用会多次提取令牌,每次在受众群体字段中使用不同的发送者 ID,并使用与指定平台对应的令牌检索方法:
- iOS (Swift) -
token(completion:)
- Android -
FirebaseMessaging.getInstance().getToken()
确保您不会在单个令牌请求中添加多个发送者 ID,否则可能会导致不可预测的结果。为每个发送者 ID 分别执行一次调用。
最后,与对应的发送者共享注册令牌,发送者将能够通过自己的身份验证密钥向客户端应用发送消息。
请注意,最多只能有 100 个发送者。
消息的有效期
当应用服务器向 FCM 发布消息并收到返回的消息 ID 时,并不意味着消息已传送至设备,而是表示其获准传递。消息在获准传递后的行为取决于很多因素。
在理想状态下,如果设备已连接至 FCM,屏幕处于开启状态且没有节流限制,那么消息将被立即传递。
如果设备已连接但是处于低电耗模式,则 FCM 将存储低优先级的消息,直到设备不再处于低电耗模式为止。这是 collapse_key
标志发挥作用的时候:如果已存储有一条含有相同折叠键(和注册令牌)的消息,且该消息正等待传递,则旧消息将被舍弃,新消息将取代其位置(即,旧消息将被新消息折叠起来)。但是,如果未设置折叠键,则新消息和旧消息都将被存储下来供以后传递。
如果设备未连接至 FCM,则消息将被存储,直至建立连接(再次遵循折叠键规则)。建立连接后,FCM 会将所有待处理消息传递到设备。如果设备不再建立连接(例如使用了恢复出厂设置),则消息最终会超时,且将被从存储中舍弃。除非设置了 time_to_live
标志,否则默认的超时时间为四个星期。
如需更深入地了解消息的传递情况:
要详细了解 Android 或 iOS 上的消息传送,请参阅 FCM 报告信息中心,其中记录了在 iOS 和 Android 设备上发送和打开的消息数量,以及 Android 应用的“展示次数”(用户看到的通知条数)数据。
对于启用了直接通道消息传递的 Android 设备,如果设备已有一个月未连接到 FCM,FCM 仍然会接受消息,但会立即将其舍弃。如果设备在您向其发送最后一条数据消息后的四个星期内建立连接,您的客户端会收到 onDeletedMessages() 回调。 之后,应用可以正常处理该状况,一般情况下会请求与应用服务器进行一次完全同步。
最后,当 FCM 尝试向设备传递消息,而应用已被卸载时,FCM 将立刻舍弃该消息并废弃注册令牌。其后尝试向该设备发送消息将导致 NotRegistered
错误。
限制和扩缩
我们的目标是始终传递通过 FCM 发送的每条消息。但是,传递每条消息有时会导致整体用户体验不佳。在其他情况下,我们需要设定限制,以确保 FCM 为所有发送者提供可扩缩的服务。
注意:本部分中讨论的限制随时可能更改。
可折叠的消息限制
如上所述,可折叠的消息是设计为可在彼此之上折叠的无内容通知。如果开发者过于频繁地向应用重复相同的消息,我们会延迟(限制)消息以减少对用户电池的影响。
例如,如果您向单个设备发送大量新电子邮件同步请求,我们可能会将下一个电子邮件同步请求延迟几分钟,以便设备可以以较低的平均速度进行同步。严格执行这种限制是为了限制对用户的电池的影响。
如果您的用例需要高突发发送模式,那么不可折叠的消息可能是正确的选择。请确保在此类消息中包含内容以降低电池损耗。
我们将可折叠的消息限制为每台设备每个应用突发 20 条消息,每 3 分钟补充 1 条消息。
XMPP 服务器限制
我们将连接到 FCM XMPP 服务器的速率限制为每个项目每分钟 400 次连接。这不会给消息传递带来问题,但对于确保我们系统的稳定性非常重要。
针对每个项目,FCM 允许存在 2500 个并行连接。
向单一设备发送消息的最大速率
您可以向单一设备发送最多 240 条消息/分钟和 5000 条消息/小时。这一高阈值是为了满足短时间的流量突发,例如当用户通过聊天快速互动时。此限制可防止发送逻辑中的错误无意中耗尽设备上的电池电量。
警告:请勿以接近此最大速率的速度定期发送消息。这可能会浪费最终用户的资源,并且您的应用可能会被标记为滥用。
上行消息限额
我们将每个项目的上行消息限制为 1500000 条/分钟,以避免上行目标服务器过载。
我们将每台设备的上行消息限制为 1000 条/分钟,以防止因不良应用行为导致电池电量耗尽。
主题消息限额
主题订阅添加/移除率限制为每个项目 3000 QPS。
有关消息发送速率的信息,请参阅扇出限制。
扇出限制
消息扇出是指向多台设备发送消息的过程,例如当您定位主题和群组,或使用 Notifications Composer 定位受众群体或用户细分时。
消息扇出不是瞬时间完成的,因此有时候您会同时运行多个扇出。我们将每个项目中并发进行的消息扇出数量限制为 1000。超出此值之后,我们可能会拒绝其他扇出请求,或者推迟请求中的扇出,直到某些正在进行的扇出完成为止。
实际可实现的扇出率受同时请求扇出的项目数量的影响。单个项目的扇出率为 10000 QPS 并不罕见,但该数字与系统的总负载密切相关,并无保证。值得注意的是,可用的扇出容量在项目之间而不是在扇出请求之间分配。因此,如果您的项目有两个正在进行的扇出,那么每个扇出只能使用可用扇出率的一半。最大化扇出速度的推荐方法是一次只有一个进行中的活跃扇出。
FCM 端口和防火墙
如果贵组织设有限制互联网上传/下载流量的防火墙,则您需要将其配置为允许移动设备连接 FCM 才能让网络上的设备接收消息。 FCM 通常使用端口 5228,有时也使用 5229 和 5230。
对于传出连接,FCM 不会提供具体 IP,因为我们的 IP 范围更改非常频繁,您的防火墙规则可能会过时,从而影响用户的体验。理想情况下,可以将端口 5228-5230 列入白名单,不受 IP 限制。但是,如果您必须设置 IP 限制,则应将 goog.json 中列出的所有 IP 地址列入白名单。此大型列表会定期更新,因此建议您每月更新规则。防火墙 IP 限制导致的问题往往是间歇性的,并且难以诊断。
需要为传入消息打开的端口:
- 5228
- 5229
- 5230
- 443
需要允许传出连接的端口:
以下选项之一(选项 1 是首选):
- 没有 IP 限制
-
默认网域的所有 IP 地址。
要检索这些地址的最新列表,请按照默认网域的 IP 地址中的说明操作。
网络地址转换和/或有状态数据包检测防火墙:
如果您的网络实施网络地址转换 (NAT) 或有状态数据包检测 (SPI),请为我们通过端口 5228-5230 的连接设置 30 分钟或更长时间的超时。这样,我们就能够提供可靠的连接,同时减少用户移动设备的电池消耗。
注意:如果您通过 APNs 接收通知,请确保您同时打开了 Apple 指定的端口。
凭据
根据要实现的 FCM 功能的不同,您可能需要下列来自 Firebase 项目的凭据:
项目 ID | 您的 Firebase 项目的唯一标识符,用于向 FCM v1 HTTP 端点发出请求。您可以在 Firebase 控制台设置窗格中找到该值。 |
注册令牌 | 用于标识每个客户端应用实例的唯一令牌字符串。 单一设备消息传递和设备组消息传递需要注册令牌。请注意,注册令牌必须保密。 |
发送者 ID | 您在创建 Firebase 项目时系统创建的唯一数字值,可在 Firebase 控制台设置窗格的 Cloud Messaging 标签页找到。发送者 ID 用于标识可以向客户端应用发送消息的每个发送者。 |
访问令牌 | 一个只在短时间内有效的 OAuth 2.0 令牌,用于对发送到 HTTP v1 API 的请求进行授权。此令牌与属于您的 Firebase 项目的服务帐号相关联。如需创建和轮替访问令牌,请按照向发送请求提供授权中所述的步骤操作。 |
服务器密钥(用于旧版协议) | 用于授权您的应用服务器访问 Google 服务(包括通过 Firebase Cloud Messaging 传递旧版协议发送消息)的服务器密钥。您在创建 Firebase 项目时获取服务器密钥。您可以在 Firebase 控制台设置窗格的 Cloud Messaging 标签页查看此密钥。 重要提示:切勿在客户端代码中的任何位置包含服务器密钥。另外,请确保在为您的应用服务器授权时仅使用服务器密钥。Android 密钥、iOS 密钥和浏览器密钥会被 FCM 拒绝。 |