本文来自网易云社区

作者:李勇

  • 背景

网易美学首页除了banner和四个固定位,大部分都是通过算法推荐获取的内容,其中的内容包括心得、合辑、视频及问答等。现在需要实现的是当推荐内容在用户屏幕曝光后(即用户一个屏幕内的内容),那么这些内容在一定时间内,如两周内都不能再出现,因此需要对这些已经曝光过的内容进行过滤。首页内容如下图所示:

  • 实现方案

目前的实现方案是客户端对用户曝光的内容进行采集,然后通过DA的SDK将这些数据发送到Kafka Broker,然后再通过Kafka消费者去消费并解析这些数据,再将解析后的数据同步到Redis中。当用户再次获取数据时算法端会从Redis中获取需要过滤的数据,再将最终推荐内容返回服务端,然后服务端去业务数据库查询算法返回的数据对应的完整信息,最后将完整信息返回给客户端,客户端对数据进行渲染展现给用户。

具体实现分为两道工序:一个是曝光数据的收集,另一个是对曝光数据的过滤。

  • 曝光数据的收集

数据的收集步骤如下:

  1. 客户端收集用户的曝光内容;

  2. 客户端通过DA的SDK将收集到的用户曝光内容发送到Kafka集群;

  3. 实时计算工程实时拉取Kafka的内容;

  4. 提取出曝光内容再进行解析;

  5. 将解析后的内容以Sorted Set数据结构维护到Redis中。

曝光数据的收集时序图如下图所示:

  • 曝光数据的过滤

数据的过滤步骤如下:

  1. 用户使用客户端刷新首页数据;

  2. 客户端向服务端请求首页数据;

  3. 服务端在向算法端发送请求获取首页数据;

  4. 算法端根据服务端发送的用户个人信息,如用户id,用户设备id等信息,计算出推荐内容;

  5. 算法端从Redis中获取该用户最近两周的曝光数据进行过滤;

  6. 算法端将过滤后的推荐内容id和内容类型返回给服务端;

  7. 服务端根据算法返回的内容id和内容类型从数据库中查询详细信息并且组装数据;

  8. 服务端将最终的数据返回给客户端;

  9. 客户端受到服务端的数据将其展现给用户。

曝光数据的过滤时序图如下图所示:

  • 为何使用Kafka和Redis?

Kafka是有LinkedIn开源的,是一个快速、可扩展的、高吞吐、可容错的分布式发布订阅消息系统,适合大规模消息处理场景。由于用户的曝光数据量比较大,因此使用Kafka作为消息系统。Kafka的优点有以下几方面:

  1. 高性能:每秒钟能够处理数以千计生产者生成的消息。

  2. 分布式:消息可以来自数以千计的服务,使用分布式处理这些数据。

  3. 持久性:Kafka会将数据持久化到硬盘上,避免数据丢失。

  4. 高扩展性:当容量不足时,Kafka可以简单的通过增加服务器进行横向扩展。

Redis是一个高性能的key-value数据库,处理数据的效率高,考虑到首页获取数据的响应要求较高,因此使用Redis作为缓存数据库。Redis的优点有以下几方面:

  1. 性能极高:Redis的读取速度是11万次/s,写的速度是8.1万/s。

  2. 数据类型丰富:Redis相比其他key-value数据库,能够支持很多丰富的数据结构如Strings、Hashes、Lists、Sets和Sorted Sets。

  3. 原子性:Redis支持事务,能够对所有操作进行原子操作。

  4. 数据持久化:Redis支持数据的持久化,可以将内存中的数据保存到磁盘中,避免丢失。

  • 使用Redis的Sorted Sets

Redis与其他key-value产品的一个不同点是它提供了丰富的数据结构类型,包括Strings、Hashes、Lists、Sets和Sorted Sets。用户曝光过的数据使用Soretd Sets数据结构进行维护,Sorted Sets与Sets类似之处是它不允许有重复的成员存在,不同的是每个元素都会关联一个double类型的分数。通过分数,Redis可以对集合中的成员进行从小到大的排序。Sorted Set的成员是唯一的,但是分数却是可以重复的,集合是通过哈希表实现的,因此,对Sorted Set进行添加、删除和查找时的时间复杂度都是O(1)。Sorted Sets中最大的成员数为2^32-1(4294967295,差不多40多亿),score是一个64位浮点类型,范围在-9007199254740992到9007199254740992之间。

既然使用Sorted Sets数据结构作为用户曝光数据的载体,那么就需要确定Sorted Sets的key,score及value分别存储什么数据?由于key是唯一的,而每个用户的userId都是唯一的,因此可以用userId作为key来标识,但因为Redis不止一个工程用到,可能其他工程也共用该Redis集群,因此将使用“expose:userId”作为key。因为只保留用户最近两周的曝光数据,所以需要定时去删除两周前的数据,如果当前时间戳作为score,可以调用Redis的zremrangebyscore命令对两周前的数据进行清理。value结构保存用户的曝光具体数据,如内容的id,内容的类型,曝光的时间戳,手机的型号等信息,由于value是字符串类型,因此将这些数据转化成json格式封装。

  • 使用到的Sorted Sets相关命令

Sorted Sets相关命令有20多种,包括了添加、删除、排序、查询、并集等命令,本方案中使用到了3种命令:

  1. zadd:zadd命令用于添加一个或多个成员元素及其分数到有序集合中,如果某一个成员已经在Sorted Sets中存在,那么更新这个成员的分数值,并通过重新插入该成员元素,来确保成员能够在正确的位置上。zadd命令的基本语法是zadd key_name score1 value1 score2 value2 … scoren valuen。具体命令如zadd 'expose:123456' 1505524199461 'This is value',表示将key为'expose:123456',score为1505524199461,value为'This is value'的数据添加到Redis中。

  2. zremrangebyscore:zremrangebyscore命令用于移除Sorted Sets合中,指定分数区间内的成员。zremrangebyscore命令的基本语法是zremrangebyscore key_name min max,具体命令如:zremrangebyscore 'expose:123456' 1505524199461 1505634107000,表示将key为'expose:123456',并且1505524199461≤core≤1505634107000的数据删除。

  3. zrevrangebyscore:zrevrangebyscore命令用于返回Sorted Sets中指定分数区间内的所有成员数据。并且数据是按照分数值递减,即从大到小的顺序排列,让分数相同时,成员的排序是根据字典序逆向排序。具体命令如:zrevrangebyscore 'expose:123456' 1505524199461 1505524199461,表示查询key为'expose:123456',并且1505524199461≤core≤1505524199461的所有数据,返回的数据根据score逆序排序。

  • Sorted Sets的原理

Sorted Sets内部是由字典(dict)和跳跃表(Skip List)来保证数据的存储有序。dict存放成员到score的映射,使用dict来实现的主要目的是为了保证查询复杂度为O(1),因为其内部是基于哈希表实现的。跳跃表则存放所有的成员,它的效率可以和平衡树相当,时间复杂度为O(logn),排序依据dict中的score,使用跳跃表的结构可以获得比较高的查询效率并且在实现上比较简单。

Redis的数据结构如下所示:

//有序集数据结构typedef struct zset {dict *dict;//字典存放成员和score的映射zskiplist *zsl;
} zset;

由上述结构可知,zset有两个成员:dict和zskiplist,其中的dict保存的是成员与score的映射,比如一个zset中存放了一个value为“abc”,score为1505524199000的数据,则dict中的key存放“abc”,value存放1505524199000。

跳跃表由zskiplist和zskiplistNode组成,它们的数据结构如下所示:

//定义跳表的基本数据节点typedef struct zskiplistNode {robj *obj; // zset valuedouble score;// zset scorestruct zskiplistNode *backward;//后向指针struct zskiplistLevel {//前向指针struct zskiplistNode *forward;        unsigned int span;} level[];
} zskiplistNode;//跳跃表结构typedef struct zskiplist {    struct zskiplistNode *header, *tail;    unsigned long length;    int level;
} zskiplist;

可以将上述的结构转化成图形,如下图所示:

从上图可以看到header指针指向一个具有32层的表头节点,而定义成32层,因为理论上来说,2^32-1个元素的查询最优,并且2^32-1-1=4294967295,对于大部分应用来说这么多元素已经足够了。位于图片最左边的是zskiplist结构,该结构包含的属性有:

  1. header:指向跳跃表的表头节点。

  2. tail:指向跳跃表表尾的节点。

  3. level:记录目前跳跃表中,层数最大的那个节点的层次(不包括表头节点),如上图层数最大的那个节点是表尾节点,它有4层,因此level=4。

  4. length:跳跃表所包含的节点数(不包括表头节点),上图中不包括表头,右侧有3个节点,因此length=3。

zskiplist右侧的四个元素为zskiplistNode结构,该机构包含以下属性:

  1. level(层):节点中L1、L2等元素,L1代表第一层,L2代表第二次,以此类推。每层都有两个属性,前进指针(forward)和跨度(span),前进指针用于向后访问其他节点,跨度则记录前进指针所执行节点与当前节点的距离,用于记录节点在跳跃表中的排名。

  2. backward(后退指针):节点中的BW表示后退指针,它指向当前节点的前一个节点,后退指针用于从表尾向表头遍历。

  3. score(分值):上图节点中的20.0、40.0、40.0代表各节点的分值,score是一个double类型的浮点数,跳跃表中的所有节点都按照score从小到大排序。

  4. obj(成员对象):上图节点中的Object1、Object2等代表节点所保存的成员对象,它是一个指针,指向一个字符串对象。

跳跃表的遍历有个规则就是总是从高层开始往低层查找。例如当需要查找分值为40.0,成员对象为Object2时,查找的过程如下:

  1. 先从zskiplist中查找表头的位置;

  2. 再从表头的最上层查找,即先从L32开始查找,由于L32指向null,因此继续查找表头的L31,而L31依然指向null,依次往低层查找,直到找到L4,然后通过L4中的前进指针中到达Object3节点;

  3. 比较Object3节点的分数是否为40.0,再比较Object3对象是否为目标对象,继续往下层遍历;

  4. 通过表头的L3继续遍历,达到Object2,最后比较发现为目标对象,结束查找。

  • Sorted Sets的其他应用场景

Sorted Sets的应用场景很多,下面介绍几种常见的场景:

  1. 关注列表和被关注者列表:可以设计两种队列,一种队列用于记录用户的关注列表,另一种用于记录关注者的列表。其中有序集合的成员为用户Id,而分值则记录了用户开始关注某人或者被某人关注的时间戳。key可以为队列标识符+当前用户id。

  2. 游戏排行榜:使用排行榜名称作为key,用户的游戏分数作为score,然后使用用户的id作为value,通过zrevrange命令(因为大部分排行榜都是按照分数由高到底排序,而有序集是从小到大排序的,因此需要使用zrevrange命令)可以很快查出用户的排名信息。

网易云大礼包:https://www.163yun.com/gift

本文来自网易云社区,经作者李勇授权发布。

相关文章:
【推荐】 关于验证码,你需要了解这些

转载于:https://www.cnblogs.com/163yun/p/9578436.html

基于Redis+Kafka的首页曝光过滤方案相关推荐

  1. activemq消息丢失_基于Redis实现消息队列的典型方案

    基于Redis实现消息队列典型方案 1 概述 2 基于List的 LPUSH+BRPOP 的实现 3 PUB/SUB,订阅/发布模式 4 基于SortedSet有序集合的实现 5 基于 Stream ...

  2. 基于Redis实现延时队列的优化方案

    一.延时队列的应用 近期在开发部门的新项目,其中有个关键功能就是智能推送,即根据用户行为在特定的时间点向用户推送相应的提醒消息,比如以下业务场景: 在用户点击充值项后,半小时内未充值,向用户推送充值未 ...

  3. 基于MM2的跨IDC kafka热备多活方案

    本文主要介绍360商业化在跨IDC kafka热备方面的实践, 接下来会按以下顺序介绍各个议题: MM2简介 跨IDC kafka热备多活方案 产品化 需要注意的风险 MM2简介 在介绍MM2之前先谈 ...

  4. 基于Redis的分布式锁实现方案

    1.分布式锁简介 简单来说,分布式锁是针对集群环境下多台机器竞争公共资源提出的方案. 单机环境下,线程共享堆内存,jdk提供了同步机制来应对资源竞争,比如synchronized关键字,AQS队列同步 ...

  5. 基于贝叶斯算法的邮件过滤管理系统的设计和实现(Vue+SpringBoot)

    作者主页:Designer 小郑 作者简介:Java全栈软件工程师一枚,来自浙江宁波,负责开发管理公司OA项目,专注软件前后端开发(Vue.SpringBoot和微信小程序).系统定制.远程技术指导. ...

  6. 电商推荐系统(上):推荐系统架构、数据模型、离线统计与机器学习推荐、历史热门商品、最近热门商品、商品平均得分统计推荐、基于隐语义模型的协同过滤推荐、用户商品推荐列表、商品相似度矩阵、模型评估和参数选取

    文章目录 第1章 项目体系架构设计 1.1 项目系统架构 1.2 项目数据流程 1.3 数据模型 第2章 工具环境搭建 2.1 MongoDB(单节点)环境配置 2.2 Redis(单节点)环境配置 ...

  7. 基于 Redis Stream 的消息队列

    文章目录 基于 Redis Stream 的消息队列 消息队列相关命令 消费者组相关命令 如何使用Stream消息队列 生产者写入消息 - XADD 消费者读取消息 - XGROUP 创建消费者组 - ...

  8. Redis 消息队列的三种方案选型

    文章目录 Redis 消息队列的三种方案选型 消息队列(Message Queue,简称 MQ) 消息队列使用场景 Redis 消息队列应用背景,选型思考 Redis消息队列发展历程 在Redis中提 ...

  9. nx set 怎么实现的原子性_基于Redis的分布式锁实现

    前言 本篇文章主要介绍基于Redis的分布式锁实现到底是怎么一回事,其中参考了许多大佬写的文章,算是对分布式锁做一个总结 分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问 ...

最新文章

  1. 自定义leftBarButtonItem的button
  2. 了解Exchange server 2010角色
  3. 【安全牛学习笔记】其他途径
  4. php5.4版本不爆错误,PHP5.4以上版本出现的错误:Fatal error: Call-time pass-by-reference has been removed | 严佳冬...
  5. POJ 1840 Eqs 解方程式, 水题 难度:0
  6. 日常API之QQ登录
  7. c语言显示数字p1=0x5b,模拟计算器数字输入及显示
  8. 应用多元统计分析高惠璇pdf_EViews统计分析与应用pdf txt mobi下载及读书笔记
  9. 30封外贸邮件模板,外贸人速收藏!
  10. 团队管理之—— 大项目:把握关键点,谋定而后动
  11. vue怎么改logo_vue项目添加网页logo
  12. python求斜边上的高_关于如何求直角三角形斜边上的高
  13. 【计算机网络】第六部分 应用层(25) 域名空间
  14. Solana中的account
  15. 相机标定(Camera calibration)原理、步骤
  16. 华为使用计算机投屏要打开什么,华为Mate20手机怎么投屏到电脑上呢
  17. Ubuntu 访问移动硬盘/U盘时弹出“An operation is pending“
  18. FPGA零基础学习:数字通信中的电压标准
  19. 抖音如何开直播?直播需要做什么准备?
  20. 20230210组会总结

热门文章

  1. 《并行计算的编程模型》一3.1 引言
  2. 《人工智能:计算Agent基础》——1.5 复杂性维度
  3. Nodejs实现的一个静态服务器例子
  4. Android 中shape的使用(圆角矩形)
  5. [笔记] SRAM Controller
  6. 庆祝.Net BI团队成立!
  7. 并发设计模式之Guarded Suspension模式
  8. LeetCode 279. Perfect Squares
  9. win10计算机桌面显示器,Win10电脑屏幕分为四屏投影测试
  10. mysql闩_Oracle闩:Cache Buffers chains