传媒百万级资源秒级调度实践
本文作者:麦晓峰,碧桂园服务产品研发高级工程师。
01 传媒调度的挑战
目前传媒资源调度正面临着的一些挑战:
1、资源调度逻辑复杂:资源调度查询条件多且要支撑各种组合查询条件,并确保这些支持条件的灵活组合以满足准确查询的需求。同时,随着业务的发展,资源调度逻辑可能随时发生变化,因此代码设计需要具有高扩展性和易维护性,以便于及时更新和改进,能够支持单元测试。
2、资源调度系统压力大:面对传媒业务近 300 万的媒体资源存量,系统需支持实时的资源检索与分配,并快速反馈结果,给系统带来了极大的负载压力。
3、资源调度过程复杂:为方便用户快速进行调度以提高系统易用性,需保证即便是在处理大量数据时也能充分满足用户的交互操作需求。为减少用户操作,需要封装各种套装方法,以供用户一键直达目标需求,但这又间接增加了系统的复杂性。
接下来让我们带着这些问题去看一下传媒的调度模块内容。
02 初识传媒调度
传媒系统负责全面管理媒体广告的售卖过程,涵盖多种广告载体,包括电梯媒体、灯箱及道闸等。在整个业务链中,资源调度环节至关重要。该模块的核心任务是根据客户的具体投放目标,有效调度符合需求的媒体资源。
让我们先来模拟一个客户投放需求:
发布内容:6 月品牌 A 投放需求
发布时间:6.15~6.29
发布城市:深圳、佛山
发布数量:两周共 926 块
发布要求:
1、T2 客梯内,6 月品牌 A 的所有渠道之间都要门洞排斥;与同期品牌 B 其他方案需门洞排斥;与其他供应商楼盘排斥。
2、按照周边门店 3 公里由近及远,入住率大于 90%,楼盘均价从高至低,15 层以上楼盘。
PS:T2 套装调度规则是每单元只要一个版位。
为满足上述及更丰富的资源调度需求,传媒系统调度模块提供了以下功能:
项目筛选:可根据项目城市和项目信息筛选,并支持通过指定地址范围内进行项目过滤。
资源筛选:可根据提供调度单、行业和客户进行资源点位互斥,通过对齐进行优先资源选择,根据具体资源信息进行筛选。
调度限制:提供 3 个不同调度维度(按需求总数调度、按项目调度与按城市调度)和 2 种不一样的资源调度方式(均衡调度和自动调度),并提供 30 种不同的位置选点套装。
03 分析与设计实现
资源调度需求可以概括为查询符合要求的媒体资源和对符合要求的媒体资源按要求进行调度。
1、查询符合要求的资源
我们首先聚焦于查询符合要求的媒体资源,即对项目和资源进行筛选查询。
通过对所有筛选条件进行分析归类,大致归纳为五大类:固定值过滤查询、数值范围过滤查询、排斥过滤查询、统计类过滤查询以及条件优先(即排序方式)查询。
对于固定值和数值范围查询,可以通过 Mysql 索引查询来满足要求,而且 Mysql 索引的 B+树结构对固定值和范围查询都有较好的查询效率。但是考虑到我们需要支撑线上 200 万资源的实时查询,而几乎每个查询条件都可能需要依赖索引,并且查询条件的组合变化多样且不可预测。这也引申出两个问题:如何合理设置索引?如何控制索引数量?即使解决了索引问题,对于排斥过滤查询使用 Mysql 也没有比较好的解决方案。
基于上述种种原因,我们计划引入 Elasticsearch(以下称 ES)来强化查询能力。
ES 是基于倒排索引算法实现的一个分布式搜索引擎,它对于固定值查询文档有天生的优势(使用 ES 的 term 查询可以实现高效的固定值查询):
ES 独特的字符串存储机制和字典树索引结构也为数据值范围查询提供了出色的性能(使用 ES 的 range 可以实现数据值范围查询);
ES 内嵌的布隆过滤器支持排除特定固定值文档的查询(使用 ES 的 must_not 等方式可以实现排斥条件过滤);
ES 具备强大的聚合统计功能(Metric Aggregations,Bucket Aggregations 和 Pipeline Aggregations),可以很好地支撑统计类过滤查询需求。
借助 ES 的这些功能,我们可以轻松解决固定值过滤查询、数值范围过滤查询、排斥过滤查询、统计类过滤查询这四类查询需求。至于优先级查询,我们可以用 ES 的 function score 实现。
接下来我们要考虑如何设计一个筛选条件的组装机制,该机制具有高扩展性且易于修改与维护。理想情况下,该机制还可以根据业务变动随时增减筛选条件而不影响其他功能条件,实现筛选条件的“即插即用”。
(1)责任链模式
我们设计了一个 ConditionNode 接口,将每种筛选条件视为该接口的一个实现。通过责任链模式把所有筛选条件串联起来,并通过 Combine 操作把链上的 ConditionNode 实例组合成一条 ES 查询语句,以获得最终结果。
通过对不同的 ConditionNode 接口,可以构建不同的筛选条件。当业务发生变化时只需增加或删除链上的 ConditionNode 即可,而无需影响链上的其他节点正常运作。由于条件被节点化,每个节点都是单独的类,不依赖于其他条件,因此可以对每个节点进行单独的单元测试。只有通过测试的节点才能添加到链上,从而降低了代码的错误率。
(2)策略模式
位置选点:为了更好地封装客户需求、简化调度操作难度,系统提供了 3 类位置选点套装,并且每类套装中都包含有 10 个不同的具体封装,共 30 个位置选点套装(见上文调度限制)。通过使用不同策略对位置选点封装出 T1(电梯维度)、T2(单元维度)和 T3(楼栋维度)3 套策略。
调度方式:拥有自动调度单(即从第一个项目开始调度可用资源,调度满后再调度下一个项目,直到调度完为止)和均衡调度(即每一个项目平均调度资源,公式为每个项目调度数=总需求数/项目数,不足需求数则每个项目继续调度一个资源,直到满足需求数为止)这两种不同的调度方式。对于这两种调度方式无法使用 ES 直接处理,但可以使用代码实现两套不同的调度方式策略。
2、按要求进行资源调度
接下来是资源调度的第二部分内容——对符合要求的媒体资源按要求进行调度)。根据不同的调度方式进行资源调度,直到满足调度需求数为止。
在系统调度大量数据资源时,可能会遇到明显的调度效率下降问题。通过对调度逻辑进行分析,我们发现项目内的调度资源之间存在互相影响,而项目间调度资源则互不相关。为此,我们采用分治思想,将一个调度任务拆分成多个子任务。首先,我们计算每个项目需要调度的资源数量,然后将每个项目具体的资源调度工作分发到不同线程的子任务中进行,最后把各个子任务的结果组合起来得到最终的调度结果。
3、ES 集成和数据同步
全量数据同步:使用 DataX 通过资源宽带 SQL 初始化全量索引。
增量数据同步:使用 Flink Mysql CDC 实时增量更新资源索引。
4、资源调度性能提升
本次调度功能升级新增了 20 个不同的调度条件、3 种调度维度和 2 种调度方式。同时,我们对项目筛选和资源筛选进行了明确区分,并为资源筛选增设 30 种不同的位置选点套装,以满足多样化的调度需求。
资源调度方面,对于上万级的资源,我们的性能提升了近 2 倍。即使在处理万级资源时,我们也实现了至少 70%的性能提升。资源查询性能方面也有一定的提升。
04 ES 的特性
1、Function Score 加权排序
在调度条件中,我们支持选择不同的条件优先(即排序方式)。一开始,这类查询也是最令我们头疼的问题之一。因为一旦在条件查询中出现了排序就一定会降低查询效率。最终,我们采用了 ES 的函数评分(function score)来处理排序。通过对条件设置合理的加权分数,并最终通过分数进行查询排序,从而解决了这个问题。
在 ES 中,函数评分是一种用于自定义文档评分的方法。它允许你通过将自定义函数与查询匹配度(query relevance)相结合,以此来影响搜索结果的排名。函数评分常用于根据特定条件或者业务逻辑对搜索结果进行加权,从而提高结果的质量和相关性。
函数评分有函数评分查询(function score query)和脚本评分(script score)两种常见的实现方式。脚本评分允许你通过编写自定义脚本来修改文档的得分。你可以在脚本中实现任何复杂的逻辑,从而根据文档的各种属性和条件来调整得分。脚本评分在一些特定情况下非常有用,比如需要对每个文档进行个性化的评分,或者需要根据文档的多个字段来计算得分。
我们使用脚本评分灵活配置各种优先条件,按照以下公式对所有优先条件进行加权处理:
对齐调度单优先:(1/0)*1000000000
行业优先:(1/0)*500000000
客户优先:(1/0)*200000000
空位多优先:剩余空位数量+85000000
撤画优先:(1/0)*40000000
空置时间长优先:最后调度资源的时间,格式为 yyyyMMdd(如果没有设置成 30000000)
PS:0 和 1 分别代表是否匹配指定类型目标值。
通过这种方式,我们能够更精准地调控资源的优先级,满足不同类型客户的需求,提高调度效率和精度。
2、Cardinality Aggregation 近似值统计
当我们使用 ES 的 cardinality 方法进行去重统计时,发现统计结果偶尔与真实结果不符。经过查阅官方文档发现,cardinality aggregation 是一个近似值统计,它并不能保证统计结果 100%准确,但可以通过设置 precision_threshold 参数牺牲一定的内存来提高结果准确度。参数的上限 40000,一旦超过阈值或者数量超过 40000,还是会出现相同的情况。
我们使用 cardinality aggregation 进行结果的数量统计和分页,对结果的准确度需求不高,提供一个近似的统计结果即可。并且根据目前的生产项目数据分析,数值远未达到 40000 的限制。同时,我们已经对结果和分页进行了相关的提示和处理,并指出统计数据是近似值而非准确值,并不影响功能的正常使用。
05 总结
在建立传媒资源调度模块的过程中,我们从需求、分析以及设计三个方面进行了详细介绍,并总结出以下几个关键要点:
1、复杂的业务逻辑与多样的筛选调度条件:针对真实客户的投放需求,我们处理了超过 50 种不同筛选调度条件的分类识别。通过将这些需求简化成固定值查询、数值范围查询、排斥查询、统计类查询和条件优先查询这五类查询问题,使整体业务逻辑更加清晰明了。
2、支持百万级别资源调度并实现秒级出结果:引入 ES 来处理复杂的资源查询问题,为每种查询问题寻找合适的解决方案。在资源调度阶段,采用分治思想,建立了任务调度中心,将大任务拆分成多个小任务进行调度,最后合并结果,从而确保高效的调度性能和快速的响应速度。
3、高扩展性、易维护性和可测试性的代码设计:通过采用责任链模式对查询条件进行封装串联,实现筛选条件的即插即用,极大地提升了代码的扩展性。同时,每一组责任链条件都可以独立进行单元测试,通过提高单元测试的覆盖率来,确保了代码质量,增强了代码的可维护性和可测试性。
4、优化的实时交互体验和高预封装能力:利用策略模式对位置选点进行封装,为客户提供强大的预封装的能力,使客户能够来回切换各种调度场景。通过一键套装切换功能,我们能够灵活地调整调度需求,提供更好的实时交互体验。
版权声明: 本文为 InfoQ 作者【智在碧得】的原创文章。
原文链接:【http://xie.infoq.cn/article/17617e7f6e9d2ab4e0b88e184】。文章转载请联系作者。
评论