交通方式是用户旅行前要考虑的核心要素之一。为了帮助用户更好地完成消费决策闭环,马蜂窝上线了大交通业务。如今,用户在马蜂窝也能够完成购买机票、火车票等操做。php
与大多数业务系统相同,咱们同样经历着从无到有,再到快速发展的过程。本文将以火车票业务系统为例,主要从技术的角度,和你们分享在一个新兴业务发展的不一样阶段背后,系统建设与架构演变方面的一些经验。前端
在这个阶段,快速支撑起业务,填补业务空白是第一目标。基于这样的考虑,当时的火车票业务从模式上选择的是供应商代购;从技术的角度须要优先实现用户在马蜂窝 App 查询车次余票信息、购票、支付,以及取消、退票退款等核心功能的开发。redis
图1-核心功能与流程数据库
综合考虑项目目标、时间、成本、人力等因素,当时网站服务架构咱们选择的是 LNMP(Linux 系统下 Nginx+MySQL+PHP)。整个系统从物理层划分为访问层 ( App,H5,PC 运营后台),接入层 (Nginx),应用层 (PHP 程序),中间件层 (MQ,ElasticSearch),存储层 (MySQL,Redis)。小程序
对外部系统依赖主要包括公司内部支付、对帐、订单中心等二方系统,和外部供应商系统。后端
图 2-火车票系统 V1.0 技术架构微信小程序
如图所示,对外展示功能主要分为两大块,一块是 C 端 App 和 H5,另外是运营后台。两者分别通过外网 Nginx 和内网 Nginx 统一打到 php train 应用上。程序内部主要有四个入口,分别是:api
四个入口会依赖于下层 modules 模块实现各自功能。对外调用上分两种状况,一种是调用二方系统的 facade 模块知足其公司内部依赖;一种是调用外部供应商。基础设施上依赖于搜索、消息中间件、数据库、缓存等。缓存
这是典型的单体架构模式,部署简单,分层结构清晰,容易快速实现,能够知足初期产品快速迭代的要求,并且创建在公司已经比较成熟的 PHP 技术基础之上,没必要过多担忧稳定性和可靠性的问题。微信
该架构支撑火车票业务发展了将近一年的时间,简单、易维护的架构为火车票业务的快速发展作出了很大的贡献。然而随着业务的推动,单体架构的缺陷逐渐暴露出来:
为了解决单体架构所带来的一系列问题,咱们开始尝试向微服务架构演进,并将其做为后续系统建设的方向。
从 2018 年开始,整个大交通业务开始从 LNMP 架构向服务化演变。
「工欲善其事,必先利其器」,首先简单介绍一下大交通在实施服务化过程当中积累的一些核心设施和组件。
咱们最主要的转变是将开发语言从 PHP 转为 Java,所以技术选型主要围绕 Java 生态圈来展开。
图3-大交通基础组件
如上图所示,总体开发框架与组件从下到上分为四层。这里主要介绍最上层大交通业务场景下的封装框架和组件层:
服务化的实施离不开基础设施体系的支持。在公司已有基础之上,咱们陆续建设了:
如上所述,初步构筑了较为完整的 DevOps + 微服务开发体系。总体架构以下:
图4-大交通基础设施体系
从上至下依次分为:
此外,外围支撑系统包括 CI/CD、服务治理与配置、APM 系统、日志系统、监控告警系统。
咱们目前尚未放开 Prod 的 OPS 权限给开发,计划在新版 CD 系统中会逐步开放。
图5-CI/CD 体系
咱们选择 Dubbo 做为分布式微服务框架,主要有如下因素考虑:
图 6-Dubbo 架构
除了保持和 Dubbo 官方以及社区的密切联系外,咱们也在不断对 Dubbo 进行加强与改进,好比基于 dubbo-fitler 的日志追踪,基于大交通统一应用管理中心的 Dubbo 统一配置管理、服务治理体系建设等。
向服务化的演进决不能是一个大跃进运动,那样只会把应用拆分得七零八落,最终不但大大增长运维成本,并且看不到收益。
为了保证整个系统的服务化演进过程更加平滑,咱们首先选择了抢票系统进行实践探索。抢票是火车票业务中的一个重要版块,并且抢票业务相对独立,与已有的 PHP 电子票业务冲突较少,是咱们实施服务化的较佳场景。
在对抢票系统进行服务拆分和设计时,咱们积累了一些心得和经验,主要和你们分享如下几点。
简单来讲,抢票就是实现用户提早下抢票单,系统在正式开售以后不断尝试为用户购票的过程。从本质上来讲,抢票是一种手段,经过不断检测所选日期和车次的余票信息,以在有余票时为用户发起占座为目的。至于占座成功之后的处理,和正常电子票是没有什么区别的。理解了这个过程以后,在尽可能不改动原有 PHP 系统的前提下,咱们这样划分它们之间的功能边界:
图7-抢票功能划分
也就是说,用户下抢票单支付成功,待抢票占座成功后,后续出票的事情咱们会交给 PHP 电子票系统去完成。同理,在抢票的逆向方面,只需实现「未抢到票全额退」以及「抢到票的差额退」功能,已出票的线上退和线下退票都由 PHP 系统完成,这就在很大程度上减小了抢票的开发任务。
服务的设计原则包括隔离、自治性、单一职责、有界上下文、异步通讯、独立部署等。其余部分都比较容易把控,而有界上下文通俗来讲反应的就是服务的粒度问题,这也是作服务拆分绕不过去的话题。粒度太大会致使和单体架构相似的问题,粒度太细又会受制于业务和团队规模。结合实际状况,咱们对抢票系统从两个维度进行拆分:
1. 从业务角度系统划分为供应商服务 (同步和推送)、正向交易服务、逆向交易服务、活动服务。
图8-抢票服务设计
2. 从系统层级分为前端 H5 层先后分离、API 接入层、RPC 服务层,和 PHP 之间的桥接层、异步消息处理、定时任务、供应商对外调用和推送网关。
图9-抢票系统分层
对于交易系统而言,无论是用何种语言,何种架构,要考虑的最核心部分终归是数据。数据结构基本反应了业务模型,也左右着程序的设计、开发、扩展与升级维护。咱们简单梳理一下抢票系统所涉及的核心数据表:
1. 创单环节:用户选择车次进入填单页之后,要选择乘车人、添加联系人,因此首先会涉及到乘车人表,这块复用的 PHP 电子票功能
2. 用户提交创单申请后,将会涉及到如下数据表:
3. 产生占座结果后:用户占座失败涉及全额退款,占座成功可能涉及差额退款,所以咱们会有退票订单表 (refund_order);虽然只涉及到退款,但一样会有 refund_item 表来记录退款明细。
订单系统的核心要点是订单状态的定义和流转,这两个要素贯穿着整个订单的生命周期。
咱们从以前的系统经验中总结出两大痛点,一是订单状态定义复杂,咱们试图用一个状态字段来打通前台展现和后端逻辑处理,结果致使单一订单状态多达 18 种;二是状态流转逻辑复杂,流转的前置因素判断和后置方向上太多的 if else 判断,代码维护成本高。
所以,咱们采用有限状态机来梳理抢票正向订单的状态和状态流转,关于状态机的应用,能够参照以前发过的一篇文章《状态机在马蜂窝机票交易系统中的应用与优化》,下图是抢票订单的状态流转图:
图10-抢票订单状态流转
咱们将状态分为订单状态和支付状态,经过事件机制来推动状态的流转。到达目标态有两个前提:一是原状态,二是触发事件。状态按照预设的条件和路线进行流转,将业务逻辑的执行和事件触发与状态流转拆分开,达到解耦和便于扩展维护的目的。
如上图所示,将订单状态定义为:初始化→下单成功→交易成功→关闭。给支付状态定义为:初始化→待支付→已支付→关闭。以正常 case 来讲,用户下单成功后,会进入下单成功和待支付;用户经过收银台支付后,订单状态不变,支付状态为已支付;以后系统会开始帮用户占座,占座成功之后,订单会进入交易成功,支付状态不变。
若是仅仅是上面的双状态,那么业务程序执行却是简单了,但没法知足前台给用户丰富的单一状态展现,所以咱们还会记录关单缘由。关单缘由目前有 7 种:未关闭、创单失败、用户取消、支付超时、运营关单、订单过时、抢票失败。咱们会根据订单状态、支付状态、关单缘由,计算出一个订单对外展现状态。
所谓幂等性,是指对一个接口进行一次调用和屡次调用,产生的结果应该是一致的。幂等性是系统设计中高可用和容错性的一个有效保证,并不仅存在于分布式系统中。咱们知道,在 HTTP 中,GET 接口是天生幂等的,屡次执行一个 GET 操做,并不会对系统数据产生不一致的影响,可是 POST,PUT,DELETE 若是重复调用,就可能产生不一致的结果。
具体到咱们的订单状态来讲,前面提到状态机的流转是须要事件触发的,目前抢票正向的触发事件有:下单成功、支付成功、占座成功、关闭订单、关闭支付单等等。咱们的事件通常由用户操做或者异步消息推送触发,其中任意一种都没法避免产生重复请求的可能。以占座成功事件来讲,除了修改自身表状态,还要向订单中心同步状态,向 PHP 电子票同步订单信息,若是不作幂等性控制,后果是很是严重的。
保证幂等性的方法有不少,以占座消息为例,咱们有两个措施来保证幂等:
服务化的实施具有必定的成本,须要人员和基础设施都有必定的基础。初始阶段,从相对独立的新业务着手,作好和旧系统的融合复用,能较快的获取成果。抢票系统在不到一个月的时间内完成产品设计,开发联调,测试上线,也很好地印证了这一点。
抢票系统建设的完成,表明咱们迈出了一小步,也只是迈出了一小步,毕竟抢票是周期性的业务。更多的时间里,电子票是咱们业务量的主要支撑。在新老系统的并行期,主要有如下痛点:
所以,卸下历史包袱,尽快完成旧系统的服务化迁移,统一技术栈,使主要业务获得更加有力的系统支撑,是咱们接下来的目标。
咱们但愿经过对电子票流程的改造,重塑以前应急模式下创建的火车票项目,最终实现如下几个目标:
咱们要完成的不只是技术上的重构,而是结合新的业务诉求,去不断丰富新的系统,力求达到业务和技术的目标一致性,所以咱们将服务化迁移和业务系统建设结合在一块儿推动。下图是电子票流程改造后火车票总体架构:
图11-电子票改造后的火车票架构
图中浅蓝色部分为抢票期间已经建好的功能,深蓝色模块为电子票流程改造新加入的部分。除了和抢票系统相似的供应商接入、正向交易、逆向交易之外,还包括搜索与基础数据系统,在供应侧也增长了电子票的业务功能。同时咱们新的运营后台也已经创建,保证了运营支撑的延续性。
项目实施过程当中,除了抢票所说的一些问题以外,也着重解决如下几个问题。
先来看用户一次站站搜索可能穿过的系统:
图12-站站查询调用流程
请求先到 twl api 层,再到 tsearch 查询服务,tsearch 到 tjs 接入服务再到供应侧,整个调用链路仍是比较长的,若是每次调用都是全链路调用,那么结果是不太乐观的。所以 tsearch 对于查询结果有 redis 缓存,缓存也是缩短链路、提升性能的关键。站站查询要缓存有几个难点:
综合以上因素考虑,咱们设计 tsearch 站站搜索流程以下:
图13-搜索设计流程
如图所示,首先对于一个查询条件,咱们会缓存多个渠道的结果,一方面是由于要去对比哪一个渠道结果更加准确,另外一方面能够增长系统可靠性和缓存命中率。
咱们将 Redis 的过时时间设为 10min,对缓存结果定义的有效期为 10s,首先取有效;若是有效为空,则取失效;若是失效也为空,则同步限时 3s 去调用渠道获取,同时将失效和不存在的缓存渠道交给异步任务去更新。须要注意经过分布式锁来防止并发更新一个渠道结果。最终的缓存结果以下:
缓存的命中率会在 96% 以上,RT 平均在 500ms 左右,可以在保证用户体验良好的同时,作到及时的数据更新。
咱们有大量业务是经过异步消息方式来处理的,好比订单状态变动消息、占座通知消息、支付消息等。除了正常的消息消费之外,还有一些特殊的场景,如顺序消费、事务消费、重复消费等,主要基于 RocketMQ 来实现。
顺序消费
主要应用于对消息有前后依赖的场景,好比创单消息必须先于占座消息被处理。RocketMQ 自己支持消息顺序消费,咱们基于它来实现这种业务场景。从原理上来讲也很简单,RocketMQ 限定生产者只能将消息发往一个队列,同时限定消费端只能有一个线程来读取,这样全局单队列,单消费者就保证了消息的顺序消费。
重复消费
RocketMQ 保障的是 At Least Once,并不能保证 Exactly Only Once,前面抢票咱们也提过,一是经过要求业务侧保持幂等性,另外经过数据库表 message_produce_record 和 message_consume_record 来保证精准一次投递和消费结果确认。
事务消费
基于 RocketMQ 的事务消息功能。它支持二阶段提交,会先发送一条预处理消息,而后回调执行本地事务,最终提交或者回滚,帮助保证修改数据库的信息和发送异步消息的一致。
歼十战机的总设计师曾说过一句话:「造一架飞机不是最难的,难的是让它上天」,对咱们来讲一样如此。3 月是春游季的高峰,业务量与日俱增,在此期间完成系统重大切换,咱们须要完备的方案来保障业务的顺利切换。
方案设计
灰度分为白名单部分和百分比灰度部分,咱们首先在内部进行白名单灰度,稳定后进入 20% 流量灰度期。
灰度的核心是入口问题,因为本次前端也进行了完整改版,所以咱们从站站搜索入口将用户引入到不一样的页面,用户分别会在新旧系统中完成业务。
图14-灰度运行方案
App 在站站搜索入口调用灰度接口获取跳转地址,实现入口分流。
图15-搜索下单分流
咱们只是初步实现了服务化在火车票业务线的落地实施,与此同时,还有一些事情是将来咱们要去持续推动和改进的:
1. 服务粒度细化:目前的服务粒度仍然比较粗糙。随着功能的不断增多,粒度的细化是咱们要去改进的重点,好比将交易服务拆分为订单查询服务,创单服务,处理占座的服务和处理出票的服务。这也是 DevOps 的必然趋势。细粒度的服务,才能最大限度知足咱们快速开发、快速部署,以及风险可控的要求。
2. 服务资源隔离:只在服务粒度实现隔离是不够的。DB 隔离、缓存隔离、MQ 隔离也很是必要。随着系统的不断扩展与数据量的增加,对资源进行细粒度的隔离是另外一大重点。
3. 灰度多版本发布:目前咱们的灰度策略只能支持新老版本的并行,将来除了会进行多版本并行验证,还要结合业务定制化需求,使灰度策略更加灵活。
业务的发展离不开技术的发展,一样,技术的发展也要充分考虑当时场景下的业务现状和条件,两者相辅相成。比起设计不足而言,咱们更要规避过分设计。
技术架构是演变出来的,不是一开始设计出来的。咱们须要根据业务发展规律,将长期技术方案进行阶段性分解,逐步达成目标。同时,也要考虑服务化会带来不少新问题,如复杂度骤增、业务拆分、一致性、服务粒度、链路过长、幂等性、性能等等。
比服务支撑更难的是服务治理,这也是咱们你们要深刻思考和去作的事情。
本文做者:李战平,马蜂窝大交通业务研发技术专家。
(题图来源:网络)
关注马蜂窝技术,找到更多你想要的内容