前言
关于营销消息,大家肯定都不陌生,甚至有些人可能对他们恨之入骨,大家可能会时不时收到一些信贷短信,商品推荐的邮件,新闻头条的手机弹窗,AI营销电话,外国的友人可能还会收到很多whatsapp的广告推送,这些营销消息通过各种各样的渠道(比如邮件,短信,手机push,微信公众号等)触达到用户,给用户宣传各种奇奇怪怪的东西。但是你必须要知道,给一个用户发消息简单,但是要给上千万甚至上亿的用户发消息,那么要处理的事情可就多了,很多公司每天推送出去的营销消息就达到数十亿条,在一些特殊时期,比如双十一,618,黑五,这个数量可能会达到数百亿甚至千亿。要应对这么庞大的数据量,就需要投入大量的研发资源建设。很多大公司往往会有多条业务线,而且现代商业几乎不管什么业务都难以离开营销消息推送,让每条业务线都自己做消息推送,不仅导致不同团队做了大量重复的工作(包括合规,数据报告,成本控制等等),浪费了很多研发资源,而且不同的营销消息推送质量也难以同时保证,所以很多大型公司都会建设统一的营销消息推送平台来供不同业务线接入使用,避免资源浪费的同时也达到统一管理的效果,作者参与过两家大型公司的营销消息推送平台的建设和维护,在高峰期甚至每天需要推送三四百亿条消息,深知建设这样一套系统所要应对的一系列挑战,在经过一系列的实践和思考后,总结两家公司消息平台的优势和劣势,然后也收集了大量其他大型互联网公司的消息平台建设资料,在没有任何历史包袱的情况下,设计一套可以支持超高吞吐量,易于维护和扩展的营销消息触达平台。
这篇文章的目标受众是拥有较深开发经验和具备相关系统设计能力但是对于营销消息平台建设缺乏相关经验或系统性知识的用户,文章会提供一种具体的营销消息触达平台的架构设计和流程设计方案,并且对系统过程设计中的一些实现难点和核心细节输出一些解决方案,并且指出用户在设计过程中需要注意和处理的一些问题,因为为了控制篇幅无法对每一个细节的实现进行详细介绍,所以我们会聚焦在整个流程和架构上的设计以及一些核心细节和难点的实现方案。
这篇文章因为是纯手打的,而且时间比较仓促内容比较多,可能会有一些同音错别字或者表达不通顺的地方请大家谅解,这块会持续优化,
关于作者
作者 Corki Tse 出生在广东的一个沿海的小渔村,是一个平凡的95后程序员,大学就读于一所普通本科,专业是电子工程,在校期间接触了 Web 服务,前端,微信小程序,Android,驱动程序的开发,建设了自己的个人网站oraen.com,也看了很多IT类的书籍,后面因为兴趣转行做了后端开发,直到现在一直过着平平无奇的打工生活。业余时间喜欢看看书,开发一些个人的软件程序,写写文章。
一 营销消息触达平台概括
首先我会对营销消息平台做一个简单的概述,让大家能够更加深刻了解一下我口中的营销消息平台,这可能让大家更加容易理解后面章节平台建设上做的一些设计和取舍。
1.1 营销消息触达平台介绍
1.1.1 平台的使命
其实我们的营销平台的基本使命很简单,就是根据业务自己的计划,把业务想要发送的消息,通过业务想要的渠道(比如邮件,push推送,短信,whatsapp,talkbot等),正确,及时地推送给用户,符合相应的法律法规,并且在要对发送的消息给业务方呈现一个数据报告,比如共发送了多少条消息、多少用户已读、多少点击了链接、多少通过链接下单,以及有多少条发送失败的消息等等,并说明发送失败的具体原因。当然随着平台迭代,还可能需要帮业务方做一些其他的事情,比如帮助业务生成文本文案,帮业务根据不同用户选择更合适的模板,自动在合适的时机给用户推送合适的消息,以及帮用户进行一些用户偏好分析等等。
1.1.2 为什么区分业务消息和营销消息
大家可能注意到我一直在强调是营销消息,可能会有人疑惑,营销消息和业务消息(比如网约车司机接单,外卖骑手到达消息,短信验证码等)本质上不都是平台发送给用户的消息吗。为什么不干脆设计一套系统既发送业务消息也发送营销消息呢,难道营销消息平台就不能发送业务消息吗。答案肯定是可以的,我们完全可以使用营销平台来发送业务消息,也可以设计一套消息平台既用来发送业务消息也发送营销消息,但是大多情况下业务消息和营销消息的特点不同,通用平台在设计上往往需要进行一些取舍,最典型表面的就是消息的及时性和触达保证的取舍,你很难设计一套消息系统能够完全满足营销类消息和业务类消息的要求,由于技术的原因你必须要做一些取舍,所以即使是通用的消息触达平台,也往往具有偏向性,是更加偏向于发送营销消息还是偏向于发送业务消息,我们本次建设的消息平台,当然也可以用于发送非核心流程类的业务消息(像用户验证码登录这类核心流程相关的消息还是比较推荐直接发送而不是通过平台),只不过我们在设计上会更加让他适用于发送营销消息,下面我们说一下这两类消息的特点
业务消息 :业务消息其实还能分为核心流程类消息,比如短信验证码登录,这类消息就是为了完成某些业务功能,用户收不到消息就会直接影响到整个业务的功能,还有就是非核心流程类消息,比如网约车司机接单通知,餐厅订单完成通知,这类消息只是业务功能的一部分,如果发不出去,可能会影响到用户体验,但是并不会影响到整个业务流程,业务消息往往对时效性的要求较高,比如订单完成消息你推迟了半个小时再发送明显是不适合的,并且流量具有规律性和可预测性,比如滴滴的业务消息发送会集中在出现高峰期,而且日常的消息发送量基本稳定,下雨天和节假日消息量会变高,
营销消息 :大部分的营销消息具有计划性,业务会按照自己的规则有计划地给用户发送营销消息,你甚至能很清楚地知道未来某个时间会发送多少消息,发送给谁,但是这些计划可能并不是有规律的,而且和公司的营销策略或者新品上市节奏相关,而且大部分营销消息对及时性和触达保证的要求并不那么严格,大多数情况下,即使延后几分钟甚至几小时收到优惠信息,也不会造成明显影响。当然系统也必须考虑到需要及时发送的营销消息,比如限时秒杀,但是营销消息对系统吞吐量的要求往往更高,在活动的高峰期,你可能需要同时给好几个上千万甚至上亿的人群发送营销消息,如果系统吞吐量不够,可能一天也发不完,而且在某些没做流量隔离的消息系统中甚至可能造成后续消息的阻塞,除此之外,营销消息的明细数据回收也很重要,毕竟大部分消息发送是需要成本的,尤其是短信,whatsapp这些,还会影响到用户体验,业务肯定需要知道自己发送的消息有多少阅读,点击,下单,需要统计消息的效果,还需要用来帮助业务调整自己的营销策略。再除此之外,营销消息受到各国和各平台的法律法规也更加严格,不同的国家和平台对消息的要求也不一样,比如欧盟严格禁止夸大宣传,法国严格禁止种族区分相关信息,whatsapp严禁对不感兴趣用户进行消息轰炸等,总体来说,营销消息在系统设计中需要考虑的因素显著多于业务消息。
1.1.3 平台的价值
营销消息平台最大的价值肯定就是营销宣传,给公司带来增加订单和新用户,其中业务最关心的是转化率,即一条消息从用户阅读、点击链接,到最终下单的转化比例,这个转化率通常会很低,毕竟大部分连营销消息看都不会看,更别说点击甚至下单了。对于公司来说,营销消息平台给公司带来价值就是最终提供增量订单,所以我们在系统建设中,不能只想着,我们只是技术平台,只需要把业务想要发送的消息发出去就好了,至于最终效果,应该由业务自己负责,相反地,平台本身应该更加关注转化率,提供更加多样化的消息,帮助业务寻找更加合适的人群,选择更加优质的模板,为用户的决策提供支持,甚至通过数据引导业务的决策,毕竟,假设对于一个五千万的中大型项目,转化率如果能从百分之3提升到百分之4,就能额外带来五十万的订单量,产生的收益可能就是几百甚至数千万。所以我们必须清楚的知道,不管是提高系统吞吐量,增加触达率,还是其他的额外功能建设,最终都是为了这个价值。用超越技术的视角来看待系统建设,创造出的东西也会更加有价值。
1.1.4 如何开发
实际上大多数复杂的大型平台都是从一个或者几个简陋的小服务开始慢慢不断更新迭代的,很少有一步到位就建设好的,因为不同公司,即使业务类似,运作模式和策略也会有差异,而且未来局势千变万化,很难预测到业务下一步的发展,过早地完全建设,不仅增加了系统开发周期,使得系统延迟落地,让业务在市场竞争中入场变慢,还经常会设计出很多用不上的功能,浪费人力的同时还增加了系统的复杂度,大部分的系统应该都是从核心功能开始,然后再慢慢扩展,对于消息平台来讲,我们不应该一开始就把所有功能完成,而且应该先完成核心的功能,然后预留一些扩展点,通过可扩展的设计,保证未来可能的新需求(比如消息黑名单,频控,文案赛马)能够很方便地接入,但是也不需要太过于追求完美,比如想要事先考虑几乎所有情况,这样不仅不现实,而且耗费了大量时间,对于很多极端的需求我们也只能通过破坏一些系统设计来暴力实现,现实中也不存在什么完美的系统。
1.2 领域名词
在开始说系统的目标和设计之前,我们有必要同步一些营销消息触达领域的通用名词,有助于帮我们理解后面的内容。
1.2.1 消息渠道
消息触达给用户可以通过各种各样的方式渠道,比如邮件,短信,手机弹窗,微信订阅消息,whatsapp,机器人电话等等,这些就称之为消息渠道。
1.2.2 占位符
很多时候,我们都会把一个消息模板发给一堆用户,比如
亲爱的{{name}}先生/女士,我们这里检测到你的银行账户涉及洗钱,已经暂时将您的银行卡冻结,如果想要恢复使用,请向我们账户转账{{num}}元保证金,等我们进一步核实后会退还保证金。
在这个模板中,我们可能希望不同的用户收到的消息有所差异,比如前面的{{name}}能替代为用户的姓名,后面的{{num}}根据用户的财富状况决定金额,这类模板中的变量就叫占位符,占位符具体的取值只能在实际发送时候才能知道,可能来源于用户信息,活动场景,当前环境信息(比如发送时的年月日,天气等)。
1.2.3 明细数据
明细数据其实就是消息平台发送消息给用户的最细维度的记录,具体到给每一条用户发送了哪些内容,往往包括用户标识,发送内容(或者发送模板),发送时间,消息状态(已发送,已送达,已读,已点击等)发送渠道,供应商,活动id等等
1.2.4 消息供应商
由于消息平台要处理的消息渠道众多,每个消息渠道的处理又大不相同,所以很多时候我们都会把各个渠道的发送交给各供应商去做,来减少我们的工作量,我们只需要把要发送的用户和消息(或模板)提供给他们,由他们来完成实际的发送,这类帮助我们完成消息发送的合作方就叫做消息供应商,比如短信的供应商中国移动,中国电信,whatsapp的消息供应商message bird,infobip,vonage,牛信云,邮件的供应商oracle,emarsys等等
1.2.5 漏斗图/转化率
漏斗图是一种可视化图表,用来展示用户在营销流程中的各个环节的转化情况,比如下图就是一个营销消息发送任务的最终漏斗图。这里的requested指的发送数,arrived就是送达,clicked就是点击,不同业务系统不同渠道供应商对于这些指标和指标名称的定义可能都不一样,可能有些地方的点击是指用户点进去看了内容,而另外一些地方的点击是指用户点击了里面的营销链接,而前面的点击对应的是这里的已读,所以尽管意思都类似,但是不同系统的描述不一样,我们在设计系统的时候要尽量统一这些名词,虽然不同供应商给的名称可能会有差异,但是我们在存储为我们自己的数据的时候还是得按照我们自己的定义来转化,避免造成理解混乱,增加运营和维护成本。
转化率则是指从某一个环节到下一个环节的用户比例,一般营销消息从发送,送达,用户已读,用户点击,用户下单等等,每个环节都会损失相当一部分比例的用户,进入下一个环节的用户都可以叫做转化成功,有时候讨论转化率时也可以直接跳过多个环节,比如讨论从用户收到消息直接到下单的转化率。
1.2.6 人群
营销消息最终是需要发送给用户,具体是发送给那些用户呢,这里要发送的用户指的就是人群。人群顾名思义就是一堆人,一个人群可以继续筛选出一个更小的人群,也能分割成多个人群,多个人群也能合并成一个人群,大多数系统在管理用户人群时往往会通过给用户添加标签的方式进行标记,比如青年,家庭主妇,男生,网约车司机,外卖员,高净值人士等等,然后在选择发送的目标人群时通过标签圈选等方式指定要发送的人群。
1.2.7 发送主体
当我们收到一条消息的时候,我们一般都会看一下发送者是谁,这个发送者的账号就是发送主体,一个企业下面可能会有多个品牌,每个品牌一般都会有一个或者多个发送主体,比如腾讯下面就有LOL,QQ音乐,QQ视频,微信等品牌,他们在给你发送营销消息的时候经常会使用自己的发送主体,甚至一个品牌下面也会划分出不同的发送主体来负责发送不同类型的消息,比如一个主体专门用来发送面试邀约消息,一个主体专门用来发送营销消息等等,当然也可能会再细分,具体和业务自己的规划有关。
1.3 系统要求
在设计一套系统之前,我们必须要明确的就是这个系统至少能做什么以及系统给到的最小保证,当然这并不是系统的全部或者终点,因为随着后续的扩展,系统的功能肯定会越来越多,下面我就会说一下我们设计的营销消息触达平台需要保证的事情。
1.3.1 消息的正确性和及时性
尽管大多数的营销消息在内容和准确性和及时性的要求并没有业务消息那么高,但是这两点也是一个消息平台必须要保证的事情,可靠的消息平台必须要保证的用户收到的消息内容和业务预期的一致,并且用户收到消息的时间应该尽可能地接近业务预期的时间。
1.3.2 吞吐量和可伸缩性
前面我们说过,在一些活动高峰期,可能同时需要给好几个几千万甚至上亿的人群发送营销消息,如果吞吐量得不到保证,可能造成大量的消息堆积,使得消息延迟好几分钟甚至几个小时才发送给用户,这肯定是我们不期望看到的,但是活动高峰期也只是一小段时间,大多数时间下的消息量可能还没有活动高峰期的零头,这时候系统的可伸缩性就显得很重要了,平常时期我们可以缩容来避免资源浪费,活动高峰期我们就扩容来提高消息吞吐量,保证消息及时触达用户,避免堆积。
1.3.3 多租户
一个企业内通常有多个业务线和团队需要给用户发送营销消息,所以我们的营销消息触达平台应该需要支持多个业务方或团队接入,并且保证不同业务方的数据是相互隔离的不会互相影响,所以我们的平台需要支持多个租户的接入,并且为不同租户的接入提供最大的便利,做到开箱即用。
1.3.4 历史追溯
营销消息和业务消息很大的一个不同点就是,业务方经常会需要发送消息的历史数据,比如送达率,阅读率,点击率,已经对应的发送时间,阅读时间,下单时间等,用于活动的复盘,在一些重大的决策上也经常需要对比总结过去多个任务的发送情况,所以对于营销消息平台来说,提供发送消息的历史数据追溯是很有必要的,业务必须能够清楚地知道,过去的某个发送任务的发送数据报告,即使发送失败,也需要清楚地知道发送失败的原因,比如消息模板违规了,用户设备不在线,或者用户拒收了,又或者供应商的问题等等。
1.3.5 实时监控与告警
消息发送中经常会出现各种各样的问题,除了自身系统异常外,还可能遇到发送账号或者模板因为一些原因被封禁(比如被用户举报),消息供应商出现异常导致发送失败等等,对于这些异常情况的感知是必要的,系统必须可以实时得了解当前那些发送任务正在执行,那些任务发送出现了异常,以及对应的异常原因是什么,一旦我们发现了有发送任务出现异常,可以及时的在技术上或者业务上做一些补救措施,比如供应商异常可以及时切换供应商,然后尽快和供应商取得联系,模板被封禁可以及时告知业务更换模板,在无法修复的情况下也可以让业务尽早地做出对应补救方案来减少损失,比如秒杀活动的营销消息出现大批量发送失败的时候可以让业务方延长甚至更换秒杀活动的时间。而不是业务投入大量人力物力后才行没什么人参加事后再找过来问技术。
1.3.6 未来流量预测
我们前面说过,营销消息大部分是具有计划性的,比如业务计划每周一早上八点钟给家庭主妇发送自动扫地机的营销信息,虽然还没到周一,你就知道未来的周一的早上八点会发出去x条消息出去,我们大多数情况下也能够提前预测出发送人群的数量,也就是这个x的数量,所以营销消息平台的一个好处就是,我们能够提前预测到未来要发送的消息量,然后提前做好扩容等准备动作。
1.3.7 发送明细数据回收和数据对账
营销消息触达平台的一个重要的要求就是需要回收发送的明细数据,就是我们的消息具体推送给了哪些用户,推送时间,推送了什么内容,还有消息的状态(发送状态,已读状态,点击状态)等等,业务方经常需要通过这些数据来做数据分析或者确定收益,比如对比不同模板的效果差异,用户接受时间的效果差异,不同用户群体差异,营销消息给本次活动带来的增量收益等等。
做消息平台的人经常会和各种各样的供应商打交道,比如email,whatsapp,短信等渠道的供应商,像infobip,oracle,emarsys,vonage,牛信云,腾讯,中国移动还有一些外国的本地短信供应商等等,我们最终会把需要发送的消息提交给他们,让他们来实际发送,所以大多数情况下我们的明细数据就由他们来提供,但是他们提供的明细数据真的可靠吗,他们说他们发送了就发送了吗,他们说用户阅读就阅读了吗,答案其实是否定的,经验告诉我们,不要太过相信三方供应商,很多供应商,尤其是便宜的小型供应商,经常会偷偷克扣消息流量来节约自己的成本,或者自己的系统异常时会为了逃避责任而给出一份虚假的发送明细数据,作为一个长期和供应商打交道的我来说,这些事情屡见不鲜,所以我们系统内部必须有一套内部对账机制,来确保我们拿到的明细数据尽可能正确,但是数据是他们发送的,我们怎么知道他们给的发送明细数据是真的假的呢,这个还是有很多方法的,后面的章节我会具体介绍一些获取明细数据和核对明细数据的方法,这里我们只需要知道,一个成熟的营销消息触达平台,就必须要具备着这两个能力。
1.3.8 任务可控
很多做营销消息平台的应该等于这个场景不陌生,一个业务找过来,说有个任务的模板发错了,倒霉的是发送人群有数亿,而且正在发送着,这也就意味我们系统正在往一个庞大的人群发送着错误的消息,不仅会对用户造成骚扰,还需要负担一笔成本。再比如,系统在发送时出现了一些bug,导致给大量的西班牙语地区用户发送了大量韩语,一个大型任务的发送可能会持续数十分钟甚至一个小时,我们在发现异常后肯定是越快处理越好,所以,管理和控制正在运行中的项目,比如暂停或者停止正在运行的任务,也是个很重要能力。
1.3.9 流量管理
作为一个通用的消息平台,可能会有多个团队接入我们平台发送消息,这时候对于各个团队的流量管理就非常重要了,首先我们得根据实际情况控制最大流量,避免对下游供应商的调用量超过和他们约定的qps,造成请求被拒绝或者打爆他们服务,最终影响到整个系统的稳定性,然后我们也要保证各个团队之间的流量尽可能隔离,避免一个团队的大任务堵塞影响到其他团队的发送任务。所以对整体流量的把控和管理也是消息平台不可或缺的一部分。
1.3.10 系统合规
在发送消息时候我们也要保证我们的发送合法合规,避免有意或者无意发送一些违反法律的内容给到用户,确保遵守相应国家和平台的规则,给公司带来不必要的麻烦。
1.3.11 安全权限控制
任何系统几乎都离不开安全控制,发送营销消息不仅需要成本,而且也是以公司的名义和用户打交道,所以并不是所有人都有权限发消息,而且就算有权限设定消息发送计划的人,有时候也需要限制一下他的权利,比如他发起的计划需要某些特定的人审批,可能是部门领导,消息渠道负责人,或者安全部门的负责人等等。但是权限控制的方式多种多样,总之最终要保证就就是每个人只能做自己权限内的事情。
1.3.12 内部扩展和外部接入的支持
随着公司或者部门内部的业务要求或者团队调整,营销消息平台可能也需要内置更多额外的功能,这些功能可能和消息发送无关,但是流程和消息发送类似,比如优惠券发放,指令下发,这个时候就相当于我们的平台需要扩展一下其他功能或者接入其他其他系统,也有一种可能,随着公司内部团队或者系统架构变迁,营销消息平台被并入了广告平台下面,成为广告平台或者其他平台的一部分。这也是消息平台这类功能纯粹且单一的平台经常会精力的事情,所以在系统设计出之初就可以在未来外部系统接入和内部扩展上做一些考虑,避免设计上就把路封死。
1.4 系统设计和实现上的难点
1.4.1 流量管理兼顾资源利用
如果只是按照不同的业务线/团队/租户,限制他们的发送速度,并且让他们的流量隔离互不影响,这其实是比较简单的,但是你如果想在高峰期限制他们流量的同时,又做到资源利用最大化,那么就有一定难度了。比如由于三方供应商能支持的最大qps是1000,现在有A,B,C三个团队,你为了防止把供应商服务打爆,分别给他们配置了300,300,400的qps,如果在C团队的空档期,只用了100的QPS,A和B又有很多消息需要发,这时候C那300qps部分的资源就一直空着,浪费了。这个时候我们能不能临时把C的qps分配给A和B呢,反正C目前也用不到。为了资源的利用,大家肯定都觉得这样是合理的,但是应该怎么把这部分流量分配给A和B呢,如果这个时候由于一些原因C需要的流量又多起来了呢。这时候应该怎么办。这些都是肯定需要考虑的问题。我们必须设计一套高效且合理的算法。
1.4.2 消息发送各环节异常处理
消息发送过程中经常会出现各种各样的异常,服务重启,各自系统内部异常,网络异常,下游服务异常,供应商异常,发送账号或者模板被封禁,我们设计的系统肯定一开始就要想好这些异常的应对策略
1.4.3 多消息供应商
消息触达有各种各样的渠道,常见的就有邮件,push,短信,微信订阅,whatsapp,talkbot,tts等等,每个渠道大多数情况下都会选择对应供应商,为了保险起见,每个渠道的供应商都会选择两到三个以上,一个是防止单一供应商出了异常导致整个渠道瘫痪,另外一个就是多个供应商选择时在商务合作商能取得一定优势。所以一般来说等平台建设好后我们可能需要同时和十几二十个供应商对接打交道,每个供应商提供的接口交互协议和明细数据回收方式等又不一样,我们必须要保证我们的系统能支持多种协议,在完成了繁琐的接入工作后后要尽可能保护系统设计和代码设计简洁,当然有时候一个渠道的发送可能已经有了其他团队在负责,这个时候我们只需要接入这个团队的系统就好了。
二 系统架构设计
介绍完系统的背景和目标,接下来正式进入系统的设计了,这个章节会介绍营销消息触达平台的架构设计,包括一些核心的概念,核心的组件以及整体的架构图,架构的设计对于系统是至关重要的,架构设计得好,系统能支撑大流量,遇到流量剧变容易进行伸缩,有新的需求时也容易扩展,提高开发效率,也容易维护,大大节约人力和其他资源成本。
2.1 核心概念
首先介绍一下我们设计的一些核心观念,他们是我们本次架构和流程设计的基石。需要注意的是,这些概念并不是所有消息平台的通用概念,而是我们本次营销消息触达平台设计上引入的概念。
2.1.1 规则
这里的规则指的就是一个发送营销消息的规则,比如每天下午五点钟给高净值人群发送一个劳力士手表的营销广告的邮件,每周周一八点给旅游爱好者发送户外运动品牌营销广告的whatsapp消息,又或者是下周一给全量人群发送品牌新品广告的push信息,也可以是每天给当天生日的用户发送生日祝贺短信等等。规则只是指定了什么时候发送什么消息给什么人。所以他的核心内容就包括了,发送渠道,发送时间(或者时间间隔等),消息模板,发送的用户群体。如果是国际化的营销平台而且需要区分不同市场运营时候,还需要加入国家等信息。
2.1.2 计划和任务
计划就是根据规则创建的单次发送计划,就是什么时候通过哪些渠道给哪些用户发送消息。计划也是业务视角上的一个概念,这个相对与技术视角上的任务,任务指的也是一次发送消息给用户的任务,也包括了具体的时间,发送的渠道,模板,以及目标人群,不同的地方就是,一个计划可以涵盖多个渠道,多个市场,而任务需要区分技术链路和数据源只能指定一个渠道或者市场,也就是说通常情况下一个简单的计划都会对应一个任务,但是也会有一些复杂的计划,比如对于同时发送给多个运营市场的计划,如果多个运营市场的用户是隔离的。或者这个计划需要通过多个渠道给用户发送营销消息。那么就可能需要创建多个任务来完成这个计划。下图是一个计划是任务的对应关系图
2.1.3 活动
活动其实并不是一个必须的概念,他主要是源于业务需要,因为业务方发送营销消息很多是基于某个活动的,有时候一个活动可能需要多个发送计划,这时候我们不妨吧一个计划所属的活动也记录起来,最终也能记录到消息明细上,方便业务根据活动维度做一些数据统计和数据分析。
2.1.4 场景
为了区分不同的营销消息类型,我们为任务增加了场景的概念,标识这个任务是在什么场景发送给用户的,比如购物车降价,新品上市,老品折扣,加入购物车未购买等等,这样我们整个营销消息触达平台就能针对不同的场景对消息做一些特殊的处理,而且不同场景也能拥有自己的占位符,模板上也能够归属于一个或者多个场景,从而使用到这些占位符,比如对于老客召回折扣场景,不同的活动可能粒度不一样,比如有些小活动只能给到9折优惠,有些大活动可以给到8折甚至7折,这时候我们就能给这个场景配置一个占位符{{discountRate}},模板上直接使用这个占位符,避免重复创建多个模板。所以场景的引入不仅方便了技术侧进行场景定制型开发,还能够优化业务侧的使用体验。
2.1.5 消息
消息顾名思义就是平台发送给用户的消息,主要就是包含了用户,发送内容(或者模板),发送时间,还有消息状态(送达,已读,点击..)以及进入各状态的时间,由于消息的数据量巨大,每天可能会有数十亿的消息,所以一般不会直接把消息放在传统的DB上记录。而且除了问题排查或者时候数据统计分析外,我们一般也很少会使用到消息这个颗粒度的消息,但是在必要时,仍可以考虑把一些消息量较小渠道的消息存储在DB上。然后选择一些合适的数据归档或者清理策略。
2.1.6 审批流
业务给用户发消息,代表的是公司给用户发消息,而且发送消息多多少少也会有成本,在何况有时候业务也会犯一些错,比如错误地创建了一些规则或者选错了发送模板,所以即使是有权限制定发送消息规则业务用户,为了安全和保险起见,规则创建后需要经过一段审批路径,经过每个审批节点后才能完成最终创建,当然这条路径是我们自己定义的,可以是上级主管,平台负责人,消息渠道负责人,安全部门等等,而且审批方式也可以是串行审批(前一个审批人完成审批后后一个审批人才能审批,这样如果前面的审批不通过,就不会干扰到后面的审批人)或者并行审批(多个审批人可以同时审批,且等他们都审批后才完成规则创建,增加审批效率)。而且一个审批节点上也可以指定多个审批人,到达这个节点时候可以设置需要所有人同意或者其中一个人同意。除此之外,自动提醒对应的审批人审批以及记录每个规则的实际审批人也是非常有必要的。
2.1.7 分片
有时候我们可能会做一些很大型的活动,比如品牌生日想把折扣消息发送给所有的用户,这时候业务方可能就会圈选全量人群,对于一些大企业而言,这个人群是巨大的,可能有近数亿甚至十几亿以上,这么大的人群,存储发送人群数据(包括用户信息,占位符等)需要大量连续的存储空间,在发送时,也无法一次性读入内存,只得一点点读取和发送,如果你想串行发送这些消息,可能需要几天甚至几十天。更何况如果发送过程中出现了异常或者服务重启,处理起来就更加麻烦了。所以我们需要把这些发送人群信息适当地分成几个或者更多分片,这样就能够方便计算机系统并行处理,假设你把一个10亿的人群分成200个分片,每个分片也就500万,利用多台机器的多线程处理,可能十几分钟或者几分钟就发送完了。如果我们把发送的人群存储在一个文件里,那么通过分片就是把这个文件分成好几个文件,每一个文件代表一个人群分片,对于分片的依据,我们可以根据市场,国家,语言等等。然后限制每个分片的最大数量,这样我们在处理分片时候一开始就知道了这些人群属于哪些市场哪些国家是什么语言,或者其他分片依据内容,还能再额外减少一些判断逻辑。分片的意义,除了提高系统发送性能外,还能将人群按照一些区别(比如市场,语言)等进行划分,也能够方便程序后续对具有某些特征的分片进行特殊处理。
2.1.8 分流
在进行了分片把大人群切分成小人群后,还能继续优化性能提高吞吐量吗,是的,我们还能再分片的基础上再做分流,简单来说就是批量发送,现代的业务系统大部分的耗时还是在于计算机的IO,比如网络调用或者硬盘读取,如果每发送一个消息给用户就需要调用一次供应商等下游接口,这无疑对吞吐量的影响很大,所以很多供应商或下游服务都会提供批量接口,一次传入多个用户,批量给用户发送信息,同样的,我们也能把这些用户一批一批发送,根据实际需要设置每批的数量,对于前面分片的例子而言,对于五百万的人群文件,如果我们每次发送100个用户,即使调用下游接口的时间略有提高,总体吞吐也能提高数十倍,原本要发送十几分钟的任务可能10秒钟就发送好了。经过分片和分流,你可能会发现,在理想情况下,原本需要发送几天的10亿超大人群,几秒钟就发送好了。
2.1.9 预处理
前面我们说的,用户在圈选了人群后,我们根据用户圈选的条件需要生成实际人群,然后再做一系列的分片处理,这肯定需要花一定时间,我们称这个过程为预处理,所以在发送前我们需要预留一定时间来做预处理,在完成预处理完成之前是无法进行发送的,这个动作大约需要几秒钟到几个小时,所以如果你的规则是每天10点发一波消息,根据任务的大小你可能8点甚至6点就要先开始进行预处理了,如果是单次规则,比如业务突然就要立刻给用户发消息,可能就不太现实,除非要发送的人群不大,预处理不需要太多时间,不过好消息是一般正常企业内部的营销都是有计划的,会进行提前规划,很少会有突然发送的情况。
2.2 核心组件
说完我们系统设计上的一些基本概念,就可以真正进入系统设计了。首先我们需要把我们系统中的组件介绍一下,包括他们的定位,功能还有他们存在的意义,他们设计上有的是为了方便业务使用,有的是是为了方便系统扩展,也有的是为了把不同逻辑解耦方便系统的开发和维护,也就是这些组件构成了整个营销消息触达平台。需要注意的是这些组件并不一定就是一个具体的服务,他只是整个系统逻辑上的一部分,比如在初期你完全可以把租户管理和模板管理放在同一个服务上,你也可以在一个处理节点上同时完成占位符填充,频控处理和黑名单处理以提高系统运行效率,甚至也可能把任务调度机合并到规则平台内,让规则平台来做任务调度的工作。但是要注意尽可能分开他们的逻辑代码,以便于后续做服务拆分和合并时比较方便处理。
2.2.1 聚合管理平台
聚合管理平台也就是我们系统给业务提供的统一的管理平台,因为营销消息平台其实涉及到的内容和模板很多,包括任务看板,人群管理,租户管理,规则配置,模板管理,成本控制,频控规则管理,供应商管理,黑名单管理,明细数据报表等等,这些消息可能来自于不同服务,如果每一块功能都需要在单独的系统上配置和查看,那么对于业务而言整个过程就会很繁琐,需要在好几个页面上来回切换,所以我们需要一个统一的管理平台,让业务能在一个地方完成一系列的事情,提升整体效率,当然也需要做好权限控制,每个人只能看到自己能看到的页面,以及只能做自己能做的事情。
2.2.2 租户管理服务
租户管理服务的主要功能就是存储和提供租户的消息,权限和配置,比如某个租户(业务线)有哪些模板,能给哪些渠道发送消息,如果公司旗下有多个品牌,还可以指定可以发送给哪些品牌下的用户,以及每个渠道的QPS限制等等,聚合平台接入租户管理服务后,我们可以在这里创建或者编辑租户,配置一些租户权限和渠道流量限制等,在发送消息时候也可能从租户管理服务获取租户相关的消息。
2.2.3 人群标签服务
这其实是一个人群管理的服务,大多数情况下用户的标签是在也谢业务逻辑中动态生成,而不是业务自己给用户加的,这样这个平台主要的作用是让业务知道有哪些标签,以及每个标签的用户量大概有多少(用户的标签一般都是动态数据,是会实时发生变化的),帮助业务在创建规则时候做一些决策参考。而且其他服务也能从人群标签服务获取对应标签的人群以及下达一些生成对应人群文件的指令。同时用户相关的占位符也是在人群标签服务给出的(如果单独用户信息服务获取用户占位符,可能会极大地影响预处理和发送速度,降低吞吐量)。由于用户量和标签众多,人群标签服务背后的数据存储大多都是基于大数据技术,很少直接使用传统关系型数据库。
2.2.4 数据仓库
想像人群,人群标签,消息明细数据这类消息,动辄就是几十亿上百亿的数据量,用传统的数据库存储这些消息是不现实的,这时候我们就需要用到大数据技术,把这类消息存储在hive表中,然后进行一些离线计算,抽数,数据清洗等操作,最终给业务系统提供数据(比如人群圈选后的结果),也是业务方用于进行数据统计和数据分析的数据源。
2.2.5 任务调度机
任务调度机就是我们整个营销消息触达平台的大脑了,也是整个系统最核心的调度部分,他的主要职责就是解析业务创建的规则,然后解析成一系列的任务,然后在每个任务预处理时间到的时候进行预处理,任务发送时间到的时候进行发送,并且调度机虽然不关心任务的发送细节,但是他需要感知道任务的生命周期,管理任务的状态机,比如待人群生成,待分片,分片中,待发送,发送中。发送成功/发送失败等。同时也会记录任务的一些生命周期数据,比如当前发送进度,在任务执行出现异常的时候,也要负责通过修改任务的状态来触发一系列重试机制或者告警机制,比如一个任务正在发送消息给人群,发送到第500个的时候,机器被重启了,这个时候任务还处于发送中,这时候我们就该把他的状态修改为待发送,然后调度机就能再次扫描到这个任务,根据调度机记录的水位线或者发送幂等机制继续给剩下的用户发送消息,又或者机器在分片中被重启了,这个时候分片文件肯定是不完整的,我们就需要把任务状态修改为待分片,让他能够被调度机扫描到而重新分片,还有就是任务执行出现异常时候,调度机也能根据具体情况判断是否需要重新执行,一般来说就是把任务的状态前移,这样就能被状态机继续扫描到而达到重试的效果了,而且正是因为调度机是感知任务的生命周期的,所以一些监控,日志和告警之类的也可以在调度机里面统一完成。调度机本身不会侵入任务在各生命周期内的具体运行逻辑,所以只能通过控制任务状态扭转来做一个整体调控,同时也不要忘记了把任务的状态机扭转过程记录下来,这样对于排查问题的帮助很大。这里只是介绍任务调度机的功能,具体的任务状态机图会在后面的流程设计章节介绍。
2.2.6 规则服务/平台
规则服务主要就是管理一系列的规则,比如发送规则,频控规则,你可以配置一些发送规则,指定在什么时间点给什么人群发送什么消息,也可以配置一直消息频控消息,比如一个活动在一天内不给同一个用户发送超过三条消息。然后业务在规则平台配置了规则后,经过一系列审批后,调度机在规则服务获取到规则并且创建相应的任务,发送时也可以从规则服务上获取频控规则,判断是否要继续给这个用户发送消息。当然你也可以根据需要把规则服务继续拆分,比如拆分了发送规则服务和频控规则服务等。
2.2.7 营销服务
营销消息触达平台肯定离开不了营销服务,营销服务主要就是提供一些营销相关的信息查询和优惠券发放的能力,有一些技术架构上需要发送消息时给用户绑券,或者查询一些活动信息,比如查询一些活动的优惠力度,然后通过占位符填充的方式填充到模板上发送给用户。
2.2.8 模板服务/平台
模板平台顾名思义就是管理模板的平台,主要包括各个渠道下模板的管理,包括模板所属的团队,模板的内容,还有模板的审批状态等,要注意不同渠道模板格式的差异,比如email的内容就是一些标签富文本,push可能是纯文本,whatsapp模板又有自己一套特殊的标示协议。另外同一个渠道不同供应商的模板可能会有差异,比如一个供应商的占位符格式和另外一个供应商的可能不一样,这时候可以在模板平台保持统一,到了发送时再做一些特殊化转化,来保持模板的通用性。在国际化的场景下,每个模板可能还需要管理不同的语音,比如同一个模板既可能发给英国的用户也可能发给法国的用户,这个时候就需要给这个模板配置不同的语言,发送时候根据实际情况选择。
2.2.9 审批流服务/平台
审批流服务其实就是负责一系列的流程审批管理,比如一个发送规则或者消息模板的创建和编辑审批。配置每类操作的审批流程,比如有几个审批节点,每个节点需要全部同意还是其中一个同意,各节点之间是串行还是并行之类的,然后也需要记录和提供审批状态,如果公司已经有了其他审批流程控制的服务,完全可以直接接入公司的审批服务,或者根据实际情况也能把审批流做在各自的业务服务中,比如规则的审批逻辑直接做在规则服务中,但是如果审批的流程都类似的情况下,这样就要应对代码重复的问题。
2.2.10 合规服务/平台
当我们给用户发送消息的时候,肯定是需要国家或者地区的法律法规的,比如在国内我们不能发一些关于宗教的消息,在欧盟不能发表一些夸大的宣传消息,除此之外,我们还必须遵守一些平台的规则,比如whatsapp不允许我们在用户未回复的情况下多次给用户发同一个模板,微信不允许我们给未订阅模板消息的用户发送订阅消息,同时为了不让我们的公司摊上事而避免给一些犯罪分子,恐怖组织,政治敏感人物发送消息,这个在对于国际化营销消息触达平台时候就更加复杂,因为需要满足多个国家的法律,而且相关渠道的限制也会更多。合规平台做的主要就是对发送的时机,用户和内容,发送主体等进行校验,确保发送的内容合法合规。比如词汇黑名单校验,用户订阅校验等。
2.2.11 三方供应商信息平台
发送消息肯定是离不开供应商的,每个渠道都有好几个供应商,每个供应商发送消息的报价,商务合作形式,合作时长也不一样,所以供应商信息平台就是用来管理供应商信息的,就是我们有哪些供应商,他们属于什么渠道,合作时间,允许的流量大小,消息成本等等,并且对外提供获取这些消息的接口。
2.2.12 处理节点
我们前面说的各种服务或平台,其实只是存储,管理和提供对应领域的消息,比如合规服务,它只是配置和保存黑名单词汇,然后提供一个接口判断发送文案是否合法,本身并不会直接拦截非法的消息,那么真正完成对应工作的,就是处理节点了,比如负责合规的节点会调用合规服务,判断文案是否合法,不合法就过滤掉这条信息,负责频控的节点会调用规则服务的接口,获取频控规则,判断消息是否符合频控规则,不符合则拦截信息不发送,进行模板择优的节点会调用算法服务,判断哪个模板效果最好,选择最好的模板发送。所以节点才是真正发送逻辑的地方,发送消息以流的形式在节点之间传递,经过一个又一个节点最终推送到供应商接口,节点能够根据业务需求对消息进行修改,或者过滤,甚至新增消息(比如你给一个未成年人发消息的时候也想顺便发给他的监护人)等等。每个处理节点专注于自己的事情,而且并不是完全独立的,它们具有顺序关系,有时候做一件事可能需要先完成另外一件事才行,而且随着业务需求迭代可以新增新的节点来增加新的功能。
节点只是一个抽象的概念,并不是一个节点就是一台/组机器,完全可以在一台/组机器中完成多个节点的事情以提高性能,但是最好做好代码隔离方便后续的分离或者合并操作。
2.2.13 算法服务
算法服务其实就是一类通过计算和数据分析,为系统提供决策参考信息的服务,比如通过对用户的分析在几个模板中选择一个预期效果最好的模板,在运作过程中各节点和服务提供给算法服务提供数据,也可能拉取算法服务的结果,对于一些需要大量计算的任务可以进行离线计算然后再拉取结果。
2.2.14 明细数据对账告警平台
明细数据对账告警平台主要就是管理我们发送给用户的数据明细,具体到发送给了用户什么内容,还有各个发送任务的数据报告,而这些明细往往来源于供应商,由于现在的消息供应商良莠不齐,有些供应商经常会偷偷克扣消息流量以节约自己的成本,或者因为自己内部系统出了问题导致发送失败但是为了防止被追责而提供了有问题的明细数据,这个时候我们就需要对这些明细数据进行核对了,明细数据平台除了收集明细数据之外,还需要对明细数据进行对账,如果有问题就需要及时报告。
2.2.15 其他业务数据服务
我们在发送消息的时候也有可能会需要用到其他公司内部团队的服务,比如通过用户服务判断用户是否订阅了某些信息,通过会员服务判断用户是否有某些优惠券,我们的营销消息平台也能够从这些业务服务中获取需要的消息,但是需要避免和业务服务之间有循环依赖的情况。
2.3 整体架构
前面说完了系统的核心组件,现在我们就能绘制出整个系统的架构图了。
2.3.1 应用层
整个系统对外部暴露的地方就是应用层,用户可以在聚合管理平台上手动操作创建一系列的发送规则和完成对模板,规则,人群,黑名单等功能的管理以及查看任务执行情况和消息发送的明细数据等等,也可以通过调用相应的API来进行对应操作或者获取相关数据,在以及有了统一管理平台还仍然对外提供API主要就是为了方便其他开发团队接入到我们平台,系统也提供直接向用户发送消息的接口。
2.3.2 配置&管理
配置&管理展示科消息营销平台支持数据管理配置的领域。他并不是真正存在的一个层,而是说明了整个系统有哪些地方是支持配置的,包括模板,租户,人群,发送规则,频控规则,合规用户和词汇黑名单,供应商等等,最终用户进行相应的配置还是需要在应用层完成,通过统一的管理平台或者调用对应API接口。
2.3.3 执行层
执行层是消息发送时完成逻辑实现的节点,包括任务调度,分片分流,模板择优,频控,合规判断渠道分发等等,消息以流的形式在这些节点中传输,最终通过消息发送节点把消息发送出去。
2.3.4 基础服务
基础服务本身不参与总体的发送逻辑,而是给节点提供自己领域的消息和数据操作接口来帮助节点实现自己的业务逻辑,服务根据领域来划分,比如标签服务提供了获取对应标签的人群和生成人群文件的接口,合规服务提供了配置用户黑名单和关键词黑名单以及判断文案是否合规的接口。随着系统扩展我们还可能需要用到其他的业务信息,比如通过用户配置服务获取用户是否设置了同意接收push消息等等。除此之外应用层也能通过基础服务获取到相关的数据或者做一些该领域的操作。
2.3.5 基础设施
基础设施就是一些中间件了,主要用于数据存储和消息推送以及服务间的通信等等。选用合适和成熟的中间件,对于系统的建设来说至关重要。比如数据库可以根据存储数据的特点使用MySQL和OB,消息队列基于营销消息平台高吞吐量要求的特点选用Kafka,缓存使用高性能并且稳定性接受过市场考验的redis等。不管基础服务,执行层,应用层都会用到这些基础设施。
三 消息发送流程
在介绍了整个营销消息触达平台的架构之后,我们可以设置整个消息发送的链路,这个才是系统复杂的地方,因为我们既需要保证系统的高吞吐量,可伸缩性,可扩展性,稳定性以及安全性,可维护性等等,中间还需要考虑各种异常的处理,所以在流程的设计上和实现细节上也需要进行大量的思考,本章节会介绍整体流程的设计,在介绍完整体流程后,才开始深入讲解一些细节实现。
3.1 规则创建和任务生成
规则创建是发送消息的起点,业务创建的发送规则后,我们的营销消息触达平台就是根据这个规则给用户发送消息。
3.1.1 规则创建
首先业务方在管理平台上(或者通过API)创建规则,指定规则名称,规则有效时间,市场,圈选人群,发送时间规则(可以是单次发送或者定时发送),发送的渠道(可以选择多个)。对应渠道的消息模板。场景,活动,还有一些规则额外配置,提交后等待审批。
3.1.2 审批流
规则创建后会进入审批流程,完成一些列的审批后才能生效,审批流程是租户维度的,可以在租户管理系统中配置,可以设置多个审批节点,每个审批节点可以选择一个或者多个人,并且根据实际情况设置为需要节点内任意一个人审批或者节点内所有人审批。不同节点之间可以设置为串行审批关系或者并行审批关系,具体实现上吗,我们可以给各个节点指定一个step,审批时候step从小到大开始进行,如果需要多个节点并行审批,可以将这些节点的step设置为一样的值,为了提高效率还可以增加一些审批人审批提醒的机制。下面一个审批流的示意图
3.1.3 规则生效
在审批流程完成之后,规则就生效了,多人也可以增加一些配置,比如审批完成后需要创建人手动确认进行生效,这个取决于业务需要,规则生效就说明系统会开始根据这个规则给用户发送消息了。创建者或者其他有权限的人也可以编辑规则(需要重新审批),暂停或者删除规则等。
3.1.4 生成任务
在规则生效后,任务调度机就可以扫描到这个规则,然后生成这个规则下一次执行的计划,定时发送类的规则是会持续创建计划的,这些创建操作都是由调度机完成,调度机扫描到刚生效的规则时会创建这个规则的第一个计划,包括任务标识,发送渠道,发送模板,目标人群,以及预处理时间(开始进行预处理的时间,必须确保在发送时间到之前完成预处理工作)等等,然后根据这个计划创建对应的任务,前面已经说到,计划也可能会涵盖多个市场和多个渠道,这种情况下就需要创建多个任务来完成这个计划。
3.2 任务发送
在根据规则创建了这个规则的第一个或者第一批任务之后,接下来就是执行这个任务了,也就是开始进行发送了,接下来我们就来仔细地讲解一下发送的流程,包括每个环节以及它的意义。
3.2.1 预处理
首先任务调度机会扫描到到达预处理时间的任务,然后通知预处理节点进行预处理操作,我们的系统使用csv文件来保存发送的人群,每行代表一个触达的目标用户,内容包括用户标识,发送地址(比如邮件,电话号码等),还有用户的各种占位符(比如用户的名称,性别称呼)等,预处理主要的工作就是生成人群文件和进行分片
3.2.1.1 生成人群文件
首先就是给人群标签服务提供人群圈选规则,场景,和模板中和用户相关的占位符,指示人群标签服务生成人群文件,人群文件主要包含用户信息,和需要的用户占位符等等,因为人群文件可能较大,而且生成人群文件通常涉及到离线大数据查询,文件IO等等,所以经常需要一定的时间,我们可以对人群标签文件下达生成人群文件指令后就结束当前线程,等待人群标签服务完成人群文件的生成后通过接口调用或者消息通知等方式通知我们的服务继续处理生成好的人群文件
3.2.1.2 分片
之前提过,当发送的目标太多时,人群文件可能会很大,因为一个用户就会占用一行,尤其是圈选全量人群时,不仅数亿的人群文件可能会占用好几G的大小,而且想要串行地给这么多人发消息,可能发送到公司破产都没发送完,所以我们需要把这个大的人群文件切分成多个较小的人群分片文件,这样利用现代的计算机集群和多线程来并行发送。等我们收到人群标签服务的人群文件生成完成的通知后,我们可以根据回调的人群文件地址(可能是一个或者多个)读取人群文件,然后按照一定规则切分成多个人群分片文件,并且记录他们的地址。
经验告诉我,由于分片的逻辑比较纯粹,只是读取一个大文件然后生成几个小文件,使用现代计算机系统,高性能文件IO库,和合适的处理逻辑,即使是10亿行数十个G的文件,分片处理也只需要十几分钟甚至更少的时间。
但是这里也有一个抉择点,因为我们是在文件服务里面拿的人群文件,任务执行是需要在文件服务上拿的文件,所以最终我们也需要把分片文件上传到文件服务,这样就出现了两种常见的方式,第一种就是最简单的先把人群文件下载到机器硬盘,然后读取硬盘文件,再分成几个小的人群文件,然后再分别上传到文件服务器,另外一种方式就是通过流式下载+分片+流式上传,不经过硬盘,把文件一批一批从文件服务读下来,然后再内存里面为每个分片预留一个缓存空间,满了再把这部分数据流上传到文件服务,最终完成下载,这样无磁盘依赖,适合容器环境等,下载和上传可以并行,大大提高性能和资源利用,另外也能实现记录进度的水位线,发生异常时下次可以直接从水位线开始,节省大量时间,还能进一步支持限流、速率控制等细粒度调度,但是也实现复杂,需要处理各种异常和数据对齐问题,这两种方案都是完全可以的,前面的方案虽然简单资源利用率低但是大多数情况下也够用。
传统的串行处理
流式读取和并行上传
3.2.2 开始发送
在完成了分片后,任务就进入了待发送状态,等发送时间到了后,调度机会指示任务分片然后开始发送,然后再各个工作节点中完成处理逻辑,需要注意的是,为了让消息平台能在大流量下稳定工作,如果不同的工作节点在不同机器上时,节点之间的流量传输我们并不会直接使用RPC通信,而是使用kafka作为消息管道,现在开始介绍发送的流程。
3.2.2.1 分流
首先分片分流服务在接受到指示后开始读取各个人群分片文件,首先就是把消息进行分流,也就是分批地把用户发送到工作节点上,比如根据实际需要每20个用户一批或者每100个用户一批,而且针对不同的渠道或者场景也可以选择不同的分批大小,即使工作节点无法一批一批处理消息,分流操作也是很有必要的,首先因为每次传输都会涉及到网络IO,所以攒一批数据再发送,能够更加网络带宽的利用率提高系统吞吐量,而且总是有一些节点或者发送渠道是支持批量处理的,所以统一进行分流也能统一一些函数接口,避免出现既要考虑单个消息又要考虑批量消息的代码处理,使得代码更加简洁。
3.2.2.2 模板择优
完成分流后,消息就进入了工作节点了,这写工作节点就是正在进行业务逻辑处理和发送的地方,首先就是模板择优,模板择优其实并不是一个必要的功能,但是对于营销消息平台来说确很常用,业务在创建规则时候可以指定一个具体的模板,然后发送时候直接使用这个模板就行了,为了提高转化率,业务也可以选择一个模板组,模板组包含了好几个模板,在发送时候,工作节点会根据每个用户的特征选择这个模板组里面最合适的模板绑定给这个用户,从而提高转化率,比如业务方想给一些家庭主妇群体推送自动拖地机营销广告,但是这些家庭主妇里面有的性格开放,有个比较传统,还有的不喜欢夸张的语言风格,对于不同的特点,不同模板的效果肯定也不一样,我们当然可以拆分这个规则,更细维度地选取人群,然后分别选用不同模板,但是这样不仅业务的工作量变大了,而且每个人都可能带有各种各样的标签的,如果相关标签一多,你要自己把这些标签进行排列组合然后决定使用哪个模板,这样工作量就很离谱了,所以经常会需要用到模板择优的功能,选择一个实际的目标群体,比如家庭主妇,工地工作者等,然后选择一些不同风格的模板,给这些模板页标注一些标签,然后在模板择优节点上调用算法服务,让算法服务选择最合适的模板,算法服务可以通过大数据分析计算,或者通过AI分析决策等方法来选择合适的模板。最终的目的就是提高消息的转化率,另外一点,模板择优也不一定非得是内容的择优,也可以是模板状态的择优,比如营销消息的whatsapp模板经常会被封禁,被封禁的模板在发送中无法发出,这时候也可以配置多个模板,选择状态正常的模板进行发送,增加消息触达率。
3.2.2.3 频控拦截
频控链接不是必要的流程,但是也是一个成熟营销消息触达平台应该有的功能,现实中各个业务线团队总是各顾各地给用户发送消息,甚至可能会有重复发送的情况,缺少一个发送消息的大局掌控人,竟然业务中没有大局掌控人,那么这件事就只能交给我们了。
频控就是为了不频繁给用户发送消息导致被用户举报或者屏蔽而出现的功能,他通过配置一系列规则,比如某个活动或者某个场景一天内只能给一个用户发送2条消息,或者某个品牌给用户发送营销消息的时间间隔不能小于两个小时,这就是频控节点要负责的事,节点可以通过规则服务获取一系列规则,然后再一些DB或者缓存中拿到用户的历史发送相关数据,在发送时候判断这条消息是否符合频控规则,不符合则进行拦截,需要注意的是,对于一些比较重要的营销消息,也应该提供一些破频机制,即可以无视频控规则继续发送,避免出现损失。
3.2.2.4 用户绑券
用户绑券也不是必要的流程,他只出现在一些特定的技术架构中,通常情况营销平台会完成用户绑券后再调用营销消息平台发送优惠券消息,这也是推荐的方式,但是有时候也可能需要消息平台发送消息前顺带给用户绑券,尽管绑券也是需要调用营销服务的接口实现,得益于我们营销平台的架构设计,我们能够很方便实现这一功能并且不侵入其他的发送逻辑,我们只需要创建一个专门的绑券节点,在接受到消息流并且需要进行绑券的时候调用营销服务的绑券接口,然后完成后再把消息传递到下一个节点即可,而且如果因为营销服务异常导致的绑券失败,我们也完全可以拦截这条信息防止给用户带来困惑。
3.2.2.4 内容生成和占位符替换
内容生成和占位符替换是一个常见但是也不是必要的流程,他负责根据信息的模板获取模板的内容,然后再根据人群文件和一些其他平台(比如营销平台)的信息替换模板里面的占位符,最终生成实际发送的内容,其实这一步就是做一个文本匹配替换,比如把模板中的{{name}}替换成记录在用户人群文件中的用户名称,有些模板是没有占位符的纯静态模板,这类就少了占位符替换的工作(尽管程序仍然可能需要扫描一下模板的占位符),还有些渠道,比如whatsapp,发送给消息供应商的时候只需要传入whatsapp模板id和占位符信息,而不需要传输实际内容,这也就不需要进行内容生成和占位符替换了。
3.2.2.5 合规化处理
合规化处理节点主要保证的就是你发送的内容是合规合法的,其中包括国家维度和平台维度,避免发送一些不符合当地法律的消息或者不符合相关平台政策给用户,如果发现不合法的内容或者不合适的发送。都应该进行拦截,比如模板内容中有一些命中词汇黑名单(通常记录一些脏话或者政治敏感词汇),或者说准备给取消了订阅的用户发送消息,也可能是避免给一些国际通缉犯或者政治敏感人物发送消息等等,也是得益于我们的架构设计,我们可以根据实际情况对这个节点进行拆分,比如把词汇黑名单和取消订阅拦截分开,或者合并到其他节点。
3.2.2.6 渠道分发和发送
在各个节点进行完消息处理后,最终就能够渠道分发和消息发送了,也就是根据这个消息所属的渠道,把他推送到各渠道的发送节点上,然后各渠道的发送节点把这些消息组装成和供应商约定好格式推送给供应商,这样,从我们的角度看来,消息就已经发送出去了。
3.3 数据回收
在把消息推送给供应商后,在我们这边看来消息的推送就完成,接下来要做的就是等待供应商把消息推送给用户,然后给到我们数据回执。我们再把这些明细数据存储起来,然后也需要进行一些明细数据核对工作,避免供应商耍赖
3.3.1 明细数据回收
不同供应商在发送完数据后都会把发送的明细和数据报告回传给我们,具体的回传方式根据不同供应商而不同,有时候也需要我们和供应商协商,可能是通过供应商回调,也可能是供应商提供查询数据的接口,也可能是供应商会把明细数据以文件的形式放在ftp服务器中我们自己去拉取,虽然供应商提供明细数据的方式多种多样,但是我们需要保证最终各渠道和供应商明细数据的存储方式尽可能一致,以方便我们统一管理是和使用。从供应商获取数据的方法会在后面专门的章节总结,这里只会介绍系统的通用流程。
由于明细数据的量级巨大,大型公司平常每天可能都有数十亿的消息平台数据需要存储,所以把他存在传统的DB上是不太合适的,而且这些明细数据常常适用于排查问题,离线分析和离线计算,很少会在系统中直接使用到这么细维度的数据,所以我们通常会把这些数据存储在数据仓库中,由于消息量巨大,我们可以通过一些高吞吐量的消息队列,比如kafka,把明细数据推送给大数据平台,由大数据平台进行明细数据存储。
3.3.2 明细数据校验
在收集完明细数据后,我们还需要校验三方的明细数据是否可靠,所以我们必须要有一套明细数据校验的机制,来校验收集到的明细数据。如果发现异常应该即使告警,以便于我们能够及时找到供应商处理问题,如果明细数据异常没有及时发现,过了好几个月甚至一两年才发现和处理,不仅是gap的量级巨大,而且数据久远,系统经过了好几次迭代,系统日志可能都不在了,这时候再来修复就很麻烦了。具体的校验方式可能根据供应商和业务实际情况不同,后面会在专门的章节介绍一些具体方法,这里我们介绍一下数据校验的设计。
数据的校验和告警有两个常见的方法,第一个就是设计一个专门的服务,要进行数据校验,由于明细数据存储在数据仓库中,所以明细校验服务需要在大数据平台拉取明细数据做校验,因为需要校验的具体方式多种多样,而且数据量巨大,这种方法被证实是可行但是繁琐的。
第二种推荐的方法就是直接在大数据平台配置一些告警对账SQL,通过编写一些对账SQL,比如使用供应商提供的数据报告给的阅读数和明细数据中用户阅读的明细数量做比较,数据差异大于某个比例则告警,对于需要业务系统数据的地方,也完全可以通过抽数的方式吧业务表抽到数据仓库,相对于起一个新的服务然后从大数据平台拿数据判断,直接在大数据平台配置对账SQL告警任务是非常高效和推荐的。
3.4 任务状态机扭转
说完了整个发送的流程后,我们还有必要梳理一下整个过程中状态机的扭转,因为这对于整个任务生命流程的了解和问题的排查都能有用处。
待预处理 :这个时候任务刚刚创建完成,等待到达预处理时间后进行预处理。
预处理中 :任务到达了预处理时间,开始进行预处理。
待人群生成(待分片) :此时已经向人群标签服务提交了生成人群的请求,等待人群标签服务完成人群文件的创建。
分片中 :人群文件已经生成,正在执行分片操作,生成多个人群分片文件。
待发送: 人群分片完成,等待到达发送时间后开始发送。
发送中: 已经到达了发送时间,任务开始发送
发送成功 :任务里面的所有分流全部发送执行成功,此时并不代表任务里面的人群全部发送完成了,只是说明发送到供应商这个过程中没有出现未预期的异常。具体的发送情况还是得根据三方给出的数据报告获取。
发送异常 :此时可能是任务的某些分流或者全部分流在发送到供应商的链路出现了异常,部分分流正常部分分流异常的情况下也把任务的发送视为异常,当任务异常时候肯定就至少有部分人群没能正常发送出去了,这个时候就需要仔细看一下发送异常的分流并且根据异常原因经常排查和修复。
3.5 动态推送
再给用户发送营销消息的时候也经常会遇到一些动态推送的场景,比如是用户进行了某些行为(比如把某些商品加入购物车)或者完成了什么里程碑又或者经过某些计算符合某些标准,我们就需要立刻或者延后给这个用户推送某些消息,但是需要触发的形式或者情况多种多样,并且配置起来也会非常复杂,设计一套通用的动态机制是不太现实的,容易吃力不讨好,除非业务的动态推送场景单一。但是虽然我们的营销消息触达平台不直接提供这个功能,但是仍能通过一些其他方法间接地完成动态推送的效果。
3.5.1 给用户添加标签
当用户做出一些行为或者达到某些里程碑后,我们可以给用户添加一些标签,然后再配合给这些标签的人群发送消息的规则达到动态给用户发送消息的效果。
3.5.2 为业务提供直接发送消息的接口
动态发送其实实现并不困难,只是形式和情况总多,但是具体的业务方肯定对于什么时候需要给用户发送消息一清二楚,这个时候我们可以为他们提供直接给用户发送消息的接口,他们自己完成逻辑判断后调用我们的发送接口直接给用户发送消息。
3.6 发送流程扩展
我们的系统设计最大的优势就是可扩展的,可以在现有的架构和流程上很方便地扩展其他功能,不需要侵入整个消息触达平台,前面的发送流程只是大多数营销消息的处理步骤,他们也不是必须的,统一的我们可以根据我们的需要新增一些节点做一些自定义处理,可以是做业务逻辑处理,或者技术优化等等,下面就是一个复杂流程的节点设计例子
我们在节点设计上有几个特点
流动自由
每个节点可以根据自己需要往任意节点传输消息,甚至往前面的节点传输(尽管很不推荐),也可以根据自己的业务逻辑判断将消息发送给不同的节点,但是自由的同时也可能带来一些风险,需要注意就是避免消息死循环流动。
处理自由
有些处理逻辑不是所有所有消息都需要的,比如你只对ABTest实验组的消息就模板择优,那么你完全可以让消息流过模板择优节点,然后这个节点判断是否处理,不处理就直接不做操作把消息推送给下一个节点,或者也可以选择前面的节点判断这个消息不需要模板择优后让他直接跳过模板择优节点,直接发给更后面的节点,虽然我比较鼓励前一种做法,毕竟很多时候我们为了节约资源会把多个节点放在一个服务上,然后一个方法代表一个节点,但是也不禁止后面的方式,这是因为我只是想给出一种营销消息触达平台的流程和架构设计,具体的规范还是由各自团队决定比较好,因为不同企业不同团队的具体情况都相差甚远。
四 流量管理和系统扩容
前面我们说过,系统的一个难点就是对于发送流量的把控了,我们既要保证各个团队/租户之间的流量隔离,让他们互不影响,也要尽可能做到资源利用最大化,避免浪费系统资源并且提升系统的吞吐量,所以本章节我们会专门讲一下营销消息触达平台在流量管理上的一些重要的策略和实现。
4.1 多租户流量管理
我们的消息平台是给不同业务方使用的,首先要应对的就是多租户问题了。
4.1.1 流量隔离
我们需要保证各个租户之间的流量是独立的,比如如果一个租户出现了大任务量发生消息堆积,可能会使得自己的任务发送出现延迟,但是绝不应该影响到其他租户的任务。
我们的营销消息平台保证这一点的实现方式是,我们之前提过我们使用kafka作为跨机器节点之间的消息管道,而kafka刚好是使用的拉取数据的模型,而且kafka是支持动态topic的,我们的系统可以动态地为不同租户创建他们自己的topic,比如message_send_{{租户id}},然后在渠道分发节点上,我们可以轮训地拉取这些topic,这样下来,就不会因为其中一个租户因为有大量消息要发送而导致其他租户饿死,因为单次拉取的数据量总是有限的,在处理完之后就会去拉取其他租户的消息消费,保证各租户之间流量的公平性。
4.1.2 租户限流
现在我们已经实现了不同租户流量之间的隔离并且互不影响,要怎么对租户进行限流呢,比如如果whatsapp供应商给我们提供了1000qps的支持,我们就需要保证所以租户之间的qps之和不超过1000,实现这一点,我们可以在租户管理平台上配置各租户在不同渠道上不同供应商的qps限制,在配置上就控制了他们的总qps不会超过供应商的要求,然后在是实现上通过分布式令牌桶来进行限流,具体地,我们把令牌存储到支持原子命令的缓存服务上(比如redis),只需为每个租户记录一个数字,表示令牌数,然后通过一个特殊的服务或者redis的lua脚本来为每个租户发送令牌,每秒给每个租户更新一次令牌,使得令牌数变成对应租户的qps的值,渠道分发节点每次在租户的队列拉取消息前,都会需要在缓存服务里面获取到对应的令牌,然后拉取对应数量的消息,获取不到这个租户的令牌则说明当前租户的qps用完了,就会跳过这个租户进行下一个租户的消息发送。这样的设计也能在为各租户进行限流的同事尽可能用满系统资源。
4.1.3 资源利用率最大化
我们想思考一个问题,如果下游whatsapp供应商给的qps限制是1000,所以我们为A和B租户各自分配了500的qps,这个时候如果A租户要发送的消息量很大,消息已经出现堆积了,而B租户这时候消息量比较小,只能用到100左右的qps,这时候,从系统整体吞吐量上来考虑,我们是不是应该考虑把B剩下的qps额度暂时借给A呢,保证不影响到B租户的同时让A租户更快地把消息发送完,减少消息堆积。
要做到租户流量隔离很容易,要做到租户限流也很容易,要做到资源利用最大化也很容易,但是要同时做到这三点就很难了,因为这三点本身在实现上本身就是有一点冲突的,要同时完成这三点,要做的额外工作就会添加很多,而且这三点也需要一起考虑,而不能一个一个独立实现。
所以我们前面的流量控制方案早就想好了这一点,我们可以加入多余令牌共享的机制,比如我们在某个特定渠道特定供应商里面,在某轮拉取中,A租户上获取到1000个令牌,但是拉取到的消息只要400条,我们还剩下600个令牌没有使用,这时候我们就把这些令牌分给同样是这个渠道这个供应商的B租户,以此类推,如果用不到的令牌就继续留给后面的租户,并且保证空闲的令牌只在一秒或一次轮询内有效,这样这些租户就在流量隔离的情况下能够尽可能地利用下游总qps资源。
当然为了应对特殊情况,这个功能是可以按渠道-供应商粒度选择开启/关闭的。
4.2 流量控制
前面说完了多租户流量隔离,限制和资源利用率最大化的实现方案,现在说一下我们怎么在发送过程中实时地控制流量。对于一个消息平台来说,在消息发送的过程中保持可控状态,包括临时扩容,结束/暂停任务,单独为租户扩容等等。这个是非常重要的。
4.2.1 整体扩容
前面说到我们的系统的设计中已经实现了系统资源的最大化利用,所以在系统资源成为瓶颈,而不是下游限制瓶颈的情况下,不管是哪些租户流量太大导致消息堆积,我们可以对整个系统进行扩容来解决这一问题。
4.2.1.1 工作节点扩容
我们的发送流程是在各个节点中完成的,然后通过kafka作为数据管道传输数据,每个节点都会往kafka推送消息和拉取消息,当消息流量大的时候,可能消息就会堆积在kafka里面,只需要通过服务集群扩展的方式给节点扩容,增加这个节点的工作实例,增加对kafka消息的拉取和处理的速度,达到提升吞吐量的效果,而且如果我们的节点是在不同机器集群上时,我们还能根据各节点的流量情况定向扩容,只扩容有需要的节点集群,而且根据这个节点的工作内容选择更加合适的机器配置,比如如果这个节点需要做大量的计算工作,那么这个节点可以部署在cpu性能比较高的机器上,如果这个节点需要在硬盘上存储大量临时数据来辅助工作进行,我们就可以把这个节点所部署的服务就可以装配固态硬盘来提高运行效率,从而更加精准地利用硬件资源。
4.2.1.2 kafka扩容
除了节点上的扩容,我们还需要注意必要时对我们的消息管道kafka进行扩容,尽管大多数情况下kafka不会成为系统性能瓶颈的地方,但是有一个细节我们必须要很清楚,就是kafka的分区问题,因为kafka内部设计的原因,一个分区只能被一个消费者消费,也就是说多个消费者无法同时消费一个分区,所以如果kafka对应租户的topic的分区数小于拉取的工作节点数时,再给工作节点集群扩容是没有任何意义的,因为新扩容的机器拉取不到任何分区,所以我们在扩容工作节点集群时也需要及时给相关的topic增加分区,保证kafka分区数大于等于消费者数量的大小。除此之外,在流量太大导致broker压力太大时我们也可以对kafka的broker进行扩容。
4.2.1.3 分片服务扩容
少数情况下,分片环节也可能成为整个流程中瓶颈,尤其是整体流量大而且任务也很多的时候,这个时候我们能很方便地分片服务集群进行扩容,提高分片数据,需要注意的是扩容分片服务并不能提高对单个超大人群文件的分片速度,好消息是目前的机器和系统已经拥有了足够的性能,即使是数亿的超大人群文件也能在几分钟内完成分片,所以我们并不需要太关注超大人群文件问题。
4.2.1.4 下游扩容
有时候吞吐量的瓶颈来自于下游,下游可以是消息供应商或者其他负责发送消息的团队,比如供应商提供的qps支持能够符合我们平时的要求,但是在黑五期间我们的消息流量可以回暴涨数倍,这个时候下游成为了我们的瓶颈,我们再怎么扩容自己的服务是没有意义的,这个时候就只能通过和下游协商,让下游进行扩容或者其他操作,最终增加对我们的qps支持量,这样我们就可以在配置中增加对各个租户的流量限制,提升系统吞吐量。
4.2.2 单独为租户扩容
前面我们说了对整个系统进行扩容,使系统能够承受能大流量,这一小节说一下怎么单独为某些租户扩容。
4.2.2.1 租户topic扩容
前面我们说到每个租户都有自己专门的topic,如果在对应topic消息堆积的时候,我们可以选择给这个topic扩容,增加这个topic的分区数,但是也应该根据实际情况适当的扩容相关工作节点。
4.2.2.2 添加租户流量配额
如果我们系统吞吐量足够了,租户的消息发送速度受限于下游,我们就可以给对应租户特定渠道特定供应商下添加配额,但是需要注意修改后供应商下所有租户的流量之和不大于和供应商约好的量。但是因为我们的系统本身就已经实现了在其他租户流量空闲的时候占用其资源,配额限制更多的只是代表了这个租户的优先级,所以我们更加经常做的往往是和供应商协商让其保证更大的流量支持,这样即使在不为这个租户单独添加配额时该租户仍然能够收益。
4.2.3 暂停/停止任务
在任务发送过程中有时候会有需要紧急停止任务的情况,比如因为一些业务的操作失误或者系统bug导致发送了错误的信息,又或者是需要给其他更重要的任务腾出资源等等,我们的做法是,对于未开始的任务,可以在聚合管理平台上关闭这个任务,即修改这个任务的状态,让他不会再被调度机扫描到从而不会启动发送,对于正在发送的任务,我们可以添加一个任务拦截节点,在这节点判断任务的状态,如果是暂停状态,我们就把这个消息发给专门暂存消息的节点,然后这个节点会在暂存这个消息后,等到这个任务的状态恢复时再把他发完下一个节点,暂存消息的方式也多种多样,比如使用mysql,或者分布式文件系统,又或者使用kafka(需要开启动态topic功能)等等,如果是停止状态,我们就丢弃这条消息,不发给下一个节点。
4.2.4 流量缩容
前面提到了流量扩容,流量缩容的做法其实就是和扩容相反,给各个节点集群缩容,给kafka缩容,又或者吧租户的流量配额调低。缩容主要用于在流量较低时减少硬件资源的占用。
五 明细数据回收和对账
前面提过营销消息和业务消息一个很大的区别就是营销消息需要保留发送的明细数据用于数据统计和复盘了,这也是我们营销消息触达平台很重要的一个功能,本章节会介绍我们的平台在明细数据相关的实现。
5.1 明细数据回收
明细数据往往需要由我们的三方供应商或者其他下游消息服务获取,毕竟真正发送消息的也是他们,所以回收消息明细数据的方式通常和供应商相关,不同的供应商反馈消息明细的方式不一样,有时候我们也可以和他们协商一些合适的回收方式,回收数据的常见的有以下几种方法
通过ftp文件
供应商把发送的明细数据文件存储在远程ftp服务器上,我们去这个ftp拉取数据,由于明细数据量往往比较庞大,这些文件往往会有过期时间,这时我们就应该注意及时去拉取了。
供应商接口
供应商自己组织发送明细数据,然后给我们提供接口,我们调用他们接口来获取明细数据,同样由于数据量庞大,供应商可能不会存储太久这些数据,所以我们也应该注意这些接口的时效性。
供应商回调
一些供应商会把用户的明细通过回调我们接口的方式给到我们,比如消息送达,用户阅读,用户点击等等事件,然后我们在收到他们回调时自行收集他们,这种情况下回调消息常常是单次的,错过了就没有了,所以我们要尽可能做好回调处理,保证代码健壮性,避免损失明细数据。
5.2 明细数据存储
尽管回收数据的方式多种多样,但是其实获取到的核心信息无法就是发送的消息,用户以及消息的状态(打开,点击等),我们在存储明细数据的时候尽可能做到统一,把一些供应商的语义进行统一,比如一条消息,有些供应商反馈称为已读,有些供应商反馈称为打开,这两个概念实际上是一样的,我们可以在存储时候进行统一字段,然后记录改消息的渠道以及其供应商,活动,场景等等,方便业务方进行统计。
在存储方式上,由于明细数据通常数据量很大,实时回收量也多,而且业务功能中很少需要用到消息粒度的数据,所以我们会把他存储在数据仓库中,业务系统在组装好明细数据后将明细数据通过kafka发送给大数据平台,由大数据平台将明细数据存储在仓库中。这样做的好处就是大大减少业务系统的压力,并且通过kafka进行很好的流量削峰。
5.3 明细数据校验
前面已经反复讲了我们不能太过于相信消息供应商以及明细数据校验的必要性,但是数据校验的方式随着供应商提供明细数据的方式以及明细数据的内容都可能不一样,但是检查明细数据是否可靠的思路其实都差不多,主要是以下几种。
供应商内部数据对帐
我们可以对供应商提供的多种类的数据进行对比,看看有没有冲突的地方,比如我们可以根据供应商提供的明细数据和数据报告做对比,如果数据报告给的阅读数和明细数据里面用户阅读的记录的数量对不上,那么说明供应商给的数据肯定有问题了
数据逻辑分析
分析供应商提供的数据送达数,打开数,点击数等,如果出现了打开数大于送达数,送达数大于我们的发送数,或者阅读率,点击率数据太过于夸张等等情况,数据就是有问题的
内部哨兵账号
业务或者开发可以拥有一些自己的账号(邮箱,whatsapp账号,app,手机卡等),发送的时候也圈入自己,可以观察到发送的消息以及发送时间。然后看看是否和供应商提供的明细数据是否对得上。
自行收集部分数据
比如除了获取邮件供应商提供的链接点击数据外,我用oneline等方式自行收集一份点击数据,然后看是否能和供应商给的提供的链接点击数据对的上,如果差异太大也能说明供应商提供的明细数据有问题。
以上这些方法可能并不完美,而且都是一些间接方法,一般会使用多个方法混合使用的方式要验证供应商提供的数据是否可靠,如果发现明细数据异常,应该立刻告警然后及时进行排查和联系供应商进行数据补数,防止烂账越积越多最终修复难度也会越来越大。
因为我们的明细数据是存储在数仓的,我们的营销消息平台做明细数据对账的主要方式就是大数据平台配置一些告警对账SQL,通过编写一些对账SQL,比如使用供应商提供的数据报告给的阅读数和明细数据中用户阅读的明细数量做比较,数据差异大于某个比例时候就把这些异常数据记录再一些专门的表中并且进行告警,如果有一些复杂的对账情况,我们也可以设计一些专门的对账服务来完成这些对账逻辑。
5.3.1 补数
如果我们发现三方供应商给的明细数据有缺漏时,在条件允许时可以联系他们进行补数,把缺漏部分追回,补数的方法其实和前面从供应商获取数据的方法类似,而且也可以进行协商,再确认补数的方式后,也要确定是供应商是否能只给我缺漏部分的数据还是全量数据,如果是全量数据我们就需要对把全量数据对老数据进行覆盖或者merge后进行去重。
5.4 明细数据使用
我们的明细数据是存储在数仓的,所以业务方要使用明细数据进行数据分析,问题排查或者离线计算时候就可以很方便地通过大数据平台提供的工具来完成。而且公司的数据开发团队也可以基于这些数据做一些开发。
六 监控,告警和流量预测
不管对于上面系统,实时监控都是非常重要的,不管是机器指标,接口流量,业务数据等数据,都需要实时掌握,本章节会讲一些系统是怎么实现监控,告警和流量预测的。包括监控服务选型,监控埋点的收集,需要监控的内容,以及可能根据监控做的一系列扩展等,都是最终部署落地完成一整套监控体系地工作量还是很大的,而且这是系统开发都需要的内容并不是消息平台独有的,所以这里只介绍一些技术选型和监控的目标,并不在实现细节上做太多说明。
6.1 监控服务选型
监控的选型其实可以根据已有的技术栈体系选择,并不是强制,毕竟市场上已经有了足够多的监控服务可以满足我们的需求,我们的营销消息平台默认情况下会使用Prometheus配合自建监控数据服务来完成系统的监控,Prometheus 是一个功能完善,稳定可靠的开源监控和告警系统,它支持多维度的数据模型、灵活的查询语言PromQL,可以很方便地实现基础设施监控,应用性能监控,数据库监控,告警与自动化响应等等,除此之外还可以通过部署 Exporter,手动采集各种业务指标用于监控,比如在消息发送失败时候就能够进行埋点,记录失败原因,然后做一些告警,然后呈现给我们。自建监控数据服务主要是提供一些无法通过Exporter埋点获取的指标,主要就是通过解析计划获得未来要运行的任务,并且还能对未来流量进行预测,然后把这些数据呈现给用户,让你对未来心里有数。
6.2 监控数据收集和计算
本小节说一下怎么通过收集和计算来获取我们需要的监控指标。
6.2.1 机器指标
其实大部分监控服务本身就能很方便地采集机器指标,Prometheus也一样,我们系统上地每一个机器,比如调度机,分片服务,工作节点,各方领域服务,使用的中间件的机器指标都是我们的采集对象。
6.2.2 业务指标埋点
在消息平台的工作过程中,我们可以在代码上自发地进行埋点,记录一些业务指标,比如请求供应商流量,三方供应商异常埋点,某核心方法的访问数量,消息命中黑名单,消息被频控,模板择优结果,分流发送埋点成功等,由于埋点是在代码中直接显式进行的,这使得我们的业务指标埋点非常灵活,你几乎可以收集任何你想要的任何数据,然后呈现在监控大盘上,比如被黑名单拦截的消息量,被频控的消息量,新的功能节点流量等等。另外我们可以通过监控日志,匹配到某些字符串或者error日志,然后进行埋点。
6.2.3 未来预测
以为我们发送消息是基于任务的,而任务又是基于规则的,根据规则我们能明确地知道下一个任务地时间,下下个任务的时间等等,也能找到任务要发送的人群,所以我们不仅能明确地找到后面会执行的任务,也能明确找到我们后面会给哪些人群发消息,所以我们能很容易地预测未来要发送地流量,但是关于预测未来流量的时候需要注意的是,对于未来的任务,虽然我们知道这些任务会发给那些人群,但是人群是根据标签圈选的,标签是一个动态的数据,每个标签的人群可能都实时在发生变化,所以我们无法真正地知道未来任务会发送给多少人,但是我们可以在创建规则圈选地时候根据当时的圈选数预测一下下个任务会发多少人,由于我们只在创建规则的时候圈选了人群,随着时间相隔越久这个预估数可能就越不准,越后面的任务可能偏离这个预估数就越多,为了解决这一个问题,我们可能每次任务完成后把这次任务发送的人数作为这个规则下一个任务发送的预估数,这样就能够尽可能减少偏差。
6.3 监控内容
前面说了监控指标的采集,现在说一下我们需要重点监控的内容,这些并不是监控内容的全部,我们也可以根据业务需要或者一些新的功能点自行添加一些监控内容。但是对于我们的营销消息平台来说这些内容的监控几乎是必要的。
6.3.1 规则监控
我们的整个发送流程几乎都起于规则,下面让我们介绍一下在规则是需要做的监控,
规则总览
不同于在聚合管理平台上可以对各个规则进行各种各样的筛选搜索查找显示,监控上更加侧重于规则对系统的影响和潜在影响,所以我们不太需要和管理平台那样把所有规则列出来,而是需要对现有的规则进行统计,比如多少规则在生效中,多少个未生效,按人群划分的大规则,中等规则,小型规则数量,以及这些数据环比同比等等,方便开发人员对规则的整体感知。
规则跟踪和历史记录
其实单个规则的任务执行记录也可以坐在管理平台上,具体取决于具体的系统设计方案。但是让业务能够清楚地知道自己规则地执行情况,比如任务具体发送时间,以及发送地状态和数据等是非常有必要的。
6.3.2 任务和消息监控
发送消息基本都是在任务中完成,对任务的监控也是整个系统的核心,我们的系统必须对现在正在进行和未来即将进行的任务心里有底,也对现在和未来的流量情况有底,当然我们也需要能够对单独任务的生命周期进行观察。
任务和消息总览
系统会对正在进行的任务和未来需要进行的任务进行统计并且呈现给管理人员,管理人员可以在监控中看到当前正在执行的任务数量,当前总共需要发送的人群,还有已经发送的人群和未发送的人群,
任务跟踪和生命历程记录
监控还需要可以展示每个运行中任务的待发送量和已发送量,包括发送的进度和预计完成的时间,能够看到这些消息并且配合一些友好的页面展示,比如发送进度条,这对于业务来说是个很好的体验,此外对单个任务的状态扭转进行记录和展示也是很重要的,对于一些异常情况的排查效率能够提升不少。
消息分流的节点流转
发送过程中我们的消息分流会经过一个又一个处理节点,有些可能会因为特殊的业务逻辑跳过一些节点,所以记录和监控消息分流经过的节点信息也是非常重要的。
6.3.3 中间件和机器指标
我们同样需要监控我们系统上各个机器上的机器指标,包括cpu,磁盘,内存,网络带宽等情况,并且相应设置好一些阈值,进行异常告警工作。比如内存占用高于百分之90,cpu占用超过百分之70等等,这个对于在日常盯盘中实时掌握系统运作情况也是非常重要的。
6.3.4 异常原因统计
系统在运行的各个环节多多少少会出现一些异常,比如在创建规则,审批时,创建模板时,人群文件生成,系统分片时,任务发送时候等等,每个环节可能出现的异常可能都多种多样,所以我们监控有必要记录和统计这些异常,然后通过报表,图标等方式暂时出来方便及时察觉出来。
6.3.5 明细数据回收,存储,对账监控
明细数据的回收,存储,和对账的监控也是非常重要的,我们不仅在它们发送异常时候能够发现,而且对于回收的进度,完成量,剩余量也应该能够尽可能地监控到,不要忽略了明细数据的回收,恰恰是明细数据一般不和系统直接相关,可能用于业务排查,甚至丢了一小部分也没有人知道,但是这样就会日积月累,gap的数据越积越多,bug也越积越多,最终达到因为明细数据和实际情况相差太大而被业务找上门的情况,这时候再来查看这几个月甚至几年的数据gap和排查这几年积累下来的bug难度就非常大了,而且业务决策一般很依赖明细数据,如果他们发现他们一直以来参考的明细数据都是有问题的,那么他们的怒火可想而知。
6.3.6 监控维度
渠道-供应商
很多时候一些异常是来自于某个渠道的,以及某个供应商的,这时候如果我们系统如果支持渠道- 供应商维度的监控,可以查看某供应商相关的任务,流量消息,那么对于我们及时发现,定位,排查问题都是很有帮助的,比如某个whatsapp渠道的供应商因为系统问题出现大量异常,而这个供应商的发送流量占我们总的发送流量并不高,如果我们不能按照渠道- 供应商维度查看大盘情况的话,那么这时候可能就会因为异常的量不高而被我们所忽略。
租户
我们的需要保证能够查看单租户的流量指标,包括同比环比历史数据,最好也需要能够对比不同租户的这些指标,那么当针对某租户的异常出现时候,我们就能很快发现,比如这个租户突然流量暴增导致消息堆积,又或者这个租户的kafka topic出现了一些异常导致其他的一些问题。正常租户维度的指标监控也能够帮我避免一些因为单租户出现大量异常但是因为占总体流量比例较小而导致异常被我们忽略的情况。
任务
我们的营销消息触达平台本身就是通过任务来发送消息的,异常的出现往往是从一个任务开始,很多是单独是一个任务出现的异常,也很多是从察觉一个任务的异常开始后慢慢发现了其他问题,所以在任务维度进行指标监控是很重要的。
集群和机器
按照集群和机器查看指标应该几乎是所以监控系统要具有的功能了,我们的营销消息触达平台也一样,因为很多异常是由于机器的原因导致,比如因为一些原因硬盘被打爆,如果无法按照机器的维度监控指标。当机器的数量庞大的时候,单个机器的异常就很可能就会被掩埋,这里的机器除了包括我们营销消息触达平台的组件外,还要包括我们用到的中间件等其他相关的机器。
活动,场景或者其他业务标识
我们的系统是为业务服务的,而业务常常会在一些活动发送消息给用户,还有就是我们发送消息也是在一些场景下进行的,尽管这些概念都源自业务定义,我们经常会需要对一些特殊的活动或者特殊的场景做一些额外的特殊逻辑处理,肯定不可避免地也会出现一些问题,所以我们的系统上也可从按照活动,场景或者其他一些业务上定义的概念的维度上做一些指标监控。这对于观察一些重点活动很有帮助。
6.4 未来计划和流量预测
我们营销消息触达平台一个很大的优势就是可以提前预测未来的流量,这得益于我们整个流程上的设计,我们可以计算当前生效规则未来发送任务的时间,然后根据任务发送的目标人群预测可能要发送的人数(前面我们说过可以在创建规则时候圈选人群时候估计人数和根据最近一个任务的发送了估计人数),尽管这个预估并不是百分百准确的,而且我们系统也提供给了上游直接发送消息的接口,但是这个通常对于整体预测的偏差不会很大,足够帮助我们对系统扩缩容以及非重要任务的发送时间点选择做决策,但是要注意的是,如果上游调我们的平台直接发送消息的流量占比较大时候,我们就需要和他们确认好他们的流量,以及接入他们的系统监控等等。必要时也可以在和上游协商后对上游发送消息的接口做限流配置,我们的限流动作只是把消息堆积起来而不是直接拒绝上游,这样在减少系统压力的同时也能够减少上游自由发送消息对我们流量预测的影响。
6.5 告警和异常处理
在说了我们对系统进行的监控后,接下来就是异常告警和异常处理了,毕竟我们不可能一直盯着监控。及时对异常解析告警然后及时介入处理是必要的,但是告警部分其实是任何企业系统的必要部分,虽然实现千差万别,目标都类似,作者曾经担任过系统稳定性负责人,负责过整个系统监控大胖和告警的设计,所以在这里想简单对异常告警和处理做一些简单的细节提醒,给读者一些简单参考。
6.5.1 触发告警
捕获异常触发告警比较常见的方法的就是对指标做一些阈值判断,比如发送异常比例大于多少或者系统硬盘占用率大于多少等等,除此之外比较重要的就是对历史数据的同比和环比做一些判断了,以为不同系统的业务情况不一样,甚至不同团队的同一套系统在使用情况上可能都有差异,在一个系统里面请求异常率百分之20以内都算正常,但是在另外一个系统请求异常率哪怕百分之10都算很需要被关注了。我们当然可以针对这个情况设置好合适的阈值了,但是如果指标很多(比如对外接口很多,每个接口特点都不一样,都需要单独设定),这个时候工作量就很繁琐而且不好处理了,一个不错的方法就是对一些值比较多的指标统一做一些同比和环比报告,比如我之前的业务系统就对统一所有前端的http接口做了环比和同比告警,比如异常数比同接口昨天或者七天前增加了百分之50以上,就会触发告警,但是这个同比环比告警永远只是辅助,不太适合当主要告警,以为可靠性并不是那么高,比如我们之前的设置方式,如果昨天和七天前刚好都出了问题导致对应指标数提升,那么今天可能就算异常指标上升了也不会告警,因为就算异常了也不一定能超过昨天和七天前的百分之50以上。所以我们仍然对核心接口单独做了阈值指标。还有的触发告警发生就是在代码中直接调用告警函数直接触发告警,或者监控日志,监控系统匹配到日志的一些字符串或者error级别日志就触发告警,另外,我们通常也会对于一些不同情况下的告警以及根据不同的阈值对告警分级,比如p1,p2,p3...,这点很多人可能不陌生,越高等级的告警就说明越紧急,低等级的告警可能只需要简单确认一下就好了。
回归我们的营销消息触达平台,我们需要进行配置触发告警的指标主要就是总发送数,发送失败数,发送失败率,创建模板失败,服务回调参数异常,分片失败。调用服务失败,生成人群失败,调用供应商异常,调用中间件异常,cpu使用率等等,其实你在代码过程中有异常的情况都应该做一些埋点,需要注意有些指标是一旦触发就应该需要告警的,比如人群文件生成失败了,这个时候可能整个任务都不会发送出去了,这时候我们就应该立刻感知到这个事情然后快速排查做出应对,也同样有一些指标是需要量大的时候应该才引起关注的,比如调用供应商异常,因为系统偶尔出现一两次网络异常的情况还是不可避免地,而且我们消息平台也有对应地重试等机制应对,更何况这么大的发送量,每次网络异常都告警那么开发和运维人员都不需要做别的事情了,所以这类指标只有超过一定阈值或者同比环比突然暴增才需要介入观察。
6.5.2 告警方式
告警方式也是多种多样,比如工作软件,短信,电话等等,大家尤其是业务开发肯定也不陌生了,有些人经常可能会半夜被吵醒,也有些人为此特意晚上把手机静音了,通常我们也会根据不同的告警等级来设定告警方式,比如p2只是发短信,p1直接打电话等等。有时候告警也需要值班同学认领以确保有人开始关注,因为及时处理告警对于快速发现和解决系统异常是至关重要的,在告警方式上其实我们的消息平台和其他系统没有太大差别,而且也根据不同的企业或者团队告警方法也不一样。
6.5.3 告警内容和异常处理
异常处理肯定没有一个统一的方法,毕竟不同系统大不相同,但是我们的目标都一样,尽可能快地定位问题和解决问题,所以告警内容就很重要了,因为我们收到告警后首先看到地就是告警内容,所以告警内容因为尽可能仔细地描述异常地内容,包括指标,指标地值,以及这个指标地含义,甚至做一些建议等,如果告警内容中能附带日志,监控地地址,甚至日志地址能直接定位到异常相关地日志关键词,时间等,那么这对于异常地处理效率提升是很大的,比如我们在分片失败时候埋下异常指标,然后打下了详细的日志,那么在这个指标告警时,我们能提供一下直接定位到日志关键字的日志地址,开发人员点击后就能直接看到异常日志,做出判断,这个方式并不能保证百试百灵,但是能大大提高开发者的排查告警的体验和效率。
七 稳定性和可靠性
前面我们几乎介绍完了整个系统的架构,核心流程设计,明细数据回收等等,这些都是营销消息触达平台的基本功能,作为一些企业级系统,稳定性和可靠性是很重要的,接下来我们就会开始接受我们营销消息触达平台在稳定性和可靠性上的保证方式以及其部分核心实现,
7.1 系统容灾
系统的容灾处理是任何一个企业级系统都需要做的事情,就是系统应对各种各样的异常情况,对于我们的系统也一样,比如下游服务,节点,中间件,供应商等等的异常情况,我们的系统几乎都会有应对方法,下面让我们一一介绍。
7.1.1 中间件异常
我们的平台会用到缓存,消息队列等中间件,我们可以选择一些本身就稳定可靠的中间件,比如kafka和redis,他们本身就能通过一些集群部署和备份机制来保证中间件可靠,即使在单点异常时其他备用节点也能快速顶上,所以这点完全是完全可以通过中间件自身的容灾设计保证的。
7.1.2 节点异常
对于我们发送节点上的容灾处理,首先是通过节点集群,因为我们是使用kafka作为消息管道的,根据kafka消费机制,部署多个节点不仅能提高吞吐量,而且在其他节点挂掉时也能够快速接手他的工作,所以我们需要保证我们每个节点的集群至少有两个,避免单点故障下线时候影响整个流程。
对于节点有一些逻辑异常导致无法拉取消息的情况,这时候不管节点集群多少个可能都没用,比如模板择优功能节点异常,不再拉取Kafka数据,我们可以通过配置(手动或者自动)暂时跳过这个节点,让这个节点的兜底节点(通常是后面一个节点)来拉取它的topic,这样就能够暂时跳过这个节点。相当于通过降级的方式来保证发送任务继续完成。
对于节点有一些逻辑异常导致拉取数据后处理异常,导致消息无法发送给下一个节点时,这个时候相当于这个节点吞了这个消息,导致发送流程中断,这个时候我们依然能通过配置解决,首先通过配置开关让这个服务停止拉取消息,这个只需要在拉取消息之前判断配置状态就好了,然后和前面一样让这个节点的兜底节点来拉取它的topic,这样也实现了降级的效果。
7.1.3 特殊节点异常
前面说的节点逻辑异常的处理,对于一些特殊的节点可能就没有效果了,比如渠道分发节点,他会根据消息的渠道把他分发到不同的渠道发送节点上,或者消息发送节点,他是把消息推送给供应商的节点,也是不能跳过的,或者一些强逻辑发送流程节点,比如生成内容或者占位符替换的节点,我们总不可能发送直接带有占位符标识的消息给用户。对于这种情况,我们还有方法其他办法,我们在跳过这些特殊节点后,通过一个特殊的队列那这些消息存储其他,等他们恢复或者我们修复后再让他们重新消费这个队列,这样就可以避免消息丢失,当然其他节点也可以通过这个发送处理异常,把消息降级转化成消息延迟。
7.1.4 依赖服务异常
前面说的都是节点自己无法控制的情况下的处理情况,由于依赖服务都是由于节点调用来完成功能的,所以依赖服务出现异常时候我们节点是可以处理的,这时候就需要依赖于我们节点对服务调用异常的代码处理了,我们可以在发送调用异常后做一些降级处理,比如模板择优的节点因为算法服务异常拿不到最优模板,那么我们就只能发送模板组的第一个模板或者转化率最高的模板(如果能获取),对于频控规则服务获取不到频控规则时就可以选择暂时去掉频控逻辑,直接发给下游,对于实在无法降级的情况,也只能考虑把这条消息暂时塞入特殊队列等待服务恢复了。
7.1.5 供应商异常
供应商接口异常时也会阻断整个流程,遇到这种情况我们可以选择自动切换供应商,这个由于业务和成本原因需要需要在规则上开启这个选项并且设定第二供应商或者自动选择,这时候在这个供应商异常的时候我们就可以把这个消息发送给备选供应商,甚至发送给到其他渠道,但是这个时候我们就需要注意到流量问题了,我们在给租户配置流量配额的时候也可以预留一点专门的异常情况应对配额(前面说过我们的系统支持流量资源最大化利用,所以这正常情况下并不会影响系统吞吐量),这样就能够在服务流量满载的时候避免异常处理流量饿死。如果在不能切换渠道和供应商,那么也一样只能把他放到特殊队列里面等到供应商恢复了,对于一些有时效性的营销消息也可以直接丢弃了。
7.1.6 熔断和降级
同样的我们节点在调用服务的时候也需要控制好调用的流量,各个服务,不管是我们系统内部服务比如模板服务或者外部服务比如用户服务,都需要做一些熔断处理,在流量太大的时候及时熔断,因为服务可能也有其他地方在用,避免发送消息时把他们服务打挂影响到其他业务方,这个可以通过一些公开或者自研的熔断库或者组件来实现。
7.1.7 分片过程异常
设想一下,如果我们在分片过程中,服务器发生了断电,或者重启,那么我们的分片就中断了,任务一直卡在分片中的状态,后续到达了发送时间也无法发送。对于这种情况,我们会有四种机制来处理,
首先就是在可控重启时(比如服务发布),分片服务会在重启前往调度机发送重启信号,并且把正在分片的任务传递过去,调度机收到信号后就能够将改任务的状态从分片中改回待分片,这样他后面就能重新被调度机扫描到,再次进行分片。
另外一种就是应对异常时,比如服务断电,这时候机器就无法通知调度了,我们为分片任务引入了心跳/健康检测和水位线机制,分片服务在分片过程中会定期往分布式缓存里面提交该任务分片进度以及该机器的节点id,如果我们系统通过心跳检查检测到机器离线了,就会通知调度机把相应的任务回滚为待分片,然后被调度机扫描后就会被其他分片机器接手继续分片,并且从记录的分片进度处继续开始(当然也可以关闭分片水位线让任务重新开始),这里需要注意的是同步的进度水位线经常是滞后的,比如刚刚上报完分片到第100000个用户时,又分片了5000个用户还没来得及上报服务就挂了,这个时候水位线就会比实际进度少5000,这个时候如果从这个进度继续开始,我们最终的分片文件就会比之前稍微变大,因为多了一些重复用户,尽管这个其实并没有太多影响,因为我们系统有防止重复发送的机制。但是我们最好还是做一些处理的,比如在准备重新分片时,通过扫描总人群文件前面的水位线内容,判断各个分片文件应有的大小,然后从这些分片文件应有的大小之后覆盖写,这样分片文件就不会多出一些额外的重复人群了。
还有一种情况是,因为分片服务的内部异常,导致他还能对外发送心跳或者提供健康检测,但是实际的分片处理工作已经停止了,这种情况我们也是可以处理的,我们的调度机可以定期去拉取任务的分片水位线(这里可以全量任务抢占式拉取或者通过桶等算法分配调度机要管理任务),如果发现某个任务没完成分片但是它的分片水位线长期不变时,我们就能认定任务出了异常,也可以让这个任务回滚,这时候还应该告警出来,避免系统出现了一些没有捕获的异常case。
再一种情况就是,分片服务还在正常分片着,但是他的网络出了问题,这时候他既无法向redis提交水位线,也无法对外发送心跳,但是和文件服务的链接还能正常进行,这个时候如果被认为是宕机,让其他分片服务接手,这时候可能会同时两台机器对一个任务进行分片了,这时候可能导致人群分片文件混乱,因为有多个地方都在写这个文件。我们解决这个问题的思路就是通过版本控制,通过记录处理的版本,每次异常重新处理时候版本号都会增加1,最终任务运行的时候都会那最高版本的分片文件来发送。
7.2 核心流程与业务查询隔离
在我们的系统设计时,我们重点对业务的查询和业务数据库进行了隔离,这个是非常有必要的,因为业务经常会需要一些发送数据或者任务,分片,分流,供应商成本等数据,他们可能会有专门的数据分析师或者委托开发进行一些数据查询,因为这些查询是多种多样的,数据量通常也不小,而且我们也无法为这些未知的查询做一些索引优化等性能优化,频繁的查询或者复杂的联表查询可能会对数据库造成压力,进而影响我们整个业务系统。所以我们通常需要通过数据平台把业务表定期抽取到数据仓库中,方便大数据平台做离线数据计算的同时也能给业务用来做数据查询。当然也可以吧业务表抽到其他的mysql表中,核心的目的是为了把一些数据分析查询和业务系统的数据操作隔离开。
7.3 防止消息重复发送
防止消息重复发送是我们的营销消息触达平台很重要的一个保障,重复发送消息不仅会增加成本,还是给用户带来不好的体验,之前作者在一家跨境电商公司时就因为系统的问题给一些用户连续发送好几百封邮件,不仅受到了用户大量的投诉,在一度在推特和Facebook上成为热点事件,让公司的名声雪上加霜,所以我们的消息平台防止重复发送的的覆盖度是很重要的,既要在拦截在业务重试机制上出现的重复发送,也在拦截代码bug导致的重复发送,所以我们通常会在即将发送前的尽可能后面的工作节点来完成重复发送的事情。防止重复发送我们主要有下面的方式。
7.3.1 发送时记录在人群文件的水位线
因为发送过程中经常可能遇到一些异常,在发送异常的时候总是需要重试来避免消息丢失,但是发送中途异常时直接重新从头发送整个人群分片文件,那么就会造成大量的重复发送,所以我们首先需要再发送时候记录人群分片文件中发送的水位线,这样异常重试时就能从原本的水位线继续开始,不需要重新从头发送,当然这个方法并不能完全解决重复发送的情况,主要还是为了能够记录任务的发送记录和提高异常处理速度(试想一个超大的人群文件,本身发送就要几十分钟,如果刚好遇到系统不稳定一直从头开始,那么就很麻烦了)。
7.3.2 幂等key判断
前面说到的水位线并不能完全解决重复发送的问题,因为水位线是记录在分布式缓存服务的,为了保证性能,我们不太可能在人群文件拿一次分流就记录一次水位线,通常会隔一段时间或者一定数量才会更新水位线,所以水位线总会有一定的滞后性,这样就不可避免也会出现一些重复发送,我们解决重复发送最终的方式就是通过幂等key,在渠道分发的前一个节点通过任务- 用户标识在分布式缓存服务上记录一个幂等key,因为一个任务不会给同一个用户发送两次消息,这样如果有重复的发送就能够在这个节点上完成拦截,但是这也给占用了redis大量的存储空间,所以我们做的幂等key也只能是保留比较短的时间,比如5分钟,当然各个团队可以根据自己实际情况调整。
7.4 防止丢失消息
说我了防止重复发送接下来就是防止消息丢失了,我们当然希望我们系统的消息尽可能发送给每一个目标用户了,保证我们消息的触达率,对于防止丢失消息的保障,我们通过下面的机制来实现。
7.4.1 重试
重试机制是一个很常见的异常处理方法,比如我们在人群分片失败了,我们可以重试,在调用其他服务失败了,也可以重复,调用供应商接口异常也可以重试,但是我们需要区分好重试异常和不可重试异常,可重试异常就是指可以通过重试回复的异常,比如网络不稳定,服务限流等等,这类异常我们只需要通过重试就有机会恢复正常,当然我们也需要限制异常重试的次数,避免在一些需要一段时间恢复的异常情况下大量重复执行。不可异重试一般无法通过重试来解决,比如认证失败,参数错误,人群文件内容异常等,这个时候就只能通过其他方式来解决了,再实在无法进行下去时就需要及时发出高优告警及时进行问题排查和制定一些补救措施。
7.4.2 提交offset时机
我们的工作节点之间是以kafka作为数据管道的,所以我们能很方便地决定提交offset的时间,为了防止消息丢失,我们可以在拉取消息并且处理之后才提交offset,这样即使在处理过程中宕机了,因为没有提交offset,集群里面的其他节点接手后也会从原来的进度开始,不会丢失信息,再配合前面的防止重复发送的机制,就实现了发送消息exactly one的效果了,还有一点就是我们的节点没必要为了提高资源利用率而对单topic进行并行消费,这会大大增加消息流的管理难度,而且我们完全可以让一个节点消费更多的topic来尽可能提高资源的利用率。
7.4.3 死信队列
可重试的异常也经常会有重试失败的情况,也有些消息可能因为一些原因就是无法成功执行,你也不确定他在未来的某个时间能否恢复,但是也不可能因为它把整个发送队列堵塞了,所以我们使用了一个新的队列,先暂时把他放在这个队列里面,然后继续发送后面的消息,我们把这个队列称之为死信队列,对于这个死信队列我们可能慢慢地尝试发送他们,我们也不知道这些消息什么时候能发送好。需要注意地时对于不可通过重试来恢复地消息就没必要放在死信队列了,而且因为死信队列本身都是一些异常地消息,所以我们也需要加强对死信队列地监控和告警。
八 系统功能扩展和AB实验
至此我们已经基本完成了对整个系统的架构,基本流程,还有一些核心细节的实现介绍,现在就开始介绍如果基于现有的架构和流程下扩展一些新的功能,还有接入AB实验,自动扩缩容,接入新的供应商等等,我们应该需要怎么做。
8.1 功能扩展
为了实现业务方的一些业务需求或者系统合规化改造,扩展我们的系统功能是不可避免的,比如业务希望发送手机push弹窗消息时根据用户在线的设备类型(iphone,Android,ipad)发送不一样的内容,并且按照根据设备在线情况按照优先级发送,又或者需要判断用户是否再个人设置里取消订阅了消息而不能给他发送营销邮件(尽管这个功能可能更加适合在业务系统完成而不是消息平台,但是可能还有一些原因,比如紧急项目那边人力不够影响了老板的决策导致我们不得不做这些),对于这些额外的功能,我们在设计之初一开始就考虑到了,我们可以在新增一个节点,在这个节点里面完成这件事,比如前者就是接入用户设备数据服务判断当前在线的设备然后进行优先级判断最终再根据设备类型选择模板,后者就是接入用户配置中心判断用户是否取消了订阅再决定发不发,我们可以把不同的功能交给不同的节点完成,虽然他们可能经常会有顺序依赖关系,但是我们也要尽可能保证不同节点之间的工作独立互不干扰,我们也可以根据工作的类型把不同的事情放在一个节点上完成,比如后面说的场景就可以考虑放入原本的合规管控节点中,这取决于系统各自的技术或业务架构定义。当然我们前面也说过,节点只是一个抽象的概念,我们并不限制他的具体实现,增加节点不一定就是增加一组服务来完成功能,也可以再代码上加一个新的消费者,也可以是再职责链代码中加一个链条上的节点,甚至只是新写一个方法,然后调用它等等。我们需要保证的只是整体系统架构和代码的整洁,保证它的基本功能并且易于维护。
消息也应该添加足够的字段标识让节点能够根据这些标识来做一些区分,比如在国际化业务的短信渠道中经常会有一些供应商对短信的消息有长度限制,所以我们就需要添加一个节点来判断这个用户消息是不是属于这个渠道并且是这个供应商的,如果是的话就进行长度判断,如果超过供应商规定的限制就给拦截了避免负担一些不必要的成本。但是这样做的前提就是这个节点必须能够知道这个消息是哪个渠道哪个供应商的,但是我们不太应该把每条消息都加上渠道和供应商这个字段,因为消息的数量巨大,每一项都加上这两个字段会额外占用很多的空间,最重要的这两个字段不是必要的,因为一个任务本身就是指定了渠道和供应商的,但是我们可以把这两个字段放在这批消息的公共的元数据里面,或者通过配合缓存对这个任务的渠道和供应商进行查询,这样节点能够通过这些元数据判断这批消息的渠道和供应商,而且也不需要额外占用太多空间,
8.2 AB实验接入
业务提出新功能需求不可避免的也会需要做一些ABTest来验证新功能的效果,这就是往往就需要设置一系列实验组和对照组,产生不同效果然后记录效果再根据记录的结果做相关数据分析,其实我们对ABTest支持也是有各自方法,比如对于新的功能在新增一个节点后,在他的前驱节点进行ABTest判断,如果是实验组就让他接入这个节点,否则就跳过这个节点直接发送给这个节点的下一个节点,这样可以提高系统的性能,但是如果实验一多可能消息流可能会变得复杂,而且代码可能也比较难以维护,所以这里更加推荐在可以的情况下让消息走遍所有节点,在这个节点来判断这个消息的ABTest组别,判断是否做特殊处理或者直接放行,实践告诉我们这样对于整个系统或者是代码更加容易维护,而且基本ABTest最终都是会放开或者关闭的,如果关闭的话直接把对应结点去掉也很方便。
8.3 自动扩缩容
因为我们的系统本身就具备实时流量统计和未来流量预测的功能,基于此做自动扩缩容是非常方便的,我们可以新设一个专门的管理服务,然后设置一些规则,如果当前流量达到了某个值或者未来相关流量达到某个值就可以做一些扩缩容工作,比如动态添加集群的服务器结点,增加kafka分区等等,都是具体还是需要根据研发基建自己开发或者接入现有的功能。
8.4 接入新的供应商
作为消息触达平台,我们经常会需要接入一些新的供应商,各种渠道的各种供应商,虽然他们的接口不同,但是相同渠道的供应商他们要做的事情其实类似,字段也不会相差太多,我们同个渠道的消息应该尽可能保证字段一致,也可以根据供应商各自添加一些差异字段,然后我们会为这个新的供应商增加一个新的节点,在新的节点中把消息转化为供应商需要的消息协议,然后发送给供应商。所以我们不太需要太多地改动整个发送链路和消息格式,主要就是完成一些字段转换逻辑就可以了,类似于一种适配器的设计模式。
九 其他工作
到这里我们已经说完了整个系统的建设了,但是营销消息触达平台的内容还有很多很多,包括很多我们需要依赖的能力或者服务需要建设,包括模板管理,大数据平台,权限控制,服务监控等等,这里每一个服务的建设难度都不小,也需要考虑各种各样的问题,虽然这些可能也是我们系统内部的一部分,比如模板平台,也可能是我们系统强依赖的一部分,比如人群服务,这些系统是我们不可剥离的一部分,而且也有一些设计难度,但是考虑到他们都是细分领域上的系统或者不同企业可能已经具备这了这些服务,我们只是需要它们提供一些必要数据支持,也不直接在整个消息发送流程里面,我们本篇文章目的是讲一种营销消息触达平台的架构和流程设计和部分难点的解决思路,所以就不花费大量篇幅去仔细讲解他们而是做了简单的介绍,但是基于它们的重要性,我们觉得最后还是应该额外讨论一下它们,包括对于他们的主要工作量,还有设计上的建议等。
9.1 模板管理
模板平台其实更应该是我们营销消息触达平台的一个子服务,因为它存储的是触达消息的内容模板,而消息内容也是消息一个重要的部分, 模板平台主要就是允许业务存储自己的模板,包括创建,编辑,查询,删除等,不要小看这个看似单纯的平台,他需要做的事情可真不少。
9.1.1 权限控制
模板的管理也经常会涉及到权限相关的控制,用户能操作那些模板,查询哪些模板,查看哪些模板等,这些过程中可能也需要一些审批流机制,在创建,编辑,删除模板时候可能都需要经过对应的审批流程,而且也需要注意隔离不同租户的模板,确保不同租户不应该也不能感知到其他租户配置的模板。
9.1.2 版本管理
而且在编辑后,业务可能还需要能查看历史内容,编辑人,甚至做一些回滚操作等,这时候就需要对模板进行版本控制,包括保存模板历史内容,以及关联模板版本号等,而且还有一个潜在需要注意到的问题,因为我们的模板可能有各种各样的占位符,我们在编辑的时候可能会增加一些需要的占位符,如果系统处理不得当可能会出一系列问题,比如人群文件生成后,业务又改了模板,增加了一些占位符,但人群文件里面没有这些占位符的填充信息,在获取模板内容的时候拿到了新的模板内容,但是因为人群文件没有这些占位符而导致消息发送失败甚至把带有占位符的消息发送给用户,这一个不难处理但是又容易忽略的问题,处理方式最简单的就是生成人群文件的时候就把模板的版本也指定了,这样获取模板的时候也直接获取对应模板对应版本的内容,相当于让编辑后的模板只对还没完成人群文件生成的任务生效。
9.1.3 渠道和内容
还有一点就是模板的内容问题,很多人可能觉得配置一些通用的模板,然后每个消息渠道都能选择这些模板来发送,把模板的内容和消息渠道解耦开,但是这个其实是不太现实的,大多数消息平台的模板都需要指定或者标明对应渠道,只要在对应消息渠道才能用到它,这是因为不同渠道的内容可能千差万别,协议也不一样,比如push的内容可能就比较简单,就是一些直接的文字,email模板经常会包含一些富文本标签,whatsapp又有自己的模板协议,包括head,body,button,sms模板虽然可能也是单纯的内容,但是可能内容会比较长,用push来发送可能会让文案都显示不全,所以你想要让不同渠道服用这些内容几乎是不现实的,而且还可能因为业务操作错误导致模板发送错渠道了造成不好的影响,所以大多数系统会让模板归属一个特定的渠道。另外一个就是存储相关的问题,有些人可能觉得应该把所有消息渠道的模板存储在同一个表里面方便处理,有些人觉得应该每个渠道单独有一个表来存储模板,毕竟不同渠道的模板字段可能都会有差异,或者有些人结合了这两种方式,公共字段放在同一张表,差异字段放在各自的表。对于这些方式作者在这不做评价,因为这些都是可行的设计,配合字段和代码设计都能做到高效和简洁的处理。
9.1.4 国际化
如果我们的营销消息触达平台需要发送给各国的用户,我们我们还需要涉及到模板的多语言问题,因为大多数情况下我们会根据用户的市场或者用户的手机语音在判断给用户发送什么语言的消息,比如你知道某个日本用户的手机语音为英语,这时候你可以给他发送日语或者英语消息,但是给他发中文就不太合适了,他大概率也看不懂中文,甚至认为是诈骗消息,所以在这种情况下我们的模板可能还需要配置多个语言的内容,然后根据用户情况选择发送什么语言的内容,这个时候就需要考虑怎么存储一个模板的不同内容了,是通过一个map的json存储,还是不同语言在数据库单独存储一行,然后通过模板id和语言关联,这些都是一些可用的实现方式,有些系统可能因为历史原因两种方式都同时存在。
9.1.5 模板数量限制
我们通常会觉得,只要我们的硬盘空间足够,我们想创建多少模板都可以,真的是这样吗?其实大部分时候确实是这样,但是总有一些例外,比如whatsapp渠道下的模板都是由meta官方管理的,尽管我们通常会在我们的系统也存储一份模板内容,meta会对每个waba下面做模板数量限制,如果超过这个模板限制可能创建模板就不会成功,为了防止这种事情,我们也只能对业务方创建whatsapp模板进行限制了,尤其是是在多个团队公用一个waba的情况下,我们可能需要给他们分别配置模板数量限制,使得他们拥有的模板数量总和不超过单waba模板的数量限制,又或者我们需要自己定时清理一些长时间没用到的模板减少业务主动管理模板的负担等等。这也是模板管理平台需要考虑的一个问题。
9.1.6 模板状态
模板的创建也需要一系列审批工作,所以我们也经常需要管理模板的状态,比如草稿,待审批,生效,暂停使用,在创建,编辑,保存草稿,审批通过,审批失败等操作时都会进行状态变更,我们还可能需要添加一个开关标识,让业务可以开启或者关闭这个模板(注意关闭模板时需要确保在没有规则正在使用这个模板),有些情况下还需要经过多重审批,比如whatsapp除了我们业务内部审批外,还需要meta官方的审批,所以这时候在业务完成审批后就不是生效了,而是待meta审批,meta审批通过,但是模板在使用过程中可能会因为用户投诉或者阅读率低的原因被meta官方临时封禁或者永久封禁,所以模板还有暂停和封禁状态,也是由于业务这里和meta的行为是互相独立的,业务随时可能修改模板,meta也可能随时完成模板审批,暂停或者封禁。如果只使用一个状态字段来保存模板状态,在模板状态机的扭转判断上会变得很麻烦并且因为一些meta回调延迟经常可能有一些异常的扭转导致数据异常。针对这种业务方和渠道方都需要管理的模板的情况,我们建议是通过两个状态字段进行管理,一个代表业务上的模板状态,比如草稿,待审批,审批通过,删除等,另外一个代表渠道方的模板状态,也是待审批,审批通过,封禁,删除等。这样在他们各自状态机的扭转上就会变得容易维护,在使用时也只需要同时判断一下两个状态就好了。
当然,在流程控制上,为了提高效率,除了串行审批外,我们也可以使用并行审批的方式。
9.1.7 单次模板
有时候对于一些临时性的简单消息,业务觉得创建模板太繁琐,想要在创建规则时候手动输入发送的内容,从他的视角上看,就是少了一次创建模板的操作,但是在我们系统看来,这个可能仍然也算是一种模板,因为我们总得需要保存业务要发送的内容,最快的方法也是为这个内容创建一个临时模板,任务发送时使用这个模板发送,同时这个模板也不需要被模板管理页面搜索到。类似于我们面向对象编程里面的匿名类或者函数内部方法,我们可以叫他规则内部模板,我们这里统一叫他单次模板,实现单次模板本身并不难,但是需要注意的是,对于一些特殊的渠道,比如whatsapp渠道,因为meta官方对于每个waba下有模板限制,所以我们在控制包括单次模板在内的总模板数量的时候也要注意当规则失效后及时从meta那里删掉这个模板,对于需不需要删除没有规则指向的单次模板(一般因为规则被删除或者规则过期),可能需要我们自己做决策,因为我们可能想要节约系统硬盘,也可能想要确保发送消息可以回溯,保留单次模板以保证我们能够知道之前发送了什么内容给用户。或者也可以选择一些折中的方式,比如清理规则已经无效两年半的模板。
9.1.8 模板复用
有时候业务方可能因为一些特殊原因需要创建很多内容相同的模板,这点硬盘浪费对于我们来说通常不算什么,没必要浪费精力去做额外的设计,但是,又是whatsapp!因为他对于waba下的模板数量有限制,相同的模板也会占用他的模板数,所以对于这种情况,我们还可能需要针对whatsapp或者类似渠道做一些模板复用的功能,其实实现的效果很简单,就是如果用户的whatsapp模板和之前的任意一个whatsapp模板内容一样,我们就需要让他和那个已有的whatsapp模板指向同一个meta模板,这样既不会浪费一个模板名额,也可以省去meta审批的环节,因为meta已经审批过这个模板了,但是实现的功能虽然简单,要处理和应对的一些细节也不少。
首先就是对内容的校验,我们系统里面可能有几千几万个模板,每个模板的内容又可能很长,把新创建的模板和老的模板逐一比对,那么效率可能会很低,这时我们可以在生成模板的时候为模板的内容计算一个校验和或者哈希值,虽然哈希值相同时候内容不一定一样,但是内容相同时候哈希值一定相同。所以我们创建模板并且计算了校验和或者哈希值后,可以找到具有相同校验和或者哈希值的模板,然后再做内容比对,这样就能够大大提高匹配效率。
然后就是模板复用对于状态机扭转的影响,同样拿whatsapp的模板举例,尽管这些模板的内容一样,但是他们其实都是互相独立的模板,每个模板都能独立操作,都能独立进行编辑,编辑后可能就需要创建新的模板或者指向其他的复用模板,但是对于meta而言他们是同一个模板,用户修改和创建模板的时机和meta通过审批,拒绝审批,模板封禁,暂停,恢复事件来领的时间都是完全随机的,收到meta事件时我们需要把所有复用这个模板的模板的状态进行变更,加上meta事件延迟的情况,模板状态可能变得更加难以维护,这也是作者前面推荐把模板业务状态和渠道方状态设置为不同两个字段让他们尽可能隔离的原因,因为这尤其是在模板平台具有复用模板能力时能够大大降低模板状态机扭转的设计和维护难度。
9.2 大数据平台和人群标签服务
大数据平台和人群标签服务也是我们营销消息触达平台不可缺少的一部分,如果模板平台提供了我们发送消息的内容,人群标签服务就是提供了我们要发送消息的目标,这也是发送消息最重要的两个要素,但是和模板平台不一样的是,人群标签服务虽然也是我们的强依赖项之一,但是他通常不是营销消息触达平台的内部系统,可能更多的是一个独立的外部平台,不仅给我们提供服务,也需要给其他业务团队提供服务,而大数据平台就是人群标签服务的基础,人群和标签数据通常都存储于大数据平台,然后也需要使用大数据平台的能力做一些离线计算,这一小节我们会讨论一下大数据平台和人群标签服务。
9.2.1 人群和标签
大数据平台需要存储人群数据每个用户的标签数据,这个也是我们消息平台能发送消息给目标用户的基础,业务在圈选人群来选择发送目标人群的时候,虽然也有上传固定人群的方式或者全量人群的情况,但是绝大部分情况下都是通过选择对应的一个或者多个标签,根据”与“(同时拥有这些标签)或者”或“(只需要拥有其中一个标签)或者更加复杂的嵌套逻辑来选中消息要发送的目标人群的,我们称为人群圈选规则,然后我们在发送时候就把消息发送给这个圈选规则里的人群。但是人群是动态变化的,随时都可能有新用户注册到我们系统,或者我们通过一些渠道获取到用户信息录入到我们的人群数据。也随时会有用户注销我们的系统,或者因为一些原因需要把用户从我们的人群中去除。除此之外标签也是动态变化的,随着用户的一些行为或者一些系统内部的计算,用户都有可能添加一些标签或者去掉一些标签。所以同一个规则的不同任务,尽管他们发送目标的人群圈选规则一样,但是因为发送时间不同他们所发送的实际人群也不一样。很显然人群和标签的数量都是巨大的,需要存储和管理这么大的动态数据也并非易事,需要处理的细节非常的多。
9.2.2 人群标签服务
人群标签服务是真正负责对外部系统提供人群圈选数据的服务,它依赖于数仓存储的人群和标签数据和大数据平台的一些计算能力,然后根据业务系统的要求,比如标签圈选规则,市场,品牌等把圈选出来的数据提供给业务系统,但是由于人群和标签数量,还需要根据规则进行一系列计算,不仅可能需要较长的计算时间,而且返回的数据量可能是非常巨大的,这并不是单词请求的缓冲区能存储的,甚至可能接收方系统耗尽所有内存也无法一次性获取这么多数据,所以我们通常会采用离线计算的方式,就是业务系统给人群标签服务下发人群文件生成指令,在收到人群标签服务确认后完成请求,完成本次请求,人群标签服务收到指令后先做一次圈选规则和市场,品牌等参数的简单校验,然后校验成功后返回确认消息才开始生成人群,等人群数据生成完成后再回调业务系统。
因为人群数量有可能是巨大的,可以把生成的人群数据放在持久存储介质上,比如关系数据库数据库,时序数据库,kafka或者文件上,然后等待人群数据生成完成后再通知业务系统,让业务系统去对应存储介质上拉取并处理这些人群数据,比如是生成人群文件的话,业务系统就可以读取这个人群操作做分片操作。我们目前在使用的方案也正是把人群数据存储在人群文件上。但是也完全可以根据需要选择存储在其他地方,如果你选择存储在kafka上的话,甚至对于一些不需要等待人群数据完全生成好就能进行的工作都能提前进行了。比如分片操作,你甚至可以立刻就消费这个kafka把已经刚刚添加的发送用户立刻就放入分片文件或者其他分片存储介质,但是这种事虽然能够提高系统整体性能的同事也需要考虑额外的问题,一个常见的问题就是,比如本次生成人群中途出现了异常,需要重新开始,那么业务系统就需要回滚之前的处理了,
9.2.3 多市场
人群和标签存储需要考虑的一个问题就是多市场问题,这通常在国际化业务中出现,多市场问题就说你的业务可能在多个市场都有运营,比如中东市场,美国市场,亚洲市场,当然市场的划分也和业务运营策略相关并不是固定的。所以你的用户也被分为不同的市场,有些用户可能因为一些原因同时处于多个市场。我们可能需要设计一个合理的策略来存储不同市场的用户,比如直接放在两个不同表里,或者在总人群表里增加用户的市场标识,又或者把市场作为用户的一个标签等等,业务在圈选人群时可能需要选一个市场或者多个市场,这时候可能还需要解决重复用户的问题。
9.2.4 多品牌
一个企业因为自身规划或者并购等原因下面可能会有多个品牌,比如美团下面就有国内品牌美团和国际品牌keeta等,滴滴下面有滴滴,青桔,99等,shein下面还有romwe,拼多多下面还有TEMU货拉拉下有lalamove,货拉拉,小拉出行等,他们看归属一个集团管辖,但是面向的业务或者市场不一样,发送的主体也不一样,它们各有各的用户,和多市场一样,多品牌也会涉及到存储方式的问题,也会有重叠用户的问题,而且业务线圈选人群时候也可能也需要筛选对应品牌下的用户。多品牌因为和多市场问题有很多共同的地方,本质上就是在不同的维度上需要对用户进行区分,甚至有时候因为企业需要规避一些法律风险,还需要吧某个品牌和其他品牌的数据做隔离,这些都是我们的平台需要考虑的事情。
9.3 权限控制和审批流
系统在权限控制和审批流上需要投入一定的开发精力,首先我们需要确保不同租户之间数据的隔离,一个租户肯定不能看到另外一个无关租户的模板或者任务信息,这不仅会给他们带来困惑,而且还会增加信息泄露的风险,然后就是我们可能需要给不同的租户做一些权限控制,比如限制一些租户只能发送特定渠道特定类型的消息,或者权限一些特定标签或者市场,品牌的人群。
然后在用户维度,我们需要对用户的权限进行管理,除了他所属租户的限制外,我们还需要限制他自己的权限,比如能创建修改哪些类型的模板,创建修改哪些类型的规则以及能看到什么页面什么信息等等,具体的权限颗粒度取决了具体企业或者团队的要求,除此之外还可能要需要对一些重要的操作比如创建或修改模板,发送规则,频控规则等等做一些审批流程控制,只有通过了对应的审批流程才能最终完成操作,这些审批流程还需要可以配置化,比如不同的租户不同的行为甚至不同的用户都会有不一样的审批流程,比如审批人,审批形式等等,同样也需要注意企业人员变动给审批流造成的影响,比如审批人已经离职或者变更到其他团队。另外再审批形式上也多种多样,可能是审批人需要再审批消息点击确认,又或者需要去专门的页面确认等等,当然审批流程和形式也需要根据企业或者团队的具体情况自行制定。
另外我们也经常需要保存整个审批流程的历史数据,记录审批流程的具体信息,比如哪个时间哪个人提交了什么内容的审批,哪个时间哪个审批人完成了这个审批。方便责任追溯问题。
所以要完成一套权限和审批流控制模块。不仅是权限颗粒度,权限形式,审批流形式的设计,还是具体的开发工作,都需要付出很大的精力。
9.4 服务监控
要做好服务的监控其实也是一个不小的挑战,当然如果公司提供了统一可靠的监控基建的话,那么我们的工作量就会降低很多。这时候我们可能只需要在业务代码中埋点,然后大盘上配置相应的指标展示和告警规则就好了。作者场景担任过过稳定性负责人,对这一块也有所了解,虽然不会在这里仔细介绍监控服务的搭建,但是也想要分享一点埋点相关的经验。
如果我们是通过埋点采集的指标,我们需要注意一下埋点的时机和内容,避免影响到我们系统的发送效率,比如每发送一条消息埋一个点就是没有必要的,因为要发送的消息很多,每条消息都不管成功还是失败都单独埋点会消耗大量的机器资源,过多的埋点也会指定影响到监控的性能,而且这种埋点本身也不是必要的,我们完全可以通过记录每个分流的发送成功和失败数,这样不经能够为监控提供实时发送情况信息,也能很方便地为业务系统或者数据分析所用。我们可以只在分流失败的时候埋点,但是对于发送失败时候我们可以对失败原因进行埋点,记录他的失败原因用于监控统计,或者在人群文件生成,分片成功或者失败时候进行埋点。因为他们的数据量并不会太大。
还有一个需要注意的就是我们在设计指标时需要避免指标存在不可枚举的字段。比如异常的原因,我们可能枚举一下我们系统可能出现的几十甚至几百种异常原因或者其错误码,然后对于其他没有捕获到异常埋为其他异常,然后通过具体的日志和告警来排查异常,而不建议不管什么异常都把异常信息作为埋点某字段的值上报,这样会导致这个字段不可枚举。又或者我们需要对分片操作的时间进行埋点时候,我们不应该把这个时间直接作为这个埋点的某字段,而且要根据时间范围,把它进行可枚举化,比如200毫秒内正常,500秒内较慢,1500毫秒内为慢,1500以上为很慢,具体的时间我们可以把他存储在数据库里面。而且我们也要避免存在太多的字段,我们需要这样做的原因有很多,主要的原因包括
维度爆炸
因为大多数指标存储系统(如 ClickHouse、Druid、HBase)都需要对各字段的所有做分组,如果字段过多,尤其是有一些字段不可枚举的情况下,就会产生大量的分组,也就是指标维度爆炸,比如你设计了一个指标,有两个字段A和B,A字段可能的值是A1,A2,B字段可能的值是B1,B2,B3,那么分组个数就是2
- 3 = 6个,但是如果你的B是不可枚举的,比如你用任务id作为这个字段的值,指标系统出现10000种ID,那么指标存储系统就需要拥有2 * 10000 = 20000个分组,后续可能还会更多,同样的过多的字段也可能会导致维度爆炸,因为各个字段对分组个数的影响是指数级的,维度爆炸会导致指标存储系统的写入、压缩、聚合成本极高,严重拖慢系统,包括我们的监控查询速度也会慢得离谱甚至不可用
无法预聚合
现代指标系统(如 OLAP/时序系统)依赖可枚举维度提前做多层聚合来提升查询效率:如果字段不可枚举,系统就无法提前聚合,只能“按需时聚合”,成本高、慢、且缓存效果差,而且现代指标系统基本是按照分组来管理的,当不断地出现新的分组时,甚至会有系统跨掉的风险。
不具备业务意义
高基数字段很多时候对业务分析价值有限,例如按手机号码统计某行为无法做群体分析,按订单号聚合转化率 ,无洞察价值,只能查看明细
所以对于一些不可枚举的值,我们可以在数据库或者缓存服务里面把他们记录下来,但是不能直接把他作为埋点某个字段的值。
9.5 聚合管理平台的建设
聚合管理平台就是我们整个营销消息触达平台的门面了,我们平台几乎所有的操作,包括任务看板,人群标签管理,租户管理,发送/频控规则管理,消息模板管理,成本控制,供应商管理,合规管理,明细数据查看,审批流程,服务监控大盘等等,都在这里。不过它本身并不会存储实际业务数据,基本都是从各领域服务获取到数据或者转发到各领域服务提供的功能接口实现操作的。从难度上来说他可能说是比较小的,但在工作量上它确完全不小,因为需要做的事情太多了,而且如果想要保证业务的使用体验,还需要付出更多心血,不过一般来说我们也并不是一开始就把所有功能接入了,而且先做核心部分,根据慢慢地完善其他功能。
9.6 国际化文案中心
很多有做国际化业务的企业可能都会有自己的文案中心,拿前面的模板来说,如果模板可能会发送给多个国家不同语言的用户,他们他就需要多个语言的文案,现在假设他同时需要中文,英语,法语三种文案,业务就需要为这三种语言写三份内容。但是很多情况下用户可能只精通其中一种语言,比如中文,至于英文,法语,直接用翻译软件翻译后直接发给用户显然是不合适的,所以他可能需要找一个会英文的和一个会法语的人来帮他填写模板的内容,如果每次创建模板都这样,而且在需要的语言类型很多时,这个工作就会变得繁琐,除非每个业务都能同时懂这些语言,但是目前的人力市场肯定提供不了这么多这样的人。
正所谓专业的事情让专业的人做,文案中心的出现正是为了解决这个问题,公司只需要在各个语言上招聘少数几个人,让他们专门负责翻译工作,每次业务需要多语言文案的时候,可能用自己擅长的语言(比如中文)写一份,指定需要翻译的语言,然后提交给文案中心,然后专门的人完成翻译后返回一个文案key和对应翻译结果,业务可以直接使用翻译结果,或者吧模板内容设置为这个文案key,开发在读取模板时候发现指定了文案key,就可以通过这个文案key和需要的目标语言去文案中心获取具体的文案,这对于整个流程来说效率会提升不少。当然在其他的地方,比如国际化App或者其他支持多语言的管理平台也能够通过文案key大大简化工作流程。当然为了防止业务滥用翻译资源,翻译文案key可能也需要接入审批流程。
后言
至此我们已经完成了整个营销消息触达的基本概念,系统设计目标,系统需要依赖的服务或能力,营销消息触达平台架构,消息发送流程的介绍,我在这篇文章中这是给了营销消息触达平台的整体解决方案,开发中需要注意或者解决的一些问题以及一些核心重要方法的解决思路,最后再简单地讨论了平台需要依赖的一些其他能力。这些都是作者自己的观点以及系统建设中积累的一些经验而不能代表绝对事实,文案给的方案也都只是参考而不是答案,作者只是给了一种设计思路,具体的实现还是得根据大家各自的背景来决定。根据具体的需要可以在一些地方会做一些修改,或者只是借鉴其中的一些方法和经验。而且任何庞大文档可靠的系统的不是一蹴而就的,都是在不断的发现问题和需求迭代中慢慢变得完善,可靠的。消息发送平台也一样,想要一次性地吧文章描述的这一整套系统,包括依赖服务全部实现,是不太可能的,这需要消耗大量的人力和时间,而且做出来之后也不一定适用于现有的业务,如果计划开发这样一套消息平台,作者建议先从根据这一整套架构和设计思路先把核心功能做起来用起来,前期的监控和明细报表等非核心功能也不需要一开始就做得很完善,后面可以找时间慢慢根据实际情况和业务需求做优化。