【沉淀之华】从0到1实现用户推荐 - 实时特征系统构建,包含特征计算,特征存储,特征查询,特征补偿超详细思路分享

文章目录

    • 背景介绍
      • 设计初衷
      • 基本概念
    • 技术架构
      • "四高"
      • 特征存储
      • 特征计算
      • 特征查询
      • 特征补偿
    • 技术难点Q&A
    • 彩蛋

背景介绍

设计初衷

作为用户推荐系统的支撑系统之一:用户实时特征系统有着举足轻重的重要,甚至说它是一起推荐行为触发的必要条件。而实时特征系统价值除了为实时推荐获取用户特征提供基础能力,对离线数据训练模型也起到关键支撑。

在这里插入图片描述
离线训练流程
在这里插入图片描述

由于我主要负责实时系统, 离线训练细节逻辑由专门算法同学负责,通过经验分享和沟通也得到了算法同学认可如上图所示

看起来实时特征的生产和离线训练生产特征 逻辑大体相同,但是实时特征对数据一致性,及时性以及对接口的响应RT是要求极高的, 我们业务中就要在峰值5000+QPS的背景下 要求P995 在500ms 以内返回,那么如何做到这一点,请听我娓娓道来…
在这里插入图片描述

基本概念

考虑有些同学可能还未接触过,这里把一些本文涉及到核心概念做了总结,让大家有个整体感受

关键词描述备注
用户画像用户画像的本质是通过对用户的行为分析和统计,挖掘用户的需求、兴趣和习惯购买行为,支付方式,浏览习惯
客群特定的、拥有共同特性(可能是年龄、收入、性别、购买行为等)的消费者群体例如:喜欢购买骑行卡的用户骑车在xx岁的用户
特征通常是描述用户特定行为或属性,也可以是城市、车辆某些行为如:购卡用户、多少日内骑行用户免押金城市、订单量大于xx的城市等
标签进一步具体刻画特征,或者说在业务实践中的常用术语技术层面对特征具象化
物料用户的原始特征,如身份、年龄、职业等技术层面理解就是未进入到特征系统的业务数据
Point-in time时间点准确预测可直接影响离线训练模型结果,也就是常说的特征穿越问题
用户活跃度活跃期、沉默期、沉睡期
召回根据原始特征快速分析用户的泛化兴趣,即粗略的共同爱好进行参与排序计算实时推荐第一步

在这里插入图片描述

其他概念都比较好理解,特征穿越可能会有点疑惑,本文重点不在这,但是文末有进一步解释

技术架构

“四高”

在这么一个在推荐业务及其重要的环节,怎么保证实时特征生产和查询的及时性,准确性,以及服务的高可用性、高并发、高性能,同时为了兼容后续业务迭代如何让特征生产具备高扩展性,几经打磨如下技术体系:
在这里插入图片描述
下面我们分析每一个业务模块

特征存储

如果说实时特征系统是用户推荐的关键一环,那么特征存储选择决定了"三高"的上限,这里我们采用ES 集群 + Redis 主从架构,

为什么选择ES?
众所周知,本身ES集群本身基于Raft协议在数据一致性可以得到很好的保障,加上本身基于lucence引擎在搜索领域早已崭露头角,高性能查询当之无愧,此外ES集群采用1主2从,多副本分片机制加之运维人员合理配置,承载高并发、高可用性也是可以轻松做到。

至于Redis,考虑的即便是实时特征也有进一步对特征时效性的区分即高时效性和一般时效性,例如对某些用户特征要求1秒级返回,有的可以允许几分钟时延,因此Redis主要是做这部分时效性一般特征的抗压组件再合适不过。


特征计算

和离线采集一样,实时特征采集也是需要经过ETL标准流程,通过承接不同业务事件,做一般时效性特征采集,转化到最终匹配到统一特征模型配置中,而针对于时效性很高的特征,分两步,有的直接通过业务接口同步更新,对于链路较长的业务特征采集,则是通过直接加载业务数据源方式构建
在这里插入图片描述
然而,随着用户行为累积,特征的表现形式也多种多样,如何灵活特征的衍生和快速配置方式让特征生效

在设计之初就考虑到了这一典故,我对特征进行元数据管理,对特征来源,特征计算逻辑也进行单独管理,事实上背后最关键的技术也是来自ES本身支持场景基本单一【这是选择ES另一个考虑】,其中内置很多计算函数:如sum,max,min,avg等,基于此可以灵活衍生多个不同维度复杂算子同时我们还有专门的管理后台对特征进行状态上下线,以及通用计算逻辑界面化配置[可以参考下],这样可以极大减少开发周期,几乎无需写代码即可实现新特征注册上线及使用。

在这里插入图片描述
在这里插入图片描述

特征计算配置得益于我们在初始化阶段就构造了20多个算子, 可以支持一般性特征扩展。


特征查询

查询其实在特征计算也提到过,对推荐系统来说,查询RT越低,在用户侧推荐效果就越好。因此我们对特征进行时效性划分, 当需要时效性一般的数据,直接走缓存,如果缓存未命中则直接从ES全量集合中获取,对于时效性较高的走ES查询,而对于时效性极高的特征,则是优先从业务数据源直接查询

可能会有疑问:这么高的并发业务库能承载的了这样的查询吗?
A:首先特征系统在接入业务数据源的时候,业务方基本都做了读写分离,加上一主多从的机制可以充分分担查询压力,同时对于这种时效极高的数据特征case是比较少的,基本只存在特定页面过来。为了防止突发流量,我们也做了相应的限流和熔断机制
在这里插入图片描述


特征补偿

特征补偿,主要发生在监听业务事件消费逻辑上,也就是非实时性较高的特征场,而且生产环境很难完全规避中间件抖动带来的影响,所以对于这部分特征我们采用异步任务,对消费异常过程的特征进行打点,然后在业务低峰期进行对应特征事件数据补偿:比如在监听用户购卡、结单消息出现问题时,通过user_id和异常消费时间线是一定可以从业务数据找到对应特征元数据并补充到ES中。


技术难点Q&A

1. 数据一致性保障?
这里存在三处不一致需要解决

  • 不可重复读:当查询请求介于更新相同特征之间时容易引发不可重复读的问题,那么常见的思路就是需要考虑加版本号控制,ES也提供了这样的机制
  • 增量更新:对于实时特征来说,基本都是基于用户当天的行为特征, 比如是否注册用户,复活用户等应该保障一天只更新1次,而不是出现特征覆盖的情况造成推荐系统紊乱
  • 数据加工一致:作为特征生产的集中站,我们从业务那里无论是异步还是同步加载特征源信息,都应该在解析,转换以及使用都应该和业务保持一致,比如数据脱敏,例如我们从用户系统获取用户信息,我们也需要保持同样的加密和解密算法逻辑,还有类似对状态,枚举的定义甚至文字说明也要保持一致,避免数据误差。

2. 数据幂等

这个最容易忽略,业务方在产生这些数据特征, 尤其是状态相关的一般也是随着业务规模才逐渐加上了幂等逻辑,而对于特征消费场景来说,幂等也是应考虑的重点,以避免对特征数据源产生较高写操作,是系统压力集中站,必须要谨慎设计

3. 如何尽最大努力保障业务数据源的稳定性
前面提到,我们会有兜底逻辑查询业务数据源,尤其是实效性极高的特征,虽然早期并发不高,但是如果业务扩展,高并发查询进来势必会对系统带来查询瓶颈,那么改如何优化?

  1. 做到同一个用户只查询1次, 这取决特征元数据抽取和特征配置表的设计思路,如下图所示
    在这里插入图片描述
    这就是看技术经理怎么设计了,一般来说从理解上看特征与数据源关系是多对多:一个数据源可以生成多个特征,一个特征可能需要来自多个数据源特征共同组成,因此如果只从技术考虑那么这个实现复杂度是N*M,因此为了降低复杂度在设计的时候我们采用了数据库第二范式冗余字段,进行优化对一个数据源一次性抽取所有特征, 即便并不关心是否一定需要最终使用,也避免多次占用网络带宽造成的性能损耗。

4. 复杂特征计算通用性
简单特征只需要经过1次预处理就可以完成写入,但是对于复杂的特征分为2类:

  • 特征消费时可以确定的逻辑:即便如此,也需要组合多个特征输入甚至还要对某些特征加工, 然后作为最终特征构建的输入,这里我们统一借助特征算子配置,以预定义函数算子 + Stream表达式方式去编排拼接得到最终的复杂特征,这个过程可以通过管理页面进行控制,而无需手动编码,可以快速响应产品需求变化
  • 对于特征生产无法确定的逻辑,比如甚至需要依靠实时推荐系统查询提供输入才能最终确定逻辑:对于这部分特征只能硬编码去根据推荐输入参数实时处理并得到期望的特征值。

还有更多的细节问题,有过一定开发经验童鞋通过网络也能顺腾摸瓜思考得以解决,千篇一律,这里就不一一举例了


彩蛋

什么是特征穿越【Point In Time】?

我也是盲人摸象,慢慢熟悉之后整个流程才了解,这个情况倒不会发生咋实时系统,而是多在离线训练发生,但是起点仍然在实时特征中心

简单来说,通过营销推荐策略推给用户导致的购买行为,本应该在T1时刻关联的对应的特征,但是因为系统时效性或者逻辑漏洞造成关联的T0 时刻特征,这就会对模型训练造成极大干扰,导致最终生产的模型不佳,举例如下:

在这里插入图片描述

采用ChatGPT举例生成,以此说明
如上图所示, 用户在9点时刻行为,只能使用9点之前用户的特征【过去购买、点击的商品】,如果使用了9点之后的信息来构建就会产生特征穿越,因为包含了未来的信息导致模型训练和预测结果存在显著差异,从而反应模型表现很差

写在最后, 深耕不易,如需转载和复制,请备注出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/589152.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【经典算法】LeetCode 160. 相交链表(Java/C/Python3/Go实现含注释说明,Easy)

目录 题目描述思路及实现方式一:哈希表思路代码实现Java版本C语言版本Python3版本Golang版本 复杂度分析 方式二:双指针思路代码实现Java版本C语言版本Python3版本Golang版本 复杂度分析 总结相似题目 标签(题目类型):链表 题目描述 给你两…

C语言——操作符保姆级教学(含整形提升及算数转换)

操作符 一.操作符的分类二.原码、反码、补码三.移位操作符1.左移操作符&#xff1a;<<2.右移操作符&#xff1a;>> 四.位操作符1.按位与—— &2.按位或—— |3.按位异或—— ^4.按位取反—— ~ 五.逗号表达式六.条件操作符七.操作符的属性&#xff1a;优先级、…

如何配置和使用Apollo的component里的plugin

关于如何使用Apollo的Component里的plugin&#xff0c;在Apollo的文档里只有如果和开发的说明却没有找到一个清楚完整说明怎么把plugin跑起来的说明&#xff0c;例如我想把lidar_detection_filter按我们的需求对目标过滤算法作修改然后编译完后&#xff0c;执行 cyber_launch …

【数据结构】链表专题3

前言 本篇博客我们继续来讨论链表专题&#xff0c;今天的链表算法题是经典中的经典 &#x1f493; 个人主页&#xff1a;小张同学zkf ⏩ 文章专栏&#xff1a;数据结构 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 目录 1.判断链表是否…

Dom获取属性操作

目录 1. 基本认知 1.1 目的和内容 1.2 什么是DOM 1.3 DOM对象 1.4 DOM树 2. 获取DOM元素对象 2.1 选择匹配到的第一个元素 2.2 选择匹配到的多个元素 2.3 其他获取DOM元素方法 3. 操作元素内容 3.1 元素对象.innerText 属性 3.2 元素对象.innerHTML 属性 4. 操作元…

springcloud微服务搭建多数据源(mysql,oracle,postgres,等等)管理模块,支持通过注解方式切换不同类型的数据库

1.背景 同一套微服务管理系统&#xff0c;业务完全一样&#xff0c;但不同的客户可能要求使用自己熟悉的数据库&#xff0c;比如&#xff0c;mysql&#xff0c;oracle&#xff0c;postgres&#xff0c;还有一些国产数据库。如果能够将数据库模块独立出来&#xff0c;兼容各家的…

IDEA启动项目报错:Error running ‘‘: Command line is too long.

1、在workspace.xml 2、 在标签 <component name"PropertiesComponent"> 添加 <property name"dynamic.classpath" value"true" />

MySQL 运维篇

回顾基本语句&#xff1a; 数据定义语言(DDL) 这类语言用于定义和修改数据库的结构&#xff0c;包括创建、删除和修改数据库、 表、视图和索引等对象。 主要的语句关键字包括 CREATE 、 DROP 、 ALTER 、 RENAME 、 TRUNCATE 等。 create database 数据库 &#xff1b; cr…

【MySQL | 第十一篇】一条SQL语句在MySQL的执行过程

文章目录 11.一条SQL语句在MySQL的执行过程11.1MySQL整体架构11.2SQL语句在Server层执行流程11.3拓展&#xff1a;InnoDB存储引擎的更新操作11.3.1问题&#xff1a;为什么写了redolog日志还要写binlog日志&#xff1f;11.3.2问题&#xff1a;为什么要两阶段提交&#xff1f;11.…

《QT实用小工具·四十七》可交互的创意动态按钮

1、概述 源码放在文章末尾 该项目实现了可交互的创意动态按钮&#xff0c;包含如下功能&#xff1a; 所有颜色自定义 鼠标悬浮渐变 两种点击效果&#xff1a;鼠标点击渐变 / 水波纹动画&#xff08;可多层波纹叠加&#xff09; 额外鼠标移入/移出/按下/弹起的实时/延迟共8种事…

springboot 自动配置源码解读

什么是自动装配 当我们程序依赖第三方功能组件时&#xff0c;不需要手动将这些组件类加载到IOC容器中。例如 当程序需要用到redis时&#xff0c;在pom.xml文件中引入依赖&#xff0c;然后使用依赖注入的方式直接从IOC容器中拿到相应RedisTemplate实例。 SpringBootApplication …

【已解决】json文件太大无法打开怎么办+ijson报错

下载了一个json文档&#xff0c;尝试了全部的编辑器都打不开。。。 搜了搜或许可以使用ijson GitHub - ICRAR/ijson: Iterative JSON parser with Pythonic interfaces "Ijson is an iterative JSON parser with standard Python iterator interfaces." 示例代码&…

【C++ —— 多态】

C —— 多态 多态的概念多态的定义和实现多态的构成条件虚函数虚函数的重写虚函数重写的两个例外协变&#xff1a;析构函数的重写 C11 override和final重载、覆盖(重写)、隐藏(重定义)的对比 抽象类概念接口继承和实现继承 多态的继承虚函数表多态的原理动态绑定和静态绑定 单继…

VTK 的可视化方法:Glyph

VTK 的可视化方法&#xff1a;Glyph VTK 的可视化方法&#xff1a;Glyph标量、向量、张量将多边形数据的采集点法向量标记成锥形符号参考 VTK 的可视化方法&#xff1a;Glyph 模型的法向量数据是向量数据&#xff0c;因此法向量不能像前面讲到的通过颜色映射来显示。但是可以通…

25 JavaScript学习:var let const

JavaScript全局变量 JavaScript中全局变量存在多种情况和定义方式&#xff0c;下面详细解释并提供相应的举例&#xff1a; 使用var关键字声明的全局变量&#xff1a; var globalVar "我是全局变量";未使用var关键字声明的变量会成为全局变量&#xff08;不推荐使用&…

【前端】-【防止接口重复请求】

文章目录 需求实现方案方案一方案二方案三 需求 对整个的项目都做一下接口防止重复请求的处理 实现方案 方案一 思路&#xff1a;通过使用axios拦截器&#xff0c;在请求拦截器中开启全屏Loading&#xff0c;然后在响应拦截器中将Loading关闭。 代码&#xff1a; 问题&…

(刷题记录2)随机链表的复制

[刷题记录2]随机链表的复制 题目信息&#xff1a;题目思路(环境来自力扣OJ的C语言)&#xff1a;复杂度&#xff1a;代码和解释&#xff1a;1.遍历一遍原链表的同时&#xff0c;在每个原节点后面插入一个相同的新节点&#xff0c;共插入 n 个新节点。2.利用两者联系&#xff0c;…

神奇的Vue3 - 组件探索

神奇的Vue3 第一章 神奇的Vue3—基础篇 第二章 神奇的Vue3—Pinia 文章目录 神奇的Vue3了解组件一、注册组件1. 全局注册​2. 局部注册3. 组件命名 二、属性详解1. Props&#xff08;1&#xff09;基础使用方法&#xff08;2&#xff09;数据流向&#xff1a;单项绑定原则&…

ThreeJS:Mesh网格与三维变换

Mesh网格 ThreeJS中&#xff0c;Mesh表示基于以三角形为多边形网格(polygon mesh)的物体的类&#xff0c;同时也作为其它类的基类。 通过Mesh网格&#xff0c;我们可以组合Geometry几何体与Material材质属性&#xff0c;在3D世界中&#xff0c;定义一个物体。例如&#xff1a;之…

vue2(4)之scoped解决样式冲突/组件通信/非父子通信/ref和$refs/异步更新/.sync/事件总线/provide和inject

vue2 一、学习目标1.组件的三大组成部分&#xff08;结构/样式/逻辑&#xff09;2.组件通信3.综合案例&#xff1a;小黑记事本&#xff08;组件版&#xff09;4.进阶语法 二、scoped解决样式冲突**1.默认情况**&#xff1a;2.代码演示3.scoped原理4.总结 三、data必须是一个函数…