前言
在技术领域有一条准则,即不存在银弹技术。在实际工作中,通常无法通过几项简单的技术组合就解决实际业务中各种场景下的复杂问题。虽然追求架构的简单简洁也是架构师的目标之一。但必须认识到架构的简单简洁和没有银弹技术是一对矛盾体。正是整个矛盾体在推进着技术的不断进步。
本方案的目的在于抛砖引玉,介绍了 GTS TAM 团队通过对实际技术服务工作中遇到的问题的思考,形成的一种解决方案。希望这个解决方案能够对大家工作中的实际问题的解决起到启发作用。如有兴趣了解详细细节,欢迎联系阿里云 GTS TAM 团队。
1. 行业背景
打车业务是出行行业的一个细分领域。这些年打车业务一直处于快速发展阶段,除了不断加大一二线城市发展,还不断开拓三四线城市市场。8月25日滴滴出行的全球日订单量首次突破了5000万单。
打车行业的基本需求是撮合司机和乘客,达到满足乘客出行需求的目的。打车系统核心是以定位、地图等地理信息服务和订单服务。除此以外,打车系统还包含支付、会员、计价等业务服务,容器、数据库、缓存、消息队列等云服务,以及人工智能、大数据、视频监控等综合性技术服务。
1.1 业务特点
打车行业的一个特点是其乘客端业务流量呈现明显的潮汐波动。此外,还会因为节假日和恶劣天气等原因导致业务流量波动,并且这种波动存在一定的不确定性。
图1
1.2 技术挑战
打车行业不易预测的业务峰值波动对包括订单系统在内的打车业务核心系统的伸缩性,以及高并发能力、稳定性提出了比较高的要求。高并发能力和稳定性一定程度与伸缩性相关,业务系统的架构设计如何提供可伸缩的设计,自然也会提升系统的并发能力和稳定性。
应用从分层的角度来说通常分为接入层、应用层和数据层。其中,数据层的伸缩是重点和难点。目前阿里云 PolarDB 是数据库领域最出色的弹性解决方案。但目前数据库弹性伸缩还是只使用业务高峰时间和流量相对确定的场景。
2. 方案目标
本方案是聚焦打车业务的订单场景的弹性伸缩解决方案。目的在于订单这一打车业务核心系统能够在无需人工操作的情况下,满足不确定的业务高峰对打车订单系统的冲击,为客户提供能满足高并发且成本可控的订单系统架构设计。
3. 详细设计
为达到上述目标,本方案提供了如下架构设计。下图是整体架构设计,左图是传统的订单系统架构,分接入、应用和数据三层。右图是弹性下单架构方案设计。在原有基础上增加了两层:前向订单服务和订单队列。在这两层之后,是订单核心服务和订单数据库。
前向订单服务将下单请求处理后,将数据写入订单队列中即可返回。订单队列提供了高并发吞吐和较低的响应时间。订单队列由缓存和消息队列组成,缓存以用户维度保持最近的叫车状态信息,而队列则提供了高并发下单请求的持久化能力。
打车业务的下单场景能采用这种设计的原因是打车业务的订单是以乘客 ID 为维度,单个乘客在同一时间段只有一个进行中订单。整个流程的顺序是先下单,再履约,最后支付。因为支付是在最后一步,而下单和履约的主体不同(下单的主体是乘客,履约的主体是司机),因此下单可异步化。
图2
接下来将介绍新架构所引入的两层新设计。
3.1 前端订单应用层
前端订单应用层的功能主要包括以下几点:
- 基本校验、风控及安全检查
- 异步下单
- 限流降级
-
功能一:基本校验、风控及安全检查
这一步是业务参数的校验,以及风控安全方面的检查。这一类操作的特点是可通过缓存等手段优化性能,并在不影响主要业务的情况下进行降级。将这些可缓存、可降级的操作前置到可弹性伸缩的应用层,有利于承载高并发的业务流量。
-
功能二:异步下单
因为新方案增加了订单队列层,因此需要前端订单应用层实现异步下单的功能。这里涉及的操作主要是生成下单请求,将请求保存到缓存和队列中,以及一些业务处理,比如根据加价金额及其它参数,控制下单队列优先级。
这里需要注意的是这一步功能需要保障下单功能的幂等性、保证数据一致性和考虑请求乱序的问题。这些问题都是分布式系统研发过程中常见的问题,此处不再赘述。
-
功能三:限流降级
因为下单链路上增加了缓存和消息队列,如果必须强依赖于这些服务,相较于原来的架构设计,可用性是下降的。因为新的订单队列层是为了实现提升系统在高并发场景下的弹性能力,因此不是必须的依赖。在 Redis 或消息队列出现异常时,前端订单服务需要能够将请求绕过订单队列层,直接调用订单核心服务,实现叫车下单功能。
3.2 订单队列层
如前所述,订单队列由缓存和消息队列组成。缓存以乘客维度记录最新的叫车状态,消息队列则持久化叫车请求。
-
接口幂等性
在具体实现中,方案建议先将叫车请求写入消息队列,再写入缓存。原因是缓存中的数据表示叫车状态,如果先写入缓存,则无法通过其中的数据判断是否写入消息队列成功。
-
请求顺序性
请求异步化之后,请求的顺序和实际处理的顺序将无法保持一致。此时需要由业务系统实现上的设计保障新请求的处理结果不会被旧请求覆盖。
-
数据一致性
缓存中的数据表示乘客的出行状态,而乘客的出行状态不只是来自于乘客的主动请求,而也会根据订单状态的不断变化而变化。因此,缓存中的乘客出行状态数据需要和后端订单处理反映的实际状态保持一致。
4. 技术架构
4.1 方案 PK
弹性应用层 + PolarDB 弹性数据库 vs 本方案(订单队列异步下单)
应用架构的弹性伸缩方案的重点是数据层。数据库采用 PolarDB,可以实现数据库层的弹性伸缩。这个方案的优点是架构简单,避免异步下单所带来的研发复杂性和难度。缺点是需要人工执行伸缩,及时性不足。同时导致十几秒(最多30秒以内)的业务中断。因此较为适合业务高峰时间明确的场景。
订单队列异步下单方案的优点是弹性伸缩能力强,适应范围广。缺点是架构较为复杂,增加研发成本。
4.2 技术选型
4.2.1 弹性应用层:ACK + ECI
在上述的解决方案中,前端的应用层需要可弹性伸缩的计算服务给予支持。本方案建议采用 ACK + ECI 的组合。ACK 是阿里云 Kubernetes 服务(Alibaba Cloud Kubernetes)的缩写,ECI 是弹性计算实例(Elastic Compute Instance)的缩写,是一种 Serverless 技术,为包括容器服务在内上层计算资源提供无服务器的计算实例。
使用 ACK + ECI 可以构建弹性的应用层。ACK 本身也可以提供一定范围的弹性伸缩能力,但 ACK 弹性伸缩分为 Pod 和 Node 弹性伸缩两部分。Pod 运行在 Node 之上,但 Node 资源充足时,只需对 Pod 进行伸缩。当 Node 资源不足时,需要先扩容 Node,然后再创建新的 Pod。因此,单纯使用 ACK 的伸缩特性的缺点就是时效性不足。同时还存在资源利用率不足的问题。
为解决 ACK 伸缩性的不足,本方案建议采用 ACK + ECI 的架构。ACK 通过 Virtual Node 与 ECI 连接。扩容时当 Node 资源不足时,会将扩容的 Pod 调度到 Virtual Node 上。 Virtual Node 通过 virtual-kubelet-autoscaler 将 Pod 扩容请求调度到 ECI 上。
图3
4.2.2 订单队列:RocketMQ + Redis
本方案推荐采用 RocketMQ + Redis 实现订单队列。
RocketMQ 是阿里开源的消息队列产品,与 Kafka 相比,虽然吞吐量不如 Kafka,但具备出色的稳定性和众多适合在线业务的特性。因此,常选用 RocketMQ 作用在线业务的消息队列,而 Kafka 通常适合大数据处理。
阿里云提供的 RocketMQ 分标准版和铂金版两种。虽然铂金版价格较高,但处于稳定性的考虑,因此此方案推荐采用铂金版作为线上订单队列中的消息队列的实现技术。
阿里云提供了多种版本的 KV 存储服务,兼容 Redis 协议。订单队列所需的 KV 存储服务建议根据实际业务量评估规格,通常采用主从版即可。
4.3 部署架构
图4
接入层和应用层容器服务双可用区部署。MySQL 和 Redis 的主节点部署在其中一个可用区。
我们是阿里云智能全球技术服务-SRE团队,我们致力成为一个以技术为基础、面向服务、保障业务系统高可用的工程师团队;提供专业、体系化的SRE服务,帮助广大客户更好地使用云、基于云构建更加稳定可靠的业务系统,提升业务稳定性。我们期望能够分享更多帮助企业客户上云、用好云,让客户云上业务运行更加稳定可靠的技术,您可用钉钉扫描下方二维码,加入阿里云SRE技术学院钉钉圈子,和更多云上人交流关于云平台的那些事。