滴滴出行平台业务架构演进

服务端思维

共 8300字,需浏览 17分钟

 ·

2021-06-02 04:42

点击上方“服务端思维”,选择“设为星标

回复”669“获取独家整理的精选资料集

回复”加群“加入全国服务端高端社群「后端圈」


作者 | DeanWu
出品 | 滴滴技术

桔妹导读:了满足不同用户在价格、体验等方的差异化诉求,滴滴提供了越来越丰富的品类,这些品类大体流程是类的,在一些细节体验上有差异,一套架构如何兼顾隔离和复用,同时支持这些品类,且看滴滴服务端技术的湾流平台怎么做。

1. 
项目背景
在滴滴打车业务中,服务端API向上接收端上请求并组装返回,向下串接订单、计价、收银等业务中台的各个系统,完成整个打车流程,湾流平台项目期望在服务端API层打造一个「出行中台」。在已有业务中台的情况下,为什么还要在业务「前台」API层打造一个「出行中台」,这和出行业务的流量模式是分不开的。

1.1 流量模式

1.1.1 传统和业界常规的“锥状”流量分配模式

传统的典型中台架构是“大中台小前台”,造就这种架构的原因与流量分配模式相关。以电商领域为例,中台抽象了电商相关的业务实体如:订单、收银台、商品等,而不同业务线之间的流量入口是分开的,不同BU间能够以小前台的方式闭环实现。这种“大中台小前台”的架构,可以支持快速构建原型产品进行试错探索,中台提供电商标准的基础能力,前台各自闭合实现B2C、C2C等业务,而业务间互不影响。

流量分配模式表现为:多业务(或品类)开放的前台流量接入,转化至统一有限的业务中台,最终落至基础平台。


1.1.2 网约车独特的“菱形”流量分配模式

滴滴网约车业务核心是打车,为了满足不同用户的需求(定价、应答率、体验等),通过品类区分提供差异化服务能力,从最初的出租车、专车、快车延展到了如今几十个品类。而入口始终围绕在司乘两端、开放平台。

流量分配模式表现为:多品类由统一的端接入流量入口(API)并完成各品类的主要业务逻辑处理,再交由统一的业务中台,最终落至基础平台


网约车的「菱形」流量分配模式注定:服务端API一方面需要支持一些跨BU的平台级需求,如:春节服务费、疫情停开服等;另一方面也要支持不同BU间的差异化需求,如出租车使用打表计价不用线上计价等。

随着品类越来越丰富,这些差异逻辑也越来越多,导致系统越来越臃肿,复杂度越来越高,迭代效率下降。所以需要将服务端API通用的部分下沉,并且开放差异定制的机制,同时兼顾隔离和复用,湾流平台项目应运而生。

1.2 服务端API职责定位

在开始前,需要先明确服务端API的核心职责。

◎ 核心职责


  • 流量染色:识别和定义接入流量中的品类、场景、功能,并转义标识为统一的业务特征。

  • 流程串接:根据不同事件/请求按照相应的逻辑和流程调用下游服务,以完成具体的功能。

  • 数据渲染:将处理结果数据按不同的端或品类/场景要求渲染成对应的数据视图。

◎ 终极问题

  • 复用:what(复用什么,复用到什么级别), how(怎么实现复用)

  • 隔离:what(隔离的是什么),how(隔离机制是什么)

1.3 湾流平台演进

在过去几年时间里,基于上述背景我们一直在不断探索,以下简单介绍下湾流平台项目前两个版本迭代的情况。

1.3.1 湾流平台1.0(2017-2018)

1.0阶段主要解决的是快速增加新品类和不同BU间代码隔离的问题,使用配置化插件化解决。配置化主要是统一了上下游产品描述协议,形成产品描述N元组,并抽象一套通用的N元组到功能的映射规则;插件化利用插件包隔离不同BU间的代码,运行时插件选择器根据流量特征分发到对应插件包。


◎ 遗留问题
 
  1. 配置化依赖于功能抽象,需要一套统一的抽象方法

  2. 插件化依赖于稳固的流程,以及清晰的功能边界。按差异开放插件点会导致插件定义不明确、粒度无法把控、插入点不稳定等问题,长期维护困难。

1.3.2 湾流平台2.0(2018)

2.0一方面要解决1.0遗留的功能抽象、流程固化等问题,一方面还要面临复杂度越来越高的服务端api系统。为此我们借鉴了DDD的思路,开启了湾流平台2.0的改造。

  • 宏观上,根据核心数据&职能对服务进行了拆分,将一个大模块拆分成多个垂直闭环的子模块,即领域化。通过分治的方式,降低了整体的复杂度,同时也解决了所有团队成员在一个模块开发导致的上线冲突和排队情况。


  • 微观上,按流程功能对领域服务进一步分析,进行功能聚合与抽象,即组件化。提高复用性,解决业务扩展性和开发效率的问题。


◎ 遗留问题

  1. 未做系统性的框架约束,迭代容易破坏原有结构

  2. 侧重于分治和抽象,未同步考虑品类间隔离问题


2. 
湾流平台3.0详细方案
2.1 总体思路



前面也提到,湾流平台核心要解决隔离和复用的问题。3.0整体思路是把服务端API的业务逻辑分为两层,一层是用于串接状态流转的流程层,一层是用于完成各个垂类功能的能力组件层。流程层既包含从预估、发单到完单的宏观打车流程,也包含每个接口的执行流程。宏观的打车流程基本品类间是统一的,与端的交互协议也是统一的;每个接口执行流程不同品类间由于业务形态差异,会有部分不一致。我们把接口的执行流程做环节抽象,形成一个个的step,沉淀一套接口标准通用的执行step,品类可以根据各自的差异,重载step。

能力组件抽象聚合了一些垂直的功能,组件内部按照策略模式,根据不同的品类场景使用不同的策略完成组件行为。如播单组件,提供了延迟播单、实时播单、轮次播单等模式,专车预约采用延迟播单,这种是在播单组件中通过配置实现。如果一些有特别大的差异,比如出租车要实现一个全新的播单模式并且不具备通用性,也可以由出租车实现这个新的模式,通过插件的形式挂载进来。

经过这样改造之后,同时配套诞生了一些平台产品,辅助提高开发效率。如流程编排中心,可以根据不同的品类场景,对接口流程环节进行编排;特征管理平台,统一管控业务特征,保持业务描述统一;品类配置中心,从品类场景视角,配置不同能力的行为模式,快速上线新品类场景。

2.2 框架介绍

为了实现总体思路,我们开发了一套代码运行框架,命名为DuKang,何以解忧,唯有DuKang!依托DuKang框架,解决我们隔离和复用的难题。

DuKang框架,针对每个接口,按照下图流程执行调度,涉及InputSource、Transport、TransportFactor、StepRuntime、Step、Ability等核心概念。


其中核心的要点是,Transport作为流程承载器,提供了一个base的基础流程实现,不同品类可继承BaseTransport,然后可以针对差异的流程环节step进行重载,但整个流程是由流程驱动引擎调度,各品类保持一致。ability是能力组件,组件内部提供了一组通用的mode,不同品类场景通过配置化方式复用这些mode,同时也向业务开放了定制mode的机制,业务可以通过使用biz定制自己独有的mode,挂载到ability下,实现差异化功能。

服务端API语言栈以php和golang为主,其中老的服务主要是用php写的,整体逐步在往golang上迁移,新服务都是直接采用的golang。Dukang框架同时支持了php和golang两个版本,下面以php版本,成单接口为例,展示dukang框架的运行过程:

//配置文件,管理流程环节,以及提供给不同品类注册各自transport{  "name": "ConfirmOrder,  "transports": {    "default": "\\DuKang\\Transport\\ConfirmOrderaseTransport",    "express": "\\DuKang\\Express\\Transport\\ConfirmOrderExpressTransport",    "luxury": "\\DuKang\\Luxury\\Transport\\ConfirmOrderLuxuryTransport",    "taxi": "\\DuKang\\Taxi\\Transport\\ConfirmOrderTaxiTransport",},  "steps": [    {      "step_id": "fetchInfoStep",      "description": "获取基本信息"    },    {      "step_id": "confirmTravelStep",      "description": "确认行程信息"    },    {      "step_id": "confirmBillStep",      "description": "确认计价信息"    },    {      "step_id": "checkStep",      "description": "成单检查"    },    {      "step_id": "fillOrderDetailStep",      "description": "订单维度填充"    },    {      "step_id": "sendOrderCommandStep",      "description": "订单处理操作"    },    {      "step_id": "sendDriverCommandStep",      "description": "司机处理操作"    },    {      "step_id": "sendSchedulingCommandStep",      "description": "调度处理操作"    },    {      "step_id": "buildResponseStep",      "description": "构建响应"    },    {      "step_id": "asyncOperationStep",      "description": "异步操作"    },    {      "step_id": "writeLogStep",      "description": "日志处理"    }  ]}
// dukang框架核心执行过程try { // 加载并解析接口配置,包括BizConfig、StepConfig $oBizConf = BizConfig::load($sConfigStr);
// 获取输入源数据,包括Request和基础数据获取 $oInputSource = new ConfirmOrdernputSource();
// 构造StepRuntime $oStepRuntime = new ConfirmOrdertepRuntime($oInputSource);
// 将接口配置对象、StepRuntime放入流程调度器 $oFlowScheduler = new FlowScheduler($oBizConf, $oStepRuntime);
// 初始化传输器路由因子 $oTransportSelectFactor = new ConfirmOrderTransportSelectFactor($oInputSource); if($oFlowScheduler->selectTransport($oTransportSelectFactor)) { // 执行流程调度 $oFlowScheduler->run(); } // 异常处理原则:接口外层只处理DuKangException和Exception,Step或者Ability处理异常则先处理逻辑再抛异常} catch (DuKangException $e) { $aResp = [ 'errno' => $e->getCode(), 'errmsg' => $e->getMessage(), ]; echo json_encode($aResp);} catch (\Exception $e) { $aResp = [ 'errno' => $e->getCode(), 'errmsg' => $e->getMessage(), ]; echo json_encode($aResp);}

接下来,再展开对dukang的一些核心概念进行讲解


2.2.1 Transport - 业务传输器/承载器

◎ 定义

  • 针对单接口内不同运力(或品类)进行抽象得来的流程载体

  • 任一业务必有其承载的流程和执行顺序

◎ 约束

  • BaseTransport覆盖当前接口业务的通用流程

    • 任一接口内有且只有一个BaseTransport

  • XxxTransport覆盖不同运力(或品类)的差异化实现

    • 任一接口内的差异化XxxTransport必须继承自BaseTransport

    • 任一Transport至少包含一个StepTransport <=> [N]Step (N >= 1)

2.2.2 Step - 流程环节

◎ 定义

  • 针对单接口内的业务进行抽象出来的环节载体

  • 单一Step是某一段业务环节或功能的具体实现

◎ 特性

  • 单一Step是大粒度差异化(如豪华车/出租车)的有效手段, 可通过Override Step实现

  • Step间的通信通过统一运行时数据总线StepRuntime串联,理论上可实现热拔插

◎ 图例

  • 业务流程驱动型接口:以串联各个处理流程为主

  • 数据驱动型接口:以构建业务特征数据为主


2.2.3 StepRuntime - 运行时数据总线

◎ 定义

  • 流程串联运行时数据总线

◎ 约束

  • StepRuntime只能作为Ability的输入,不能在Ability及下层逻辑中对StepRuntime的业务特征和数据进行修改

◎ 图例


2.2.4 InputSource - 输入源

◎ 定义

  • 外部输入数据源,为TransportFactor准备数据

  • 用于消除外部执行环境差异化,隔离外部入参

◎ 约束

  • 进入Step之后具有只读属性,被StepRuntime引用,后续业务逻辑不可修改其包含的所有特征及数据内容

◎ 图例


2.2.5 Transport Factor - 传输器因子

◎ 定义

  • 业务传输器Transport决策因子

  • 不同接口可能会采取不同的因子进行Transport决策

    • 目前实现的有针对订单维度product_id,针对司机维度car_level

    • 可选择端来源作为决策因子,如滴滴出行app、开放平台、礼橙专车app等

◎ 约束

  • Factor必须是确定可选择的几类因子,不能由RD同学自由编写

  • 同类业务接口原则上TransportFactor要尽量保持一致

2.2.6 Ability - 能力组件

2.2.6.1 概念描述


◎ 定义

  • 以特征数据为视角,对聚焦业务进行提炼和抽象,形成能力组件

◎ 设计原则

  • 面向可复用设计

  • 面向可扩展差异化设计

2.2.6.2 业务特征

◎ 业务特征概念归纳


  • 业务表达:播单计划 =  $sStartAddDuseTime + $bIsDelayBroadcast + $bIsRepeatAssign+ $iBroadcastAssignType + $iBroadcastExpire

◎ 业务特征解决的问题

  • 规范业务属性或字段语义,避免歧义和未知语义

  • 定义:业务 = 有序流程 * 控制(特征X, 特征Y, 特征Z, ...), 控制 = 染色 | 填充 | 复写  |  合并  |  标记

2.2.6.3 能力组件抽取过程

针对一组业务特征,将围绕这些业务特征的生产、修改操作聚合,形成能力组件。如围绕播单特征的播单组件、价格特征的计价组件等等

◎ Ability扩展性

支持以Addtional 的业务扩展Ability Mode,即前面提到的biz形式定制化mode,从而达到不同品类在Ability上的隔离。

◎ Ability+品类场景配置最终思路

复用:基于品类+场景(N元组)配置化,mode selector灵活决策能力组件的执行模式
差异化:业务通过biz实现mode的定制,配置化动态加载


2.3 应用框架目录结构

php模块目录结构示例,golang模块整体类似

// 模块根目录├── Dukang // 新引入的内容,区隔老代码,未来将替代hermes│   ├── Ability // 能力目录│   │   ├── Common // 公用能力│   │   │   ├── DispatchOrder│   │   │   │   └── DispatchOrderComponent.php│   │   │   └── VirtualPhone│   │   │       └── VirtualPhoneComponent.php│   │   └── Express // 品类特有能力扩展│   │       └── DispatchOrder│   │           └── DispatchOrderComponent.php│   ├── Config // 接口配置│   │   └── ConfirmOrder.json│   ├── InputSource // 接口输入│   │   └── ConfirmOrderInputSource.php│   ├── StepRuntime // 全局数据总线│   │   └── ConfirmOrderStepRuntime.php│   ├── Model   // 公用model│   │   ├── Dao│   │   ├── Driver│   │   │   └── DriverModel.php│   │   └── Order│   │       └── OrderModel.php│   ├── Service  // 公用service│   │   └── Driver│   │       └── DriverService.php│   ├── TransportFactor // Transport因子│   │   └── ConfirmOrderTransportFactor.php│   └── Transport // 流程串接│       ├── Base│       │   └── ConfirmOrderBaseTransport.php│       └── Taxi│           └── ConfirmOrderTaxiTransport.php└── vendor    └── dukang        └── framework            ├── idl  // 数据字典(Dimensions,标准dto)            └── src  // 框架代码

— 本文结束 —


● 漫谈设计模式在 Spring 框架中的良好实践

● 颠覆微服务认知:深入思考微服务的七个主流观点

● 人人都是 API 设计者

● 一文讲透微服务下如何保证事务的一致性

● 要黑盒测试微服务内部服务间调用,我该如何实现?



关注我,回复 「加群」 加入各种主题讨论群。



对「服务端思维」有期待,请在文末点个在看

喜欢这篇文章,欢迎转发、分享朋友圈


在看点这里
浏览 170
点赞
评论
1收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
1收藏
分享

手机扫一扫分享

分享
举报