首页 > 数据库 >基于Redis的Set、Zset类型实现好友关注,共同好友(求并集)

基于Redis的Set、Zset类型实现好友关注,共同好友(求并集)

时间:2023-11-01 23:35:58浏览次数:47  
标签:Set Zset userId blog key offset follow id 好友

好友关注

基于Redis的Set、Zset类型实现好友关注,共同好友(求并集)_Redis

@Override
    public Result follow(Long followUserId, Boolean isFollow) {
        // 1.获取登录用户
        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        // 1.判断到底是关注还是取关
        if (isFollow) {
            // 2.关注,新增数据
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            boolean isSuccess = save(follow);
            if (isSuccess) {
                // 把关注用户的id,放入redis的set集合 sadd userId followerUserId
                stringRedisTemplate.opsForSet().add(key, followUserId.toString());
            }
        } else {
            // 3.取关,删除 delete from tb_follow where user_id = ? and follow_user_id = ?
            boolean isSuccess = remove(new QueryWrapper<Follow>()
                    .eq("user_id", userId).eq("follow_user_id", followUserId));
            if (isSuccess) {
                // 把关注用户的id从Redis集合中移除
                stringRedisTemplate.opsForSet().remove(key, followUserId.toString());
            }
        }
        return Result.ok();
    }

将数据存入Redis中,使用Redis的set类型,key为"follows:"+当前登录用户id,value为要关注的用户ID

关注功能

public Result followCommons(Long id) {
        // 1.获取当前用户
        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        // 2.求交集
        String key2 = "follows:" + id;
        Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
        if (intersect == null || intersect.isEmpty()) {
            // 无交集
            return Result.ok(Collections.emptyList());
        }
        // 3.解析id集合
        List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
        // 4.查询用户
        List<UserDTO> users = userService.listByIds(ids)
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        return Result.ok(users);
    }

使用Redis的set类型,取两个key的交集


关注推送

关注推送也叫做Feed流,直译为投喂。为用户持续的提供“沉浸式"的体验,通过无限下拉刷新获取新的信息。

基于Redis的Set、Zset类型实现好友关注,共同好友(求并集)_Redis求并集_02

Feed流的模式

Feed流产品有两种常见模式:

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

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

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

本例中的个人页面,是基于关注的好友来做Feed流,因此采用Timeline的模式。该模式的实现方案有三种:

拉模式

基于Redis的Set、Zset类型实现好友关注,共同好友(求并集)_Redis_03

优点:节省存储空间

缺点:延迟性较高

推模式

基于Redis的Set、Zset类型实现好友关注,共同好友(求并集)_Redis分页_04

推拉结合

基于Redis的Set、Zset类型实现好友关注,共同好友(求并集)_Redis实现共同关注_05

总结:

基于Redis的Set、Zset类型实现好友关注,共同好友(求并集)_Redis_06

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

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

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


基于Redis的Set、Zset类型实现好友关注,共同好友(求并集)_Redis分页_07

Feed流分页问题

基于Redis的Set、Zset类型实现好友关注,共同好友(求并集)_Redis求并集_08

代码实现->新增博客:

public Result saveBlog(Blog blog) {
// 1.获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 2.保存探店笔记
boolean isSuccess = save(blog);
if(!isSuccess){
    return Result.fail("新增笔记失败!");
}
// 3.查询笔记作者的所有粉丝 select * from tb_follow where follow_user_id = ?
List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
// 4.推送笔记id给所有粉丝
for (Follow follow : follows) {
    // 4.1.获取粉丝id
    Long userId = follow.getUserId();
    // 4.2.推送
    String key = FEED_KEY + userId;
    stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
}
// 5.返回id
return Result.ok(blog.getId());
}
使用Redis的zset类型实现分页

实现思路:

第一次查询是前端传入当前时间,从Redis中取出2个博客id,在根据博客id查询博客详细数据,并将偏移量offset和博客发布的最小时间,返回前端。但用户查询下一页时,前端将上次返回的最小时间和偏移量offset返回给后端,获取博客数据。

注意:zset类型的偏移量是从0开始的,0代表这篇博客本身,1代表不包含这篇博客本身,所以下面代码中的offset默认为1

/**
max为发布博客的时间戳
offset为偏移量
*/
public Result queryBlogOfFollow(Long max, Integer offset) {
    // 1.获取当前用户
    Long userId = UserHolder.getUser().getId();
    // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
    String key = FEED_KEY + userId;
    Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
    .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
    // 3.非空判断
    if (typedTuples == null || typedTuples.isEmpty()) {
        return Result.ok();
    }
    // 4.解析数据:blogId、minTime(时间戳)、offset
    List<Long> ids = new ArrayList<>(typedTuples.size());
    long minTime = 0; // 2
    int os = 1; // 2
    for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
        // 4.1.获取id
        ids.add(Long.valueOf(tuple.getValue()));
        // 4.2.获取分数(时间戳)
        long time = tuple.getScore().longValue();
        if(time == minTime){
            os++;
        }else{
            minTime = time;
            os = 1;
        }
    }

    // 5.根据id查询blog
    String idStr = StrUtil.join(",", ids);
    List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();

    for (Blog blog : blogs) {
        // 5.1.查询blog有关的用户
        queryBlogUser(blog);
        // 5.2.查询blog是否被点赞
        isBlogLiked(blog);
    }

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

    return Result.ok(r);
}

核心代码详解

Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 2);

这段代码是使用 Spring Data Redis 操作 Redis 中的有序集合(ZSet)数据结构。具体来说,它使用了 opsForZSet() 方法获取一个 ZSetOperations 对象,然后调用该对象的 reverseRangeByScoreWithScores() 方法来获取指定分数范围内的有序集合成员,并按照分数从高到低进行排序。

参数解释如下:

  • key:指定要操作的有序集合的键名。
  • 0:指定分数范围的最小值,这里设为 0,表示从分数为 0 的成员开始。
  • max:指定分数范围的最大值,根据实际需求进行设置。
  • offset:指定结果集的起始偏移量,表示从第几个成员开始返回结果。
  • 2:指定返回结果的数量,表示最多返回两个成员的信息。

返回结果是一个 Set<ZSetOperations.TypedTuple<String>> 类型的集合,其中 TypedTuple 是 Spring Data Redis 提供的一个泛型接口,用于表示有序集合中的成员和对应的分数。在这个集合中,每个元素都包含一个成员和它的分数。

需要注意的问题

Redis的zset类型中的score可能会出现相同的情况,如果相同则需要获取相同发布博客的条数作为offset

标签:Set,Zset,userId,blog,key,offset,follow,id,好友
From: https://blog.51cto.com/AmbitionGarden/8133958

相关文章

  • Pset_SiteCommon
    Pset_SiteCommon站点公用:IfcSite所有引用的定义公用的属性。请注意,几个站点属性在IfcSite实例中直接处理,站点编号(或短名称)由IfcSite.name处理,站点名称(或长名称)由IfcSite.LongName处理,描述(或注释)由IfcSite.description处理。土地所有权编号也作为显式属性IfcSite.LandTitleNumber......
  • Error loading wikitext data raise NotImplementedError(f"Loading a dataset cached
    ErrorloadingwikitextdataraiseNotImplementedError(f"Loadingadatasetcachedina{type(self._fs).name}isnotsupported.")QAIwastryingtoloadthewikidataset,butigotthiserrortraindata=load_dataset('wikitext','......
  • 【软硬件环境与工具使用】setuptools模块
    前言  1)setuptools之setup函数参数详解BuildingandDistributingPackageswithSetuptools-setuptools68.0.0.post20230808documentationPython库打包分发(setup.py编写)简易指南|Huoty'sBlogsetup.py实现C++扩展和python库编译_pythonsetup.py编译_ming7771的博客f......
  • 基于Redis的ZSET数据类型实现点赞排行榜
    点赞排行榜(ZSET实现)实现原理:使用redis的zset进行存储,score为当前时间,值为用户IDpublicResultlikeBlog(Longid){//1.获取登录用户LonguserId=UserHolder.getUser().getId();//2.判断当前登录用户是否已经点赞Stringkey=BLOG_LIKED_KEY+id;Doublescore=stringR......
  • map和set的使用
    序列式容器和关联性容器首先序列式容器和我们之前学的线性表很相似,序列式容器的功能就只是单纯的储存数据。序列式容器例如:vactor/list/deque等等而关联式容器则并不单纯的储存数据,数据之间式存在关联关系的,有了这个关联关系我们才能更好地去做查找。关联式容器由map/set等等。两者......
  • getter/setter(访问器/设置器)
    classStudent{privateintid;privateStringname;Student(intid,Stringname){this.id=id;this.name=name;}publicintgetId(){returnid;}publicvoidsetId(intid){this.id=id;......
  • 每日一练:css关键词:inherit、initial、revert、unset解释
    1、inherit(继承)inherit关键词用于将一个属性值设置为其父元素的相同属性值。它是一种实现样式继承的方式,使子元素继承父元素的样式属性。如果父元素没有明确定义该属性,子元素将继承到该属性的默认值。这个关键词通常用于处理文本属性,如文本颜色、字体等。<div>......
  • React学习笔记15-13-setState同步异步问题
    先说结论:setState处在同步的逻辑中会异步更新状态,更新真实dom。连续调用setState不会连续进行虚拟dom的对比和页面的更新setState处在异步的逻辑中,同步更新状态,更新真实dom。 1.同步状态先看同步状态/*eslint-disablereact/no-direct-mutation-state*/importRea......
  • 编译第三方前端项目时候出现Syntax Error: TypeError: Cannot set properties of unde
    编译第三方的前端项目时候出现下面问题 ERROR Failedtocompilewith1error                                                             ......
  • bitset用法
    1、简介bitset在bitset头文件中,它类似数组,并且每一个元素只能是0或1,每个元素只用1bit空间。//头文件#include<bitset>2、初始化定义初始化方法代码含义bitsetaa有n位,每位都为0bitseta(b)a是unsignedlong型u的一个副本bitseta(s)a是string对象s中含有......