马蜂窝大交通业务上线,火车票业务系统建设与架构演变经验分享

2024-11-04
来源:网络整理

交通方式是用户出行前应考虑的核心要素之一。为了帮助用户更好地完成消费决策闭环,马蜂窝推出了大交通业务。现在,用户还可以在马蜂窝上完成购买机票、火车票等操作。

和大多数业务系统一样,我们也正在经历从无到有、然后快速发展的过程。本文将以火车票业务系统为例,主要从技术角度,与大家分享一个新兴业务发展不同阶段背后的系统建设和架构演进的一些经验。

第一阶段:从零开始

现阶段首要目标是快速支撑业务、填补业务空白。基于这样的考虑,当时的火车票业务选择了供应商采购作为模式;从技术角度来看,需要优先考虑用户在马蜂窝App上查询火车余票信息、购票、支付、退票、退票等核心任务。功能开发。

图1-核心功能及流程

技术架构

考虑到项目目标、时间、成本、人力等因素,我们当时的网站服务架构选择了LNMP(系统下++PHP)。整个系统从物理层分为接入层(App、H5、PC操作后台)、接入层()、应用层(PHP程序)、中间件层(MQ、)、存储层(、)。

对外部系统的依赖主要包括公司内部的支付、对账、订单中心等第二方系统,以及外部供应商系统。

图2-火车票系统V1.0技术架构

如图所示,外显功能主要分为两部分,一是C端App和H5,二是操作后台。两者通过外网和内网统一到PHP应用程序中。程序内部有四个主要入口,分别是:

供其他二方系统调用的模块

运行后台调用的模块

处理App和H5请求的核心模块

处理外部供应商回调模块

四个入口将依靠底层模块来实现各自的功能。外部调用有两种情况。一是调用二方系统的模块,满足其内部依赖;另一种是致电外部供应商。基础设施依赖于搜索、消息中间件、数据库、缓存等。

这是典型的单体架构模型。部署简单,层次结构清晰,易于快速实施,能够满足早期产品快速迭代的要求。也是基于公司已经成熟的PHP技术,所以不需要太担心稳定性。和可靠性问题。

这个架构支撑了火车票业务发展近一年的时间。简单易维护的架构为火车票业务的快速发展做出了巨大的贡献。但随着业务的进展,单体架构的缺点逐渐暴露出来:

为了解决单体架构带来的一系列问题,我们开始尝试向微服务架构演进,并以此作为后续系统建设的方向。

第二阶段:建筑改造与服务化的初步探索

从2018年开始,整个大交通业务开始从LNMP架构向服务化演进。

架构转型——从单一应用到基于服务

“工欲善其事,必先利其器”。首先简单介绍一下大交通在实施服务过程中积累的一些核心设施和组件。

我们最重要的改变就是将开发语言从PHP改为Java,所以技术选型主要围绕Java生态。

开发框架和组件

图3-大型交通的基本组成

如上图所示,整体开发框架和组件从下到上分为四层。这里主要介绍顶层交通业务场景中的封装框架和组件层:

基础设施系统

服务化的实施离不开基础设施体系的支撑。在公司现有基础上,我们先后打造了:

如上所述,初步构建了一个比较完整的+微服务开发体系。整体架构如下:

图4——大型交通基础设施系统

从上到下分为:

此外,外围支撑系统包括CI/CD、服务管理与配置、APM系统、日志系统、监控报警系统等。

持续集成/持续交付系统

我们还没有将Prod的OPS权限开放给开发,计划在新版本的CD系统中逐步开放。

图5-CI/CD系统

服务化框架

我们选择分布式微服务框架主要是出于以下因素:

成熟的高性能分布式框架。目前已被很多公司使用,经受住了各种性能测试,比较稳定;

可以与框架无缝集成。我们的架构是基于构造的,访问的时候可以非侵入代码,访问也很方便;

具有服务注册、发现、路由、负载均衡、服务降级、权重调整等能力;

该代码是开源的。可根据需求定制、扩展功能、自主研发

图 6-架构

除了与官方和社区保持密切联系外,我们也在不断进行增强和改进,例如基于大交通统一应用管理中心的日志跟踪、统一配置管理和服务治理体系建设。

基于服务的抢票系统初步研究

向服务化演进绝不能是大跃进,那样只会将应用割裂成碎片,不仅大大增加运维成本,而且没有任何好处。

为了保证整个系统的服务化演进过程更加顺畅,我们首先选择了抢票系统进行实践探索。抢票是火车票业务的重要组成部分,抢票业务相对独立,与现有的PHP电子票业务冲突较小。这是我们实施服务化的一个更好的场景。

代码同步调用_同步代码工具_小程序多人开发代码版本同步

我们在拆分服务、设计抢票系统的时候,积累了一些见解和经验。我们主要与大家分享以下几点。

功能和边界

简单来说,抢票就是用户提前下达抢票订单,正式发售后系统继续为用户尝试购票的过程。抢票本质上是一种不断检测所选日期和列车的剩余车票信息的手段,目的是在有剩余车票时为用户发起预留座位。至于成功占座后的处理,与普通电子客票无异。了解了这个流程后,在尽可能不改变原有PHP系统的前提下,我们将它们之间的功能边界划分如下:

图7-抢票功能划分

也就是说,如果用户下票并支付成功,并且票被成功占用后,我们将把后续的出票工作交给PHP电子票系统。同样,在抢票的反向方面,只需实现“未抢票全额退款”和“抢票退差价”的功能即可。已出票的线上退票和线下退票均由PHP系统完成。这大大减少了抢票的开发任务。

服务设计

服务的设计原则包括隔离、自治、单一责任、有界上下文、异步通信、独立部署等,其他部分相对容易控制,有界上下文一般体现了服务的粒度。这也是做服务拆分时绕不开的一个话题。如果粒度太大,会造成与单体架构类似的问题。如果粒度太细,就会受到业务和团队规模的限制。根据实际情况,我们将抢票系统从两个维度进行拆分:

1、从业务角度,系统分为供应商服务(同步和推送)、正向交易服务、反向交易服务、活动服务。

图8-抢票服务设计

2、从系统层面分为前后端分离的H5层、API接入层、RPC服务层、PHP之间的桥接层、异步消息处理、定时任务、供应商外部调用,并推送网关。

图9-分层抓取系统

数据元素

对于一个交易系统来说,无论使用什么语言、什么架构,最终要考虑的核心部分还是数据。数据结构基本反映了业务模型,也影响着程序的设计、开发、扩展、升级和维护。我们简单梳理一下抢票系统涉及到的核心数据表:

1、订单创建环节:用户选择车次进入填表页面后,需要选择乘客并添加联系人,所以首先会涉及到乘客列表,这是一个复用的PHP电子票功能。

2、用户提交创建订单申请后,会涉及以下数据表:

3、占座结果生成后:用户占座失败涉及全额退款,占座成功可能涉及退还差价,所以我们会有退款订单表();虽然只涉及退款,但是也会有一张表格。记录退款详情。

订单状态

订单系统的核心点是订单状态的定义和流程。这两个要素贯穿整个订单生命周期。

我们根据之前的系统经验总结了两大痛点。首先,订单状态定义复杂。我们尝试用一个状态字段来连接前端显示和后端逻辑处理。结果单单状态多达18个;第二,状态流。逻辑复杂,流程中前置判断过多,后置if else判断过多,代码维护成本高。

因此,我们使用有限状态机来梳理正向抢票订单的状态和状态流程。关于状态机的应用,可以参考之前发表的一篇文章。下图是抢票订单状态流程图:

图10-抢票订单状态流程

我们将状态分为订单状态和支付状态,并使用事件机制来促进状态的流动。达到目标状态有两个前提:一是原始状态,二是触发事件。状态流按照预设条件和路由流动,业务逻辑的执行和事件触发与状态流分离,达到解耦、便于扩展和维护的目的。

如上图所示,订单状态定义为:初始化→订单成功→交易成功→关闭。支付状态定义为:初始化→待支付→已支付→关闭。正常情况下,用户下单成功后,会进入订单成功、待付款;用户通过收银台支付后,订单状态不变,支付状态为已支付;之后,系统将开始帮助用户入座。成功后,订单将进入交易成功,支付状态保持不变。

如果只是上面的双状态,那么业务程序的执行会很简单,但是无法满足前端用户对单一状态的丰富展示,所以我们也会记录关闭的原因命令。目前订单关闭原因有7种:未关闭、订单创建失败、用户取消、支付超时、操作关闭、订单过期、抢票失败。我们会根据订单状态、支付状态、订单关闭原因计算订单的对外展示状态。

幂等设计

所谓幂等性,是指一个接口的一次调用和多次调用产生的结果应该是一致的。幂等性是系统设计中对高可用性和容错性的有效保证,并且它不仅仅存在于分布式系统中。我们知道,在HTTP中,GET接口本质上是幂等的。多次执行GEI操作不会对系统数据产生不一致的影响。但是,如果重复调用POST和PUT,可能会出现不一致的结果。

具体到我们的订单状态,前面提到了,状态机的流程需要通过事件来触发。目前,抢票的正向触发事件有:下单成功、支付成功、占座成功、订单关闭、支付订单关闭等。我们的事件一般是由用户操作或者异步消息推送触发的,这两者都无法避免重复请求的可能性。以占座成功事件为例,除了修改自身餐桌状态外,还需要与订单中心同步状态,与PHP电子票同步订单信息。如果不实行幂等控制,后果将非常严重。

有很多方法可以确保幂等性。以占座消息为例,我们有两个措施来保证幂等性:

占座消息具有唯一性,受协议约束,推送服务可以判断该消息是否已被正常处理。

业务侧的修改实现了CAS(And Swap),简单来说就是数据库乐观锁,比如set=2=『1234'and=1and=2。

概括

实施服务化有一定的成本,需要一定的人员基础和基础设施。初期从相对独立的新业务入手,与旧系统整合复用,可以很快见效。抢票系统在不到一个月的时间里就完成了产品设计、开发和联调,并上线测试,也很好地证明了这一点。

第三阶段:

服务提升、系统能力提升

抢票系统的建设完成对于我们来说代表着一小步,而且只是一小步。毕竟抢票是一个周期性的生意。很多时候,电子客票是我们业务量的主要支撑。新旧系统并行期间,主要存在以下痛点:

由于当时的因素,原有的电子客票系统与特定的供应商绑定紧密,受到供应商的很大限制;

由于兼容抢票系统和其他主要交通系统的成本很高,我们很难实现统一的链路追踪、环境隔离、监控报警等;

PHP和Java桥接层承担的业务过多,性能无法保证。

因此,我们下一步的目标是去掉历史包袱,尽快完成旧系统的服务化迁移,统一技术栈,为主营业务提供更强的系统支撑。

与业务同行:电子客票流程转型

我们希望通过电子客票流程的改造,重塑之前应急模式下建立的火车票项目,最终实现以下目标:

建立马蜂窝火车票业务规则,改变以往业务功能和流程受供给方规则限制的局面;

改善用户体验和功能,增加在线选座功能,优化搜索和下单流程,优化退款速度,提升用户体验;

提高数据指标和稳定性,引入新的供给侧服务,提高可靠性;供应商订单分配系统,提高占座成功率和出票率;

技术上完成了向Java服务的迁移,为后续业务奠定了基础

我们要完成的不仅仅是技术上的重构,而是要结合新的业务需求不断丰富新的系统,努力实现业务和技术目标的一致性。因此,我们将面向服务的迁移与业务系统建设结合起来。进步。下图为电子客票流程改造后火车票的整体结构:

小程序多人开发代码版本同步_代码同步调用_同步代码工具

图11-电子客票改造后的火车票结构

图中浅蓝色部分是抢票期间已经内置的功能,深蓝色模块是电子票流程改造新增的部分。除了类似抢票系统的供应商接入、正向交易、反向交易外,还包括搜索和基础数据系统,同时还增加了供给侧的电子票业务功能。同时,我们建立了新的运营后端,以确保运营支持的连续性。

项目实施过程中,除了抢票过程中提到的一些问题外,我们还重点解决了以下问题。

搜索优化

我们首先看一下用户在站点搜索中可能经过的系统:

图12-站台查询调用流程

请求首先到twl api层,然后到查询服务,到tjs访问服务,最后到供给端。整个通话环节还是比较长的。如果每次调用都是全链路调用,结果并不乐观。所以对于查询结果就有缓存,缓存也是缩短链接、提高性能的关键。缓存站点查询有几个困难:

对数据的实时性要求非常高。核心是剩余票数。如果数据不实时,用户下单、占座的成功率会很低。

数据比较分散。比如出发站、到达站、出发日期,缓存命中率不高

供给侧接口不稳定。平均以上

综合考虑以上因素,我们设计网站搜索流程如下:

图13-搜索设计流程

如图所示,首先,对于一个查询条件,我们会缓存多个通道的结果。一方面我们想比较哪个通道的结果更准确,另一方面可以增加系统的可靠性和缓存命中率。

我们将过期时间设置为10s。缓存结果的有效期定义为10s。先取有效的;如果有效的为空,则取无效的;如果无效的也为空,则同步时限为3s调用通道获取。同时无效和无效的都是现有的缓存通道被异步任务更新。请注意,分布式锁用于防止对通道结果的并发更新。最终缓存结果如下:

缓存命中率将在96%以上,平均RT在96%左右,可以保证数据及时更新,同时保证良好的用户体验。

消息消耗

我们有大量的业务是通过异步消息来处理的,比如订单状态变化消息、席位占用通知消息、支付消息等。除了正常的消息消费之外,还有一些特殊的场景,比如顺序消费、交易消费、重复消费等,主要是基于.

顺序消费

主要用于消息有顺序依赖的场景。例如,订单创建消息必须在占用消息之前处理。它支持消息的顺序消费,我们基于它来实现这个业务场景。原理上也很简单。生产者被限制只能向一个队列发送消息,而消费者被限制只能有一个线程读取。这样,全局单一队列、单一消费者就保证了消息的顺序消费。

重复消费

保证的是At Once,但不是Only Once。之前我们在抢票的时候也提到过。首先,要求业务方保持幂等性。另外,利用数据库表rd和rd来保证消费结果的准确一次性交付和确认。

交易消费

基于交易消息功能。它支持两阶段提交。它会先发送一条预处理的消息,然后回调执行本地事务,最后提交或回滚,有助于保证修改数据库信息和发送异步消息的一致性。

灰度操作

歼10战斗机总设计师曾说过:“造飞机不是最困难的事情,难的是让它飞上天”。我们也是如此。 3月是春游高峰期,业务量与日俱增。在此期间,系统发生重大切换,需要完整的解决方案来保证业务切换的顺利进行。

方案设计

灰度分为白名单部分和百分比灰度部分。我们首先在内部进行白名单灰度,稳定后进入20%流量灰度期。

灰度的核心是入口问题。由于这次前端也进行了全面改版,我们从站点搜索入口向用户引入不同的页面,用户将分别在新旧系统中完成业务。

图14-灰度运算方案

搜索订单流程

应用程序在搜索门户时调用灰度接口获取跳转地址,实现门户导流。

图15-搜索顺序导流

效果对比

短期计划

我们在火车票业务线上才初步实现了服务化。同时,还有一些事情我们未来会继续推动和完善:

1、服务粒度细化:目前的服务粒度还比较粗。随着功能的不断增加,粒度的细化是改进的重点,比如将交易服务拆分为订单查询服务、订单创建服务、占座处理服务、票务处理服务等。这也是必然趋势。只有细粒度的服务才能最好地满足我们快速开发、快速部署、风险控制的要求。

2. 服务资源隔离:仅在服务粒度上实现隔离是不够的。 DB隔离、缓存隔离、MQ隔离也是非常有必要的。随着系统的不断扩展和数据量的增长,资源的细粒度隔离是另一个重点。

3、灰度多版本发布:目前我们的灰度策略只能支持新旧版本并行。未来除了多版本并行验证之外,还将结合业务定制需求,让灰度策略更加灵活。

写在最后

业务的发展离不开技术的发展。同样,技术的发展也必须充分考虑当前的业务状况和当时的场景条件。两者相辅相成。我们应该避免过度设计而不是设计不足。

技术架构是不断演变的,而不是从一开始就设计好的。我们需要根据业务发展的规律,阶段性地分解长期的技术方案,逐步实现目标。同时我们也要考虑到服务化会带来很多新的问题,比如复杂度骤增、业务拆分、一致性、服务粒度、链路过长、幂等性、性能等。

比服务支撑更困难的是服务治理,这是我们都需要深入思考和去做的事情。

分享