微服务间通信方式主要有2种:RPC和消息传递。
通常来说在请求/响应的场景下使用RPC更加合适,具体实现通常是REST API或者基于长链接的协议(例如gRPC/Thrift/Zero ICE等)。两个服务有比较强的依赖关系, 调用者依赖被调用者的处理结果,调用者处理该请求被堵塞以等待响应结果,同时还要进行负载均衡、限流、熔断、错误处理等。通常是同步的、一对一的交互,异步RPC本质也是要等待响应结果才能继续处理该请求。
消息传递以异步消息作为载体,通过事件代理(消息中间件)连接消息处理的上游和下游,上游和下游松散耦合,通常上游的处理逻辑不依赖下游的处理结果,具体实现通常有发布者/订阅者和事件流等。而事件驱动架构(EDA)就是基于消息传递,通过解耦生成、传输和处理事件,协调事件生产者、消息中间件、消费者工作的一种架构模式,具有松散耦合、流量削峰、非阻塞、易于扩展、更高的弹性和错误处理能力等优点。不过也带来性能损耗、复杂性等方面的问题。
推送系统的业务形态
在推送系统中,一个推送请求的处理流程大致如下:
1. 接收请求,进行用户身份认证、参数校验、请求权限校验
2. 请求解析,构建推送任务
3. 根据推送任务,筛选推送目标用户,以及获取推送目标用户的基本信息
4. 根据推送策略以及各推送目标用户的信息选择推送通道执行推送任务
5. 各推送通道负责具体的推送操作。
从业务场景看,极光推送平均每秒接收2万~3万的推送请求,包括regid、tag、alias、广播等推送方式。各推送目标的推送目标用户数量也不同,少则几个,多则几千万甚至上亿的目标数量。极光支持多推送通道,客户可以根据需要选择使用哪个通道进行推送,各个通道由于服务质量、地域距离等方面的因素,请求耗时、稳定性各有不同,此外还有各自的限速逻辑。业务面临着很大的挑战:
- 接收外部推送请求数大,峰值尖刺甚至可能翻倍
- 一个推送请求可能产生大量的消息,例如一个广播或者一个大tag推送,有千万级别的目标用户,意味着一个推送请求扩大为千万级别的系统内部请求。
- 整个系统中同时处理的消息体量非常大,常态化的业务峰值超过千万级别,并且一天时间有多个峰值,不可精确预测。
- 处理流程中涉及多个处理环节,各个环节的处理速度并不相同,甚至有可能有巨大的差异,例如某些推送通道处理快,某些通道推送处理相对慢。
推送系统如何使用事件驱动提升系统的整体性能
从客户的需求和产品需求出发,希望每个请求能够快速、正确地响应并处理,能够及时地把消息推送给每个目标用户。因此我们需要解决大量消息以最快的速度推送的问题。
从业务的角度看,可以从租户分级、租户隔离、推送方式、推送目标规模等维度进行部署隔离,减小各租户、各请求间的相互影响,更加快速更加稳定地处理整个推送请求,提供更好的服务质量。
从纯技术角度看,采用了多种技术手段和策略,包括缓存优化、异步处理、并行处理等,提升整体性能,实现更高效的推送操作和更好的系统稳定性。
其中采用事件驱动架构来组织整个处理流程,并行、异步处理,实现系统整体性能的提升,并且在突增流量、异常处理方面都能够很方便地应对。
推送流程的EDA实现
首先,将上述流程简化为多个核心处理环节,并构建为服务,通过消息中间件进行交互。
- pushAPI 接收请求,构建推送任务
- segmentGateway 筛选推送目标用户,选择推送通道,执行推送任务
- pushChannel 负责各个推送通道的推送操作,其中包含多个具体的推送通道。
各服务基于事件传递状态转移 (Event Carried State Transfer)或者事件通知(Event Notification)模式生成事件消息,然后投递到消息中间件中。同时各服务通过消息队列或者订阅的方式从消息中间件消费消息,根据实际需要由消息中间件推送事件消息给消费者或者由消费者主动拉取事件消息。对于重复消息的处理,通常有Exactly once和At least once +业务幂等性处理,建议以第二种方式处理。
注:消息中间件的选型、消息投递服务质量的原理不在本文的描述范围,未展开说明。
pushAPI接收推送请求,生成推送任务事件消息,然后投递到消息中间件。消息中间件根据相关信息发送到指定队列中。
segmentGateway消费指定队列,消费其中的事件消息。经过处理后(查询目标用户和相关基本信息),批量填充各推送通道的目标用户到推送任务,并生成新的推送任务事件消息投递到消息中间件中。消息中间件根据相关信息发送到各推送通道相关的队列中。
pushChannel的各推送通道服务消费各自的队列,执行推送操作。
以上流程中,事件消息的生产者不需要消费者的处理结果,消费者也不依赖生产者,完全解耦。
EDA解决推送系统的痛点
并行处理:各服务多节点部署,并行处理请求/消息,当服务出现性能不足以处理业务时,K8S环境下增加节点副本数横向扩容即可;此外同一个推送请求通过不同推送通道推送时,多个推送通道并行推送消息。
异步处理:各服务专注自己的业务逻辑,不依赖业务下游,通常也不受下游的影响,无需等待处理结果,整个流程异步处理,减少空闲等待时间,可以最大化利用资源。
异常处理:当有突增流量时,请求流量压力过大,超过了某个处理环节的所有服务节点的处理能力;或者某个推送通道因为网络抖动、网络中断等处理慢。这些服务节点作为消息中间件的消费者,因为处理能力不足或者处理变慢,未来得及处理的请求堆积在消息中间件,等待扩容或者采取其他处理措施。缓存请求到消息中间件中,从某种程度上也是背压(Back Pressure)模式的一种处理方式,当然还可以进一步的向上游反馈负载压力信息,由上游采取处理措施。例如大量消息需要推送到苹果的推送服务,由于网络波动或者苹果服务器限流,可能出现推送变慢,这个时候推送iOS消息可能会堆积在消息中间件中;其他推送通道并不受此影响,依然能够正常地快速推送消息给其他通道。
其他:松散耦合使各服务更加独立,在进行业务变更时(包括代码逻辑变更和发布变更)通常影响面很小,某些情况下甚至能够不影响上下游逻辑,例如某些推送通道没有进行推送速率的限制,当增加限速逻辑时只影响该通道的服务,对于其他通道和上游服务都不影响。
借助EDA,极光推送能够轻松处理高并发推送请求,实现数千万级别的消息的快速推送,有更大的弹性应对不可预测的、更大流量的消息推送,以及有更好的异常处理能力。
未来的展望:ServiceMesh/EvenMesh混合架构,统一平台化
实际上,在极光推送系统中,同时存在2种通信方式,例如在上面的推送流程中,需要根据推送方式获取推送目标用户以及相关基本信息,需要从其他子系统服务通过RPC进行查询获取结果。整个推送系统的其他功能也是如此,根据业务场景、数据/业务量级做权衡取舍,选择合适的架构模式来构建系统,保证系统整体性能以及系统的可用性、可维护性。
为了让开发者更专注地处理核心的业务代码逻辑,减少各种通信交互的处理细节(例如超时处理、限流等),目前请求/响应的模式比较主流的做法是Service Mesh,并且经历了Sidecar/Proxyless/Sidecarless几种模式的发展,也做了各种权衡取舍。
EDA也有相对应的模式,Event Mesh就是其中之一,通过创建能够高效可靠地处理任务的网状代理网络,解决大规模事件驱动架构的挑战,包括事件路由、发现和交付,实现跨复杂分布式系统的事件驱动通信。
我们的推送系统实际上是混合2个通信方式的架构,更理想、更适合我们的架构应该是二者并存。
我们也持续关注相关技术,在充分验证的情况下引入相关技术,保持架构持续更新优化,避免架构腐化,保证推送系统的高性能、高可用性、高可维护性。
关于极光
极光(Aurora Mobile,纳斯达克股票代码:JG)成立于2011年,是中国领先的客户互动和营销科技服务商。成立之初,极光专注于为企业提供稳定高效的消息推送服务,凭借先发优势,已经成长为市场份额遥遥领先的移动消息推送服务商。随着企业对客户触达和营销增长需求的不断加强,极光前瞻性地推出了消息云和营销云等解决方案,帮助企业实现多渠道的客户触达和互动需求,以及人工智能和大数据驱动的营销科技应用,助力企业数字化转型。