「定制旅行」已经逐渐成为旅游行业「消费升级」的一个主流模式。
随着用户对旅游服务的要求越来越高,对行程安排有个性化需求的消费者来说,常规的跟团游和自由行产品已经很难满足他们的需求。在这样的环境下,马蜂窝电商业务平台也在不断探索在提供门票、机票、酒店等这样旅游电商「标品」之外,如何充分结合现有的供应商、旅行定制师资源优势,更好得满足用户需求,创造多样化服务。
2018 年 6 月,马蜂窝电商业务上线了支持多工单抢单的「定制旅行交易系统」(图 1)。不同于之前电商标品连接人和商品的属性,定制旅行的本质是连接人和服务,将有定制旅行需求的消费者与有承接能力的供应商、旅行定制师实现更好的匹配。
图 1—马蜂窝定制旅行交易系统
抢单模式带来的定制旅行平台升级
通过定制旅行平台,用户可以根据自己的定制主体(个人/企业)、出发地、目的地、往返时间、人数、预算,提交个性化旅行需求,系统通过抢单、派单的方式将需求与有承接能力的供应商进行对接。
最初,定制旅行系统采用的是将一个需求分发给一个供应商的一个定制师的解决方案。这样的做法存在几个明显的缺陷,比如:
人力承接问题:假设供应商的控制资源出现饱和,需求分配之后没有能力承接,造成需求资源的浪费;
资源控制问题:当某个目的地进入淡季,供应商为了降低成品,控制资源能力减弱,没有办法承接需求。
为此,研发团队首先开发了多工单系统,在用户提交定制需求时,根据用户选择服务的定制师数量,由系统派发给多个供应商的多个定制师,然后在此基础上进行系统升级,引入了抢单功能,主要用于热门目的地有定制游需求的个人用户。
用户提交定制需求后,系统将会根据用户特征数据和供应商、定制师特征数据进行算法匹配,使用户和定制师之间进行双向选择。每个定制需求最多可以支持三家供应商的定制师抢单同时为用户提供旅行方案,由用户挑选最终确认其中一个定制师的旅行方案为用户服务。抢单系统带来的几个好处是:
提升商家的积极性,通过竞争,提升商家的跟进速度和转化率
解放 BD 的大量工作量,取得更好的商家运营和转化的结果
提升平台整体转化率,将需求在马蜂窝平台释放给长尾商家机会,解决长尾商家无法从商品排序上获取流量的问题,从而达到培养、发掘潜力商家的效果
供应商主动选择需求单,可优先选择自己擅长有优势的需求单,提升转化率
图2-多工单系统改造
抢单系统的关键是高效的资源匹配和信息沟通。因此,抢单系统的核心设计主要有两点,一是抢单池的消息队列,如何满足并发需求;二是消息通知服务,如何及时有效地通知定制师。
核心技术实现
并发控制
抢单功能允许同一需求被多个供应商的多个定制师同时抢到。用户提交需求后,会进入统一的抢单消息队列。每个需求最多会有三个定制师同时为用户提供服务;在同一企业内,只有一个定制师可以抢单。
在降低并发方面,主要是通过以下两点来实现:
1. 定制师分级,使用马蜂窝消息总线延迟消息服务,按照等级延迟通知
根据供应商和定制师的业务范围,BD、运营根据定制师的服务能力对定制师进行定期考核划分等级,不同的定制师通过商家后台或者微信公众号可以看到不同的抢单池队列。
通过使用电商自研的消息总线服务,根据定制师服务范围异步分发,同时利用等级信息过滤抢单池队列,保障所有的相同等级的定制师在同一时刻看到相同的抢单池队列。
2. 控制并发锁粒度及锁释放,防止死锁
抢单接口调用时进行十余种业务防刷控制,抢单分配时,将死锁放置到可控最细粒度,保障并发度,抢单结束后,通过长连接自动更新抢单池,将该已抢需求从抢单池移除,避免过度打扰用户,同时减少定制师之间竞争造成的资源浪费。
定制旅行交易系统使用 Ko 框架,关于控制并发是采用文件锁还是 Redis 锁的问题,主要是考虑:
文件锁 Ko_Tool_Lock.php 不存在 expire 自动释放锁的机制,如果获得锁未能正常释放会死锁。
#p#分页标题#e#
Ko 的 $oRedis->bSetNX 加锁,可以设置 expire 但是问题是不支持 setNx 同时设置 ex(拆成 2 步,不是原子操作),性能测试抢单并发度较高,对于锁超时的偶发情况,系统可以回收资源进行再次分配,因此对于发起抢单请求的定制师,可以首先检查是否有超时未释放的锁,如果存在,则强制释放锁,然后再次尝试获得锁。
图3-锁的控制
消息通知-长连接
据统计,定制师联系用户需求越及时,需求转化率越高,及时获得抢单消息通知至关重要。消息通知的方式主要有两种:PC 端消息弹窗,以及在移动端通过微信公众号的模板消息来实现。
为了实现有抢单池变更可以第一时间提醒定制师,让定制师不用自己刷新抢单池就可以实时看到最新的待抢需求,这里引入了长连接服务的解决方案,并进行了以下应用层优化:
1. 长连接复用
定制师在查看商家后台时,会打开多个页面,系统需要在当前的活跃页面上进行通知。理论上来讲,每个窗口理论上都需要提供一个长链接的服务。但这样无疑上会造成长链接的资源浪费。使用“长连接复用”的方式可以解决这个问题:
(1)同一个浏览器多个tab页之间共用一个长连接ID(setcookie),重启浏览器新建conn id
(2)同一个浏览器,来回切换用户,一个用户只产生一次长连接ID(redis hash)
2. 消息广播
同一个浏览器多个 tab 页复用 conn id,互相广播,对于活跃窗口如果出现没有监听的消息,广播到活跃窗口上去。
图4-抢单系统消息通知与广播
3. 重试机制
网络异常是不可避免的,我们不知道什么时候网络会断,导致长连接断掉,会对消息通知带来影响。采用重试机制,限定时间周期,监听网络有没有异常。在长连接断开时指定短时间内自动重试,超过最多重试次数后,自动延长重试间隔,防止服务异常雪崩问题。
图5-重试机制
4. Lua 协程
最初长连接请求打到服务器之后,服务器的承接方式是通过 PHP 进程。项目上线后发现了一个问题,服务器上的请求长时间挂在一个比较高的点上,可以处理的长连接需求受限。而且特点是内存比较大,但 CPU 消耗比较小。后来我们采用了 Lua 协程的解决方案,不是把请求转到 PHP FPM 上,而是转接到 Lua 上,减少内存的消耗。
小结与未来规划
定制抢单功能的上线促进了供应商之间的良性竞争,能够适应供应商服务能力和资源控制力多样性而进行动态分配,确保用户的需求得到充分满足,另外,也在一定程度上促进了供应商内部的定制师考核制度不断完善。更重要的是,使定制游平台上的收益转化率有了超过 70% 的明显提升。
本文作者:王伟阳,马蜂窝技术专家,2017 年加入马蜂窝,现负责马蜂窝电商度假业务开发。2010 年研究生毕业于中国农业大学,曾在甲骨文、百度工作,先后从事分布式拓扑环境任务调度与报告系统,工程效率与质量平台与工具,评估测评系统等方向的研发工作。
【本文是51CTO专栏作者马蜂窝技术的原创文章,作者微信公众号马蜂窝技术(ID:mfwtech)】