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


客户端应该在下次使用它看到的最大序列号(本例中seq=2)重新连接 。 这样可以告诉服务器即使3号被写进套接字中 , 它也不会被发送 。 这样 , 服务器就会重新发送相同的消息或者以seq=3开始的任何新优先级更高的消息 。 该协议建立了所需的可恢复流式连接 , 服务器做大部分的存储工作 , 在客户端实现起来很简单 。
为了解连接是否处于活动状态 , 服务器每4秒发送一条单字节大小的心跳消息 。 若客户机在7秒内未看到心跳或消息 , 则认为连接已中断并重新连接 。
在上述协议中 , 每当客户端使用更高的序列号重新连接时 , 它作为一个确认机制 , 允许服务器刷新旧消息 。 当网络运行良好时 , 用户可以保持长达几分钟的连接 , 从而使服务器继续积累旧消息 。 为减轻此问题 , 应用会每30秒调用/ramen/ack?seq=N 。
协议的简单性允许用许多不同的语言和平台快速编写客户端 。
设备上下文存储
每次建立连接时 , RAMEN服务器都附加存储设备上下文 。 这个上下文公开给Fireball , 这样用户就可以访问设备上下文 , 该id根据用户及其设备参数产生 , 具有唯一的哈希值 。 这样 , 即使用户同时使用多个设备或应用 , 且设置不同 , 也可以隔离推送消息 。
消息存储
RAMEN服务器将所有的消息保存在内存中 , 或者备份在数据库中 。 如果连接不稳定 , 服务器可以继续重试发送 , 直到TTL到期 。
实施细节
首代RAMEN服务器是用Node.js编写的 , 使用了“Ringpop”这个优步内部一致的哈希/分片框架 。 Ringpop是一种去中心化的分片系统 。 所有的连接都用一个用户UUID进行分片 , 并使用Redis作为持久化数据存储 。
在全球范围部署RAMEN
随后的一年半时间里 , 该推送平台在全公司得到了广泛应用 。 该系统最多保持60万个并发流连接 , 在高峰期每秒向三种不同类型的应用程序推送超过7万条QPS推送消息 。 该系统很快就成为服务器客户端API基础设施中最重要的部分 。
随着流量和持久连接数量的增加 , 我们的技术选择也需要进行扩展 。 基于Ringpop的分布式分片是一个非常简单的架构 , 但是不能随着环中节点的增加而扩展 。 Ringpop库使用gossip协议对成员进行评估 。 gossip协议的收敛时间随环的大小增加而增加 。
此外 , Node.jsworker是单线程的 , 它会增加事件循环的滞后程度 , 从而使成员信息的收敛变得更慢 。 这会导致拓扑信息不一致 , 以及消息丢失、超时和出错 。
2017年初 , 我们决定重启RAMEN协议的服务器实现 , 以便继续扩展 。 我们在本次迭代中使用了下列技术:Netty、ApacheZookeeper、ApacheHelix、Redis和ApacheCassandra 。
Netty:Netty是一个广泛使用的高性能库 , 用于构建网络服务器和客户端 。 Netty的bytebuf支持零拷贝缓冲区 , 这使得系统非常高效 。
ApacheZooKeeper:拥有一致的网络连接哈希 , 可以在中间不需要任何存储层的情况下直接传输数据 。 但是 , 我们没有选择分散式的拓扑管理 , 而是选择了ZooKeeper的集中式共享 。 ZooKeeper是一种非常强大的分布式同步和配置管理系统 , 可以快速检测连接节点的故障 。
ApacheHelix:Helix是一个强大的集群管理框架 , 在ZooKeeper上运行 , 支持定义自定义拓扑和重新平衡算法 。 它还很好地将拓扑逻辑从核心业务逻辑中抽象出来 。 它使用ZooKeeper来监控连接的工作器 , 并传播分片状态信息的变化 。 它还允许我们编写一个自定义的“Leader-Follower”拓扑结构 , 以及自定义的渐进式再平衡算法 。
Redis与ApacheCassandra:由于我们正准备进行多区域云架构 , 所以需要适当地复制和存储消息 。 Cassandra是一种持久的、跨区域复制的存储 。 在Cassandra之上使用Redis作为容量缓存 , 以避免由部署或故障转移事件中常见的碎片系统引起的惊群问题(thunderingherdproblems) 。
译注:惊群问题(thunderingherdproblems) , 是计算机科学中 , 当许多进程等待一个事件 , 事件发生后这些进程被唤醒 , 但只有一个进程能获得CPU执行权 , 其他进程又得被阻塞 , 这造成了严重的系统上下文切换代价 。

Uber 实时推送平台实践:gRPC 推动基础设施的发展
文章图片
图3:新RAMEN后端服务器的架构
StreamGate:该服务实现了Netty上的RAMEN协议 , 并且拥有所有的逻辑 , 包括连接处理、消息和存储 。 这个服务还实现了一个ApacheHelix参与者 , 它与ZooKeeper建立连接 , 并维护心跳 。
StreamgateFE(StreamgateFrontEnd):该服务作为一个ApacheHelixSpectator , 监听ZooKeeper的拓扑变化 。 它实现了一个反向代理 。 每个来自客户端Fireball、网关或移动应用的请求都是使用拓扑信息分片并路由到正确的Streamgate工作器 。