饿了么即时配送分流的可运营架构演变

简介

分流是饿了么物流即时配送部门负责实时运力调度的系统。经过不断的迭代,经历了“能用”、“可用”、“好用”三个关键性的提升,目前已成功由一套纯后端系统演变为一套运营、研发、商分共同参与的可运营体系,稳定地支撑饿了么日常流量分配、运力调度、数据分析等工作。随着业务的不断发展,该系统已日承载上千万的运单流转、上亿的数据处理,成为物流部门最关键的系统之一。

围绕系统演变的过程,给大家逐一分享我们的架构设计及踩坑经历。

  1. 系统初期,如何分析业务,如何搭建系统;
  2. 系统中期,业务扩张的同时遇到了哪些问题,如何应对系统异常,如何打造系统的稳定性;
  3. 系统后期,如何满足日益变化的运营需求,如何实时验证运营策略的准确性,如何应对异常策略的发生。

阶段分析

系统初期--业务分析及系统搭建

系统初期,最重要的就是了解业务的现状,分析业务的本质,预测业务的未来变化。对业务本质的分析,最重要的三点:业务定义、业务元素、业务诉求。

业务定义

分流是物流内部根据运单信息、运力形态、运营规则等综合因素动态决定运单流向的即时决策系统,负责各个运力线之间流量分配及运力线之间的快速降级,为饿了么商家提供稳定的物流配送能力。 从定义可以看出,他的核心概念是决策系统,决策系统相应的就是规则的定义,根据规则来决定系统流程的走向。而规则的制定是由人来定,这个人可以是业务,可以是运营,可以是研发,甚至可以是系统自动生成的。

业务元素

业务元素的分析,更多的是来自于业务经验。对于研发来讲,就需要更多去了解业务,体验业务。为此,我们在系统设计初期,专门组织了各种业务交流、线下体验、领域专家咨询等任何有可能帮助了解业务的活动。最终总结出以下几个关键元素:运单、运力(内部+外部)、负载、规则、数据、人。 业务元素分析

业务诉求

然后,我们总结业务诉求,要能够满足业务人员对符合某些特征的运单按一定规则动态的调整运力,并能够实时感知策略效果,具体细节如下:

  • 支持多种运力,支持运力间的任意组合(内部运力、外部运力);
  • 支持规则可定制;
  • 支持运单按一定规则给到某一运力;
  • 未来要支持运单精细化配置;
  • 未来要支持自动规则分流;
  • 未来要支持实时数据分析、数据监控;
  • ....
初期架构设计图

最后,我们在此基础上进行了第一版的系统架构设计(见下图-系统初期系统架构图-v1),并完整支撑了业务的日常策略变更,当然也仅限于业务可运行,万事开头难,任重而道远,我们的研发改进之路还在不停的前进。 系统初期系统架构图-v1

系统中期--性能优化及稳定性建设

随着业务的不断扩张,尤其是 17 年开始,系统流量开始极速增长,毫不夸张的说,每天都在面临新的挑战,解决不同的问题。为此我们开始了一系列的系统升级,主要是提升系统的性能及稳定性。接下来我们结合我们的踩坑经历从以下几点来一一说明,如何应对系统异常,如何打造系统的稳定性(稳定性是饿了么研发中心的核心指标,是维护客户利益的重要手段,说句毫不夸张的话,系统宕机 1 分钟,业务损失将有可能达到百万,对此,饿了么专门设立了稳定性部门,足见其重要性)。 系统稳定性目标

容量设计

系统初期完成后,一个非常重要的问题来了,业务在不断的扩张,系统能不能扛住,万一公司突然有个活动,系统会不会宕机。带着这个问题,我们开始了一系列的调研准备工作(ps: 数据仅供参考,不代表真实值)。

  • 业务调研,了解业务近一年的增长预期,预计年终将会由现在的 200w 增长至 500w,最终我们在此基础上以 800w 作为预估。

  • 系统分析,分析现有单量曲线,日平均 100qps,午高峰 150 qps,偶尔会存在因上游系统宕机,出现补偿尖刺达到 250qps,最终按 300qps 作为预估。同时 机器 CPU、JVM 堆栈信息、网络带宽,数据库 TPS、QPS、单表增量、总表增量,Redis OPS、内存使用量、连接数、命中率,MQ 堆积、消费延迟等指标也都做了相应评估。

  • 系统压测,为了解系统最大极限及短板所在,每周按午高峰的 2 倍即 300qps 进行压测,即时优化短板。在此过程中,我们所遇到的短板大概以下几类:

  1. 机器,客户端、服务端线程不够用,借助负载均衡随时加节点可相对缓解,当然也要分析程序的处理逻辑;
  2. 线程,程序多线程处理不理想(过高 or 过低),适当调整线程池数量;
  3. MQ 消费者,queue 出现堆积,配置 MQ 启动参数 concurrency 增加消费者数量;
  4. 数据库,db 查询慢,连接数量不够用,数据量过大等;
  5. Redis内存不够用,连接数不够用,命中率过低,Key 值使用不合理等;
  6. 单任务的处理能力,比如每次从数据库里拿到需要处理的数据,我们通常会设置 limit (注意,一定要设置,否则很容易导致内存溢出异常),增加动态配置入口,可根据实际情况实时调整。
  • 资源冗余,前面说到我们是按照午高峰 2 倍的进行的压测,这个还不够,为防患于未然,我们所有可能的地方都做了一定的冗余,比如机器会多部署 3 台、预留 2 台备用机器等,同时制定了一套扩容 SOP(例如 2018 年双 11,我们临时扩充了 1.5 倍的机器资源、Redis 资源,提前归档数据,释放 30% 的数据库资源等等)。
数据库优化

数据库这层,除了 缓存、索引、归档、读写分离、分库分表等常规优化外,我们重点做了以下几点优化:

  • 数据表结构优化,适当拆分大表,冗余公共字段,将全部的查询语句简化到单表查询(数据一致性由补偿 job 来保证);

  • 去事务,抽象主流程逻辑,简化处理流程,不使用事务(状态机模型被抽象,补偿 Job 可以在任意时间、任意状态拉起,并流转至终态)

  • 去尖刺,为防止数据库 TPS 过高,对主流程之外的操作进行异步处理,依靠 MQ 进行消费处理,对有些并发性较高的操作,增加时间戳概念,主动丢弃消息。针对日志级别的消息 or 异步线程池,也会进行 dml 合并。

  • 内存缓存,我们的核心缓存是 Redis,但对于有些非关键逻辑中(比如一些降级方案),我们也使用了内存缓存(极少数,慎用)。

中间件优化

中间件示意图

如图中 1、2 所示,我们构建了 Client -> Proxy -> Middleware -> Infrastructure 这样一条链路,通过 Zookeeper 实现了中间件的节点注册,可以有效的完成中间件的动态扩容、健康检查、负载均衡。同时也可以保证 Infrastructure 的连接数量不会太高,不会像传统模式那样,随着 Client 的增加,连接数暴涨,Infrastructure 的性能指标直线下滑,甚至宕机。

此外我们在标识 1 处,做了很多防护措施,比如:

  • 监控,实时检测 Infrastructure 性能指标,动态调整流量分布,剔除异常节点,对于读多写少的情况,主库甚至可以承担一定的查询流量;

  • 限流,防止 Infrastructure 收到高压冲击;

  • 埋点,实时监控操作指令情况;

  • 路由,根据 SQL 入参快速选择定位数据所在分片。

系统保护

削峰、限流,上文也提到,上游系统性能有可能会高于下游系统,如果不进行一定的处理,我们的系统可能会持续满负载运行,很容易带来一些意想不到的问题。对此,我们在和上游交互的过程中,引入了 MQ(推模式),借助 MQ 的堆积能力,完成对核心流程的异步化处理。在这里我们有个重要约定:接口调用成功,即代表交互成功,下游系统需保证之后流程的完整性。与此同时,如果异步化依然无法解决性能问题,则需要对调用方发起限流操作,确保自身系统稳定。

熔断、降级,当接口交互在一定时间内频繁发生异常,会被视为系统不可用,触发熔断,client 端会自动终止向服务端发送请求,并以异常形势抛出。对此类异常,我们做了一些归类:

  • 依赖方可降级,编写降级处理逻辑,继续完成后续流程,避免引发雪崩拖垮整个系统;
  • 依赖方不可降级,异常往上抛,不做任何处理,直至最上层处理结束。

监控报警,系统运行状态如何,如何能先于线下感知到系统故障,离不开监控。这里我们对系统指标分为两类:系统指标、业务指标。有了监控,我们就可以实时感知系统异常,实时推送异常消息到我们的研发团队进行处理,最佳情况我们可以在线下感知到系统异常的情况下,已经处理好故障,保证用户的顺畅性。

  • 系统指标,通常会监控系统的网络情况、存活机器数量、Consumer 存活数量、异常数量等。

  • 业务指标,依赖我们在程序中主动埋点,最主要的就是主流程的流转,其次是各个子项的监控,比如我们系统需要重点关注下游各运力的系统监控状态。

资源隔离

物理隔离,侧重点是部署隔离,各自拥有独立的环境,不受相互影响。首先要提的是,饿了么终极大杀器 “多活” (传送门),即多个机房,每个机房拥有完整的一套硬件资源,没有任何的关联,相互之间仅通过专线进行数据同步,来保证数据一致。其次是单机房内的集群隔离,独立部署一套机器资源,他们之间共享底层基础设施。最后要提的是,作为关键系统,基础设施的部署要做到单独部署,不要因为其他应用的宕机,导致关键系统的服务不可用。

流量隔离,首先,借助多活,我们完成了地区的运单流量隔离,北京地区的宕机,不会影响上海地区的服务正常运行。其次,我们对系统流量上做了归类,一类是从上之下(运单至运力),一类是从下至上(运力至运单),这两类流量的业务划分很明显,且相互之间无依赖,理应做到相互隔离。最后,在从上之下的过程中,我们还涉及了与多个运力系统之间的交互,每一个系统的处理能力参差不齐,为防止互相之间有影响,做了相互隔离,可归结为业务隔离。

代码优化

研发理念升级,随着业务的不断变化,原始的堆业务代码的形式,已逐渐现出各种弊端:迭代慢、效果差、业务理解不一、依赖变跟需大量改动、新业务拓展难等等。对此我们在一期对业务元素的分析基础上,引入了 领域驱动设计 DDD,从代码层面细化了业务形态,抽象出整个系统架构,进一步拆分系统职能,降低外部依赖,简化系统交互。 ddd项目目录示意图

设计模式,Interceptor (传送门),分流业务系统最核心的设计,该模式在 WebService、Spring AOP 等框架中体现较多,但真正用于具体业务架构的则很少(技术是服务于业务的,真正的架构师应该是一名合格的业务分析师)。鉴于分流业务的特征:规则固定可复用、需要大量的规则组合、规则组合实时可变更、规则内需要挑选实时最优运力,我们将业务规则独立抽象为两类:筛选过滤类、排序类, 每一个 interceptor 都拥有 before 和 after 方法,在一次流程中,都将有两次执行的机会。在此基础上,我们实现了业务规则的任意组合、任意时刻变更,在确保基础架构不变的情况下,完美支持了各种新业务规则的发展。 Interceptor模式应用

幂等性,在业务量变大后,系统异常率上升,上游系统重试的情况屡次发生,在分流这种实时策略系统中,如果不进行幂等处理,对同一单的处理将会出现两个不一样的结果,最终就是一团糟。幂等性我们主要体现在以下几个方面:

  • 接口幂等,同一个参数短期内请求幂等,如某一运力在回传数据时发生的快速重试;
  • MQ 消息处理的幂等,如日志记录,同一个状态的消息不会被重复记录到 DB;
  • Job 幂等,两次 job 触发同一数据的修改,结果一样;
  • 数据更新幂等,使用 CAS 乐观锁机制进行数据更新。

业务补偿,前面提到我们和上游系统约定,一旦接口调用成功,需保证之后流程的完整性。对此,我们完整了抽象了分流过程的状态机,在状态机的基础上,确保流程随时可被拉起并继续往下流转(Job 定时扫描未到终态的流程)。对于交互过程中的异常,我们处理相对简单:

  • 系统异常发生现场,进行间隔 100ms 的重试

  • 如果依然失败,丢到延迟队列中,进行间隔 5min 的重试。

一致性保证,两个系统间的交互,最大的难题就是如何保证相互之间的数据一致性。我们的做法相对简单,对上游,首先系统上确保我们的数据可以正常回传,其次,在内部流程处理中,以上游数据为准,内部做相应的变更; 对下游(各个运力系统),约定状态码,准确识别状态异常的情况,并进行内部数据的相应变更,如有必要会同步上游系统。

解耦,在完成模块抽象后,还有一个重要的事情,就是进行业务逻辑主流程的抽象,确保主流程中都是必须的操作,一切非必须的操作,都可以做异步化处理。这里我们定义了一个概念 “关键流程状态节点” ,对应每一个节点,都会有相关的数据记录及消息广播。借助这些信息可以完成 非关键流程的逻辑异步处理、相关数据分析、其他系统的对接等等。同时在异步处理过程中,不可避免的会出现了逻辑处理的先后顺序问题,在这里我们有两种处理手段:丢弃、延迟重试,eg 在和下游运力交互中存在推单、取消单两个过程:

  1. 取消单,因网络原因先一步到达下游系统,下游系统发现数据不存在,返回异常码,分流识别该异常码,选择 1min 后重试;
  2. 推单时,检查上游运单状态,发现已被取消,则主动丢弃该消息。

并发锁,虽然数据库更新我们都会有 CAS 处理,但很难保证其它流程不会出现异常,这里我们对同一运单的操作进行了 Redis 分布式锁,确保在同一时间只会有一个处理流程。这里我们曾遇到一个极端情况,Redis lock 的设置,set、expire 是分两条指令执行,但因为 expire 执行异常,导致我们的整个流程卡死, 希望大家注意一下。

中期系统架构图

中期系统核心逻辑架构图

如图,这期我们重点优化如下:

  • 基础组件全部独立部署隔离,确保不同服务之间互不影响;
  • 集群部署隔离,区分主流程上、下行流量;
  • 同一集群内业务隔离,确保不同业务之间互不影响;
  • 优化中间件,提升基础组件的可用性;
  • 支持上下游流量削峰,平滑处理上下游之间的交互;
  • 新增消息总线,异步解耦非关键流程,确保主逻辑代码干净易维护;
  • 抽象业务流程状态机,增加补偿机制,确保所有的异步处理都能够降级;
  • 优化数据表结构,优化缓存机制;
  • 优化系统监控,强化系统容量设计。

系统后期--运营能力支撑

在度过系统稳定性搭建后,业务的发展已经到了亟需定制化的阶段。回想系统初期时候设想的,未来需要支持的功能,现在都成为了现实刚需。未来要支持运单精细化配置、未来要支持自动规则分流、未来要支持实时数据分析、数据监控等。 运营支持

开放平台,在一期对运力管理的基础上,我们制定了运力交互的 SPI,极大的降低了新运力的接入成本,为业务拓展提供了有力的支撑。

规则引擎,为了支持运单的精细化配置、自动规则分流,我们对运单属性进行了因子化拆解,在 spring el 基础上搭建了一套规则引擎,通过对运单因子(时间、地区、天气、距离等特征)的组合匹配,匹配出一个分流规则。该功能的引入,得益于我们在 DDD 抽象是对规则匹配部分留足了适配空间。

规则配置台,既然有了规则引擎、特征因子、运力负载、运力规则,那我们就在此基础上,为运营提供了一个配置台,支持运营进行各种形势的分流规则组合,真正实现了分流的可运营化。

ES 数据中心, 为了支持实时数据分析,我们在“关键流程状态节点”的广播消息上,结合规则引擎、运单中心将分流数据完整的嵌入了 ES 中,并在此基础上构建了一套基于运单特征因子、运力形态等多维度的数据查询平台,运营可实时查看当前分流数据质量,为分流策略验证、策略分析提供了非常快送的支持,成功的驱动了业务的发展。更有甚至,产品同学通过该数据中心,发现了产品漏洞。

数据日报,基于大数据定期抽数,我们结合分析师,定制了多种维度的数据日报,推送到参与分流的所有人员,真正做到了运营质量的透明化。

异常查询工具,当然了,随着业务的不断迭代,每天都会有一些惊喜出现,为此,我们为运营人员量身定制了,一套 bad case 查询工具,产品、运营、研发、技术支持等都可以随时去定位系统问题,实现了异常运营的快速反馈。

系统终态架构

系统终态架构


未来展望

分流系统当前仍然面临很大的挑战,业务在不断的变化,新的运营策略层出不穷,需要有更多的人员加入来实现精细化运营,随时会有可能打破现有的既定流程。系统的运营规则在很大程度上还是靠人为制定、修改,如何降低运营成本已成为当前系统要面临的最大问题,未来可能希望能够根据现有流量、运力、政策等多种因素实现自动化规则推荐及应用。

猜你喜欢

转载自www.cnblogs.com/Pibaosi/p/10681262.html