王洋:猫眼电影商品业务线技术负责人、技术专家。主导了猫眼商品供应链和交易体系从0到1的建设,并在猫眼与美团拆分、与点评电影业务融合过程中,从技术层面保障了商品业务的平稳切换,同时也是美团点评《领域驱动设计》课程的讲师。在加入猫眼电影之前,曾就职于蚂蚁金服,参与了阿里网商银行从0到1的建设,以及支付宝钱包、花呗等产品的研发。

导读:互联网电影行业在2016年经历了较大的变动,其中包括猫眼电影和原美团的拆分,以及猫眼电影和点评电影业务的融合。业务发生大的变化时,技术通常也会做出较大的重构,猫眼后台技术团队在整个拆分、融合的过程中,对系统架构、领域模型进行了比较多的调整和思考,探索出一套成本和收益较为平衡的技术方案。本文将分享实践的具体过程、步骤和方法,希望给后互联网时代,遭遇业务拆分和融合的技术团队提供一些参考案例。

一.问题的提出

具有一定规模的互联网公司,通常会有很多的细分领域和垂直业务,为了提高效率,大部分公司都会采用平台化的思路:建设一套通用的基础平台,所有业务线基于这套基础平台搭建自己的业务和技术服务。这种做法一方面可以缩减人力成本,另一方面也可以提高新业务的开发效率。猫眼的商品业务一开始为了快速发展,就采用了这种发展模式,将整个业务的交易都搭建在美团的团购体系之上,如图1所示:

从上图可以看出,商品业务完全依赖于美团的商品、订单、支付、促销、劵服务,耦合性非常高。由于当时美团和猫眼是同一家公司,这种耦合性是可以接受的。但后来猫眼独立了,组织架构、业务规划出现了较大的不同,这种耦合性就变成了商品业务后续发展的一大难题。

基于独立发展的考虑,猫眼需要将商品业务从团购体系拆分出来,并和点评电影的商品业务融合在一起,组建一套新的业务和技术体系,用来支撑后续的业务发展。

经过大量的现状分析和调研之后,团队确定了此次技术升级的目标:

● 产品需求不能中断。
● 最低的客户感知度。
● 新旧体系可以自由切换。
● 最低的试错成本。
● 持续分阶段交付、验证。

电商体系从供应链到交易再到结算,复杂度已经很高,再加上业务切换、数据整合的时间,必然会是一场持久战。为了减少风险,让商务、财务等团队更好地配合我们,技术团队必须分阶段产出,做线上验证。

确定上述的5个目标之后,接下来我们就要思考如何围绕这些目标设计整体的方案。

二.技术拆分方案

首先,我们来分析一下业务场景,图2是商品业务的简图:

从图2可以看出,整个业务可以被拆解为四个大的模块,这样我们就粗略地确定了团队的分工:供应链、交易、消费、消费后服务。确定好团队的组成,接下来我们需要对服务的重要性和优先级做一个明确的划分。分析的维度如下:

● 基础数据层面,如商家、用户、合同,这些是公司长期积累下的资源,短期内不可能重建,所以这个层面的服务,尽量复用以前的服务。
● 核心服务层面,供应链的核心是上单,交易的核心是商品、订单、支付、促销,消费的核心是物流、商品券和与之对应的网关服务,这些都是交易的主流程,必须重建。
● 财务层面,如对接商家的结算,以及反应业务运转情况的账务,在公司拆分时,通常是需要独立核算的,所以这个层面的服务也必须重建。
● 非功能性服务,例如客服、售后,这些服务通常优先级不高,但是没有的话,会影响用户的体验,所以尽量考虑复用以前的服务,后期再考虑重建。
经过上面的分析,我们确定了一期工程需要重建的服务。

2.1 拆分的前置方案

确定了需要重建的服务之后,还不能急于开发,要提前思考如果所有的服务都已经有了,该如何做切换。需要考虑的事情有这么几点:

● 切换期间,商品如何售卖。

在前面我们确立了“新旧体系可自由切换”的目标,所以在切换过程中,需要保证商家和运营上一次单,就可以在美团、点评、猫眼三个体系中售卖,这样可以提高切换的灵活性,减少切换带来的交易损失。

● 版本问题。

前文已经提到,商品业务一开始是在团购之上搭建起来的,客户端调用的大部分接口都是美团团购的接口。最直观的做法是在团购接口中加上判断,将电影品类的交易请求导流到猫眼这边,这种做法速度快,且没有流量损失,但以后修改接口参数、扩展业务的负担比较重,可维护性比较差。

● 切换的方式和粒度。

为了达成“新旧体系可自由切换”和“最低试错成本”的目标,在切换过程中,必然会处于一个新老并存的状态,所以我们不能简单地替换接口,而要采用更灵活的切换方式和更细的切换粒度。

● 与猫眼现有业务的关系。

猫眼本身有一套选座交易,但受制于行业模型,暂时无法支撑商品交易。考虑到未来肯定是需要融合的,所以需要提前考虑两套交易的关系。

2.1.1 上单双推

针对售卖问题,猫眼采用了“上单双推”的做法,如图3:

正常情况下,上单系统在确认商家提交的商品信息后,会将商品推送到商品中心。为了让商品在美团、点评、猫眼三个体系中售卖,猫眼需要做以下几步操作:

● 历史单迁移。

先从美团商品中心将当下可以售卖的商品同步到猫眼,保持对齐。

● 建立关系映射。

对于商家来说,无论商品在几个体系中售卖,都是一个商品,在统计销量、结算时,都应该是一个商品,所以我们需要将多个商品中心的商品关联起来。这个做起来比较简单,只要给猫眼的商品分配一个id,然后在商品数据中存下对方的id就可以了。

另外,为了区分两个体系的商品,我们采用分段的策略来分配商品id,比如当下的美团商品最大id是X,那么猫眼的商品就采用2×X作为id的起始,而在美团商品id增长到2*X之前,我们肯定完成了切换,不用再考虑id序列统一的问题了。

● 商品双推,库存按比例分配。

猫眼上单系统确认商品信息后,将商品推送到美团、猫眼两个商品中心,并创建各自的库存,根据当下的交易比例分配库存量。由于美团、点评两个体系是经过融合的,所以只需要推送美团团购,点评就可以售卖了。

2.1.2 业务入口回收

解决了售卖问题之后,接下来我们来考虑版本和切换方式的问题。

众所周知,移动App一旦发版就很难再改动代码了,所以切换完成后,老的版本很难支持现有的体系。但经过分析我们发现,猫眼涉及的几个App,2~3个版本可以覆盖到90%以上的客户端,而核心 服务自建足够支撑3个版本以上的迭代,这样全部切换完成的时候,可以把老版本的流量损失降到5%以内(老版本的用户交易意愿有一定折损),这是一个可以接受的范围。

为了加快切换的速度,我们需要在核心服务自建之前,先把交易和消费环节所有的业务入口,也就是客户端调用的接口全部替换成猫眼自己的接口,由猫眼来对接美团的服务,如图4所示:

在美团的服务之上,先建立一个业务流程层,给客户端提供交易和消费环节所需要的接口,然后由业务流程层转接美团团购的基础服务。例如下单操作,原先是直接调用美团订单的接口,现在由交易业务系统做转接,一旦猫眼的订单系统开发完成,只需要将美团订单服务替换成猫眼订单服务就可以实现无缝对接了。

2.1.3 切换方法和粒度

收回业务入口以后,接下来需要考虑切换的粒度。

目前有这么几个粒度:商品、影院、App、入口位置(场次页、支付页、取票机、搜索、推荐)。综合起来看,影院+入口位置是比较合理的粒度。

商品粒度太细,且数据是动态变化的,维护起来比较麻烦;App维度又太粗,一次性切换起来涉及的范围太大,且无法回滚;影院的粒度处于中间,数据变化小,流量也比较好控制,所以我们采用了影院作为主要的切换粒度。

另外,由于商品业务本身的特点比较碎片化,入口众多,还得对接美团、点评的搜索、推荐等服务,所以将入口位置作为辅助的切换粒度。

切换之前,先给所有的展示入口分配固定的渠道号,如图5所示。支付页是渠道1,取票机是渠道3,客户端在请求后台数据时,需要带上渠道号;然后商品业务系统将查询逻辑抽象成处理器,例如猫眼支付页处理器对接猫眼的商品中心,而美团的支付页处理器对接美团的商品中心,公共流程如排序、最低价计算,可以抽象到父类中。

当一个查询请求到达商品业务系统后,由渠道分发控制器根据渠道号选择对应的处理器,同时,渠道分发控制器需要询问渠道切换服务的建议,看看是走团购体系还是走猫眼体系。而我们只需要在渠道切换服务内部维护一个走猫眼体系的影院+渠道列表,然后用配置推送服务动态更新,就可以实现影院+入口位置两个维度的自由切换了。

通过以上的办法,后台系统已经在商品展示层面实现了两个体系的自由切换,但后续的交易流程还有很长,需要客户端能够分辨后续该使用哪些接口,才可以走到正确的交易流程。图6是客户端需要配合做的改动:

客户端需要维护两套交易接口的列表,一套走美团,一套走猫眼。后台接口在返回商品数据时,会带上商品是否猫眼的标记,如果为true,则后续流程都使用猫眼的交易接口;如果为false,则都使用美团的交易接口。等到切换完成,后台返回的标记全部变成true,客户端也可以考虑删除这段逻辑,直接使用猫眼的接口列表。

2.1.4 统一id服务

最后,考虑商品交易和已有业务的关系。

交易的核心是订单,而猫眼内部已经存在一套选座订单了,但这两者的模型差异很大,无法复用,所以只能选择自建订单,在订单号层面做好统一,降低未来数据融合的复杂度。

统一订单id,就需要一个统计id服务,如图7所示。每个交易业务在下单前,都从统一id服务获取订单号,然后再下单,这样可以保证整个公司的订单号分布在同一个递增序列下,降低促销、结算等系统融合的复杂度。

2.2 核心服务自建

完成了前置方案的设计,接下来要分析交易需要哪些核心服务,下面是商品业务的行为分析图:

从上图中可以看出,整个交易过程可以简单的划分为三个阶段:交易前(商品为核心)、交易中(订单为核心)、交易后(即消费过程,商品券为核心)。下面详细分析每个阶段的核心模型该如何设计。

2.2.1 商品输出模型

根据商品的作用,我们可以将商品信息的存在形式分成三个阶段:编辑阶段、使用阶段、业务聚合阶段。以下是这三个阶段的说明:

● 编辑阶段,即还未成形的商品。

这个阶段以上单流程为核心,维护商品的写模型,所以需要重点关注商品的增删改操作,并做好审核机制和操作记录。这个阶段的模型主要维护在上单系统里边。

● 使用阶段,即通过审核,允许售卖的商品。

这个阶段维护的是商品的基础信息,一部分信息是只读的,例如商品标题、价格、所属的门店等;另外一部分是可变的,例如库存量、销量、商品状态等。使用阶段的模型主要维护在商品中心里边。

● 业务聚合阶段,即整合其它信息以后的商品。

这个阶段维护的是商品的读模型,不能改变商品的任何信息,例如商品列表是聚合了多个商品信息,商品详情则是聚合了商品基础信息和促销信息。当外部系统,例如搜索等服务需要接入商品体系的时候,都应该输出业务聚合阶段的商品。这个阶段的模型主要维护在商品业务里边。

定义好商品的三个阶段以后,就可以根据每个系统使用商品的方式来组织系统的关系,得到图9所展示的输出模型。

2.2.2 订单处理模型

订单是交易过程中最重要的模型之一,也是最容易和业务耦合过重的模块。所以在设计订单模型的时候,需要重点考虑如何让订单和业务流程分离,只关注订单的基本信息和状态流转。考虑点主要有以下几点:

● 命令模型和查询操作需要分开,即CQRS。

例如下单和查询订单详情两个操作,前者是交易的主流程,会写入订单的信息,而后者不会改变订单的任何信息,只需要查询到订单即可。另外,下单重视的是业务流程,核心指标是稳定性和准确性,而查询订单详情重视的是数据聚合,核心指标是完整性和用户体验。

● 业务流程和订单处理过程需要分离。

例如下单操作可能会分为好几个步骤:判断商品是否可售、计算商品的价格、锁库存、写订单数据等。这些步骤是业务流程,订单不应该关心。而且每个业务的交易流程可能会不一样,所以需要一个灵活性更高的办法来处理交易流程。

考虑以上两个问题后,我们设计出了订单处理模型,如图10所示。

首先得开发一个任务处理引擎,负责处理业务流程中的单个任务,例如锁库存、生成商品券、推送订单信息等等。每一个任务都对应一条数据库记录,用来说明要使用哪个处理器、用到的数据该从哪里获取,以及步骤完成之后该怎么办,记录之间也会使用编号连接起来,用来处理优先级和依赖关系。

接单服务负责将不同业务的下单流程拆解成一个个子任务,并确定好任务之间的优先级和依赖关系,进行简单的编排,然后写入到任务引擎的数据库中,而任务引擎则负责捞取这些任务,分析并执行已经编排过的任务。这种方式的好处在于业务流程被拆得很细,不同业务之间可以重用一些任务处理步骤,当一个新的交易业务接入的时候,只需要添加对应的子任务处理器,然后在接单系统中配置编排的规则,即可快速支持。

如图10所示,我们将订单的写模型和查模型分开,让增删改三个操作都走订单核心系统的写模型,而用户查询操作则走订单查询系统的查模型。每次订单信息有变化的时候,订单核心会通知任务引擎,由任务引擎负责更新查模型。这期间会有时间上的延迟,所以订单查询维护的查模型只能给用户端展示需求使用,而退款过程中查询订单状态则必须走订单核心的写模型,因为写模型的状态是实时的。

用户成功支付订单以后,订单核心会收到支付成功消息,然后将不同类型的订单拆分,并挨个通知任务引擎,执行支付成功后的业务流程,例如生成商品券、推送订单信息到美团订单列表等。

2.2.3 商品券与结算模型

商品券和物流是消费过程中的核心模型,而结算刚好要的就是消费维度的数据,所以这两者的设计紧密相关。由于商品券和物流比较类似,所以本文只介绍商品券的模型。
商品券的本质是一串数字,用来作为兑换服务的凭证。它的特点:

● 一是要足够乱,不能让你随便输入一个数字就可以使用;
● 二是不能重复,否则无法分清对应的是哪个服务。

在数据操作上,商品券需要支持生成、验证、撤销等操作。
经过以上分析,我们设计出了商品券和结算的模型,如图11:

为了足够乱,系统需要一个随机数生成的模块,同时为了不重复,需要为随机数建立一个防冲突表,每次生成完随机数,都需要到防冲突表里查一下是否有使用过。由于随机数是有限的,生成的码越多,冲突的概率就越高,对并发度会有一定的影响。为了优化这个问题,我们采用了异步生成的方式:先预生成一定数量的劵码,并添加到一个可用劵码的队列中,在不同的并发数下,只要调整队列的容量和预生成速度就可以支持了。

当用户通过pos机等设备验证券码的时候,商品券会发消息给结算系统,结算系统会写一条流水数据到数据库中,并按照结算周期聚合到一起形成付款计划。结算系统也会每天扫描合同中约定的结算周期,触发打款操作。

2.3 线上线下切换

在前文介绍的前置方案里,我们已经提前将切换点埋入到了客户端中,当交易模型搭建起来时,线上实际已经有大量的客户端支持在两个体系切换了。但此时,我们还不能着急将所有的商家都切换到新体系,需要考虑更多的问题:

● 旧体系已有的业务,在新体系是否都已经支持。例如促销、优惠券,必须在业务对齐的情况下才能切换,否则会干扰到商家的正常运营活动。
● 商家结算的模式是否支持切换。在拆分过程中,结算、财务方面的数据是唯一不能迁移的数据,任何的变化都需要两个公司财务之间核算清楚才能执行。所以在切换之前,需要根据结算的模式,将商家做好分类,优先将结算方式简单的商家切换到新体系,用于验证线上服务的正确性和切换方案的合理性。
● 交易过程中是否依赖第三方系统。例如猫眼在交易后的过程中,依赖了影院的第三方券系统,导致了这部分影院必须在对接完依赖的第三系统之后,才可以切换。
分析完上述三个因素,确定好可以切换的商家列表之后,就可以开始和商务一起推进线上线下的切换了。切换的过程中,需要注意线下的切换通常比线上要缓慢,所以要预先启动线下的切换,并让商务团队给商家做好培训。

三.技术融合方案

经过技术拆分,猫眼实际拥有了一套完整的商品供应链和交易体系,而此时点评的商品业务也有一套自己的供应链和交易。为了降低今后的维护成本,我们必须将点评的商品业务适配到猫眼的这套供应链和交易体系之上。

供应链层面的解决方案和拆分的方案类似,这里不再详述,重点说说交易层面的适配和融合办法。设计融合技术方案的时候,需要重点考虑这么几个问题:

● 客户端调用接口的迁移问题。点评客户端之前对接的都是点评的业务服务,需要替换成猫眼的业务服务。
● 交易流程衔接以及页面跳转的问题。融合之后,核心的如订单、支付、券消费肯定得走猫眼的交易体系,所以整个交易的页面和跳转都应该使用猫眼的页面。
● 已经有的数据整合问题。在融合之前,点评已经积累了大量的商品订单和券数据,这部分数据需要和猫眼的数据进行整合,才能满足用户和商家的查询需求。

3.1 业务入口适配

针对客户端调用接口的问题,解决思路和拆分的方案差不多,也是提前建立业务层,将客户端使用的接口都回收到业务系统,然后再适配到猫眼的业务服务,如图12所示。

为了尽量不维护两套业务系统,猫眼在融合一期工程的时候先采用了简单的适配,目的是将业务入口先牵引到猫眼的交易体系,然后在后续的开发中再让点评客户端直接对接猫眼的业务系统,逐步取代点评的适配层,等到需要适配的版本越来越少的时候,就可以废弃掉这个适配层了。

3.2 交易过程使用触屏版

交易流程的衔接和跳转是个比较棘手的问题,一是猫眼以前没有在点评客户端做过开发,为了融合单做一套native交易页面的代价较高;二是今后在做功能迭代的时候,要适配的端太多,会拖慢产品迭代的速度。综合考虑了流量、体验和团队多方面的因素之后,猫眼决定使用触屏版来解决这个问题。

商品交易有一个特点,即大部分的下单操作都会先经过商品详情页,所以只要从商品详情页开始,做一套触屏版页面,就可以将交易流程都引导到猫眼的页面了。

然而商品的展示通常比较碎片化,可能会有搜索、推荐、商品列表、猜你喜欢等各种入口,所以进入交易的第一步就是尽可能的让展示入口都跳转到触屏版的商品详情页,然后继续下单、支付,再跳转到触屏版的结果页。订单成功之后,猫眼再将自己的订单数据 推送到点评的订单中心,同时将订单详情页的跳转设置成触屏版。一旦用户点击订单列表,就又回到了猫眼可控的范围内,后续的退款、消费、客服就走回到猫眼的交易体系,具体的做法如图13所示。

整个过程中,可能会遇到一些其它的问题,例如跳转登录:点评客户端登陆的是点评账号,而猫眼的触屏版登陆的是猫眼账号,所以这个做法还依赖于账号的融合。在跳转收银台的时候也需要小心谨慎,需要点评收银台和猫眼收银台采用同样的验证方式和解析规则,否则极容易出现金额错误,或者无法支付的情况。这些也是触屏版需要考虑的问题。

使用触屏版的前提是:交易流程必须经过商品详情页,假如交易流程不经过商品详情页的话,就必须对多个业务入口进行适配,但跳转到收银台之后的流程,仍然是可以复用的。

3.3 数据整合思路

数据整合是技术融合中最繁琐的部分,做法通常有三种:

● 数据层面不做整合,在代码层面区分该走哪个数据源。这个方法的优势在于数据可不做改动,但需要维护两套数据源,成本较高。
● 数据层面建立映射关系,然后通过转换层将数据转换成业务方需要的模型。这个方法的改动成本比较可控,可以快速实现两个体系的互通。
● 数据层面做彻底的整合,将两方的数据迁移到一个数据源中。这个方法的优势是只保留一套数据模型,后期可维护性高,但整合的难度大,需要重点处理差异数据。

猫眼和点评数据的整合过程中,主要以后两种方法为主,如图14所示:

查询数据通常使用的都是id,而我们可以简单的将数据id分为两类:一类是不能变化的,例如影院id;一类是可以变化的,例如订单id。

对于影院id,猫眼和点评都有自己的一套序列,而且这两套id都依附在各自的商家体系上,不能轻易统一起来,所以只能通过建立映射关系的方式来实现互通。图14中左半部分的示例就是解决数据id不能改变的场景:

先将点评的影院id和猫眼的影院id建立映射关系,例如猫眼的影院id=1和点评的影院id=2,可能指向同一家影院(只是举例使用,不保证一定是对应关系)。而猫眼app和点评app依然使用之前的id,只是在查询数据的时候,会先经过数据转换层,由数据转换分析映射关系,拿到统一之后的影院信息。

对于订单id,用户通常不会手动记录订单id是多少,完全依靠后台返回,这时候就可以将点评的订单数据按照猫眼的格式,迁移到猫眼的数据库中,并将id转换成猫眼序列的id,字段如果不同的话,可以使用扩展字段或者差异表做一层兼容。

图14的右半部分描述 的就是这种做法:用户在查询订单列表的时候,返回的是猫眼的订单id,那么进入到详情的时候自然就会查询猫眼的订单数据,这样就实现了数据整合。

需要注意的是:修改订单id的时候,通常需要修改所有和订单相关的数据,例如促销、商品券等系统,都是以订单id为区分依据的,这就需要同时改动所有受牵连的数据,需要小心谨慎地去推进。

四、案例启示和教训

技术拆分和融合的过程在猫眼内部持续将近半年时间,期间遇到了不少困难,对系统架构和模型也进行了较多的思考,以下是从案例中获得的启示:

● 在大公司做垂直业务时,如果要复用平台的服务,最好在客户端和平台服务之间建立业务层,做一层服务转接,这样可以为服务的替换提供更好的灵活性。
● 在设计系统模型时,尽量让业务入口渠道化、处理过程组件化,这样不仅可以提高系统的横向扩展性,而且也可以对每个入口做更精细的把控
● 不同业务在快速发展时,可以有自己的核心服务,但必须使用统一的id生成策略,保证模型的主要id属于同一个递增序列,这样可以为以后的融合,或者平台化减少数据整合的复杂度。
● 数据模型层面,尽量将命令模型和查询模型分开,一方面可以将主流程和数据展示操作彻底分开;一方面也可以降低数据操作的复杂性。
● 系统的流量控制和切换粒度要足够精细,这样可以提高应对风险的能力。

也从本案例中积累了一些教训:

● 设计技术拆分和融合方案时,需要全面考虑非技术因素的影响,比如结算、财务,数据既不能迁移,也不能修改,此时财务的结论很有可能会影响整体的切换方案和模式设计。
● 线下的切换要提前进行,以便尽早收集到业务一线的反馈,可以并行的去修复问题,这样才能保证在线上开始切换的时候,不被拖慢进度。

五、总结和感谢

技术拆分和融合是一个庞大的工程,涉及的技术点比较多,由于篇幅有限,本文只是介绍了整体的设计方案,希望给行业中遭遇相同问题的工程师们提供一个参考案例。

最后,在此感谢所有参与本次技术拆分和融合的技术、产品、运营和商务、财务团队所有的小伙伴,以及美团点评热心帮助的同事们。有了大家的紧密协作,才能在有限的时间里,完成如此复杂的技术升级。

11月9-12日,北京国家会议中心,第六届TOP100全球软件案例研究峰会,美团外卖算法架构师郝井华将分享《美团配送智能调度系统演进》;美团点评酒旅质量团队工具链负责人王鹏将分享《微服务架构下的自动化测试和持续集成工具链实践》。

TOP100全球软件案例研究峰会已举办六届,甄选全球软件研发优秀案例,每年参会者达2000人次。包含产品、团队、架构、运维、大数据、人工智能等多个技术专场,现场学习谷歌、微软、腾讯、阿里、百度等一线互联网企业的最新研发实践。大会开幕式单天体验票申请入口


壹佰案例
649 声望429 粉丝

分享世界级软件研发团队最佳管理实践,加速研发团队成长曲线。[链接]