支撑马蜂窝「双11」营销大战背后的技术架构

4,961 阅读18分钟

(马蜂窝技术原创内容,公众号 ID: mfwtech)

引言

消费者的狂欢节「双 11」刚刚过去。在电商竞争环境日益激烈的今天,为了抓住流量红利,双 11 打响的已经不仅仅是「促销战」,也是「营销战」,这对平台的技术支撑能力提出新的要求。

从 2014 年的「318 大促」,到正在进行的 「马蜂窝双 11 全球旅行蜂抢节」,马蜂窝旅游电商业务的大促已经走过 5 年时间,仅仅是双 11、暑期、十一黄金周、年终这些关键节点的 S 级促销就张罗了 50 多场,每年上线活动达几百个。

图:马蜂窝11.11全球旅行蜂抢节

在这个过程中,马蜂窝营销平台也在经历着优化和改进,不断探索灵活高效的营销活动运营开发方式,更好地支撑业务营销活动的模式创新和投放需求,努力实现平台商家与马蜂窝旅游消费者的高效匹配,目前已经形成了一套较为完整的技术体系。

本文将以马蜂窝营销活动后台的技术实践为重点,分享马蜂窝营销平台的架构设计思路,希望能让遇到类似问题的同学有所收获。

一、马蜂窝营销平台架构

1. 营销中心体系

谈到大促,大家可能首先会想到的海量数据、高并发、高流量等关键词。其实除了这些,营销活动数量多、周期短、功能复杂多变等,都是营销活动运营开发需要应对的挑战。并且由于我们的很多活动会涉及到一些奖励或权益的下发,对于安全性的要求也很高。

针对以上问题,马蜂窝营销系统的技术架构要具备的核心能力包括:

  • 打造灵活、高效的活动开发模式

  • 提供高可靠、高可用的活动运营支撑

  • 保证营销活动业务的安全运行

因此,我们本着「平台化、组件化、模块化」的方法,将营销体系的架构设计如下:

马蜂窝整体营销体系分为 B 端和 C 端两部分。B 端主要面向商家,帮助商家在马蜂窝招商平台进行大促活动的提报以及商品选取;C 端主要是面向马蜂窝用户,平台用户可以在业务营销页面完成活动商品的购买、秒杀、大促红包赢取等具体的营销活动参与互动。

2. C 端营销平台

C 端营销平台的系统架构主要分为主要分为营销应用层、中间层、投放平台、搭建平台四个部分。

  • 活动开发平台:营销平台最核心的部分,也是本文重点。包括前端页面搭建层「魔方」、业务逻辑层「蜂玩乐园」、奖励规则控制层「奖池」三部分

  • 投放平台:是指营销活动页的投放,包括投放策略、运营策略和机制等

  • 中间件:负责并发处理、分布式缓存和容灾限流等等

  • 营销应用:包括马蜂窝大促营销、业务营销、新人礼包等

下面,我们重点介绍营销搭建平台的核心部分——活动开发平台,是如何实现高效、灵活的营销活动开发模式的。

二、活动开发平台的实现

2.1 灵活高效的开发模式

通过上图可以看到,由 MySQL、ElasticSearch、Redis 组成的数据池在底层为活动开发平台提供支撑。其中 MySQL 为最主要的存储方案,用于会场搭建配置数据、蜂玩乐园的用户运行数据、奖池配置数据等的存放。ElasticSearch 是搜索引擎,支持活动页面商家活动报名与筛选过程。Redis 有 2 种用途:1)活动任务并发锁;2)奖池的奖品数据存放;3)限流和削峰。

之前我们提到,活动开发的挑战包括数量多、周期短、功能复杂多变。为了降低开发同学的工作量,提升研发效率,我们将前端和后端组件进行了整合,并封装成功能模块对提供服务,形成了目前的魔方、蜂玩乐园、奖池三个子系统,使整体结构更加清晰。每个部分解决的问题和主要功能模块示意如下:

2.1.1 系统分层

魔方

「魔方」系统希望通过组件、工具的方式完成营销页面的搭建,实现统一维护和复用,从而减少前端团队在活动开发中承载的重复性开发工作。目前为止我们已经在「魔方」中开发了 80 多个组件模块,例如秒杀模块、货架模块、店铺模块、导航模块、领券模块、游戏互动模块等。

现在,小型活动完全可以不用开发人员支持,只需要业务同学操作即可搭建促销会场上线活动,提升了活动运营效率,也大大解放了前端开发人员。关于「魔方」更多的细节我们会在后续文章单独介绍,本文不过多展开。

蜂玩乐园

(1) 逻辑功能抽象

营销活动的核心是创新和吸引力。每次活动开始前,运营同学都会在创意策划上绞尽脑汁,尽可能创造出与众不同的新玩法。这些新颖有趣的游戏玩法,可以在微信,App 等渠道引起用户的好奇心和兴趣,为卖场拉新,进而创造更多的交易。

随着「花样」的不断翻新,活动开发的复杂度也在增加,有时甚至让技术同学应接不暇,也促使我们探索更加高效的开发方式。

我们开始思考在复杂多变的活动玩法下,是否潜藏着一些不变的模式和规则?通过对不同业务活动模式的分析和抽象,我们将活动的流程和用户的行为进行了一个有趣的类比:

  • 首先,开发活动就创建了一个「乐园」

  • 我们会根据不同的「规则」去设计每一个「活动」,激发潜在「参与者」的兴趣,或建立他们希望赢得奖励的期待。

  • 进入活动后,我们会验证参与者「身份」,和需要满足这次活动的「条件」,来确定他是否可以开始。

  • 活动开始时,参与者参与一次活动需要发生的行为,就是在完成「任务」

  • 完成「任务」后,为参与者发放相应的「权益」或「奖励」。

这个类比模型在历届促销活动中进行了推演,结果显示基本是通用的,但完成任务可能伴随奖励服务,也可能没有,由具体业务需求决定。举个例子,在一场红包裂变的营销活动中有一个需求是下红包雨,用户可以点击掉下来的红包领取相应的红包奖励。那么「领取」这个动作就可以视为活动中的一个任务;另一个需求是每当用户成功邀请一位好友后就可以在任务中心领取一个邀请红包奖励,那么我们可以把在任务中心领取邀请红包也看成一个任务。

这两个任务有一个共同的特点就是触发后都有红包奖励,只是在第二个场景中的任务,本质上是用户发起了一个请求。

经过进一步的梳理、规整,我们抽象出了「参与者」、「活动」、「任务」、「奖品」等业务逻辑功能。

(2) 技术实现

蜂玩乐园将每一个业务逻辑功能收归到一个唯一的入口和统一的体系中,形成独立的功能组件模块,如数据请求模块、自定义数据配置模块、验证器模块 、执行器模块、奖励服务模块等。每个活动的任务开发都可以选择模块配置,模块配置信息以 yaml. 的格式进行统一管理,这样的配置具有灵活性、扩展性和可复用性。

在使用的时候解析配置数据,并向组件注册中心注册该任务所需要的组件模块,再按照定义好的顺序执行即可。流程如下图所示:

为大家介绍几个关键模块的实现。

  • 数据请求模块

数据请求模块定义了客户端与服务端约定好的请求参数规则:

request:
       -
        field:  deviceId
        rule: required  #必填项校验
        method: post
        message:  deviceId参数错误
       -
        field:  sex
        rule: in:[0,1] #范围校验
        method: post
        message: 性别范围错误
       -
        field:  phone
        rule:   regex:/^1[3456789]\d{9}$/ #正则校验
        method: post
        message: 手机号格式错误

(i) field - 传入参数的 key (ii) rule - 校验该参数的规则,目前我们已经实现了一些常用的规则:
(iii) required - 必传参数

  • in:验证所传参数必须在指定范围内

  • regex:正则表达式校验

  • min,max:自定义规则最小和最大长度

  • integer:必须是数字

  • method:定义 GET、POST 请求方式

(iv) message - 规则验证失败返回的错误信息。这一层会读取配置模块中的请求参数模块配置内容,将内容解析出来,按照所配置的字段规则做响应的校验,如校验通过继续向下执行,没有通过则直接返回规则提示。

  • 参数配置模块

参数配置模块定义了该任务执行中所需配置的所有静态数据配置项。营销活动的特点是多样性、创新性,所以很难去穷举各种场景建立一个有针对性的配置中心,因此这里就为每一个任务单独开辟了一个没有结构化的小空间,可根据具体场景的特定需求为任务自由配置,使程序代码里基本不用再写各种不合理的硬编码。

params:
    stockRedPacket:
     amount: 1
     stock: 3
     stockKey:  limit_key
     stockField: limit_key_90
     timeWindow:
       beginTime: "2019-11-06 00:00:00"
       endTime: "2019-11-10 23:59:

以一个用户开启红包的配置信息为例:

(i) stockRedPacket 配置了活动设定的固定库存与固定金额红包的业务逻辑

  • amount 金额

  • stock 库存

  • stockKey、stockField 用来加锁的字段

(ii) timeWindow 定义了该任务的活动开始和结束时间

  • 验证器模块

验证器模块的功能主要是是对业务或者规则的校验。它定义了该任务要执行的业务验证规则,特点是具有单一性、普适性,能提供一种适用于大多数场景的方法。这些验证规则可以拆解得足够细,越细则越灵活,得以在不同任务中灵活组装使用。

validator:
   - MCommon_Validator_TimeWindowValidator
   - MCommon_Validator_AuthValidator
   - MCommon_Validator_LockValidator
  • 这里使用了活动时间验证 TimeWindowValidator,不在活动时间内则返回错误提示

  • 登陆验证 AuthValidator,参加活动必须要登录,前端通过判断错误状态码统一跳转到登陆页面

  • 并发锁 LockValidator,避免一个用户同样的操作多次提交

  • 取出所有的验证器,然后通过反射依次按照顺序调用,如果其中一个验证器失败,则直接返回错误信息,终止向下执行。

  • 执行器模块

执行器模块定义了该任务要执行的一些业务逻辑,比如要执行一段写日志的逻辑,要执行一个异步调用的逻辑等,都可以使用此模块定义一个执行器去执行。

command: MSign_Command
afterCommand: MSign_Command_After

执行器又分为前置 command 和后置 afterComman:

  • 如果需要执行奖励模块,则先执行前置 command,再执行奖励逻辑,最后执行后置 afterCommand,最终返回结果

  • 如果没有奖励,则先执行前置 command,接着执行后置 afterCommand

  • 奖励服务模块

奖励服务模块决定该任务是否需要执行奖励发放,如果配置了奖励,任务在执行时会根据奖励的配置规则下发奖励。在我们的实际场景中,主要涉及到的奖励类型包括奖励机会、红包、抽奖、优惠券等:

  • 奖励机会:有 2 种规则,分别是按固定频次给用户初始化机会数,和奖励增量机会数。

  • 发送红包:设定固定红包和随机红包,随机红包按需求设置发放的概率与用户群。

  • 抽奖:对接奖池系统,下文详细介绍。

  • 优惠券:与马蜂窝优惠中心直接打通,只需要配置优惠券 SN 和渠道号,即可把优惠券发送到用户卡券。

奖池

在营销活动中,许多场景都涉及用户抽奖或奖品发放。营销技术平台因此对奖品发放的整个生命周期进行了抽象和封装,创建了「奖池」。

(1) 主要功能

奖池的主要功能点包括:

  • 创建奖品池:为每次活动创建一个或多个奖品池

  • 设置奖品:在单一奖品池中均匀放置奖品

  • 用户抽奖:用户在单一奖池中抽奖,支持按概率抽奖,支持奖品的发放和领取

  • 中奖统计:包括奖品已发放数量,已领取数量,剩余数量

如下图所示,只需创建好奖池,配置好奖品信息,把对应的奖池 ID 填写到任务,即可实现抽奖功能:

(2) 方案设计

奖池早期的设计非常简单,奖品实体仅定义「余量」的概念,利用关系型数据库中单行记录存储一次活动的奖品、总量、发放量、余量等数据。在用户流量较小且均匀的情况下,发放过程平稳正常。每次进行奖品发放时,在单行记录上进行 update 操作,扣减余量并增加发放量即可。

然而随着公司业务的发展,一次营销活动带来的效果让我们不得不立刻改进奖池方案。这次营销活动带来的流量远超预期,但奖品数量的配置却一如往常。当活动开启后,奖品消耗很快,并在一段时间后被提前抽光。为了不影响用户体验,营销运营同学不得不持续向奖池中补充奖品。

经历这次问题开发同学发现,奖池提前抽光的原因在于设计中忽略了时间分布的因素,使奖品抽光的速度只与访问量相关。因此,大家开始思考如何让奖品固定且均匀分布在活动周期内。

通过学习与比较,最终选择了业界比较通用的方案,使用 Redis 的有序集合(Sorted Set)创建奖池和设置奖品,从而使奖品在活动时间段内均匀分布,防止提前抽光的情况出现。

(3) 实现算法

1. 时间戳:根据奖品的数量和活动时长,为每 1 份奖品设置一个出奖时间戳,这份奖品仅能在这一时间点及之后被抽出。这一步使出奖时间戳尽量均匀分布在活动时间范围内。

2. 创建奖品池:为每一组奖品设置一个奖池,在 Redis 创建一个 zset 数据结构,将其中的每 1 份奖品作为 1 个成员(Member),将时间戳作为分值(score)。
3. 放置奖品:使用ZADD 奖池 出奖时间戳 1 份奖品语法,在 Redis 中布置一个奖品。

4. 抽奖:使用 Sorted Set 的排序方法,每次排序后查看排名第一的奖品,比较当前时间戳与奖品时间戳的大小。如果当前时间晚于或等于出奖时间,则使用 ZREM 指令出奖,否则不出。

示意图如下:

2.1.2 体系统一

为了让开发同学只专注于任务的设计开发,我们抽象出「账户」的概念,每个任务产生的数据资源会存储在所在的「账户」体系下,使其支撑多个类似的活动。这种设计的好处在于:

(1)同一用户在参与不同的活动时得到的奖励都是相互独立的,不会出现混淆的情况。

(2)之前每次活动都需要单独创建数据表,活动下线后表不能复用。时间长了造成系统占用许多无用的数据表。而把数据库表以抽象的任务形态创建,不针对具体的某一业务类型,就可以使数据表实现复用。这样我们只专注任务的设计开发,不用再关心数据表的设计。

在营销大促的活动中,我们也接入了风控中心、并发锁和限流服务,以保障整个活动的安全和稳定。

2.2 可用性和可靠性

秒杀模块是大促流量的最高峰。结合业务实际,我们针对这种场景也做了限流和削峰处理。

限流采用的方案是限制时间窗内最大请求数据,用户再抢会员权益时,第一步会读取限流配置 key 和 value,判断单位时间内是否超过限制的最大请求数 value,如果超过则返回信息提示结束请求;如果没有超过阈值,则进入下一步操作。目前的限流系统只是在应用层面的实现,为了更好地支撑业务发展,后续我们也会接入网关服务,通过 Sentinel 和 Hystrix 做限流熔断,避免流量进入应用层,提高服务的高可用性。

削峰部分结合实例说明。

以秒杀金卡会员的场景为例,我们会先用 RabbitMQ 承接瞬时的流量洪峰,再平滑地消费所有请求,并提前把库存数量对应的 Token 写入 Redis 缓存(这里我们针对性的对不同的用户引入了 Token 机制,以保证我们的秒杀商品在时效和库存上得以保障)。用户在秒杀时会顺序地从 Redis 中 rPop 对应的 Token,避免库存超卖的情况;用户拿到 Token 之后再去收银台下单开通金卡会员,就可以避免流量同一时刻去下单。

随着业务和技术的发展,系统的不确定性以及流量的预估都更为困难。我们也在不断学习业界的先进经验,来更好地提升系统的可用性和可靠性。目前我们正在调研基于 Noah 的「自适应」限流技术并积极推进,以期针对不同的服务器性能以及当前的流量情况进行针对性的限流控制,相信我们会在后续的优化中会做得更好。

2.3 风险控制

目前是接入公司统一的风控中心。在营销活动需求确定好后,我们会向风控服务中心提供需要风控的业务场景逻辑。风控中心根据业务配置不同策略,给出不同的场景 key。我们只需要在营销活动任务中的自定义参数配置模块配置好风控场景 key,就可在奖励服务模块自动调用风控接口去校验用户,如果识别出是风险用户则会被拦截,终止活动参与。

可用性和可靠性、风险控制的实现流程如下图所示:

三、近期规划

1. 完善监控体系

目前对于活动运行中的数据监控,主要依赖数据组的统计与输出。线上活动的运行情况并不能通过「蜂玩乐园」与「奖池」系统实时并综合表现出来。

未来会补齐运行时的活动监控功能,通过活动、任务、奖品的运行时数据指标,指导运营同学第一时间调整活动参数,取得最佳运营效果。

2. 服务化改造

营销基础平台依旧搭建在单体架构上,部分功能的边界与职责并不完全清晰。接下来营销技术平台会进行技术架构的升级与改造,从单体架构转向微服务架构,使服务能力与开发能效同步提升。

小结

随着营销的逐年发展,活动的趣味性和复杂度会一起上升,这需要我们不断更新对营销活动的认识。在这过程中也要反复尝试新的抽象和重构,通过不断改进现有系统,支持更多和更好玩的营销活动,让马蜂窝用户玩儿得更省心,玩儿得更省钱。

本文作者:马蜂窝电商研发营销中心团队刘远匀、任浩、唐溶波。