1.业务背景
榜单在经历了供给量迅速增长及C端分发场景多样化等迭代,数据量及峰值流量呈十倍百倍增长,这必然带来数据库的极大存储压力和C端查询性能降低。为满足未来各类复杂定制化规则和亿万级数据甄选,综合引导消费者的购物决策,得物商品榜单生产迁移及B/C端数据存储隔离应运而生。
2.概述
得物榜单作为C端核心导购场景之一,通过建立丰富的规则矩阵,多维度提供用户购买参考建议,帮助用户快速决策并完成商详转化。目前涵盖六种类型包括热销榜、新品榜、趋势榜、种草榜、好评榜及回购榜,主要入口包括商详、品牌主页、分类tab、瀑布流、会场、频道等。
2.1 “圈选”+“排序”是核心:
通过圈品条件圈选出一系列商品,再根据排序规则排名后取TOP20商品入选榜单。**圈品范围包括类目、品牌、系列、标签等,通过【6类模型、N项指标因子】综合计算排序后的总分代表商品的综合竞争力,分值越大,代表该商品表现越好。**得物榜单基于上述算法模型严格把控上榜商品品质,从而帮助用户根据自身诉求快速决策。
2.2 是否存在更优的排序?
通过对榜单排序逻辑优化可针对性提升榜单承接效率,探索一套最佳排序规则公式需要通过不断的尝试,因此整个实验周期较长,在设计上就需要支持实验的快速推进。
来看下目前如何实现榜单的创建及生产。
涉及表
-
基础表:记录榜单基础信息;
-
圈品条件表:记录榜单圈品维度信息包括类目id、品牌id、系列id、标签id、商品ids集合等;
-
商品集合表:记录榜单下关联的top20商品id,当前榜单B/C端流量都是走了这个表的查询;
如何绑定圈品范围?
-
人工榜单通过后台新增榜单时绑定捞月规则或人工直接配置商品ids集合,并同步写榜单圈品条件表;
-
半自动化榜单通过后台新增榜单时绑定类目id、品牌id、系列id等维度规则,并同步写榜单圈品条件表;
-
自动化榜单通过预先设定的规则批量写榜单圈品条件表;
数据流转?
商品后台新增榜单基础信息至基础表,将圈品范围信息(类目、品牌、系列、标签、spuIds等)保存至圈品条件表,搜索每两小时定时从库中捞取圈品条件表数据,获取最新数据刷到商品集合表中。
-
搜索数仓H+1/T+1 dump:搜索离线数仓通过离线计算排序因子数据,生成离线宽表;
-
搜索离线圈品排序引擎:对照组榜单由搜索生产。每2小时定时调度,扫描圈品条件表全量数据,从宽表中筛选商品并进行排序,排序结果通过DTS数据同步回流榜单商品集合表;
-
搜索离线圈品排序引擎:实验组榜单由商品圈品排序引擎生成,底层实现和搜索大致相同。
3.系统缺陷及解决思路
3.1 链路强耦合
商品/搜索存在双写榜单商品表场景,由于搜索通过dts数据同步方式回流数据,导致数据相互覆盖甚至主键冲突,通过id隔离的方式可以暂时解决。
3.2 重复造轮子
目前榜单商品生产链路强依赖搜索,由搜索实现商品圈选及排序,搜索榜单商品生产方式单一,无法满足榜单圈选/排序规则定制化供给。而 「捞月」 作为得物核心选品投放平台,已经具备强大的圈品排序能力。
3.2.1 捞月指标体系
海量选品指标维度(商品基础信息,活动信息,价格与库存,流量与转化等)支撑各业务,分钟级别选品实时指标数据;
-
odps离线指标数据:商品/交易指标进行计算产出离线宽表。
-
实时交易指标:如活动期间GMV,买家数等交易数据。
- 离线DUMP:离线数仓通过离线计算指标对应值经由datawork同步任务将指标值同步至捞月B端ES。
- 实时DUMP:业务系统/实时数仓或其他对接模式准实时的通过商品Feature(一些业务逻辑性强的指标)、DB BinLog或其他方式将数据通知到捞月指标中心,再由捞月指标中心将数据落到捞月B端ES。
3.2.2 捞月实时选品引擎
分钟级别执行引擎更新选品结果。
3.2.3 捞月排序中心
支持个性化、统计字段、自定义权重配比等多维度复杂升降序排序规则;也支持用户特征,进行推荐算法个性化排序。
具体细节不在本文展开,重点关注捞月指标体系、选品及排序能力在本次迁移中的应用。
针对第一个和第二个问题,我们提出榜单迁移捞月——通过复用捞月现有圈选/排序能力,完成榜单商品生产能力搭建,将榜单生产从搜索侧迁移至商品侧,移除榜单底层能力搭建对搜索的依赖。
3.3 无法支撑海量数据的高并发读
未来榜单需支撑类目下沉场景,“类目下沉”即针对目前以类目为维度的榜单,再按品牌、系列、标签等维度进行榜单延伸。如:跑步鞋热卖榜下,再细分出耐克跑步鞋、入门跑步鞋、透气跑步鞋等榜单,更多维度扩充包括人群、风格等。按照笛卡尔积生成方式将任意维度与类目进行两两组合,将产生百万级甚至千万级别 海量数据 ,造成极大的存储压力。同时,榜单从生产到C端分发,经历榜单商品圈定、审批流及其他状态控制,最终能够在C端成功分发的榜单数量有限,每次查询都会触发有效数据的实时过滤。随着榜单数量的快速增长,必然带来C端查询性能降低,如大key、索引失效等,存在性能隐患。榜单在商详分发,必然伴随着 高并发读 。
针对这个问题,虽然可以尽量地从优化 sql、优化索引、缓存等等方面进行优化,但总会有到达极限的时候。关于海量数据的存储选型已经有非常广泛的案例,该如何进行存储选型?存储选型的目的还是为了我们的使用场景和用户服务,因此在选型前需要回答一些业务指标&技术指标方面的问题,以便于我们清楚存储选型的应用环境:
-
数据量及日增数据量:数据量在可控范围内日增稳定;
-
读写偏好:榜单状态及上榜商品不频繁变更,读多写少;
-
运行性能要求:并发量峰值商详、首页,低谷像一些二级页面;
-
查询复杂度:复杂条件查询、聚合查询、join查询;
-
其他性能要求:实时性要求不高;
结合榜单业务特征以及海量数据和高并发的特点,可能的解决方案不限于
-
使用缓存的方式通过程序代码将数据直接保存到内存中,如ConcurrentHashMap、Caffeine等;或使用缓存框架如Redis等;
-
数据库优化:数据库优化的方式很多,常见的可以分为:数据库表结构优化、SQL语句优化、分区、分表、索引优化、使用存储过程代替直接操作等;
-
使用NoSql技术:HBASE、MongoDB等;
-
使用搜索引擎技术:ElasticSearch等;
设计实践中,要基于需求、业务驱动架构,无论选用 DB/NoSQL, 一定是以需求为导向,最终数据存储方案必然是各种权衡的综合性设计:
-
分库分表:垂直切分适用于表中存在业务耦合,且拆分后单表数据量依旧很大;水平切分的关联查询性能差;
-
HBASE:列存储分布式数据库,适合TB级别数据的实时入库和快速随机访问场景,缺点是查询仅能通过rowkey和range检索,不支持复杂查询;
-
MongoDB:文档型NoSql,适合非结构化数据存储,表结构可随意变更,因此插入效率高,同样的也是不支持复杂查询如多表查询等;
-
ElasticSearch:写入性能低,实时性低,但是通过为所有字段添加索引可支持复杂的聚合查询和条件查询。
综合考虑,B/C端数据存储隔离成为当下性价比最高的解决方案。
以下将阐述本次实践的详细实施点。
4.技术实施点
整体改造将分为两个阶段进行:首先完成链路改造,即榜单生产迁移捞月,待数据验证通过进行第二阶段的存储改造,即B/C端数据存储隔离。
4.1 链路改造
4.1.1 捞月指标创建
捞月需支持榜单实验能力,因此在榜单对照组通用指标的基础上新增实验组通用指标,同时预留对应的实验组排序规则。可支持同类型榜单同时段进行一项实验,实验结束后根据实验结果修改通用算法模型。实验指标可在多次实验复用,不会造成捞月指标数量的递增。
-
对照组热销榜分数指标(best_seller_score) :根据7日内销量与成交金额计算综合排序
-
对照组新品榜分数指标(new_product_score) :根据上架时间和收藏人数等指标综合计算排序
-
对照组趋势榜榜分数指标(soare_score) :根据近7日销量和收藏人数等指标综合计算排序
-
对照组种草榜分数指标(collect_score) :根据当日新增商品销量等指标综合计算排序
-
对照组好评榜分数指标(favorite_score) :根据商品好评率和好评数等指标综合计算排序
-
对照组回购榜分数指标(rebuy_score) :根据商品年度累计回购人数等指标综合计算排序
-
实验组热销榜分数指标(best_seller_score_test):根据累计付款人数和收藏人数等指标综合计算排序
-
实验组新品榜分数指标(new_product_score_test) :根据上架时间、点击、收藏、销量等指标计算综合排序
-
实验组趋势榜榜分数指标(soare_score_test) :根据近7日销量和收藏人数等指标综合计算排序
-
实验组种草榜分数指标(collect_score_test) :根据当日新增商品销量等指标综合计算排序
-
实验组组好评榜分数指标(favorite_score_test) :根据商品好评率和好评数等指标综合计算排序
-
实验组组回购榜分数指标(rebuy_score_test) :根据商品年度累计回购人数等指标综合计算排序
4.1.2 排序规则创建
-
对照组热销榜排序规则(sort_best_seller)=best_seller_score * 100% desc
-
对照组新品榜排序规则
(sort_new_product)=new_product_score * 100% desc -
对照组趋势榜排序规则
(sort_soare)=soare_score * 100% desc -
对照组种草榜排序规则
(sort_collect)=collect_score * 100% desc -
对照组好评榜排序规则
(sort_favorite)=favorite_score * 100% desc -
对照组回购榜排序规则
(sort_rebuy)=rebuy_score * 100% desc -
实验组热销榜排序规则
(sort_best_seller_test)=best_seller_score_test * 100% desc -
实验组新品榜排序规则
(sort_new_product_test)=new_product_score_test * 100% desc -
实验组趋势榜排序规则
(sort_soare_test)=soare_score_test * 100% desc -
实验组种草榜排序规则
(sort_collect_test)=collect_score_test * 100% desc -
实验组好评榜排序规则
(sort_favorite_test)=favorite_score_test * 100% desc -
实验组回购榜排序规则
(sort_rebuy_test)=rebuy_score_test * 100% desc
4.1.3 指标、排序规则同步ES供后续选品及排序
{ "name": "best_seller_score", "type": "long" }, { "name": "new_product_score", "type": "long" }, { "name": "soare_score", "type": "long" }, { "name": "favorite_score", "type": "long" }, { "name": "rebuy_score", "type": "long" }, { "name": "best_seller_score_test", "type": "long" }, { "name": "new_product_score_test", "type": "long" }, { "name": "soare_score_test", "type": "long" }, { "name": "collect_score_test", "type": "long" } { "name": "favorite_score_test", "type": "long" }, { "name": "rebuy_score_test", "type": "long" }, { "name": "sort_best_seller", "type": "long" }, { "name": "sort_new_product", "type": "long" }, { "name": "sort_soare", "type": "long" }, { "name": "sort_collect", "type": "long" }, { "name": "sort_favorite", "type": "long" }, { "name": "sort_rebuy", "type": "long" }, { "name": "sort_best_seller_test", "type": "long" }, { "name": "sort_new_product_test", "type": "long" }, { "name": "sort_soare_test", "type": "long" }, { "name": "sort_collect_test", "type": "long" }, { "name": "sort_favorite_test", "type": "long" }, { "name": "sort_rebuy_test", "type": "long" }
改造后全链路数据流转如图所示:
-
榜单创建:圈品条件写入捞月选品规则数据表;
-
排序规则:超时中心调用排序引擎更新每个商品的排序分同步捞月B端es;
-
实时选品:圈品引擎分钟级别扫描待执行的选品规则数据,从底表中筛选符合条件的商品并生成选品集id同步捞月B端es;
-
完成绑定:捞月es将选品结果回流到榜单商品表,并将选品集id回流到榜单基础表,榜单id和选品集id绑定完成;
-
商品更新:监听捞月选品集结果变更消息。
4.2 存储改造
4.2.1 B端数据源-捞月B端ES
在链路改造环节我们已经完成了榜单和捞月集的绑定,因此查榜单下的商品就等同于查捞月集下的商品。
具体逻辑如下:
那么是否完全可以复用捞月存储结构,释放榜单商品集独立存储的空间?首先根据B/C查询场景划分为正向链路和反向链路。正向链路,也就是从榜单id获取到捞月集id,从捞月es根据捞月集id获取捞月商品结果集,B端查询方式均为正向链路,因此可以复用捞月存储结构。反向链路,即从捞月es根据商品id获取其所在的捞月集ids,从捞月集ids查询榜单ids。由于捞月集id暂无场景打标,只能遍历捞月集ids判断是否属于某一个榜单,查询成本极高,榜单在C端的核心分发场景商详便是满足了这个链路特征,当然我们可以通过建立捞月集场景打标体系、构建榜单商品ES大宽表的方式来解决问题,有没有性价比更高的解决方式?
###4.2.2 C端数据源-榜单集合mysql表
针对当前业务规模及C端查询复杂度,考虑采用B/C端查询隔离的方式进行实现。捞月选品结果变更、榜单显示/隐藏、审核通过/驳回、生效/失效都会影响榜单的分发状态。最终能在C端分发的榜单十分有限,为避免在C端做大量数据的实时过滤,降低索引失效和大key风险,原B/C端公用数据源榜单集合表仅存储可分发榜单商品数据供C端查询,实现方式及其简单:在状态变更及捞月结果集变更时触发实时更新/删除榜单集合表即可,整个改造过程无需改动C端代码,仅通过几行业务代码,就将DB存储数据量量级降至原来的40%,可支撑未来两至三年数据量的稳定增长。
4.3 灰度设计
相对于功能完整性,如何实现平滑稳定的切流是整个项目中比较重要的一环。
为了实现用户无感切换,降低切换过程中可能出现的故障对系统的影响,通过多个灰度读写开关保证切流过程的平滑和稳定性,整个过程可,做到“随切随停”。整体灰度分阶段逐步推进,采用:
-
“增量数据维护-数据验证-全量数据刷数-数据验证-灰度切流-数据验证”的方式
-
各个阶段均有补偿策略或回滚方案,风险可控性较高
即采用先进行增量数据维护,待增量数据check+fix通过后,进行全量数据刷数,待全量数据check+fix完成后,实施灰度读切流,并进行双读check,一旦出现问题则开关关闭;写切流采用先写临时表,临时表数据验证通过后切换写主表,一旦出现故障或脏数据,预案启动,可保证数据一小时内回滚。具体来说,我们关注以下四点:
4.3.1 数据维护
首先进行风险评估,涉及榜单需绑定捞月集数量为N,以每个捞月集限制M个SPU为准,选品结果集总数量可达N*M,写入高流量将造成ES实例整体性能急剧下降。另一方面,数据修复成本极高,绑定错误只能重新绑定新的捞月集,需全量删除旧捞月集后再绑定新捞月集,两次写入成本极高。因此先进行增量数据维护,待增量数据check+fix通过后进行全量数据分批刷数。
4.3.2 读/写切流
- 读切流:读切流的核心是将B端数据源切换到捞月ES。这一步执行的节点十分关键,需放在链路改造完成,存储改造开始之前进行。原因是如果先进行了存储改造再执行切流,由于存储改造完成后rank_list表仅存储可分发榜单,那么切流后一旦出现问题应立刻切换回老链路,原B/C端公用数据源rank_list表数据已经不完整,完全不能满足B端使用,无法快速止血,只能快速定位问题并修复上线。确定执行时间节点后,写好开关并进行双读check,发现问题切换回原链路即可。
- 写切流:搜索更新榜单商品开关关闭后,捞月选品结果全量写入榜单集合表的风险较大,通过建立榜单集合临时表,待临时表数据验证通过后切换主表。
4.3.3 数据校验
双读check、不定时全量数据check+fix 、增量数据check+fix 、应用监控、日志告警埋点等;
4.3.4 数据订正
针对灰度过程可能出现的所有数据错误预备对应数据订正接口。多想一步,如果在写切流过程中切换写主表后出现未识别到的数据错误,如何快速止血? 在迁移的过程中我们暂时保留搜索更新榜单商品的能力作为数据修复预案,可在一小时内完成数据修复。
具体灰度切流推进流程如下:
经历两个星期的灰度,已移除对搜索的依赖实现全链路闭环,依照灰度方案通过切流开关及预案等手段确保“随切随停”,上线期间零故障。
5.总结
综上所述,榜单通过生产迁移彻底解决了一直以来榜单底层能力支撑不足的痛点。完成链路合并后,借助捞月圈品排序能力降低未来各类复杂定制化供给场景的维护成本,功能上线后已提升榜单各类业务迭代效率提升50%以上。并通过B/C端数据存储隔离,以极低的改造成本降低表存储成本60%。
在这个基础上,思考是否有更多的发力点:结合当前系统现状和未来的可能性,结合业务规划,围绕用户对平台榜单预期,未来将演变出多维度(内容、品牌、sku、spu等)榜单通用生产引擎,在供给充足基础上实现场景化个性化分发。
以上就是我们在得物商品榜单生产改造探索实践中的一些经验和总结,分享出来希望对阅读本文的你有一些帮助!
文/希希
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net