目录
当登录的用户点击共同关注时,需要展示当前页面的用户与登录用户直接的共同关注,此时我们可以在数据库中维护一张关注表里面记录了关注者与被关注者的id,我们可以直接拿着该用户id去查询登录他关注了哪些人,同样拿着当前访问主页用户的id去查询该用户的关注然后求交集即可,那么问题来了如何求交集呢?我们可以通过Redis中的set数据结构的求交集命令来处理
首先在项目中经常会有这样的需求就是实现关注于共同关注的功能,在我们实现关注的时候,首先我们将关注的信息存入数据库中,插入之后再将该关注信息以用户id作为redis中的key的组成,以被关注者的id作为value存入redis的set集合中,取消关注时删除数据库中关注信息后将被关注用户的id从set中移除,此时当用户访问到某个用户显示共同关注时会将访问的该用户的id传回服务器,此时服务器拿着被访问用户有的id与当前登录用户的id构造成两个set集合个key通过redis的 sinter 命令求交集获得共同关注的所有用户id,然后拿着id在从数据库中查询这些共同关注用户的信息返回给客户端
关注推送也叫Feed流,直译为投喂。为用户持续提供沉浸式体验,通过无限下拉刷新获取新的消息,他的实现主要有两种实现方式,一种是TimeLine方式,他不会做内容筛选,只是简单的按照内容发布的时间进行排序,常用于好友或关注,比如朋友圈,他的优点是信息全面不会缺少,实现起来相对简单,缺点就是消息噪音多,用户不一定感兴趣。还有一种是智能排序,利用智能算法屏蔽掉违规的、用户不感兴趣的信息,缺点是如果算法不准确,可能会起到反作用
用户在平台发布新的内容,粉丝收到该消息
拉模式是粉丝相获取相应的内容时去每个关注者的发件箱里获取相应的内容。将所有的内容拉取下来
推模式是用户发布内容后将内容推送到每个粉丝的收件箱里,粉丝查看内容时只需要从收件箱里拿出来即可
推拉模式则是既推送也拉取,比如对于活跃的粉丝可以采用推模式,而对于普通的粉丝可以采用拉模式
拉模式 | 推模式 | 推拉结合 | |
写比例 | 低 | 高 | 中 |
读比例 | 高 | 低 | 中 |
用户读取延迟 | 高 | 低 | 低 |
实现难度 | 复制 | 简单 | 很复杂 |
使用场景 | 很少 | 用户量少 | 有千万级别用户量的大V |
在项目中我们需要实现博主发布视频或者发布文章需要给用户将该信息进行推送,类似微信当朋友圈以及抖音,当用户发布视频后推送将该视频对其粉丝进行推送,此时我们可以基于上述Feed流来实现,当用户发布相关的消息后此时我们先将该消息存入数据库,后在将该消息id与当前时间戳作为value推送给每个粉丝的收件箱,当粉丝访问相应页面时直接从收件箱通过滚动排序拉取对应的消息,此时为什么不直接采用分页查询呢,如果使用传统的分页来实现时,比如此时有5条数据我们要根据按时间查最新的在最上面,此时也就是时间戳越大越新,此时我们查询第一页每页2条时,查询出来了5,4此时查询第二页之前收件箱里又来了一个消息马,此时消息下标发送变化导致4被重复查询出
首先我需要在用户发布内容时将内容推送到每个粉丝的收件箱中,每个粉丝在访问相应页面时开始滚动查询此时刻的推送内容,当他刷新时滚动查询刷新时刻的内容,那么对于redis实现时的收件箱数据结构有什么要求呢:他要按照时间排序,所有有排序功能,且他实现分页得支持下标查询。
reids中满足上述需要的有list与sortedSet,但是Feed流的数据是不断变化的,角标也是不断变化的,所以使用后者更合适
使用sortedSet实现的时候,使用zrevrangebyscore key max min limit offset count 命令来进行查询,根据时间戳的区间进行滚动分页查询,其中max是score的最大值,min是区间的最小值,offset是偏移量,count是查询条数。我们在实现的时候,第一次在查询时score的max也就是当前的时间戳而最小值则为0即可,偏移量也是0,查询条数可自己定义,那么第二次的最大值是上面呢,是第一次查询到数据中的最小值,此时偏移量还是0吗,此时如果是0则上一次查询到的score最小值对应的数据又被查询出来了,那么偏移量是1呢当然也是不行的,如果上一次查询出来的最小值有两个或者多个,执行上述命令查询第二次时,比如第一次按score查score是5 4 4 4 那么第二次查询时是从第一个最小值也就是第一个4开始的,而如果第二次查询时此时偏移量如果为1则 三个4又被重复2查询,那么偏移量如何来定呢,当然是上一次查询分数最小值的重复次数,比如上述例子也就是3,此时第二次查询即可正常进行,那么我们使用代码简单实现一下
-
- @Autowired
- private StringRedisTemplate redisTemplate;
-
- public ReturnModel sub(Long max,Long offset) {
- // 1.获取粉丝id
- Long userId = 1L;
-
- // 2.查询收件箱
- String key = "FEED:USER:" + userId;
- Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate.opsForZSet()
- .reverseRangeByScoreWithScores(key, 0, max, offset, 3);
-
- // 3.内容非空判断
- if (typedTuples == null || typedTuples.isEmpty()) {
- return ReturnModel.fail("");
- }
-
- // 4.解析收件箱数据:ids,minTime,offset
- List<Long> ids = new ArrayList<>(typedTuples.size());
- long minTime = 0; // 下次查询的最大score值
- int os = 0; // 下次查询的偏移量
- for (ZSetOperations.TypedTuple<String > tuple:typedTuples) {
- // 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查询对应内容
- // 去数据库查询id对应的内容封装后返回前端
-
- // 6.返回数据
- // 要将上述准备的下一次查询的偏移量与最小时间戳(作为下次查询的最大score值)一并封装返回
- return ;
- }