【王喆-推荐系统】线上服务篇-(task2)用Redis存储特征

举报
野猪佩奇996 发表于 2022/01/22 23:30:31 2022/01/22
【摘要】 学习总结 本次task学习推荐系统的存储模块(遵循“分级存储”原则,在开销和性能中平衡;具体而言:把越频繁访问的数据放到越快的数据库甚至缓存中,把海量的全量数据放到廉价但是查询速度较慢的数据库中)和对S...

学习总结

  • 本次task学习推荐系统的存储模块(遵循“分级存储”原则,在开销和性能中平衡;具体而言:把越频繁访问的数据放到越快的数据库甚至缓存中,把海量的全量数据放到廉价但是查询速度较慢的数据库中)和对Sparrow Recsys中的redis实践。其实和计算机的存储设备一样(分为寄存器、Cache、内存、SSD等金字塔形)。我们麻雀推荐系统的存储结构如下:

在这里插入图片描述

  • 使用内存数据库redis(两大特点:key-value形式存储;纯内存数据库)。在具体的特征存取过程中,要熟悉利用 redis 执行 SETGET 等 Redis 常用操作的方法。
  • 搭建一套完整的推荐服务,有3个大问题:用 Jetty Server 搭建推荐服务器问题、用 Redis 解决特征存储的问题,还有下个task的线上服务召回层的设计。

在这里插入图片描述

一、推荐系统的存储模块

大数据平台的数据处理是离线的,而上次task我们搭建Jetty推荐服务器是线上环境,本次task的内容是让离线的特征数据导入到线上供服务器使用,所使用的redis数据库存储特征。
在这里插入图片描述

图4 推荐系统技术架构示意图,源自王喆课程

Netflix 采用了非常经典的 Offline、Nearline、Online 三层推荐系统架构。架构图中最核心的位置就是在图中用红框标出的部分,它们是三个数据库 Cassandra、MySQL 和 EVcache,用来存储特征和模型参数。
在这里插入图片描述

图1 Netflix推荐系统架构中的特征与模型数据库

并非简单的将推荐特征和模型用数据库存储起来,然后给推荐服务器写几个SQL让它取出数据就可以。之所以Netflix搞这三个数据库是因为:
(1)速度要快:由于线上的 QPS 压力巨大,每次有推荐请求到来,推荐服务器都需要把相关的特征取出。这就要求推荐服务器一定要“快”。
(2)数据很大:多用户和物品特征所需的存储量。

几乎所有的工业级推荐系统都会把特征的存储做成分级存储,把越频繁访问的数据放到越快的数据库甚至缓存中,把海量的全量数据放到便宜但是查询速度较慢的数据库中。

举栗子:如果你把特征数据放到基于 HDFS 的 HBase 中,虽然你可以轻松放下所有的特征数据,但要让你的推荐服务器直接访问 HBase 进行特征查询,等到查询完成,这边用户的请求早就超时中断了,而 Netflix 的三个数据库正好满足了这样分级存储的需求。

在这里插入图片描述

图2 分级存储的设计

比如说,Netflix 使用的 Cassandra,它作为流行的 NoSQL 数据库,具备大数据存储的能力,
(1)但为支持推荐服务器高 QPS 的需求,还需要把最常用的特征和模型参数存入 EVcache 这类内存数据库。
(2)而对于更常用的数据,我们可以把它们存储在 Guava Cache 等服务器内部缓存,甚至是服务器的内存中。
(3)而对于 MySQL 来说,由于它是一个强一致性的关系型数据库,一般存储的是比较关键的要求强一致性的信息,比如物品是否可以被推荐这种控制类的信息,物品分类的层级关系,用户的注册信息等等。这类信息一般是由推荐服务器进行阶段性的拉取,或者利用分级缓存进行阶段性的更新,避免因为过于频繁的访问压垮 MySQL。

强一致性:可以理解为在任意时刻,所有节点中的数据是一样的 弱一致性:相当于异步,系统并不保证续进程或者线程的访问都会返回最新的更新过的值

推荐系统存储模块的设计原则:“分级存储,把越频繁访问的数据放到越快的数据库甚至缓存中,把海量的全量数据放到廉价但是查询速度较慢的数据库中”。

二、SparrowRecsys 的存储系统

麻雀推荐系统的存储模块:使用基础的文件系统保存全量的离线特征和模型数据,用 Redis 保存线上所需特征和模型数据,使用服务器内存缓存频繁访问的特征。

在实现技术方案之前,对于问题的整体分析永远都是重要的。我们需要先确定具体的存储方案,这个方案必须精确到哪级存储对应哪些具体特征和模型数据
在这里插入图片描述

图3 特征和模型数据

根据上面的特征数据,做一个初步的分析:

  • 首先,用户特征的总数比较大,它们很难全部载入到服务器内存中,所以把用户特征载入到 Redis 之类的内存数据库中。
  • 其次,物品特征的总数比较小,而且每次用户请求,一般只会用到一个用户的特征,但为了物品排序,推荐服务器需要访问几乎所有候选物品的特征。所以可以把所有物品特征阶段性地载入到服务器内存中,大大减少 Redis 的线上压力。
  • 最后,我们还要找一个地方去存储特征历史数据、样本数据等体量比较大,但不要求实时获取的数据。可以放到分布式文件系统(单机环境下以本机文件系统为例)——因为类似 HDFS 之类的分布式文件系统具有近乎无限的存储空间,因此可以把每次处理的全量特征,每次训练的 Embedding 全部保存到分布式文件系统中,方便离线评估时使用

在这里插入图片描述

图4 SparrowRecsys的存储方案

文件系统的存储操作较为简单,在 SparrowRecsys 中就是利用 Spark 的输出功能实现的。而服务器(如我们上次task搭的Jetty服务器)内部的存储操作主要是跟 Redis 进行交互,下面学习Redis 的特性以及写入和读取方法。

三、Redis 基础知识

Redis 是当今业界最主流的内存数据库。

3.1 所有的数据都以 Key-value 的形式存储。

Key 只能是字符串,value 可支持的数据结构包括 string(字符串)、list(链表)、set(集合)、zset(有序集合) 和 hash(哈希)。这个特点决定了 Redis 的使用方式,无论是存储还是获取,都应该以键值对的形式进行,并且根据你的数据特点,设计值的数据结构

3.2 所有的数据都存储在内存中

磁盘只在持久化备份或恢复数据时起作用。
这个特点决定了 Redis 的特性:一是 QPS 峰值可以很高,二是数据易丢失,所以我们在维护 Redis 时要充分考虑数据的备份问题,或者说,不应该把关键的业务数据唯一地放到 Redis 中。但对于可恢复,不关乎关键业务逻辑的推荐特征数据,就非常适合利用 Redis 提供高效的存储和查询服务。

QPS:每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。

四、Sparrow Recsys中的Redis实践

在实际的 Sparrow Recsys 的 Redis 部分中,用到了 Redis 最基本的操作,set、get 和 keys,value 的数据类型用到了 string。

4.1 安装 Redis

Redis 的安装过程在 linux/Unix 环境下安装参考:(http://www.redis.cn/download.html)。
Windows 环境下的安装:(https://www.cnblogs.com/liuqingzheng/p/9831331.html)。

在启动 Redis 之后,如果没有特殊的设置,Redis 服务会默认运行在 6379 端口,没有特殊情况保留这个默认的设置就可以了,Sparrow RecSys 也是默认从 6379 端口存储和读取 Redis 数据的。
下图即安装好redis后,在命令行中进入redis的目录,然后启动对应的redis客户端程序,显示正确端口号,表示服务已经启动。然后可以测试一下读写:
在这里插入图片描述

4.2 运行离线程序,通过 jedis 客户端写入 Redis

在 Redis 运行起来之后,我们就可以在离线 Spark 环境下把特征数据写入 Redis。这里以(【王喆-深度学习推荐系统实战】特征工程篇-(task5)Embedding实践)中生成的 Embedding 数据为例,来实现 Redis 的特征存储过程。

实际的过程:
(1)首先利用最常用的 Redis Java 客户端 Jedis 生成 redisClient;
(2)然后遍历训练好的 Embedding 向量;
(3)将 Embedding 向量以字符串的形式存入 Redis,并设置过期时间(ttl)。

具体实现请参考下面的代码(代码参考 com.wzhe.sparrowrecsys.offline.spark.featureeng.Embedding 中的 trainItem2vec 函数):

if (saveToRedis) {
  //创建redis client
  val redisClient = new Jedis(redisEndpoint, redisPort)
  val params = SetParams.setParams()
  //设置ttl为24小时
  params.ex(60 * 60 * 24)
  //遍历存储embedding向量
  for (movieId <- model.getVectors.keys) {
    //key的形式为前缀+movieId,例如i2vEmb:361
    //value的形式是由Embedding向量生成的字符串,例如 "0.1693846 0.2964318 -0.13044095 0.37574086 0.55175656 0.03217995 1.327348 -0.81346786 0.45146862 0.49406642"
    redisClient.set(redisKeyPrefix + ":" + movieId, model.getVectors(movieId).mkString(" "), params)
  }
  //关闭客户端连接
  redisClient.close()
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

4.3 在推荐服务器中把 Redis 数据读取出来

刚才的存储方案说了,把所有物品 Embedding 阶段性地全部缓存在服务器内部,用户 Embedding 则进行实时查询。缓存物品 Embedding 的代码如下:

就是先用 keys 操作把所有物品 Embedding 前缀的键找出,然后依次将 Embedding 载入内存。

//创建redis client
Jedis redisClient = new Jedis(REDIS_END_POINT, REDIS_PORT);
//查询出所有以embKey为前缀的数据
Set<String> movieEmbKeys = redisClient.keys(embKey + "*");
int validEmbCount = 0;
//遍历查出的key
for (String movieEmbKey : movieEmbKeys){
    String movieId = movieEmbKey.split(":")[1];
    Movie m = getMovieById(Integer.parseInt(movieId));
    if (null == m) {
        continue;
    }
    //用redisClient的get方法查询出key对应的value,再set到内存中的movie结构中
    m.setEmb(parseEmbStr(redisClient.get(movieEmbKey)));
    validEmbCount++;
}
redisClient.close();

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在具体为用户推荐的过程中,我们再利用相似的接口查询出用户的 Embedding,与内存中的 Embedding 进行相似度的计算,就可以得到最终的推荐列表了。

具体操作:
(1)安装好 Redis;
(2)运行 SparrowRecsys 中 Offline 部分 Embedding 主函数,先把物品和用户 Embedding 生成并且插入 Redis(注意把 saveToRedis 变量改为 true)。
(3)然后再运行 Online 部分的 RecSysServer,看一下推荐服务器有没有正确地从 Redis 中读出物品和用户 Embedding 并产生正确的推荐结果(注意,记得要把 util.Config 中的 EMB_DATA_SOURCE 配置改为 DATA_SOURCE_REDIS)。

五、数据库分类

除了 Redis,还有多种不同的缓存和数据库,如 Cassandra、EVcache、GuavaCache 等等,都是业界非常流行的存储特征的工具。在掌握了特征存储的基本原则之后,了解每个数据库的不同和它们最合适的应用场景。
取决于使用哪个数据库的因素有:
数据结构;查询模式;需要处理的数量或规模。

5.1 SQL

结构取决于我们用来确定将使用哪种类型的因素
如果需要ACID属性,则需要使用关系DBMS。如MySQL,Oracle,Postgres等
付款系统主要需要交易和原子性。
强一致性主要可以通过SQL数据库来实现。

5.2 NoSQL

假设正在尝试为诸如Amazon之类的商品建立目录,想在其中存储有关具有各种属性的不同产品的信息。例如,不同产品的这些属性通常不同。药品将有有效期,但冰箱将具有能量等级。
在这种情况下,我们的数据不能表示为表格。这意味着我们需要使用NoSQL数据库。
如果需要BASE属性,则可以使用非关系数据库前进。
对于最终一致性,我们可以使用NoSQL数据库
最常见的NoSQL DB是MongoDB,Cassandra,DynamoDB.

(1)基于文档(基于查询的数据)

如果我们拥有大量数据-不仅是数量,而且还有各种各样的属性-并且我们需要运行各种各样的查询,则需要使用一种称为Document DB的东西。
使用文档数据库,随机查询或其他查询最有效
Couchbase或MongoDB是一些常用的文档数据库

(2)图形存储

这些类型的数据库使数据可视化更加容易。
它们非常善于在节点的帮助下存储不同数据点之间的关系。
图形存储可能不是最可扩展的数据库。
但是,它们在防止欺诈等使用案例方面效率很高。
图形数据库的常见示例是Neo4j 和 JanusGraph。

(3)Key-value 存储

这些都是非常简单的数据库管理系统,存储关键值对。
最终目标是快速获取基本数据
这些类型的数据库的常见用例是排行榜和购物车数据。
redis是流行的key value 存储。

(4)柱状数据库(不断增加的数据)

有限的查询种类,但是数据库的大小持续快速增加。例如订单,目录
现在,Uber司机的数量将逐日增加,即每天收集的数据也会逐日增加。这成为越来越多的数据。
在这种情况下,我们使用诸如Cassandra或HBase之类的列式数据库。

六、作业

课程中存储 Embedding 的方式还有优化的空间吗?除了 string,我们是不是还可以用其他 Redis value 的数据结构存储 Embedding 数据,那从效率的角度考虑,使用 string 和使用其他数据结构的优缺点有哪些?为什么?

(1)redis keys命令不能用在生产环境中,如果数量过大效率十分低,导致redis长时间堵塞在keys上。生产环境我们一般选择提前载入一些warm up物品id的方式载入物品embedding。

(2)Redis value 可以用pb格式(protobuf)存储, 存储上节省空间. 解析起来相比string, cpu的效率也应该会更高

(3)1.redis这种缓存中尽量放活跃的数据,存放全量的embedding数据,对内存消耗太大。尤其物品库,用户embedding特别多的情况下。
2.分布式kv可以做这种embedding的存储
3.关于embedding的编码可以用pb来解决。embedding维度太大的时候,redis里的数据结构占用空间会变大,因为除了embedding本身的空间,还有数据结构本身占用的空间。

七、课后答疑

(1)文中的两部分redis相关的代码,可以在Maven项目中找到吗?可不可以提供以下路径信息方便找到?
参照 com.wzhe.sparrowrecsys.offline.spark.embedding.Embedding中的trainItem2vec函数
以及com.wzhe.sparrowrecsys.online.datamanager.DataManager中的loadMovieEmb函数

(2)在IntelliJ的Maven porject里用到的工具比如spark, redis, 这些需要我们额外下载安装到电脑上吗?还是说在Maven项目中已经通过代码添加依赖,就已经完成了安装?

spark本质上是一个java lib,所以可以被maven安装依赖。
redis是一个数据库,需要按照文中的方式安装到电脑上。

Reference

(1)https://github.com/wzhe06/Reco-papers
(2)《深度学习推荐系统实战》,王喆
(3)常见数据库介绍和使用场景

文章来源: andyguo.blog.csdn.net,作者:山顶夕景,版权归原作者所有,如需转载,请联系作者。

原文链接:andyguo.blog.csdn.net/article/details/120979624

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。