阿里妈妈内容风控模型预估引擎的探索和建设
作者:徐雄飞、金禄旸、滑庆波、李治
内容作为营销的重要载体,能够促进信息的交流和传播。在营销场景中,广告高曝光的特性放大了风险外漏带来的一系列问题,因此对内容的风控审核就显得至关重要。本文将为大家分享阿里妈妈内容风控模型预估引擎的探索和建设。
一、业务背景及问题
1.1 业务背景
内容作为营销的重要载体,能够促进信息的交流和传播。在营销场景,广告内容需要满足广告法要求,常见的风险类型有两类:一类是面底线的 A 类风险,如政治等,一类是面向平台调性,广告法要求的 B 类风险,如低俗不雅、公序良俗等。一旦风险外漏,由于广告高曝光的特性,会导致舆论压力,甚至有业务关停的风险。
阿里妈妈内容风控核心由机审、人审两部分组成,其中机审防控包括增量和全量防控(链路如下图所示),机审与人审环节都依赖模型服务,由于广告数据和主站存在差异,且风控的强对抗性、模型都自研实现。随着模型不断增多,模型的线上服务部署和管理变的越来越臃肿,出现诸多问题。
1.2 现存问题
现有的模型预估引擎(Inference-kgb)由于历史业务演进、临时性支持妥协,随着模型数量不断增加问题逐渐暴露出来,下面从能力、效率、质量、成本四个方面详细分析现有的问题,为我们重构新的引擎打下基础。
1.2.1 能力问题
缺乏统一的加速方案:仅部分模型通过 GPU 进行加速,大部分模型性能较差;异构混部模型缺乏通用的接入、导流、蓄洪和限流等服务特性和运维特性。
一致性问题:离线和在线模型结果一致性难以保障,没有形成通用方案;跨平台模型 Case by Case 上线,没有统一的服务 API;运维干预纯靠人工经验,无可靠的管控 API。
1.2.2 效率问题
研发周期长:Inference-kgb 框架底层采用 C++实现,算法同学上线前需要将训练的 Python 代码翻译成 C++代码,开发周期长且部分库无法直接翻译需要手写,会导致实验结果和线上不一致。
部署上线流程复杂:每个模型上线都需要代码开发,像分类模型代码及配置都相同只更换模型文件,由于框架限制需要走完开发、测试、部署、上线全流程。过去模型部署链路在/近/离线是分别管理的,模型上线过程中需要跨多个平台进行配置,操作繁琐。每次模型上线需要通过邮件申请 Metaq(阿里内部消息中间件)消息,等 Metaq 同学回复邮件后才能够继续发布流程;研发流程无法保障多人协作安全、高效。
1.2.3 质量问题
缺乏统一的接入标准:早期整个框架是完全自研实现,可参考的方案较少,不断演进之后与业界方案存在差异。如缺乏统一的 Tensor in Tensor out 模型层面无业务语义的接口,用户接口设计不合理,接口核心字段为 imageUrl 和 textString 导致很多字段都通过拼接的方式传入 textString 中;输入非结构化存在诸多问题,如离线冷启动执行模型时,需要手动按照线上逻辑进行字段拼接。
子模型问题:将多个通用分类、检测模型部署在一个服务中,然而服务及运维属性都是按照模型粒度设置的,无法根据子模型定制:如 Cache、计算资源等是针对整个服务维度生效的,无法根据子模型进行精细化控制。
同质代码缺乏复用:如检索类模型提取向量特征之后需要进行检索库匹配操作,过去的服务框架将特征提取和检索融合在一起,由于检索库的差异会导致部署模型数量膨胀。
1.2.4 成本问题
GPU 利用率低下:部分大流量的模型之前没有 GPU 版本,GPU 模型普遍使用率比较低。离线无法充分利用资源提取特征,由于模型缺乏 GPU 镜像,导致离线提取特征是无法充分利用智能引擎(MDL)资源池、主搜(Hippo 资源调度)资源池的离线 GPU 资源。
二、业界对比选型
基于以上问题,我们考虑对现有模型预估系统(Inference-kgb)进行重构,优先考虑复用集团或开源现有的能力,因此我们对集团及开源方案进行了调研。
2.1 业界方案对比
分别调研阿里云 EAS、达摩院 Aquila、CRO 灵境、阿里妈妈 HighService、开源 TritonServer。
2.2 业务诉求
以上平台/框架,有些包含完整的模型部署、运维功能,有些只包含模型加速功能。我们尝试推演将模型整体部署到平台上,遇到以下几个问题:
资源互通问题:以上平台(如 EAS),需要在 ASI(Alibaba Serverless infrastructure)上统一申请资源,现有的 Hippo 资源无法直接迁移过去使用。
离线计算问题:目前已有平台解决的基本都是在线服务部署的问题,由于风控业务特性,我们遇到风险事件时需要在离线环境进行大批量的特征提取及全量数据的风险防控,在指定时效内完成风险清理,这部分没有现有能力支持。
缺乏偏业务语义的功能:针对偏业务语义的功能,平台没有很好的支持,如模型对应的风险管理,版本迭代后的业务效果,模型结果复用等。
考虑到业务诉求及已有能力最大程度复用,核心构建上层偏业务的服务能力,底层能够接入 EAS、HighService、Aquila 等多种 Backends,进行模型服务和加速。
三、模型服务框架
风控业务场景,模型服务核心解决机审风险识别及人审工单拦截等相关问题。模型分别服务于在/近/离线业务,引擎内核复用同一套逻辑,确保在/近/离线数据一致,底层接入多种 Backends,对模型进行部署、加速。可以在各个调度器及资源池中进行计算。重构后新的风险预估引擎 RD(RiskDetection)层次结构如下图:
RD 内核部分,参考 NIVIDIA Triton Server 实现(NVIDIA 开源推理框架),我们定义了一套标准业务接口,支持多 Backends 对接的在线服务。其中 Standard API 支持 Tensor in Tensor out 标准模型协议,由 Model、Version、Tensor 标准三元组构成,模型内部支持动态 Batching 特性。通过对接集团及开源社区已有的 Backends 低成本的实现模型预估能力。
3.1 标准化接口
接口定义分为面向业务及面向数据两个维度,面向业务包含图片、文本、视频等内容识别输入输出接口;面向数据面,我们与主流的 KServe(开源云原生模型推理框架)对齐,采用 Tensor in Tensor out 进行模型预估逻辑。
3.1.1 业务接口
业务侧支持图片、文本、视频等素材类型,包含检测、识别、分类、检索等模型服务。过去的系统(Inference-kgb)所有的模型都共用一个接口,输入是图片、文字,输出是 Map 对应的 K,V。系统运行一段时间之后,发现输入、输出变的越来越复杂,于是在字段上进行各种拼凑,导致输入、输出变的越来越复杂。吸取 Inference-kgb 的经验教训,也参考阿里云等开源接口设计,我们针对不同类别的模型进行独立接口设计。
3.1.2 数据面接口
在模型内部,都是以 Tensor 数据格式进行传输,我们支持调用方通过 Tensor 接口调用模型服务,通过 TensorFlow、KServe 以及内部 EAS 对 TensorFlow/PyTorch 模型的服务接口抽象,定义我们自己需要的 TensorFlow/PyTorch 模型服务标准 API 如下图:
3.1.2.1 Predict 接口
1)TensorDataType:TensorDataType 用于标准化指定 Tensor 的数据类型。
2)TensorShape:用于标准化指定 Tensor 的维度。
3)TensorDataContent:内部的 EAS 和外部开源的 TensorFlow 其实都没有 TensorDataContent 这个抽象,都使用的完全平铺的方式定义 Tensor 内容,KServe 最新版本对 TensorData 做了抽象,是能够包含 EAS 和 TensorFlow 的定义的,因而采用。
4)InferParameter:InferParameter 这个数据结构,内部的 EAS 和开源的 TensorFlow 都没有定义,但是 KServe 最新版本的 API 定义里预留出来了,目的是未来能够基于标准化的 Tensor 定义支持跨多个不同的非标准化推理服务平台后端,通过拓展参数增强跨平台的可移植性。考虑到内容风控未来线上模型也会对接多个非标准化 Backend,所以考虑在前面统一的 API Protocol 标准化 Tensor 结构定义的基础上,也引入 InferParameter 以实现跨平台的对接能力。
5)TensorEntity:把 TensorDataType、TensorShape、TensorDataContent 和 InferParameter 组合,得到一个标准化定义的 TensorEntity 数据结构。
6)PredictRequest:如前面所述,我们把原子的 Tensor in Tensor out 的推理服务命名为 Predictor,PredictRequest 则是每个 Predictor 角色的标准化输入接口入参,这里提取 EAS、TensorFlow 和 KServe 各自有用的部分组合而成。
7)PredictResponse:PredictResponse 核心返回数据结构是 TensorEntity,同时把 PredictRequest 原始请求的模型信息、拓展参数信息也带回来(参考 KServe 设计,原生 TensorFlow 和 EAS 没有)。
3.1.2.2 Metadata 接口
1)TensorMetadata:Tensor 的元数据由 Tensor 名、Tensor 数据类型和 Tensor 维度组成的三元组表示。
2)ModelPlatform:后端支持的模型框架平台类型,目前支持原生的 Tensorflow、PyTorch、TensorRT 三类,以及集团内部 HighService、EAS、Aquail 等。
3)ModelMetadataRequest:ModelMetadataRequest 是用于请求模型推理服务元信息的标准化抽象接口,由模型名(name) + 模型版本(version)构成二元组。
4)ModelMetadataResponse:根据 ModelMetadataRequest 指定的模型名+模型版本二元组获取详细的模型推理服务信息,主要由传入的模型名、模型版本、输入 Tensor 元数据和输入 Tensor 元数据这四部分组成。
3.2 RD 内核技术方案
RiskDetection:提供标准的模型用户接口,提供模型服务特性和运维特性,串联模型前处理、模型预估、模型后处理阶段。
Predictor:提供模型标准数据面 Tensor in Tensor out 接口,兼容多种 RTP 协议的 Backends。Predictor 逻辑上是拆分成独立的服务,物理实现为了减少 I/O 请求,可以考虑和前后处理部署在同一台机器上。
Transformer:Transformer 用于模型服务的前后置处理,它和 Predictor 共同完成一个完整的模型推理请求。Transformer 一方面可以通过拓展前处理逻辑把外部输入转换成 Predictor 接收的标准输入格式,另一方面可通过拓展后处理逻辑把 Predictor 返回的结果做进一步转换供给业务方应用。
Backends:底层具体采用的模型服务框架,包含集团及开源解决方案。
3.3 数据一致性保障
风控业务包括了在/近/离线场景,不同场景对资源、时效、吞吐等要求不一样,各场景提取出来的特征需要保障最终一致。对一致性保障可以划分成两个层级:特征完全一致、业务效果一致。
1)特征完全一致:高保障业务场景,对风险判断要求更严格,我们需要确保模型输出 Tesnor 保持一致,因此在/近/离线需要保证镜像、模型文件版本、计算资源完全一致。
2)业务效果一致:在一些保证级别要求不高,且计算量大的场景,我们允许在/近/离线计算资源不一致,如在线采用 GPU,离线采用 CPU(GPU 通过 TensorRT 半精度加速后结果有损),只需要在业务效果上保障一致即可。
四、模型推理加速
过去的 Inference-kgb 基于原生 TensorRT 进行模型加速,随着模型迭代更新越来越频繁,每次算法同学训练模型到上线都需要将 Python 代码用 C++重新实现一遍。通过对集团及开源方案调研,现有的框架很多都支持 Python 直接进行模型部署、加速,能够解决 Python 语言带来的 GIL 锁冲突问题,新的框架我们采用 Python 进行模型预估开发,并且能够支持多种模型加速 Backends(如 HighService,EAS,Aquila 等),可以直接复用已有能力,无需从 0 开始重新搭建模型推理加速功能。由于整体框架初见雏形,我们先接入了 HighService 和 EAS 两个 Backends,支持我们核心模型的落地。
4.1 HighService Backend 接入
HighService 是阿里妈妈异构计算团队内部开发提供的,基于 Python 多并发异构计算服务框架。HighService 框架通过解耦 GPU 及 CPU 计算,使用多进程进行 CPU 计算,充分利用多核 CPU 资源,在多个 CPU 进程与单个 GPU 进程之间通信,避免 GPU 进程饥饿,同时 CPU 进程数不受 GPU 大小内存限制。内部集成了 TensorRT 加速功能,成倍地增加了 PyTorch 模型的计算能力。使用 HighService 框架服务的动机,除了它的高性能以外,还有一个重要原因,就是利用 Python 模型易于迭代、维护、定位问题的优势,可以更好地服务业务,解决了业务方研发周期长的痛点。相比于过去的 Inference-kgb 框架,利用 HighService 的 RD 框架上线一个 GPU 模型的研发周期得到大幅度提升。
4.2 EAS Backend 接入
EAS 广泛在集团使用,它与 PAI 平台(模型训练平台)无缝衔接,并且底层拥有完善的 Blade 加速方案,服务和运维特性非常全面。在与 EAS 同学深入沟通后,发现由于计算资源池及离线业务特殊性等原因,无法直接将服务完全托管。但 EAS 提供的 SDK 能够给支持模型的部署、加速能力,这部分能力可以直接接入 RD 作为 Backend,接入后基于镜像部署可以同时支持在/近/离线的业务。
EAS 主要的接入方式有两种:Processor 方式接入和 Mediaflow 方式接入,Processor 方式需要将模型部署在 EAS 平台上,能够方便的部署模型,并且提供标准化的 Tensor 接口;由于我们无法直接将模型部署到 EAS 平台,因此我们采用 Mediaflow 的方式接入。具体实现 demo 如下:
Mediaflow 方式是 EAS 团队今年新研发的方式,可以将整个模型流程采用 DAG 进行串联(也可以是单个节点),能够轻量级接入 SDK,并且享受到底层 Blade 加速效果。
五、业务支持及效果
完成 RD 系统重构,我们进行压测及效果验证达标后,开始对接业务。目前 RD 主要服务于阿里妈妈内容风控在/近/离线模型计算,为内容风控机审风险识别和人审提效服务。在/近线都是常驻型服务,拉起后提供 HSF 服务接口,离线是通过批量任务的方式启动。
5.1 在/近线业务支持
对比过去的 Inference-kgb 服务拉起配置较为繁琐,需考虑了多个子模型 DAG 之间的串联,采用 OP 算子进行关联。每个模型上线都需要开发 OP,哪怕仅更换模型文件拉起不同的服务。我们将子模型组装 DAG 前置到业务编排组件,RD 就是纯粹的进行特征提取、模型计算,因此在服务拉起及服务运维方面有较大提升。
5.1.1 快速服务拉起
拉起服务主要依赖 镜像+模型文件+模型配置 三元组,三元组唯一确定一个模型服务,因此快速拉起服务的首要任务就是管理服务依赖的三元组。
镜像:RD 所有支持的模型都共用相同的镜像,同时支持 CPU、GPU 模型运行,这样方便整体的管理和运维。统一镜像也会带来一些负面作用,如修改 A 模型可能会影响 B 模型,这个需要我们在开发过程中,修改到公共部分时特别小。需要借助单元测试、集成测试等标准化流程规避此类问题。
模型文件:统一管理在 OSS 上,由模型名称+版本号唯一确定一个模型文件。可以根据模型文件恢复历史任意的模型服务。该功能为后续做模型效果对比等功能提供底层能力支持。
模型配置:吸取过去的 Inference-kgb 复杂模型配置教训,RD 极大的简化配置内容,将配置分为静态和动态两部分。静态模型配置:如模型代码路径,模型 Backend 配置,模型是否需要 Cache 等,这些配置与部署的模型实例无关。如部署 GPU、CPU 版 Face,以上配置都是相同的。动态模型配置:如服务接口、模型版本、资源类型等,每个部署实例有不同的配置。动态配置能够方便的启动相同模型的不同服务版本,能够支持业务进行效果对比或者 CPU、GPU 混部等能力。
如上图,通过以上设计我们能够更方便的针对同一个模型启动各种版本,使用不同类型和保障级别的资源,支持各种隔离级别的业务。
5.1.2 模型服务/运维特性
参考集团及开源方案,核心服务/运维特性主要包括 Caching、Batching、Scaling,支持以配置化地方式拓展服务/运维特性从而拓展系统处理能力,主要特性描述如下:
5.2 离线业务支持
通过在线防控,我们能够解决增量风险问题,然而如果有风险外漏,或者监管调整,我们都需要对全量数据进行风险防控。同时算法同学需要在离线进行大量实验、评测,这些都依赖于离线具备大规模模型计算能力,因此 RD 需要具备离线计算的能力。
5.2.1 离线任务对接
Starling 是阿里妈妈风控自研的基于主搜 Drogo(资源调度管理平台)之上进行业务调度平台,底层使用主搜 Hippo 资源池。风控场景在离线需要提取百亿级图片、文本特征进行实验或者风险防控,模型计算需要大量的计算资源,Hippo 资源池拥有海量非保障(离线)资源,Starling 的初衷是可以削峰填谷地使用这些资源支持业务特性。
RD 对接上 Starling 之后,便拥有了离线调度计算的能力,能够灵活的拉起 Hippo 资源,并与 ODPS(对外产品为 MaxCompute)打通,对 ODPS 数据进行读写。同时能够通过 ODPS 进行任务调度,进行定时增量数据特征提取。
如上图为 RD 与 Starling 对接的架构图,Starling 的 Master 节点管理和调度 RD 进行模型计算,和模型配置文件分发,Starling SDK 实现 ODPS 对接及 Slave 节点状态上报。
上图为通过 ODPS 配置 Starling 任务节点进行调度,实现定时特征提取功能。
5.2.2 单容器多实例部署
对于模型计算而言,离线与在线存在的核心差异是数据源及消费方式,在线是流式源源不断输入,离线则是固定数据集运行特定的模型。一个任务计算量是亿甚至百亿级别,内容场景有很多图片模型。考虑到拉图成本及多模型任务依赖的管理成本,我们通过单容器支持多个模型运行的方式,节约拉图 60%左右,以及简化任务配置实例。
支持单模型多实例部署的前提是模型之间没有相互冲突,所以 CPU 资源上更容易实现,GPU 资源由于 16G 显存的限制,很容易 OOM。CPU 也需要根据模型大小、内存申请等控制实例部署个数,例如 30G 内存部署 3 个模型。以下是合并前和合并后任务节点的配置,可以发现合并之后节点明显简洁得多。
5.3 业务效果
RD 上线在/近线迎来首个双十一考验,离线需解决大规模特征提取问题,无论是性能、效率都需要有显著提升,能够给业务带来可感知变化,接下来分别介绍在/近/离线因为 RD 上线带来的相关改变。
5.3.1 离线清理支持
离线部署 6 个核心模型支持全量及增量数据提取任务,通过 Starling 使用非保障资源批量提取增量百万级图片特征可在 20 分钟内完成。
5.3.2 双十一业务支持
大促结束后平台要求广告主在规定时间内下线双十一相关内容,因此相当于整体的广告内容这个时刻开始全量更新一次。
RD 目前对外提供的接口是同步请求方式,QPS 超过模型服务单机承载能力(CPU/GPU Load 打满)时,会造成模型服务 RT 成倍增长影响业务稳定,因此需要对每个模型进行精确限流。内容风控模型服务众多,从模型部署角度上讲:同一个模型可能跑在 GPU 或者 CPU 上,也有可能部署在不同机房(机房拉图带宽有上限,需要多机房部署),不同集群之间有 Backup 的关系(比如 GPU 集群被限流则自动将流量导入到 CPU 集群),从业务角度上讲:对于高优先级场景中的底线类风险需要百毫秒级以内得到模型结果,而一些低优先级的体验类风险则可以使用一些便宜的机器进行计算,能够承受一定 RT 延迟。仅依赖 HSF(阿里 RPC 框架)的限流配置或 Sentinal 限流(阿里分布式限流框架)组件远不能满足这种异构部署和业务上多样化的导流需求,因此我们自建了 InferenceProxy 服务来支持基于业务标签的 QoS 和导流能力,在保护下游 RD 服务能力的同时,对物理层面的调用关系进行配置化的编排。
导流表其中几项配置如下:
由用户自定义的一段逻辑从请求原始 Payload 提取信息并打上标签,例如上面这个请求会打上 label=1125 这个标签。执行时会首先查出符合条件的所有导流项:1、2。按照同优先级+权重的方式分配流量,详细来说:其中 1 和 4 的 priority 都是 0,1 的 weight 是 10, 4 的 weight 是 50,则这个请求以 1/6 的概率打在服务 1 上,以 5/6 的概率打在服务 4 上。
如果请求 priority=0 的服务失败或超时,则会打到 priority 等于 1 的第二项服务 2 上,仍旧超时或失败,则该请求会进入离线兜底队列。参考导流表设计,一个请求到来时,会筛选出符合条件的几个导流策略,按照 priority 进行排序分层,各 priority 之间使用责任链模式串联,如果请求失败或超时,则进入责任链的下一个策略 priority 进行执行。
核心类图:
过去的 Inference-kgb 由于性能问题,TOP3 请求的模型存在严重的性能瓶颈。RD 上线后,我们可以同时支持 GPU 版和 CPU 版在线上运行,性能相比线上 CPU 版有了几十倍的提升,通过 InferenceProxy 进行模型服务接入,能够精准的控制机器和流量配比。
通过 InferenceProxy 切流方案,我们能够将新的 RD 安全稳定的接入到在线服务中,承载 TOP3 模型流量,优雅解决了双十一大促过程中模型积压的问题。切流链路如下所示:
六、未来展望
6.1 能力方面
模型推理加速:完成 Inference-kgb 迁移 RiskDetection GPU 加速,并且持续打磨 HighService 及接入更多 Backends,让模型接入更加简单方便,形成标准化接入流程;新增 CPU 通用加速流程,形成 CPU 标准化加速方案,并在 ID 模型上落地;调研 PPU 加速方案,尝试采用 PPU 解决在/近/离线资源瓶颈问题。
服务运维特性:完善核心三大服务及运维特性:Caching,Batching、Scaling;其中 Batching、Scaling 依赖 Backends 提供相应的能力,ID 实现通用配置化管理。
6.2 效率方面
模型源数据管理:模型文件、镜像、配置进行标准化管理,在/近/离线复用同一套管理标准。
简化模型发布流程:借助已有平台标准化模型效果验证、配置、部署、回滚、灰度发布流程。
6.3 质量方面
代码质量:由于 Drogo 多 Role 部署不能直接按照 Aone 发布流程部署,需要手动在 Drogo 上部署,导致 Aone 上设置的 CodeReview、单元测试等卡点无法直接生效。因此我们需要制定发布标准,并严格按照标准执行。
服务质量:标准化开发、测试流程,对模型服务质量、代码质量、服务质量提出明确要求,并遵守标准规范,不合规范不允许上线。完善系统监控,报警信息。
6.4 成本方面
提升资源利用率:通过 CPU、GPU 加速、Batching 等功能提升系统性能,从而达到减低计算成本的作用。通过弹性缩扩容落地,更加合理使用资源提升整体资源使用效率。
对模型类型约束:相同类别的模型尽量复用同一套代码(如分类模型)限制模型类别,不过度限制模型数量。类别固定有利于模型更方便的复用,减低开发、运维成本。
降本增效:持续度量模型 ROI,替换低 ROI 模型及策略。
相关阅读
[01] 云原生模型推理服务框架-KServe
https://www.kubeflow.org/docs/external-add-ons/kserve/kserve/
[02] NIVDIA 模型推理服务框架-TritonServer
https://developer.nvidia.com/nvidia-triton-inference-server
[03] 机器学习平台 PAI-EAS
https://www.aliyun.com/product/bigdata/learn?utm_content=se_1012296429
[04] 达摩院图片文字识别服务-读光
https://duguang.aliyun.com/experience
[05] NIVIDA GPU 加速-Multi Process Service
https://docs.nvidia.com/deploy/mps/index.html
[06] 云原生大数据计算服务-MaxCompute
https://help.aliyun.com/product/27797.html
[07] 阿里中间件 Metaq 介绍
https://www.jianshu.com/p/9860a09153f1
[08] 阿里妈妈 AI 技术路线简介
https://baijiahao.baidu.com/s?id=1702157600438083362
[09] 阿里搜索资源池调度平台-Hippo
https://blog.csdn.net/hahachenchen789/article/details/80848925
[10] 阿里搜索调度管理平台-Drogo
https://www.pianshen.com/article/6481701214/
[11] 揭开阿里巴巴复杂任务资源混合调度技术面纱-ASI
http://gxsns.yfsoft.com.cn/5200.html
[12] 阿里巴巴开源分布式限流框架-Sentinel
https://sentinelguard.io/zh-cn/docs/introduction.html
[13] Dubbo 和 HSF 在阿里巴巴的实践
https://xie.infoq.cn/article/4d94b3eb548b2aed1962dd0ad
版权声明: 本文为 InfoQ 作者【阿里技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/2f9827a4384d7ce6b66987107】。文章转载请联系作者。
评论