我认知中的营销活动及其系统

20年上半年的写的一篇文章,迁移过来的,还可以简单凑合看,有产品有技术吧,新的系列正在更新中。

这是一篇大长文,是对我上份工作的总结,对主要工作内容的总结、也是对 “2020技术驿站” 补交的作业,望前东家前团队能更好,也祝愿自己在新的环境中能继续满心欢喜的前行。

18年参加工作,到今年3月底离职,一共历经了1年8个月的时间,开始的半年做广告相关的,之后到离职一直是在做营销活动。

第一段工作经历的飞速成长期就发生在这段做营销的时间。运气比较好,遇见了一个很棒的团队,很棒的leader和师父,也有幸一开始就参与了千万峰值的项目,虽然到我们的模块没有那么高,但也是整个项目的规模和量级也是业内很难经历的,也正是这些东西让我发现了把一件事做好很难,想做好一件事对自己技术底蕴、技术深度;广度的要求都非常的高。

然后请大家原谅我本文对营销活动类系统的以偏概全,内容仅仅是我对营销及营销系统的认知及技术方面的相关积累。

一、何为营销

入职的第一天,老板就跟我说,我们是C端的营销,但是还挺懵懂的,对营销基本是一无所知。在我印象中营销就是销售人员的各种推销说辞和各种app 上的push,直到现在才有了一点基础的认知,当然啦,一年多的经历,我的见解也大概率是片面的,大家就这么一看。

指企业发现或发掘准消费者需求,让消费者了解该产品进而购买该产品的过程。

营销学关于企业如何发现、创造和交付价值以满足一定目标市场的需求,同时获取利润的学科。

上面是对营销常见的两个定义,个人而言比较认可的是第二种。以市场需求为目标,发现、创造和交付价值,从而获取利润。市场需求实际上是公司的业务范畴,发现、创造和交付价值通常是挖掘用户需求并刺激使用的过程。而作为营销人员的我们要做的就是根据公司业务范畴,通过某些形式进行获客;经营,刺激用户使其发生交易行为,从而产生利润。

这里的某些形式,也就是我们核心的关注点了。

二、无法吃透所有领域的营销

营销的业务场景十分的多,某两大手机厂商门前两个卡通人物的互动、电视上插播的洗发水广告、春晚的摇一摇、朋友圈的明星推广、购物支付时的返现和优惠券,这些都是在不同领域下的不同形式的营销手段。

而每个领域都有自己专属的特点,我们无法照搬或者复制方案,需要具体业务具体分析。

至于本文要讲的线上营销,毫不夸张的说互联网目前涉及到哪些领域,就有哪些领域的营销,电商、医疗、教育、金融、游戏;文娱等等。每个领域都有自己特有的盈利方式,面向人群的行为属性差异也非常大,这就导致营销的方式都差异较大。

我们需要做的实际上是:掌握营销的核心思想;本质,洞察业务特点以此构建营销模型,对营销模型进行多样化的落地实现,从而最大力度刺激用户。

对我个人而言,接触也相对较窄,除了自己亲自实践的信贷领域,其他的也都是来自间接经验,但不妨碍对营销有一个较深刻的理解。

三、线上营销的落地

上面提到了“通过某些形式进行获客;经营”、“构建营销模型,对营销模型进行多样化的落地实现”,对于互联网行业来说落地的方式有很多,常见的有广告营销、营销活动等,通常来说这些都是柔和在一起进行的,比如投放一个营销活动的广告。

广告方面就是在信息流中根据不同的人群属性投放对应的物料,或者通过有影响力的事或人代言产品并在高曝光的场合出现,对于信息流来说就是买流量,通过自己的DSP系统或者第三方流量买卖系统进行竞价投放。

营销活动通常是结合产品流程,以“小游戏”、“优惠刺激”等方式,利用人们的贪心/好奇心等若干心理来构成的营销方式。大家感触最深的应该是“帮我砍一刀”吧。而这些营销活动通常是基于背后的权益系统(优惠券、红包等)、触达系统(push、短信等)、活动流程系统(规则引擎、基础活动单元等)、支撑性组件、算法平台等来共同构建出来的。

而面向活动的营销系统,就是本文要阐述的重点了。

四、详细说说活动

这里的活动具体指的就是上面所提到的营销活动。

活动实质上就是在时间、场景、成本限制下,对不同用户根据不同规则进行权益投放,以达到获客、经营、品牌传播等目的最终构成交易的行为。

虽然最终目的都是赚钱,但中间阶段的目标是不同的(对于打工仔来说就是KPI不同),也就促成了各种各样的活动形式:

通常有离线触达类刺激复购和获客、常规流程内补贴提高交易欲望、周期性“游戏”类提升用户活跃度的经营or获客目的、大型营销活动的品牌宣传获客。

2. 常规离线触达活动

对于常规的离线触达类活动,目的基本是刺激复购或获客。

这种活动通常分为两类:

(1)干巴巴的文字游戏

这种活动形式较为简单,一般通过“诱惑性“、”误导性“,利用人们的”贪心“心理玩文字游戏,进行获客和经营行为,说实话就是一种合法的”短信诈骗“,通常是广撒网的形式。

随着各种营销短信的爆炸式增长,这种方式基本没有太大的效果,但碍于成本低基数大,也是一种常规存在的形式。

(2)湿漉漉的文字游戏

这部分通常是真正的结合一些优惠活动,直接给予用户一些优惠券,或者引导用户参与活动。这种形式通常为流程内补贴或者游戏类活动的附属子活动,作为宣传的推销的作用。

这种活动其中涉及的到的技术:(后面会详细的进行介绍)

  • 触达平台:通常包含文案模版的配置管理、内容发送控制等(主要为对接各大电信运营商平台、内部push平台、公众号平台等)
  • 算法平台:稍微高级一点的触达平台定义好模版后,有一些关键字和语句是可以通用户喜好进行填充的,效果更佳。
  • 短链服务:一个1k的链接毫无疑问是不适合进行投放的,大多是通过短链服务进行链接的精简来进行投放。

3. 常规产品流程内补贴

我们的产品,从用户遇见,到开始使用,再到发生交易通常是拥有一个漫长的流程的。

拿购物来讲:

看到某app的广告,不小心点击去,下载、注册登陆、点击产品、加入购物车、提交订单、绑定银行卡、唤起收银台、完成支付、点击评价、后期查看。

每一个环节都可能面临用户的流失,尤其是前几步代价较高的;用户不熟悉的阶段,毫无疑问在获客之后,如何推进流程、如何促使复购,每一个环节都是不可忽略的,所以我们通常需要对每个节点进行刺激,刺激的大了成本不够、刺激的小没有用、不对口味没有用。

这就要求我们需要建立在每一个操作环节建立营销行为,并且根据环节特点和不同人群定制营销手段和激励措施。比如说“绑卡返现”、“购物给券”、“登陆有礼”、“再购更便宜”等很多恶心的措施。

对于我们系统而言,产品流程高度业务可配变的十分的重要,对应背后的流程编排引擎要求较高。

对营销系统而言,需要能简单配置可上线的简单激励活动,背后需要更多的活动单元、更灵活的规则引擎。

4. 周期性“游戏”类活动

这类活动较前几种而言,活动的存在感更强了,有用户感知更深刻的活动形式,比如说邀请得奖励、购物得抽奖机会、集卡赢大奖等等。

这类活动一般有一整套的活动规则,也会有大部分用户乐在其中“薅羊毛”(这类活动有羊毛,但是实际上不多),通常是小恩小惠用于提升产品活跃度的,像是分享、邀请等功能通常也会有一定的获客效果。

这类活动的活动形式:

闯关小游戏、摇一摇、红包雨抽奖、集卡、战队比拼等,对于我们的系统而言对应的通常是:任务系统、签到系统、抽奖系统、邀请关系系统、用户代币系统、价值交换系统等基础活动单元。偶尔会加上活动编排引擎涉及1-2个基础单元等。

5. 大型营销活动

这种活动重在品牌推广和新业务获客,最常见的有央视春晚历年的活动、某热播节日活动、自造节大促活动等,这个大家应该都有所感知,也都没少剁手也没少转发。当然啦,这类活动是最适合薅羊毛的,通常来说是真有东西的,有一点技术基础的同学现在就可以动手啦,马甲号用起来积少成多~

从技术角度来看,这类活动都较为复杂,针对预热期、白热期、冷却器都有不同的活动形式存在,但这些形式是作为一个活动整体存在的。通常能把我们现有组件的能力都给用上,所以就不一一列举可能用到的系统啦。

在技术特点上具有:活动逻辑复杂、性能要求较高、资损风险较大、数据分析困难等特点。

这类活动是对于营销系统最大的挑战,很多的降级措施、应急预案、性能调优其实都是对这类活动特殊准备的。

五、营销系统设计的问题域

1. 需求特点

上面着重说了常见的营销活动类型,粗略介绍了面向的场景。如果大家经常使用一个产品或者关注一个产品,就会发现各类营销活动层出不穷,每个细节都是营销的样子,几乎每天看都会有新的不同。

这种直观感受侧面反映出营销活动相关需求的特点:

  1. 需求量无比巨大,且多变
  2. 时间紧急,倒排需求通常占80%以上

面对这样的情况,增加人力;临时借调,是一种解决方案,但也只能算是下策,不可能为营销投入50%以上的人力(但是我感觉pdd可能是50%以上,也可能是营销系统已经非常成熟),临时借调研发效率、质量等都有不小的风险。

作为一个技术人员,我们要做的就是“以技术手段解决工程效率问题”,这就要求我们能需要做一种功能丰富且更加灵活易用营销系统,来达到小需求简单配置可上线,大活动释放降低80%以上的人力成本的目的。

2. 技术特点

(1)性能要求较高(成败往往就在峰值)

营销活动的访问量通常较大,而且峰值突出。拿春晚活动、娱乐节目来说,通常是伴随口播等引导动作带领用户参与的,80%的用户都会集中在那一分钟左右涌入,而只是依赖于加机器是不现实的,降级限流等只会白白浪费流量并且造成负面营销,营销大促场景下,我们需要的就是抗。

3. 安全及风险

上面频频提到了薅羊毛,这其实就是营销系统面临的非常重要的一个问题:资损。

资损的来源通常有活动设计、代码实现、羊毛党的大量存在等多方面的原因,尤其是在这样的需求特点及大环境因素下,营销系统是一个极易发生资损的点。

下面就来看一下是如何用技术的手段解决上述问题,及其中核心系统;组件的实现原理。

六、我认为营销系统的样子

先看一下上面提到的两个概念或者说实体:

  • 营销:营销学关于企业如何发现、创造和交付价值以满足一定目标市场的需求,同时获取利润的学科。
  • 活动:在时间、场景、成本限制下,对不同用户根据不同规则进行权益投放,以达到获客、经营、品牌传播等目的最终构成交易的行为。

然后结合业务领域,根据营销场景下的问题域(现在;将来),通过技术手段解决效率;质量问题,以此最终确定系统的职责、定位及内部架构。

这一板块的内容十分庞大。

1. 聊一下方案选择

对于方案的选择通常有这么几个标准:人力成本研发质量成本研发效率成本机器成本。

一个系统方案的实现,最优的是人力、机器投入较少的情况下,能够确保质量的最快支持需求。

对于营销类系统来说就是:

建设非常通用且高性能的基础设施组件,实现相对通用的系统支持常规迭代,紧急且重大的需求依赖基础设施进行特化实现。

像支付域、广告域的这些系统,面对的需求虽然也不少但是主要逻辑是确定的,一般都是对链路、功能进行丰富和优化。而营销场景下的需求讲真的,千奇百怪,除了上述所说的一些固定类型的活动形式及基础的活动单元,很多时候伴随运营的临时且全新的想法,我们需要面临决策,全新临时方案 or 通用解决方案的抉择。

通用的系统这个大家都能接受,但对于当前所要实现的逻辑显然不能复用的情况,我的建议是采用全新的临时方案仅依赖基础组件,新的数据存储、新的server,一切只为解决当前临时需求。因为就风险和成本来说,实现一个临时方案相对于改一个相对复杂的系统要简单的多,并且风险完全收敛,我们也能集中精力解决当前问题,而不是竭尽所能把系统建设的啥都可配制化,看似系统牛逼,但为了暂时的需求,投入产出比太低。

营销场景下永远都有临时且重大的需求,我们要做的是成本投入较小的情况下快速迭代。

2. 营销系统架构

系统功能:

  1. 对业务系统或直接对用户提供营销能力输出
  2. 向业务方提供营销业务的配置化能力
  3. 提供效果洞察、成本、资损监控能力

理想目标:

常规活动配置化可上线,大型活动节省80%以上成本。

活动效果可洞察,资损及故障易发现。

系统架构:

活动流程层主要为构建实现营销活动流程实现能力的标准化输出,以活动单元能力或活动组件基础能力为原子单位,进行活动流程编排。

  • 活动单元层:提供常用营销活动的能力(比如抽奖、秒杀、邀请),每个单元面向一类营销形式,对外暴露标准接口。内部依赖于活动组件。
  • 活动组件:提供标准的单一的营销功能(签到、分享)或业务能力(价值交换、标签输出)或系统能力(调度组件、权益高效发放)等。
  • 权益系统:主要包含业务场景下内部权益、现金权益、外部权益,提供权益的实际发放、核销能力。
  • 数据处理:主要针对数据库(mysql、redis)、日志数据等数据进行标准化处理,为监控、数据一致性提供数据源
  • 监控方面:对业务方提供可视化、标准化的效果展示,及资损监控;系统监控等能力。
  • 对账补单:提供数据一致性的最后一层保障。

3. 常用组件

要开始深入技术细节啦,这块内容背后的知识体系基本就是我这1年8个月的基础技术能力的成长了,选几个具有代表性的组件来说。

(1)唯一单号服务

这个组件非常非常重要,所以会说的相对仔细。

在繁多的业务场景下n多系统的交互场景下,我们通常需要一个唯一id来作幂等处理,同时海量的数据(DB、log等)我们也需要一个唯一单号作为线索完成高效的数据分析工作,所以说每个请求、每个环节我们都是需要唯一id的。

看到上述背景,不难想到唯一单号服务所面临的核心问题

功能性要求:

  • 唯一性保障
  • 业务相关性且安全

系统要求:

  • 高性能
  • 高可用

这四点即是难点,也是唯一单号的特征。

唯一单号服务的实现方案有很多种,最常见的有自增键、UUID、雪花算法、Leaf-segment三种方案。

(2)自增键

利用自增键是最容易想到的一种方式,实现方式通常有mysql主键自增、redis incr。但这两种方式很显然都会存在单点的问题,而且自增主键性能受限、redis 易发生数据丢失(性能只是相对mysql更好),但实现大学作业的时候还是可以用的,真实生产环境是肯定不会选用的。

(3)uuid

UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx的36个字符,具体标准可见:http://www.ietf.org/rfc/rfc4122.txt(墙),目前业界有5种常用的生成uuid的方式。

这种方式优点是性能高,纯本地生成,无网络IO发生。

缺点也很突出:

存储方面:

  1. 不易存储(实在太长了)
  2. 这类id由于通常要落库并挂索引,对于mysql来讲,UUID的无序性会使数据位置频繁变动,严重影响性能,尤其是作为主键的情况下(主键越短越好)

业务相关安全性:

  1. 最常用的基于mac地址生成的方式会暴露mac。
  2. 无任何业务标示,排查问题和统计时十分不便。

(4)雪花算法

雪花算法最初是推特用于标记消息的,跟uuid结构类似,通过划分命名空间实现,基于时间戳+机器码+业务标示实现,这种方式极易横向扩展,由于是本地生成,且每台机器码值都不同,可用性和性能都得到了保证。并且灵活的结构添加业务标示也变的轻松了许多。

实现方式与uuid相似,虽然相对提及会小一些,存储方面的问题稍微得到缓解。

但是有一个很严重的风险点,一旦发生时钟回拨,那就很惨了,mongoDB使用的就是这样方式。

(5)Leaf-segment

这种方式是我除雪花算法外最看好的实现方式,依赖于mysql存储保证数据不丢,并且利用行锁保证单调递增,一张表维护多个活动的id,通过异步化的方式批量批发单号对外提供服务保证性能,多个单号批发商对外提供服务并容忍单号浪费以此保证可用性。

这段话可能说不明白直接看代码和图:

`activity_id` bigint(20) NOT NULL AUTO_INCREMENT,
`activity_name` varchar(512) NOT NULL,
`sn` bigint(20) NOT NULL DEFAULT ‘0’,
`step` bigint(20) NOT NULL DEFAULT ‘10000’,
/**
* 每次批发时:
* 1、返回sn ~ maxSn(sn + step) (就是所批发走的号段)
* 2、sn = sn + step + 1
* 提供服务时:
* 内存中输出小于maxSn的sn,并对sn++即可
**/

mysql可以只用一个,通常来说内存中大量的号段足以支撑mysql恢复,如果想用多个mysql同样需要进行划分命名空间。

(6)规则引擎

在任何配置化的场景下规则引擎都是核心的存在,通常来说表达式在可视化的配置化后台,结合配置模版+配置元数据编译成“规则”,规则在执行时被翻译成表达式或者执行从而达到业务逻辑可编排,决策逻辑可配制的作用。

规则引擎一抓一大把,大家自行百度即可。

基础的规则引擎中表达式的生成与解释也是营销场景非常常用的,比如波兰表达式等,涉及标签的地方就大概率涉及波兰表达式。

(7)价值交换组件

这里的价值交换组件指的是营销场景下,营销活动系统中各种“积分”、“代币”、“机会”等价值载体的交换体系,不同的活动单元中所使用的价值载体是不同的,要串联几个组件完成整个“大活动”的正常运作,出上层的规则引擎外,更重要的就是不同价值载体之间的转换,比如说“抽奖机会”<——>”任务积分”<——>”权益”。

价值交换组件的场景更像是一种存在汇率的转账系统(但是相对转账来说,要简单的多,通常是单向并不可退的,仅仅作为系统逻辑转换的内部存在)。

可以简单看一下价值交换组件的内部实现:

cache、异步记账等存粹为了性能,其中最核心的点就是数据一致性问题,这个点会在后面单独介绍。

(8)用户标签体系

用户标签通常分为用户实时业务状态标签、用户画像特征标签两种。实时业务特征通常来源于实时业务系统的接口或者DB,而用户画像特征标签这个通常是算法平台提供,用户标签所要承受的qps数是除唯一单号服务外最大的,所以通常性能要求高(承载qps量大、耗时短),但可用性要求倒是没那么高。(一个合理的系统,在不知道用户信息的情况下也应该能正常作出兜底反应的)

通常来说,我认为一个合理的在线标签系统是这样的:

单独具有流量控制功能,保证大部分流量可用,降级掉一部分请求,对常用的标签进行缓存控制<标签key,用户id,统一有效时间>,根据需要获取可接受时间范围内用户标签。

未命中缓存的标签,交由标签任务管理器进行处理,决定单个标签处理最大时长、标签并行;串行处理机制及处理顺序、处理任务调度等,底层维护并发模型。(线程数、任务存放机制等)

再下一层,维护对外部接口、业务数据或接口、用户画像数据的处理单元。

(9)短链服务

上文在触达单元中就提到了短链服务,首先业务常见、技术特点鲜明,所以单独拿出来说了。

短链服务的应用场景非常多,除了短信中的链接,平日里的链接分享、网页分享、二维码分享其实斗志这种方式,只是表现形式不同而已。

短链接实现思路很简单:

找一个短链接试试看:3.cn/j/10nd-pE9

至于这里为什么用的是302而不是301,是因为临时重定向时能够统计到短链接点击次数,通常也会被用作效果分析。用301永久重定向也是可以的。

至于后面“10nd-pE9”这个码,通常被称为短码,生成方式有很多种,比如上面提到的唯一单号,还有就是摘要法(存在小概率重复的可能性,但能够保证链接够短)。

如果是小型企业大可以利用现成的短链服务提供商,比如百度的:https://dwz.cn/

4. 基础活动单元

抽奖活动单元将常见的活动形式进行了一定程度上的抽象,做了通用化处理,以应对其高频的需求。这里拿几个经典的活动形式进行阐述。
活动单元的建立,可以根据具体的业务场景下的各种运营的需求来进行拆分抽象。

(1)谈谈抽奖

拿抽奖来说,摇一摇是抽奖,九宫格转盘是抽奖,n选一砸金蛋也是抽奖,大家点的火热朝天的红包雨其实也是抽奖,这类抽奖活动对用户活跃度非常有帮助,用户分享意愿也很强,基本是大型活动每次必选的组成部分。所以就完全可以对其进行抽奖建立一个通用的抽奖模型。

整体看下来一个抽奖系统是这样的:

其中的核心组件有:

1、概率模型

用户控制用户中奖的概率,实现方式很简单:

(1)确定可抽奖总区间,确定奖品区间段,一次性进行抽奖。

(2)Alias Method

可以直接看这一篇实现:http://www.keithschwarz.com/darts-dice-coins/

大致意思:把N种可能性拼装成一个方形(整体),分成N列,每列高度为1且最多两种可能性,可能性抽象为某种颜色,即每列最多有两种颜色,且第n列中必有第n种可能性,这里将第n种可能性称为原色。 想象抛出一个硬币,会落在其中一列,并且是落在列上的一种颜色。这样就得到两个数组:一个记录落在原色的概率是多少,记为Prob数组,另一个记录列上非原色的颜色名称,记为Alias数组,若该列只有原色则记为null。

2、抽奖机会控制

提供用户可抽奖机会的控制,是一种价值的载体。抽奖系统对外暴露次数增加接口,内部抽奖行为消耗抽奖次数。

3、奖池控制

每个用户群面向自己的奖品集合,高价值的用户或者潜在用户面向价值较高的奖品,风险用户或者下沉用户面向低价值的优惠券等,就是这么黑。

这里通常依赖于表达式引擎,根据标签计算用户所选奖池。

4、中奖限制

曾经的我也以为奖可以随便中,除了面向奖品集合之外,通常对用户会额外加n多限制条件。

比如说,a奖品最多中2次,b奖品最价值不能超过2元,命中iphone大奖后其他不能再中大奖并且其他奖品概率下降。

5、库存控制

所有的奖品都是有成本预算的,不能无限制发放,这就要求对奖品进行库存控制。

这里有一个设计的思路,分别设置库存供应量、库存消耗量,剩余库存=库存供应量-库存消耗量,这样相对于拿一个数值表示库存进行增减操作要灵活的多并且安全很多,尤其适合在库存增加时。

另外,库存的消耗操作很显然是一个存在竞态条件的复合操作,我们需要保证其原子性,可以利用上锁等措施串行化,也可以利用现有工具的原子能力(比如reids 操作的原子性:incrby、lua等。

(2)裂变营销

这里还有一个值得提的是列表营销,当下正火的营销方式,最经典的莫过于“帮我砍一刀”。

分开来看,营销上面说过了,裂变概括来说,按生物学来说是细胞核的裂变,由一个裂变成2个,2个裂变成4个。换作人来说,就是当前用户是否能够帮忙获客。

对于裂变营销而言,通常包含三个环节,设计裂变流程,裂变流程变为转化漏斗,漏斗分析。

裂变营销重在逻辑,性能等要求一般不会很高。

对于系统设计而言有几块格外的重要:

1、底层邀请关系组件

裂变营销往往是从人出发,以人结束,造出一颗颗庞大的树,所以我们需要维护整个树的结构,不仅仅是为了存储活动的邀请记录供激励使用,对后期判断“散点”也是尤为重要的。

2、标签组件

活动的重心在于拉人,什么样的人能拉人,是相当重要的,要建立与裂变营销关联性很强的人群画像标签十分重要。

这块了解的不多,暂时就这些~

七、数据的一致性处理

数据一致性一直是各种系统很头痛的点,尤其是分布式系统越来越昌盛的今天。数据一致性也存在cap、base这样的著名的理论,实际上平衡一致性与性能、可用性等特性是需要根据产品特点来做的,像跟钱相关的一致性要求会非常的高(不能出错),积分数量这种虚拟权益类其次,签到记录等用户就不太关心了。

1. 营销场景下的一致性要求

营销场景的数据一致性要求其实是相对宽松的,只需要在较短的时间内保证最终一致就可以了。

拿抽奖活动举例,一次抽奖行为的发生,涉及到次数减扣、库存减扣、抽奖记录更新、奖品发放等一系列的操作,很有可能会发生部分失败,按照常理来说像奖品发放失败这样的情况,短时间未到账用户大概率也是无感知的,通常需要绕道卡券包等,所以这种情况我们只需要一定时间段内给用户补上即可,需要保证的主要是和成本密切相关的次数、库存等。

当然啦,一致性要求虽然没有那么高,但是还是要尽可能照顾到的,没有数据一致性的保证,性能、可能性再高的系统可能都是一堆垃圾。

2. 常见的一致性实现方案

先来看一下什么是数据一致性:数据的变更,与现实世界变更的预期结果保持一致。

后来在这个基础上延伸出:

(1)各种一致性4

  • 强一致性(也称线性一致性): 任何一次读都能读到某个数据的最近一次写的数据,系统中的所有进程,看到的操作顺序,都和全局时钟下的顺序一致。
  • 弱一致性:用户读到某一操作对系统特定数据的更新需要一段时间,我们称这段时间为“不一致性窗口”。
  • 最终一致性:是弱一致性的一种特例,保证用户最终能够读取到某操作对系统特定数据的更新。
  • 顺序一致性:任何一次读都能读到某个数据的最近一次写的数据,系统的所有进程的顺序一致,而且是合理的。(相对于强一致性少了系统时钟)

事务的诞生,很大程度上就是为了保证数据的一致性。

(2)基础理论

  • cap:指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)只能满足两其中两点。
  • base:BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩
  • 写。BASE理论是对CAP中AP的一个扩展,保证可用性和分区容错的情况下,允许存在软状态,但会保证最终一致性。

(3)两阶段提交

这部分理论知识蛮陈旧的就不展开说了,大体能懂就好:二阶段提交的算法思路可以概括为:

参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。

两阶段的缺点:

  1. 同步阻塞问题。
  2. 协调者的单点故障问题。
  3. 第二阶段执行中,协调者与参与者共同挂掉的场景导致不一致。

(4)三阶段提交

三阶段提交协议在协调者和参与者中都引入超时机制,并且把两阶段提交协议的第一个阶段分成了两步,引入了中间状态: 询问,然后再锁资源,最后真正提交。

解决了“同步阻塞”、”单点故障“等小缺点,及二阶段无法解决的“协调者与参与者共同挂掉的场景导致不一致”这个很重要的问题。

说实话不管是两阶段或者三阶段,都无法彻底解决一致性问题。

(5)刚性及柔性事务

  • 刚性事务:具备我们熟知的的ACID的特性的事务,通常的实现有WAL(white ahead log)(这是目前市面上几乎所有数据库的实现方式),还有一种是影子分页。
  • 柔性事务:基于base理论实现的追求最终一致性的事务模型,通常实现方式包括TCC补偿性事务、异步确保型、最大努力型。

tcc分布式事务是一种二阶段的变种,将操作分为尝试、提交、撤销,是一种实时链路上的的操作行为。大致模型是这样的:事务发起者发起一个事务,参与者进行事务的提交操作,如果其中几个参与者本地事务失败,会告知失败,其他已提交的参与者事务进行补偿操作进行回滚。

异步确保型事务则为将同步阻塞的事务操作变为一步操作,避免对数据库事务的争用,对操作进行汇总处理,通常对于热点资源的操作采用这种方式。

最大努力型事务,依赖于补单消息的补偿机制或者重试机制,对事务进行最大程度的推进,以此保证最终一致性。

(6)营销场景下的数据一致性设计

首先看完上述的上面各种的一致性定义和分布式事务实现可以发现,数据的一致性实现是比较困难的,也是业界一直在寻求最优解的问题之一,但就目前而言,实时链路上的各种分布式事务能保证一定程度上的数据一致性,而且通常来说只是局部某个环节(不可能把所有事情都扔进事务里面),并且还会有一定的瑕疵。

对于整个业务逻辑推进中数据一致性的根本保证,还是最原始的:“建立业务流转的状态机,依赖于唯一单号,异步对账补单,达到最终一致性”。

对于营销场景下,性能要求较高,但一致性要求没有那么强。

建议的实现方式有两种:

  1. 核心操作采用TCC事务模型实现,保证核心链路上核心数据的一致性,对于附属操作可通过异步对账补单保证最终的一致性。
  2. 实时链路不做分布式事务处理,完全依赖于对账补单实现。

每种方式都有自己的优缺点,第一种方式所需的对账补单量级较小,常见的掉单失败等在实时主链路上就给干掉了,对于数据不一致引发的问题的影响时长会减少,但是引入了TCC事务,增加了三方协调和更多的网络IO,数据操作逻辑的复杂度会直线上升,性能会有一定程度的下降,可能会产生新的风险和问题。

而第二种方案,主链路代码精炼,性能会很好,但对于数据不一致引发的问题的影响时长会较长,并且对账补单系统规模会变的庞大,要处理的任务也会变多。

既然各有优缺点,我们就需要根据不同营销场景来选择,像是营销组件完全是组件内操作本地事务或者不需要事务即可解决,对于常规活动依赖于活动单元组件这些建议使用第一种方案,直接建立较为完备的措施,一劳永逸。而像是性能要求非常高并且逻辑相对简单的临时活动,第二种方案就足够了,释放出人力处理性能优化。像是非常大型的营销活动,就需要根据具体场景来衡量了,数据一致性可能产生的影响面,性能、可用性、一致性的折中。

这块内容没有标准答案,包括了解的各种业务场景都是这样的,数据一致性通常需要根据场景定制化开发,并不存在通用解决方案。而我们需要做的就是积累一致性处理经验和数据一致性保证的可选方案(除了TCC这些,业界存在很多解决方案,除了TCC这种还有事务型消息、本地消息表等,但是都逃不出上面的那一堆概念的基础理论知识)。

八、我所接触过的性能优化

营销场景下性能要求通常是比较高的,尤其是非常规的面对大促的营销活动,而且性能优化中涉及到的技术相关的点会非常之多,常用的软件研发工具的选择、高性能系统的架构模型、语言的选择、底层的网络原理、代码编写上比如并发模型的优化、语言底层的优化等等。

除了优化的具体行为,我们还需要有科学的性能衡量标准,高效的检验能力。

总结来说性能优化的策略:

在最开始的时候先说结论,给出一个性能优化的顺序:

  1. 确定合理的架构方案(最大程度上决定了我们的系统能扛多少)
  2. 确定技术选型(包括语言、工具,对内存、epoll的高效利用几乎是现在性能工具选型的根本)
  3. 代码逻辑优化(很多时候性能优化就是砍IO,少计算,上缓存,异步化,减少锁,还有工具的正确使用)
  4. 最后才是工具、语言的底层调优(这一步很多时候根本不需要,但是咱们得知道)

技术不是炫技,最小成本把事做成就好。

1. 什么是高性能

衡量性的性能的标准通常有很多,比如常见的平均响应时间、最大并发数等,其实最核心的就是吞吐量,单位时间、单位资源内,系统所能支持的请求处理数或者事务处理数。优化最大并发数、缩短响应时间最终目标都是吞吐量。

高性能mysql一书在第二、三章一开始的位置,就先说的衡量标准和测试方式,这点是非常赞同的,因为只有明确了什么是高性能,才能进行合理的设计系统,进行调优及压测,非常推荐大家去看一下那两章。

2. 如何合理的压测

除了评定标准,在优化的具体行为之前最应该熟悉的就是如何压测,如何高效的压测。

对于压测而言重要的有这么几点:

(1)重中之重,要有合理的压测方案和周知下游、qa、op,也就是下面的说的几点,切勿啥都不准备上来就开干。

(2)数据完全隔离

如果想切实的了解系统在生产环境的抗压能力,那就必须要在生产环境进行压测,那么数据隔离是首要的,不能影响线上不能因为压测产生故障或者资损,隔离的方式很简单:用户id区分。

我们可以使用现在没有在用及近一段时间也不会有的用户账号进行压测,但是这样需要面临清理数据(一个相当头疼的活)打压测表示,这需要我们在系统设计时就需要做到的,每个系统都要天然的支持压测。

第二种方式无疑是日常压测中最合适的,也是系统设计时需要考虑的。

(3)灵活高效的压测系统,需要qps实时可控,要有红线触发机制,比如说压测前确定最大值,压测qps调节过程无论如何都不能超过预值。

对于流量控制,也就是qps的调控所依赖的算法其实很简单,就是限流算法的变相使用,常见的有计数器控制法、令牌通控制法、漏桶控制,并且基于滑动窗口对于流量进行整形,保证均匀,不出现过高的瞬发峰值,这部分内容之前单独写过,可以看一下Go系列文章中限流算法实战,还有高性能系统中的限流算法原理。

(4)压测的核心关注点

压测过程中需要有合理的指标:

关于你的服务:cpu(cpu idel、user、cpu.load)、内存(这个通常来说问题不大),剩下的你需要看下游系统的压力,切勿把下游打挂。

除此之外,压测的目的是发现问题解决问题,而不是仅仅是测试系统能扛多少,所以一定要关注核心依赖,比如说mysql、redis等,发现各种问题才能避免线上挂的惨,大多时候通常是代码写的有问题,但是在三方应用上暴露出来,比如说hgetall。但是有时候线上机器也是可能存在问题的,需要压测来检验。

(5)合理的压测过程

压测需要循序渐进的来,有问题能及时发现,并且一上来流量太大高估了自己的系统,可能瞬间打挂。

压测的流量规律需要根据线上真实流量的变化趋势,或者预测趋势来进行压测,不能想当然。建议对线上流量进行复制,以供压测使用,这样压测的链路才是真实的。

(6)完整的留存

压测数据完整留存、压测日志完整留存、火焰图记录完整留存。

这一块儿是关键也是我们压测的最终产出,发现那些地方存在问题或风险之后,我们需要根据这些来具体分析,以此得出优化结论。

3. 高性能技术选型

曾经见到这样一句话,大概是这样的don’t do it,cache it,asyn it,pipline it。其实性能优化就是这样,尤其是对于高性能业务系统的设计而言。

(1)don’t do it

对于主链路而言,是否存在可以不做的的事情,如果有那就砍掉,额外的IO和计算只会导致性能的下降。

(2)cache it ; asyn it

如果没办法不做是否可以直接缓存结果及异步处理。但是如果使用缓存和异步处理,会面临非常的大的数据一致性问题,尤其是缓存。

(3)pipline it

对于无法异步化和缓存的IO处理,直接建议同一个链接pipline批量处理。

通常来说,高性能系统其实就是基于上面的几个思想,结合业务场景给出解决方案。

(4)高性能工具的最大化利用

我们开发时毫无疑问会依赖于一些开源的或者内部的工具,比如说redis、nginx、mysql、kafka等,想要提升性能要做的第一件事儿就是结合业务场景对这些工具充分利用,下面详细来看。(由于篇幅过长,就仅仅只说一下nginx和redis吧)

nginx + lua5:

对于营销系统而言,很多时候性能要求苛刻,但是逻辑相对简单,这种时候就完全可以采用nginx+lua构建openresty应用,迅速解决问题。还有逻辑不太变的接入层,也非常适合这种方式。

nginx作为一个高性能的http和反向代理web服务器,不同于 Apache的一点就是,Nginx采用单线程,非阻塞,异步IO的工作模型。整体来说就是对epoll的充分使用,用它来作为第一层承接流量再合适不过。

这里要说的不仅是nginnx,更多的是配合lua的使用模式,Lua是一种轻量级、可嵌入式的脚本语言,在nginx之上运行lua脚本来实现相对简单的接入层逻辑或者部分业务逻辑。

市面上常见的方式是由章亦春将Lua和Nginx粘合的ngx_lua模块,并且将Nginx核心、LuaJIT、ngx_lua模块、许多有用的Lua库和常用的第三方Nginx模块组合在一起成为OpenResty,使用Lua编写脚本,然后部署到Nginx Web容器中运行。从而非常轻松就能开发出高性能的Web服务。

其中ngx_lua是Nginx的一个模块,将Lua嵌入到Nginx中,从而可以使用Lua来编写脚本,部署到Nginx中运行,即Nginx变成了一个Web容器。

常见的lua工具库有:

luarestymemcached
luarestymysql
luarestyredis
luarestydns
luarestylimittraffic
luarestytemplate

对于nginx+lua的使用方式通常有:

  1. 最基础的负载均衡
  2. 单机闭环,利用lua实现nginx-lua-static-merger,直接在接入层进行本机静态资源读取完成静态资源合并。
  3. 分布式闭环,解决单机闭环中数据不一致问题(分布式集中存储)、存储瓶颈(实现分片逻辑)
  4. 作为接入网关使用,比如说过滤请求、防DDOS、Nginx Proxy Cache完成缓存、更细粒度的限流、降级预案等

在极端的性能场景下,nginx+lua完全可以实现业务逻辑,插入数据到redis中后续异步处理,性能非常之高。

至于其中的原理可以看下,我之前的一篇文章《高并发 Nginx + lua是如何抗住的》,其核心就是基于epoll实现的单线程处理多链接的思路。

Redis + lua:

上面说的是nginx+lua,这一篇幅要讲的是redis+lua,但是需要注意的一点是,redis+lua的性能其实一般,这一点要格外谨慎,我们要利用的是其操作的原子性。

我们想要进行高性能的数据操作时,很多时候都会直接选用redis做存储或者缓存,但是依赖于redis数据做操作但涉及具有竞态条件的复合操作时就会比较麻烦,大概率会涉及分布式锁,其实redis+lua就是一种变通的方式,将密集的redis操作更装成lua脚本,这样既解决了原子性问题,又把这部分逻辑变的可插拔(据说游戏领域经常会存在这样的动态逻辑替换)

但是切记不要在lua中实现过多复杂的逻辑,尤其像是各种json处理,会导致redis性能急剧下降。

redis+pipline:

对于redis性能的充分利用,正确的打开方式是redis+pipline,一次性灌命令的方式实现redis若干逻辑处理,其他工具也是一样的。

在redis+pipline编码时需要注意,pipeline 期间将“独占”链接,此期间将不能进行非“管道”类型的其他操作,直到 pipeline 关闭;如果你的 pipeline的指令集通常比较庞大,为了不干扰链接中的其他操作,可以为pipeline操作新建链接,让pipeline和其他正常操作分离。还要注意pipeline所能容忍的操作个数,也就是socket-output缓冲区大小/返回结果的数据尺寸,这些是受限于server的物理内存或网络接口的缓冲能力。

这一块说白了就是网络连接的高效利用,最基础的手段是使用短链接一个请求一个链接干到底或者使用连接池+长链接,而pipline就是在这基础上,进一步利用缓冲队列,对命令进行一次性传输和结果的一次性返回,kafka所谓的微批处理其实也是这种实现思路。如果仔细看各种工具对于网络IO的优化思路,几乎都是这样的策略–减少网络IO的产生并且充分利用epoll,我们业务系统优化的思路毫无疑问也是这样的。

redis的集群方案:

对于redis的充分利用还有很重要的一点是其集群方案,一般就是两种类codis方案、官方的cluster方案,这些都是从单点到主从再到主动+哨兵一步步演变过来的,主要一步步解决了单点、可用性、吞吐量等问题。

简单的看一下这两种方案的实现思路:

codis方案(中心化配置存储):

cluster方案(去中心化实现思路):

推荐这一篇博客,说的很清楚:https://www.cnblogs.com/pingyeaa/p/11294773.html

看完集群方案,其中一点非常明显,为了提高系统的吞吐量,数据是分片存储的,而分片的核心就是hashtag,hashtag是一种在我们知道redis主库选择的hash规则后,支持自主定义所存储的redis服务器的能力。

关于hashtag的使用有两点需要注意:

1、保证分片的均匀

通常来说后我们需要让每个服务器承担基本一致的流量压力,最合适的方式就是根据用户id求余(针对分片数量求余)来做分片处理,这个结论是在假设用户id是均匀的基础上成立的,如果用户id不均匀那就在id的基础上加一层hash值再做处理。

2、使用lua时的坑

在使用lua时,是根据第一个key值进行分片选择的,所以说独立原子操作时根据key值hash后选择的分片,使用lua操作会找不到,或者lua操作的key值独立的原子操作会找不到,这一点一定要注意。

分布式锁实现:

这一块按理说是一致性部分的内容,但是因为密切和性能相关,所以就在这里说吧。

分布式锁是用来保证分布式环境下数据操作的原子性的,和我们常用的锁的使用方式一样,加锁+逻辑处理+解锁。(主动解锁、超时释放)

常见的分布式锁的实现方式有三种:

  1. 基于redis的红锁实现及其简化版本
  2. 利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。
  3. Google 公司实现的粗粒度分布式锁服务,底层利用了 Paxos 一致性算法。

后面两种实现方式通常适用于较粗的锁粒度,并且具有等待队列,看起来更像是一把锁,但不太适用于高性能场景。而对于营销这样的场景来看,基于redis的分布式锁更适合。

分布式锁通常会存在以下几个问题:

  1. 锁超时后,其他请求加锁后的误释放问题。通常可以利用唯一单号来鉴别加锁者解决。
  2. 锁的可用性问题,这个问题通常发生在单点的redis服务上,红锁已经给了完整的解决方案(超过半数加锁成功),但是这种方式一定程度上损耗了性能。

需要注意的是分布式锁的实现,像分布式事务一样,不管哪种方式都存在一定的问题,比如redis的红锁,redis本人也是说存在瑕疵的,我们最需要的是根据场景来进行鉴别选择,解决痛点问题,容忍带来的其他问题。

3 关于存储

(1)存储的选择

数据的存储对所有的系统都是非常的重要,而且是重中之重,在写这块内容的十分钟之前恰巧看到了刚看了oceanbase的发展之路,颇为震撼。

对于营销场景下的存储,很显然没有金融领域那么的苛刻,但是数据的可靠存储依然十分重要,但是相对多了一些选择。

对于常规场景而言,使用基于磁盘的关系型数据库mysql就足够了,特性十分丰富,也经过了大环境的检验,但不要觉着所有的存储只能拿mysql来做。

在极端的性能场景下,我们可以稍微激进点,拿redis做存储,虽然有过redis挂掉疯狂补数据的经历,但是依然觉着在某些场景下是可行的,至少比mysql提升了近一个数量级,如果想用redis做存储,首先必须要要有预案措施也就是redis挂了怎么办,其次要有数据的落盘行为必须保证可恢复,这里比较好用的有日志留存用户相关数据快照、异步落库,在建立恢复机制后,我们就可以依赖于redis快照数据+挂掉这部分时间的日志或者数据库数据来推演出当前redis中的数据了。切记需要根据具体的业务场景来定制化选择,最痛的点及其他方面的容忍度,就算是激进也得有个度。

(2)数据拆分

在性能场景下,单mysql或者redis存储完全满足不了(查询性能(cpu瓶颈)+IO瓶颈),这适合就要面对数据的拆分,来应对CPU+IO两大问题。

而数据拆分无外乎垂直拆分、水平拆分,对于redis字典型非关系数据库而言,使用hashtag+散列的key值很显然同时做到了。

对于mysql而言其实就是分库分表。(水平+垂直)

但是在拆分过程中非常重要的几点:

  1. 分库分表,需要了解瓶颈在哪,然后才能合理地拆分,不能为了分库分表而拆分。
  2. 选key很重要,既要考虑到拆分均匀,也要考虑到非partition key的查询,可以利用上面提到的用户id或者订单id进行拆分。

另外,分裤分表会带来大量的问题,为了问题尽可能的少、系统易维护拆分规则越简单越好。

相关工具:

  • redis:codis、cluster等方式自带hashtag功能。
  • mysql:sharding-sphere、或者自定义实现。

4. 若干的语言

确定完架构方案、工具选择,剩下的就是语言和相关的优化了,在选择语言时依然是上面提到的几个标准:人力成本研发质量成本研发效率成本机器成本,具体来说:

  1. 从语言的特性出发,衡量各种成本的变化情况
  2. 从当前人员构成和公司内基础设施出发,衡量各种成本的变化情况

两者结合考虑,并根据当前项目的紧急程度确定方案的选择。如果是基础设施的建设,直接从第一点考虑就OK了。

然后下面仔细的说一下各种语言及其擅长的领域,大家准备好,可以开喷了,本章不涉及语言的开发细节,只说特性和适用程度。

(1)Java

这是我的第一门编程语言,大概是大二的时候开始接触,对于语言的熟悉发生在大三下学期和实习之后。

Java的优势很明显,生态十分庞大,性能相对优良,但极端性能场景下还是需要c系,研发效率处于中等位置,研发质量较高,并且Java相关人才比较好招。由于它的生态Java看起来适合非常多的场景:Hadoop、spring等,语言特性很多,工具库异常丰富。

对于营销系统而言,Java非常适合开发重业务的系统,比如活动单元及活动流程层,在Java的基础上我们能够把业务逻辑尽可能的抽象,对于底层细节而言,像垃圾回收(适合各种场景的垃圾回收器:G1、cms、ZGC)网络IO的处理(官方库中基于水平触发epoll的IO处理,netty中给予边缘触发)等等一切都是非常完备的,对于中规中矩的系统非常适合。

(2)Go

这是我的第二门编程语言,19年几乎一年都在写go,go是一门非常年轻的语言,看现在的发展也迅猛无比。

优点:(网上通常有各种各样的介绍,对于营销而言主要是以下两点)

  1. 天然的并发性
  2. 相对较高的开发效率

缺点也十分的明显:

  1. 生态较差,你用Java可轻松找到的工具,如果用go恐怕要自己实现了。
  2. 相对Java较为垃圾的垃圾回收,以至于性能没有那么极致。
  3. if err != nil

对于营销场景而言:

go由于天然的并发性和对epoll的高效支持,对于网络IO处理非常的擅长,最经典的应用莫过于前东家开源的bfe。落地到营销场景而言,go很适合做流量调度型基础组件。

其他的话,go适合做系统级应用开发,docker、k8s这些都是基于go来实现的,如果做基础架构,go是一个非常不错的选择。

下面要说的PHP和C++ 没有真正的实战过,没有太多的发言权,只是简单的说几句,像python这种只写过两个时间的,就不拿出来献丑了。

(3)PHP

世界上最好的语言的PHP,简直就是为web场景而生的,极致的开发效率,对于最原始的营销场景更是如此。由于前东家的PHP做了很大程度上的优化,所以已经不仅仅是原生的PHP了,这点需要考虑进去,如果是原生的PHP性能场景可以排除掉了。

(4)C++

c++比较适合极端的场景,比如说需要内存精确控制的场景像是搜索领域,对于营销场景而言,C++使用程度其实一般。如果存在老的C++营销系统,拿go或者Java来重构掉是一个不错的选择。

对于语言和工具方面,工作一年多的时间里也做了一些积累,大家可以参考一下之前的博客:《redis系列》《kafka系列》《go语言开发系列》《Java concurrent 系列》《高性能系统构建系列》,后面这些内容也都会持续更新的~

https://www.jianshu.com/u/636e09dd8775

(5)单独提一下epoll

在重IO的业务系统中,一个工具或者语言对于epoll的支持程度大概率决定了是否用它,它是IO中性能的基础。

Java对epoll的支持相对较好,原生网络包中提供了基于水平触发的epoll,netty补充实现了基于边缘触发的epoll(Dubbo就是基于它实现的)

Go,采用协程+epoll的方式进行网络编程,效率十分之高,这也是go适合做流量调度的根本原因。

nginx最大的性能来源其实就是epoll,同时支持边缘触发和水平触发两种模式,再加上精确的内存操作所以性能炸裂。

redis的单线程处理多链接的模式底层也是基于epoll实现的,可以看一下redis ae库中的实现。

如果想看一下互联网时代网络的发展史,可以从C10k问题起再到如何高效的进行并发编程。

并发编程是充分利用cpu高效处理的一种手段,如果涉及并发有两点是无法忽略的:

  1. 并发编程模型
  2. 锁的处理。

(6)并发编程模型

对于并发编程模型通常来说是伴随着语言的,比如go实现的基于协程和管道的CSP模型,Java基于内存共享的线程模型,在我们选择语言的时候就已经确定了。这两者有着非常大的区别:CSP

并发模型的核心概念是:“不要通过共享内存来通信,而应该通过通信来共享内存”。两种方式各有各的好处,根据场景选择即可,这也是语言选择的一个重要的点。

我们需要根据具体场景来选择,即使语言有限制,go使用内存共享,Java来实现基于管道通信也未尝不可。

(7)锁的使用

这是这一篇章的核心内容,对于锁的使用。对于优化来说通常有下面几点:

1、避免加锁

一些能够牺牲空间来进行线程或者协程私有数据空间,就没必要使用锁了,加锁完全是为了并发下逻辑的正确,如果有更好的解决方式,请避免使用锁。

2、锁选择

拿Java举例,就synchronized、ReentrantLock来分析比较的话,看到网上有好多博客都在说sychronized 在争用频次非常高的情况下性能会急剧下降,这种观点是存在时效性的,就当前1.8版本使用体验而言,sychronized在大量争用的情况性能其实还好并不会出现所谓的急剧下降,倒是在激烈争用时sychronized的性能要好一些,这个问题去官网确认了下,官方是建议使用sychronized的,这次的体验也是sychronized更好。因为当前JVM是对于sychronized做出了优化了,借鉴ReentrantLock的CAS加锁方式,并且引入了偏向锁、轻量级锁等特性后,常规情况下两者比较相似,实践中得到的体验是sychronized性能更好一点。

3、锁粒度

如果非得需要锁,粒度要尽可能的控制到小,避免不必要的加锁。因为同步块越长,线程持有锁的时间就越长,其他线程等待的时间就越长,如果整个都是加锁的,那么整个程序就变成串行处理了。

但是要主要不要频繁加解锁。

4、相关并发工具的选择

最简单的方式其实就是尽可能使用原生经过反复验证的官方工具(concurrenthashmap、waitgroup等),不要尝试使用自己编写工具,大概率出问题,但是为了学习,就很有必要了。

5. 代码优化

(1)代码逻辑优化

其他的代码优化语言层面感受到的有CPU使用减少、IO减少、语言底层优化三方面:

对于cpu来说:

  1. md5、Json序列化反序列化等这些都是非常耗性能的,如非必要,建议砍掉。
  2. 充分回忆起大学时的《数据结构和算法》,例如可以用Hash O(1)打表解决的,请不要几层for一通干。
  3. 谨慎使用明确存在性能风险的工具,比如go中的定时器,踩坑之深,至今难忘。
  4. 不要觉着起协程或者线程效率高,很多时候无端的起协程和线程得到的收益是很小的。

对于IO来说:

还是上面提到的那个思想,基于epoll的基础上,砍IO、pipline。

最主要的是在IO操作评估时,要根据具体的日志来量化分析统计,而不是看代码,拍脑门决定。

(1)语言底层优化及其他

对于语言底层的优化,在日常开发中其实接触的不会很多,很多的是出现在面试中,哈哈哈哈哈哈。实际操作过程中,也偶尔会涉及到,主要是对于GC相关的内存分配。

对于GC而言,垃圾回收器的选择还有代码精减(减少临时对象等)要比瞎折腾调参数强的多,如果选择了合适的垃圾回收器,然后才是具体参数的调优,这一块要充分利用语言的原生工具,比如说jmap、jstack、火焰图工具等,确定问题的根源,再动手。

再其次,才是即时编译等参数。

九、可怕的资损

营销场景下最大的问题其实是资损,营销场景下因为其逻辑多变且临时,rd的开发周期极短,并且方案往往很难有时间反复推敲,不管是运营逻辑还是很代码代码实现都存在较大的风险,是极易发生资损的。而面对这些问题,也是建立一个较为完善的营销系统的目的所在。

1. 经典案例

(1)某东

京东无门槛优惠券被大量羊毛党领取,在优惠叠加后可以用极低价格(接近0元)购买数百元的小家电,涉及资损金额数千万。

(2)某多

腾讯视频“0.2元开通VIP”处理、东航0.4折白菜机票bug。

2. 如何可防可控

对于资损问题,最重要的有几个方案:

(1)严控流程问题

保证运营方案、技术设计、代码cr等主要环节发现问题,并及时解决。

(2)完善资损监控

这时候资损的发现环境,很多时候资损总是不经意间发生,我们最需要的是在资损发生的第一时间能够发现,而不是等几小时、几天后铸成大错才意识到。资损监控需要针对活动流程定制化开发,资损监控最好是在设计阶段就要考虑进来的,根据运营逻辑、系统设计来确定资损点从而定制化开发,这样不光完善了资损监控,更加后悔使rd同学方案设计及代码开发时更谨慎。一般来说围绕以下四点进行监控能发现绝大部分的资损问题:

  1. 单个权益是否超标
  2. 单个用户领到的权益量是否超标
  3. 是否存在不符合条件的用户领取到对应权益
  4. 是否存在异常峰值流量进行刷单
  5. 监控止损

对于止损问题,是一个比较难处理的环节,通常需要在保证活动正常运行的情况下进行止损,比较重大的问题可以直接活动下线。(会存在监管合规风险)

在活动设计时应具有专门针对资损的预案处理,比如:

权益方面:

  1. 权益的回收机制(降低资损程度)
  2. 权益迅速降级机制(原本发红包,改为优惠券)

活动流程方案:

  1. 一键不中奖
  2. 活动规则范围内增加门槛
  3. 接入专业风控;反欺诈能力

这里主要是针对专业羊毛党用户的,防止作弊流量刷单,杜绝黑名单用户参与活动。

十、总结

整篇文章到这里就很急促的结束啦,想写的业务内容还有技术内容还有很多,受限于篇幅和时间原因就先写这么多吧,文章写的越长,对它的整体控制能力稍微有点开始下降了。

整体来看一个营销系统的核心就是:以技术能力解决营销场景下的效率、质量、风险等问题,高效稳定的提供营销能力输出。

系统实线时的关注点:

  1. 性能
  2. 数据一致性
  3. 高度配置化能力
  4. 完备的效果洞察及监控能力
  5. 系统的可扩展性

本文作者 @邹志全 。

版权声明

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部