「一键加电」是蔚来为车主提供的代客加电服务。用户在 App 中下单后,系统的调度模式如下:用户下单后,调度系统会根据用户期望的取车位置 / 取车时间、车辆状态(例如续航里程)等因素,求解服务专员和服务电力资源的组合方案。系统在求解过程中会考虑一系列的地理信息因素(如限行围栏、道路状况等),然后结合服务人员的空闲情况、电力资源的服务能力和再生能力,以及系统的配置,求出服务距离、服务时长综合最优的服务方案。
为用户提供的加电资源主要有两种:换电站和移动充电车。换电站可以在 3 分钟内将旧电池拆下,换上新电池,提供非常好的加电体验。移动充电车则适合于远郊或低电量场景,除换电站和移动充电车以外,在部分资源还没有密集布局的地区,我们也会利用三方的充电桩进行加电服务。
以下是蔚来一键加电的服务流程图:
(其中虚线框表示服务流程,绿色段表示对资源的占用,蓝色段表示对服务专员的占用)。
随着车辆的逐渐交付,线上一键加电的订单需求日益增多。为保障一键加电的服务完成率和准时率,我们有大量的优化问题需要被解决,如:
- 预测 traffic model 的变化可能带来的服务时长、人员利用效率的影响
- 预测不同的人力和电力资源供给情况下,系统的服务能力上限
- 为服务人员的资源布局提出建议
- 评测待上线的算法和调参的效果
这些问题对系统的调度性能提出了离线和可测量要求。
对于包含调度行为的 O2O 系统,服务承载能力难以通过一个简单的模型或者线性公式进行数学推演,直接得出有参考意义的指标。为了更好地评测和调优系统,以及给出一线运营资源的配置建议,我们设计并实现了 Sim Cloud —— 一个可以精确重建线上系统和环境,引入线上流量或自定义流量,估算系统供需能力曲线、评估调度策略性能的仿真平台 。
名词简介
- 服务专员:为用户进行加电服务的专员(以下简称专员)。
- 换电站:蔚来自研的电池更换站点,可以在3-5分钟内为蔚来的全系车辆更换电池,占地约3个正常车位。
- 充电桩:一个连接电网,通过充电插头向电动汽车充电的充电设备。
- 充电桩群:充电桩一般批量建设,一个批量建设并提供充换电停车空间的场所即为桩群。
- 移动充电车:蔚来自研的移动充电设备。充电车自带电池,可以利用自身的电量向其他车辆充电。
- 加电资源:在下文中特指 移动充电车、换电站、充电桩群。
- 资源:在下文中特指专员、移动充电车、换电站、充电桩群。
- SOC: State of Charge,反映电池包内当前电量和总体可用电量之间百分比比值的电量。
- 取车位置:用户下单时指定的专员取车位置。
- ETA:Estimated Time of Arrival,(特定事件的)预计完成时间。
- 服务步骤:包括前往取车、取车地点寻车、前往到服务点、充换电、还车到原定位置等一系列为完成一键加电订单的专员活动。
架构
目标和挑战
如上文所述,我们希望 Sim Cloud 系统可以精确仿真线上环境,回放线上下单流量,达成以下业务目标:
- 模拟线上系统在给定资源数量、状态及分布情况下的服务承载能力。
- 测试线上系统在不同的业务配置和算法策略下的服务能力表现。并通过以下指标,指导运营及开发人员调整配置及调度算法,以达到调度效率与准时的最佳平衡点。
- 系统效率指标 [下单成功率,最大下单成功数,专员移动距离及服务时长]
- 用户满意度指标 [准时率,用户感知服务时长]
- 通过控制变量,模拟出资源的位置对系统效率指标及用户满意度指标的影响,以指导未来资源的地理分布和数量规划。
除了上述业务以外,从工程层面,我们也致力于:
- 提高订单流量的模拟速度
- 降低开发和维护成本
- 保证模拟能力,能满足各种各样的线上事件模拟
要实现上述的设计目标,会面临一些挑战。在整个仿真流程中,确定性仿真是我们最关注的问题,仿真是否有效会直接影响到实验结果。此外,数据安全、仿真速度、重复仿真也是 Simulator 成败的关键。基于此,我们把 Simulator 需要解决的关键问题归类如下:
- 确定性仿真
- 一个订单从开始到结束的一个完整生命周期内,模拟器需要根据每一步的状态决定下一步的操作及时间。
- 在保证安全性和速度的情况下拉取线上数据,尽可能真实的还原线上状态。(专员的排班,服务能力,服务城市,使用的工具,上班位置,加电资源的分布,状态,在各个系统中关于专员、加电资源、用户车的状态及位置)
- 用户车、专员、用户、及其手机号等机密信息,需要在使用之前替换掉,并保证所有依赖数据同步。
- 有些服务由于需要车机联网或者证书等依赖,模拟环境无法调通,需要mock。
- 加快模拟器中的时间流速
- 重复仿真:仿真线上环境在不同的业务配置和算法策略下的服务能力表现,需要有重复还原线上环境的能力。
整体流程介绍
Sim Cloud 进行一次完整仿真有以下的几个步骤
- 拉取 / 准备数据
- 初始化环境
- 回放流量 / 事件
- 效果评估
下面逐一介绍每个过程要解决的问题和方法:
拉取 / 准备数据
Sim Cloud 目前能根据两种方式生成实验数据输入(Simulation Input):线上真实数据的分布 / 实验者自定义分布。通过指定不同的数据生成配置,实验者可以使用数据生成器,生成不同的实验数据输入。
对于不同的数据生成方式,我们通过不同的 generator 来生成对应的 simulation input data。以线上流量分布生成为例,generator 会生成以下四类数据
- 专员:拉取线上专员的排班、岗位数据
- 加电资源:拉取线上加电资源数据,包括移动充电车、换电站、充电车的状态、位置、服务能力配置
- 下单意图:拉取线上订单信息,将所有有下单行为的用户和车辆信息,使用资源库数据进行脱敏,并增加下单时间
- 系统及业务配置:拉取将线上核心系统的配置快照
通过以下的方式,我们就能开始将服务专员、加电资源、系统及业务配置,用户车信息等同步到仿真环境。
对自定义的供需分布,我们可以产生根据参数,生成确定性的输入和输出,并允许用户手动调整最终的结果。
初始化环境
环境的初始化主要包含两个步骤:
- 将业务数据注入到 Sim Cloud 环境(用户 / 车辆 / 服务人员 / 电力资源)
- 初始化 Sim Cloud 各子系统的配置
完成后,Sim Cloud 能感知到的环境上下文就完成构建了。
回放流量 / 事件
动态事件模拟
由于拉取线上的数据是瞬态的,用线上数据初始化仿真环境的做法对于某些动态事件(例如专员上下班、绑定/解绑工具、系统派单、服务步骤、用户下单、下单时的用户车SOC及位置变化)就无能为力了。根据发生的原因这些事件可以分为以下两种:
非依赖事件:不依赖其它事件,到固定的时间就会执行。比如专员上下班,系统派单。
依赖事件:依赖其它事件,比如服务步骤
里面的几个事件都依赖前面的步骤,毕竟取车失败
也就不会有后面的到服务点加电
事件了。
我们使用一个按时间自动排序的 Event Queue 来模拟动态事件。在 SIM 运行之前,会初始化一些非依赖事件以及下单事件到 Event Queue 中。
SIM 运行时内部会维护一个当前时间(与现实的当前时间无关),每次都会将预期该时间执行的 Event 取出并执行。那么对于依赖事件,比如服务步骤中取车
Event 执行完并且成功,会根据返回向 Event Queue 放入服务步骤的下一步及其触发时间。当 Queue 中没有 Event 时,SIM 运行也随之停止。
效果评估
在仿真完成以后,会进入效果评估阶段。
效果评估和线上的指标统计 pipeline 使用相同的 code base,通过从业务数据的存储介质中拉取数据,对整体的指标进行汇总计算。
Tricks
在开发 Sim Cloud 的过程中,遇到过不少痛点,这些困难在所有仿真系统都可能遇到,这里也分享一下我们的方案。
依赖管理
为了准确定义系统行为和获取评测数据,仿真环境最好能提供系统层面和数据层面的隔离。这里就存在系统要完整跑通时,依赖系统过多的问题。一键加电业务的核心模块约有10个,而其他相关的服务子模块 / 微服务大概有 40 多个,涉及的后台基础设施(例如存储)可能会更多。部分系统也有跨组或跨部门的依赖,环境的协调和搭建会比较耗时。
目前我们进行了几个改动,尽可能减少依赖:
- 对于非核心模块的调用,假如不影响系统调度行为,则 NSC 中配置开关进行降级。线上开启,仿真关闭。关闭时使用默认降级行为进行处理。
- 部分难以跳过,但又不甚稳定的模块,自己进行 mock。
经过一段时间的优化,要单独维护仿真环境的模块下降至 5 个左右。比起 40,属于比较理想的状态了。
有同学可能会问,有没有考虑过线上压测的方式?这里有几方面的考虑:
- 离线仿真和线上压测的频率不同。线上压测可能只做一两次,每次用特定流量进行测试。离线仿真会抢时间做实验,一系列的离线实验可能排队 7*24 小时高速运转,所以产生的数据量会比压测大很多。
- 离线的实验会对调度逻辑进行改动,这部分的代码更多是为了验证想法,花在性能优化和业务逻辑的回归覆盖上的时间有限。即使在只加流量的场景中,相比于离线仿真,线上压测也会增加线上系统的稳定性风险。
- 数据标注。线上压测的情况下,全链路都需要对数据进行标注,避免统计数据产生偏差。
当然单独维护一套环境也会带来额外的维护成本,例如线上版本的更新就会带来 Sim Cloud 的事件队列处理和线上版本的兼容性维护工作。
仿真批量编排
想要高效逼近优化目标,需要有能力编排出一系列的仿真,对不同的调度策略和参数配置进行验证,甚至参数的自动化调整。Sim Cloud 在前端提供了类似 DSL 的支持,实验者可基于自己的想法或需求,生成一系列用于各次仿真生成仿真环境的配置,然后批量提交给仿真管理器。仿真管理器接收这一批次的仿真以后,就能根据实验者的指示自动触发。
举个例子,当需要估算线上服务承载能力极限的时候,我们需要不断调整输入单量和分布。这种情况下,我们可以先指定系统拉取最近 10 个周六的线上流量,然后指定 10 个采样比例,然后编排 10 个实验,每个实验都将采样后的流量压缩到一天,统一提交仿真测试。所有仿真完成以后,在报告页面查看各次仿真的结果对比。
确定性
我们对仿真的基本要求是相同的输入能够得到相同的输出。对于 O2O 的业务系统,有较多的自由度或随机性,需要被管理或规范化,例如:
- 根据数据分布生成仿真事件时的随机性。指仿真发起者在指定输入数据分布时,要确保相同的输入产生相同的输出或采样结果,这一般可以通过指定随机数种子保证。
- 调度系统中实现层面的随机性。指对于相同的上下文环境,系统需要从实现层面上保证确定性的输入和输出的对应一致。这是对业务系统的正常要求,但在并发且对资源存在竞争的情况下,也需要有能力保证。
- 仿真系统中定义的事件和调度系统的时钟系统不一致。事件的触发时间和真实输入仿真系统的时间肯定是不一样的,因此仿真系统需要有能力在系统内部指定和应用事件的「发生时间」。这里我们对每个仿真事件都增加了时间信息,而调度系统和资源系统的全部接口通过 AOP 增加了
custom_timestamp
的公共参数用以接收外部的特定时间信息。然后,调度系统封装出一个获取当前时间的 Helper 函数,当调用中存在custom_timestamp
信息时,Helper.getCurrentDate()
会返回自定义时间,而非new Date()
,这样可以无侵入地让系统增加指定接口调用时间的能力。为了避免人为的疏漏,我们还增加了对new Date()
代码检测的单元测试,确保没有新代码因为不熟悉此规则进入主干分支,静默引起不确定性。
仿真效率
- 上述的独立时钟系统的设定,也能起到加速作用 —— Simulator 不再需要等待真实时间的消逝,只需要按照时间戳维护好事件队列,就可以通过按序执行事件,得到和真实世界相同的时序和数据变化。这能够大大加速仿真的执行效率
- 和很多时间敏感性型的功能(例如爬虫)一样,Sim Cloud 不希望自己的时间是有闲置的,而效果评估(Spark Job)部分,因为依赖仿真产出的业务数据,而本次仿真产生的业务数据,又应该在下一次仿真发起之前清除。所以假如要基于业务系统的存储进行评估,就会阻塞下次的仿真的触发。为了提高效率,我们将业务数据先 dump 到一个额外的存储上,然后并行通知:1. Evaluation 模块利用额外存储数据进行评估;2. Simulator 清理上次仿真生成的数据,并开始进行下一次仿真。统计和仿真互不阻塞,这样就为连续仿真节省了较多的时间。
小结
Sim Cloud 系统目前的特性:
- 高精还原线上环境,能指定回放特定的下单流量,评估系统效率指标
- 仿真速度快,一天订单流量在 5 分钟以内就回放完成
- 提供实验管理器,支持基于配置的参数化实验
- 提供不同实验之间的对比功能
蔚来的「一键加电」项目在 2018 年获得了蔚来年度「超越期待的用户体验」价值成就大奖。在不断优化调度策略、缩短服务时长方面,Sim Cloud 也发挥了举足轻重的作用。