Uber 实时推送平台实践:gRPC 推动基础设施的发展( 二 )


这些触发器全部被过滤 , 然后转换成对各种API网关端点的调用 。 为了产生合适的本地化响应负载 , API网关需要诸如设备区域、设备操作系统和应用程序版本等用户上下文信息 。 在调用API网关时 , Fireball获取设备上的下文RAMEN服务器 , 并将其添加到头文件 。
生成消息负载
来自Uber应用的所有服务器调用都由API网关提供(在此阅读有关我们网关演变的更多信息) , 推送的负载也以同样的方式生成 。
当Fireball确定谁以及何时推送消息时 , API网关负责确定“推送什么” 。 网关调用各种域服务来产生正确的推负载 。
在产生负载的方式上 , 网关中的所有API都是类似的 。 但是 , 这个API可以分为PullAPI和PushAPI 。 PullAPI指的是在移动设备上为任何HTTP操作调用的端点 。 所谓PushAPI , 就是从Fireball调用的端点 , 还有一个附加的Push中间件 , 它可以截取来自PullAPI的响应并将其转发给Push消息传输系统 。
在这两者之间设置API网关有许多好处:
PullAPI和PushAPI共享了大多数端点业务逻辑 。 一个给定的负载可以从PullAPI无缝地切换到PushAPI 。 举例来说 , 不管应用是通过PullAPI调用来拉取用户对象 , 还是通过PushAPI调用来发送用户对象 , 都使用相同的逻辑 。 网关负责处理许多交叉问题 , 如推送消息的速率限制、路由和模式验证 。与网关一起 , Fireball生成推送消息 , 并在适当的时候发送给用户 。 “推送消息系统”负责向移动设备发送此消息 。
推送消息负载元数据
为进行优化 , 每个推送消息都有不同的配置 。
优先级
因为对于不同的用例 , 会产生数百种不同的负载 , 所以首先要对发送给应用的东西进行优先级排序 。 我们接下来将会看到Uber所采用的协议限制了在一个连接上发送多个并发负载 。 接收设备的带宽也受到限制 。
为获取相对优先级的感受 , 在理解其影响的基础上 , 将消息大致分成三个不同的优先级:
高:重要的核心用户体验消息中:增量用户体验功能消息低:高数据负载大小或低频非关键消息这种优先级配置随后将用于管理平台的各种行为 。 举例来说 , 当建立连接时 , 消息以优先级的递减顺序被放入套接字中 。 如果RPC发生故障 , 通过服务器端重试 , 高优先级的消息会变得更可靠 , 并支持跨区域复制 。
存活时间
推送消息是为了改善实时体验 。 因此 , 每条消息都有一个定义的存活时间值 , 范围从几秒到30分钟不等 。 消息传输系统会将消息持久化 , 并重新尝试传输消息 , 直到存活时间值到期 。
重复数据删除
该配置确定了在通过不同的触发器或重试多次产生相同的消息类型时 , 推送消息是否应该被重复数据删除 。 对大多数用例而言 , 发送特定类型的最新推送消息就足够了 , 这使我们能够降低总体数据传输率 。
消息传输
推送消息系统的最后一个组成部分是实际的负载传输服务 。 它的功能是在消息负载到达之后 , 保持与全球数以百万计的移动应用程序的有效连接 , 并快速地将它们同时发送 。
世界各地的移动网络提供了不同程度的可靠性 , 所以传输系统需要对故障作出可靠响应 。 本系统提供“至少一次”的传输保障 。
RAMEN传输协议
要提供可靠的传输渠道 , 我们必须利用基于TCP的持久连接 , 以便从应用向我们的数据中心提供传输服务 。 在2015年的应用协议中 , 我们的选择是利用HTTP/1.1与长轮询、WebSockets或最终的服务器发送事件(Server-Sentevents , SSE) 。
根据安全性、对移动SDK的支持、二进制大小的影响等因素 , 我们最终选择了SSE , 这对于Uber已经支持的HTTP+JSONAPI协议栈是非常简单可行的 。
然而 , SSE是一个单向协议 , 即数据只能从服务器发送到应用 。 为提供至少一次上述保证 , 需要在应用协议之上建立传输协议来进行确认和重试 。
基于SSE定义了一种非常优雅和简单的协议方案 。

Uber 实时推送平台实践:gRPC 推动基础设施的发展
文章图片
图2:SSE协议的服务器-客户端交互
客户端在任何新的会话开始时 , 在第一次HTTP请求/ramen/receive?seq=0时开始接收消息 , 序列号为0 。 服务器用HTTP200和“Content-Type:text/event-stream”响应 , 以维持SSE连接 。
接下来 , 服务器按照优先级降序发送所有待处理的消息 , 并关联递增的序列号 。 因为底层的传输协议是TCP连接 , 所以如果没有发送seq#3的消息 , 则该连接应该已断开、超时或失败 。