如何使用Redis实现内容推送功能

news2024/11/26 3:51:20

导读

在日常使用中,我们经常能看见内容推送功能。

常见的场景有,比如你在bilibili关注了某个up主,当up主发布视频后,就会推送到你的收件箱或者是动态中,让粉丝能够及时得知所关注的人发布了内容。

又比如朋友圈,也是按照时间的顺序,将好友发布的动态推送给你,如果你下拉刷新,就可以获取到新的好友的动态。

想知道这些功能是如何实现的吗?接着往下看吧!

这个需求,其实我们又把他叫做Feed流,关注推送也叫做Feed流,直译为投喂。为用户持续的提供“沉浸式”的体验,通过无限下拉刷新获取新的信息。

对于传统模式的内容解锁:我们是需要用户去通过搜索引擎或者是其他的方式去解锁想要看的内容

 而对于Feed模式,则是主动推送给用户内容

 Feed流的两种模式

Feed流的实现有两种模式:

Timeline:不做内容筛选,简单的按照内容发布时间排序,常用于好友或关注。例如朋友圈

  • 优点:信息全面,不会有缺失。并且实现也相对简单

  • 缺点:信息噪音较多,用户不一定感兴趣,内容获取效率低

智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户

  • 优点:投喂用户感兴趣信息,用户粘度很高,容易沉迷

  • 缺点:如果算法不精准,可能起到反作用

我们本次关注-推送的功能,采用的就是Timeline的方式,只需要拿到我们关注用户的信息,然后按照时间排序即可。

该模式的实现方案有三种:拉模式,推模式,推拉结合

拉模式(读扩散)

该模式的核心含义就是:当张三和李四和王五发了消息后,都会保存在自己的邮箱中,假设赵六要读取信息,那么他会从读取他自己的收件箱,此时系统会从他关注的人群中,把他关注人的信息全部都进行拉取,然后在进行排序

优点:比较节约空间,因为赵六在读信息时,并没有重复读取,而且读取完之后可以把他的收件箱进行清除。

缺点:比较延迟,当用户读取数据时才去关注的人里边去读取数据,假设用户关注了大量的用户,那么此时就会拉取海量的内容,对服务器压力巨大。

推模式(写扩散)

推模式是没有写邮箱的,当张三写了一个内容,此时会主动的把张三写的内容发送到他的粉丝收件箱中去,假设此时李四再来读取,就不用再去临时拉取了

优点:时效快,不用临时拉取

缺点:内存压力大,假设一个大V写信息,很多人关注他, 就会写很多分数据到粉丝那边去

推拉结合模式

推拉模式是一个折中的方案,站在发件人这一段,如果是个普通的人,那么我们采用写扩散的方式,直接把数据写入到他的粉丝中去,因为普通的人他的粉丝关注量比较小,所以这样做没有压力,如果是大V,那么他是直接将数据先写入到一份到发件箱里边去,然后再直接写一份到活跃粉丝收件箱里边去,现在站在收件人这端来看,如果是活跃粉丝,那么大V和普通的人发的都会直接写入到自己收件箱里边来,而如果是普通的粉丝,由于他们上线不是很频繁,所以等他们上线时,再从发件箱里边去拉信息。

Redis实现思路

我们使用Redis来实现Feed流,就需要选择合适的数据结构。

Feed流有两种特点,一是需要进行时间排序,二是数据不断在更新变化,即角标不断变化。

可以进行排序的数据结构有list,sortedset,都满足第一点需求。

feed流推送过来的内容,我们通常不是一次性查询所有内容,而是需要分页查询。传统的分页查询使用page和size来圈定范围,但是feed流不断进行内容推送,数据不断进行更新(类似栈结构,时间戳越大的在上头),数据角标不断变化,使用传统的分页模式就会导致内容的重复查询。所以需要使用滚动查询,滚动查询指的是记录上一次所查询的位置,下次查询时接着往下查询。所以使用sortedset结构我们可以记录每次查询最小的时间戳,下次查询的时候再找比这个时间戳更小的,就实现了滚动查询的效果。而list结构只能通过角标查询。所以我们最终选择sortedset结构。

 

 

流程

1.保存笔记并推送

在用户发布笔记保存到数据库的同时,也需要将笔记id推送到粉丝的收件箱中,即sortedSet集合中。key是“feed:粉丝id”,value是blog的id,score是当前时间戳

savaBlog():

  1. 验证用户是否登录

  2. 验证blog是否完整(发布者id,内容,关联商户)

  3. 保存blog到数据库

  4. 推送blog给粉丝

    1. 获取登录用户粉丝集合

    2. 循环遍历每个粉丝,保存blogId到粉丝sortedset集合

  5. 返回blog的id

2.获取推送笔记

如何使用Redis的sortedSet结构来实现滚动分页,获取推送的笔记内容?

ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]

命令解释:根据score值进行降序排序,查询count条集合中score范围为max到min的值

  • key:键

  • max,min:查询max到min范围的值

  • [WITHSCORES ]:是否带上score值

  • offset:偏移量,表示对于max值的偏移量,0表示对max偏移量为0,即取max值

  • count:查询几条

实现滚动分页,我们将时间戳作为score值,对score值进行降序排序就可以根据发布时间对笔记进行排序。

滚动分页的基本思想是:记录上一次查询的最小时间戳,下次查询时从所记录的位置开始查询。如果是第一次查询,max值就是当前时间戳,offset值取0,min值取0(因为时间戳最小为0,不可能为负数);如果不是第一次查询,max值就是上一次查询的最小时间戳,offset值取1(即不包括上一次查询的位置,否则会重复查询),min值取0。

需要注意的是,offset的值第一次查询时为0,之后的查询不一定为1,因为有可能会出现时间戳相等的情况:

比如score值降序排序后为5 2 2 3 2 2,每次查询3条记录。

那么第一次所查到的score是5 2 2,此次记录的最小时间戳是2。下次查询时,redis会查找到score值为2的位置,即第一个2,然后根据偏移量进行便宜,如果偏移量为1,那么结果就会为 2 3 2 ,而不是 3 2 2。所以偏移量offset的值应该是当前查询最小时间戳的重复次数。

所以通过上面的分析,我们发现min和count值是不变的。max和offset都需要通过前端作为请求参数传递。

当第一次查询时,前端发送的请求参数max为当前时间戳,offset默认为0可以不传递,当后端查询出所需要的笔记集合,还需要记录本次查询的最小时间戳,以及计算偏移量,并返回给前端,这两个数据将作为下一次查询的参数。而前端每次上下拖动的时候,就会刷新会发起请求。

queryBlogOfFollow(max,offset):

  • 检验是否登录,获取当前登录用户

  • 查看收件箱

  • 滚动查询收件箱,获取笔记id

  • 根据笔记id查询笔记

  • 封装返回

 @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        //获取当前用户
        Long userId = UserHolder.getUser().getId();
        //查看收件箱
        String key = FEED_KEY + userId;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, max, offset, 2);
        //非空校验
        if(typedTuples == null || typedTuples.isEmpty()){
            return Result.ok();
        }
        //取出收件箱的笔记id放入集合
        List<Long> idList = new ArrayList<>(typedTuples.size());
        long minTime = 0;
        int os = 1;
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            idList.add(Long.valueOf(typedTuple.getValue()));
            //获取最小的时间戳,记录偏移量
            long time = typedTuple.getScore().longValue();
            if(time == minTime){
                os++;
            }else{
                minTime = time;
                os = 1;
            }
        }
        //根据id集合查询blog
        String idStr = StrUtil.join(",",idList);
        List<Blog> blogs = query().in("id", idList).last("ORDER BY FIELD(id," + idStr + ")").list();

        //查询blog点赞和相关用户
        for(Blog blog : blogs){
            queryBlogUser(blog);
            isBlogLiked(blog);
        }

        //封装并返回
        ScrollResult result = new ScrollResult();
        result.setList(blogs);
        result.setOffset(os);
        result.setMinTime(minTime);

        return Result.ok(result);
    }

  

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

离了大谱,这所院校太卷了!524人进复试,刷247人!

一、学校及专业介绍 桂林电子科技大学&#xff08;Guilin University Of Electronic Technology&#xff09;&#xff0c;简称“桂电”、广西一流学科建设高校、是广西唯一拥有国防特色重点专业的高校&#xff0c;是总装备部通信装备预研项目定向发布的30所高校和北斗ICD授权的…

力扣 322. 零钱兑换

题目来源&#xff1a;https://leetcode.cn/problems/coin-change/description/ C题解&#xff08;来源代码随想录&#xff09;&#xff1a;题目中说每种硬币的数量是无限的&#xff0c;可以看出是典型的完全背包问题。动规五部曲分析如下&#xff1a; 确定dp数组以及下标的含义…

从初学者到专家:Java方法的完整指南

目录 一.方法的概念及使用 1.1什么是方法 1.2方法的定义 1.3方法的调用 1.4实参和形参的关系 1.5没有返回值的方法 1.6方法的意义 二.方法重载 2.1方法重载的实现 2.2方法重载的意义 2.3方法签名 一.方法的概念及使用 1.1什么是方法 方法就是一个代码片段. 类似于 …

21款美规奔驰GLS450更换中规高配主机,汉化操作更简单

很多平行进口的奔驰GLS都有这么一个问题&#xff0c;原车的地图在国内定位不了&#xff0c;语音交互功能也识别不了中文&#xff0c;原厂记录仪也减少了&#xff0c;使用起来也是很不方便的。 可以实现以下功能&#xff1a; ①中国地图 ②语音小助手&#xff08;你好&#xf…

内网ip与外网ip

一、关于IP地址 我们平时直接接触最多的是内网IP。而且还可以自己手动修改ip地址。而外网ip&#xff0c;我们很少直接接触&#xff0c;都是间接接触、因为外网ip一般都是运营商管理&#xff0c;而且是全球唯一的&#xff0c;一般我们自己是无法修改的。 内网IP和外网IP是指在…

线程间三种常见的通信手段

线程间通信是指多个线程之间通过某种机制进行协调和交互&#xff0c;例如&#xff1a;线程等待和通知机制就是线程通讯的主要手段之一。 在 Java 中有以下三种实现线程等待的手段 &#xff1a; Object 类提供的 wait()&#xff0c;notify() 和 notifyAll() 方法&#xff1b;C…

Python实现SSA智能麻雀搜索算法优化BP神经网络分类模型(BP神经网络分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 麻雀搜索算法(Sparrow Search Algorithm, SSA)是一种新型的群智能优化算法&#xff0c;在2020年提出&a…

新能源电动汽车充电桩控制主板结构组成

你是否好奇过&#xff0c;充电桩主板到底是由哪些部分组成的?又分别是做什么的?今天我们就来一探究竟。 充电桩主板是充电桩的核心部件&#xff0c;它由中央处理器、内存、辅助存储器、输入/输出接口等组成。 中央处理器是充电桩主板的核心&#xff0c;它负责将充电桩的读写指…

vue里搜索框实现防抖功能

进来调用一个闭包函数debounce()&#xff0c;赋值给一个变量debounceFunc&#xff0c;&#xff08;包闭的功能就是说里面的变量timer和参数一直驻留在函数里面&#xff09; input事件调用一个函数debounceFunc&#xff08;&#xff09;&#xff0c;并且传一个回调searchs函数&a…

MATLAB 2023a的机器学习、深度学习实践应用

MATLAB 2023版的深度学习工具箱&#xff0c;提供了完整的工具链&#xff0c;使您能够在一个集成的环境中进行深度学习的建模、训练和部署。与Python相比&#xff0c;MATLAB的语法简洁、易于上手&#xff0c;无需繁琐的配置和安装&#xff0c;让您能够更快地实现深度学习的任务。…

深入理解 go协程 调度机制

Thread VS Groutine 这里主要介绍一下Go的并发协程相比于传统的线程 的不同点&#xff1a; 创建时默认的stack大小 JDK5 以后Java thread stack默认大小为1MC 的thread stack 默认大小为8MGrountine 的 Stack初始化大小为2K 所以Grountine 大批量创建的时候速度会更快 和 …

plt取消坐标轴刻度、自定义取消绘图边框(或坐标轴)、白边处理、自定义颜色图谱、设置坐标轴刻度朝向

目录 1、取消坐标轴刻度 2、自定义取消绘图边框&#xff08;或坐标轴&#xff09; 3、去掉图片周边白边 4、自定义颜色图谱 5、设置坐标轴刻度朝向 import matplotlib.pyplot as plt 1、取消坐标轴刻度 ax plt.subplot() ax.set_xticks([]) ax.set_yticks([]) 2、自定…

redis分布式集群-redis+keepalived+ haproxy

redis分布式集群架构&#xff08;RedisKeepalivedHaproxy&#xff09;至少需要3台服务器、6个节点&#xff0c;一台服务器2个节点。 redis分布式集群架构中的每台服务器都使用六个端口来实现多路复用&#xff0c;最终实现主从热备、负载均衡、秒级切换的目标。 redis分布式集…

基于SpringBoot的旅游网站的设计与实现【附ppt|开题|万字文档(LW)和搭建文档

主要功能 前台界面&#xff1a; ①首页、旅游线路推荐、旅游资讯、线路搜索、查看更多等 ②旅游线路、度假旅游、探险考察、文化旅游、短程旅游、观光旅游、远程旅游、最新路线等 ③添加购物车、立即购买、评论、点我收藏等 ④个人中心、我的订单、我的地址、我的收藏、客服等…

vue实现a组件中数据变化b组件实时更新(ab组件无关联)

需求&#xff1a;A组件新增、编辑或者删除数据时&#xff0c;B组件实时更新数据 // src/utils/bus.js// bus.$emit(bridge-updated) 是在事件总线实例 bus 上触发了一个自定义事件 // data-updated&#xff0c;相当于发布了一个事件。// bus.$on(bridge-update…

考研算法44天:玛雅人的密码

题目前置知识 1.为啥BFS比DFS更加适合找最短路径&#xff1f; BFS 的使用场景&#xff1a;层序遍历、最短路径问题 - 知乎 (zhihu.com) 题目原题 解题思路 这道题和原先写的那些题目不同的是&#xff0c;这次BFS所搜索的树里面存放的是一个字符串&#xff0c;也就是说没有路…

SQL server中substring 的用法

一&#xff1a;substring函数是SQL中截取字段数据中的其中一部分 --列&#xff1a;提取abdcsef中的abc数据&#xff0c;使用substring实现select substring(abdcsef,1,3) --‘1’表示截取的起始位置是从第一个字符开始,‘3’表示截取后得到的字符串长度为3个字符 二&#xff1…

day7 ADC模数转换

ADC简介 作用&#xff1a;采集传感器的数据&#xff0c;测量输入电压&#xff0c;检查电池电量剩余&#xff0c;检测温湿度等。 ADC性能指标 量程&#xff1a;能测量的电压范围 分辨率&#xff1a;ADC的分辨率通常以二进制数的位数表示&#xff0c;位数越多&#xff0c;分辨…

腾讯面试题:使用Redis分布式锁可能会出现哪些问题?

嗨大家好&#xff0c;我是你们的小米&#xff01;今天要和大家聊一个有趣的话题&#xff0c;那就是“腾讯面试题&#xff1a;使用Redis做分布式锁可能会出现哪些问题&#xff1f;”没错&#xff0c;就是腾讯大佬们在面试时经常会问到的一个问题&#xff0c;我们来一起深入了解一…

ubuntu中安装python

最简单方便的是 apt 使用第三方的 ppa 源&#xff0c;然后直接 apt 安装 python3.9 安装 software-properties-common 获取add-apt-repository命令&#xff1a;apt install -y software-properties-common添加第三方的 ppa 源&#xff1a;add-apt-repository ppa:deadsnakes/p…