面试官:Redis 如何实现每周热评功能?说说思路!(redis评论)

  本篇文章为你整理了面试官:Redis 如何实现每周热评功能?说说思路!(redis评论)的详细内容,包含有redis实现热榜 redis评论 redis文章热度排行榜 redis实现热门文章排行榜 面试官:Redis 如何实现每周热评功能?说说思路!,希望能帮助你了解 面试官:Redis 如何实现每周热评功能?说说思路!。

  分享Java技术,高并发编程,分布式技术,架构设计,Java面试题,算法,行业动态,程序人生等。

  
做每周热议,应该用缓存来做,如果直接查库的话,会对数据库造成压力。用缓存做的话,用Redis 来做缓存的话比较合适一点。

  

# 利用Redsi 添加 数据命令

 

  # day:1 指的是在1号的时候 post:1 第一篇文章添加了 10 条评论。

  #后面 6 post:2 指的是 在1号第二篇添加了6条评论

  zadd day:1 10 post:1 6 post:2

  zadd day:2 10 post:1 6 post:2

  zadd day:3 10 post:1 6 post:2

  zadd day:8 10 post:1 6 post:2

  #这样就完成了7天的记录

  

 

  上面的命令可以帮我们记录一下7天的所有的评论数。但是还没有帮我们计算出来谁是评论最高的。看Redis 的sorted set有序集合有个命令就可以帮我们实现这个功能。

  这个命令可以帮助我们实现并集,我们只需要把7天的评论给做个并集就可以求出来。

  

# Redis 命令

 

  #意思是并集把这7天的 放到一个新的集合里面 新的集合是 week:rank 这样这个新的集合里面就有了我们的

  #7天的记录了

  union week:rank 7 day:1...day:8

  

 

  Redis 命令实践一下看看

  本地命令行测试

  

127.0.0.1:6379 ping

 

  127.0.0.1:6379 zadd day:1 10 post:1

  (integer) 1

  127.0.0.1:6379 zadd day:2 10 post:1

  (integer) 1

  127.0.0.1:6379 zadd day:3 10 post:1

  (integer) 1

  127.0.0.1:6379 zadd day:1 5 post:2

  (integer) 1

  127.0.0.1:6379 zadd day:2 5 post:2

  (integer) 1

  127.0.0.1:6379 zadd day:3 5 post:2

  (integer) 1

  127.0.0.1:6379 keys *

  1) "day:1"

  2) "day:2"

  3) "day:3"

  

 

  查看当天的排行榜命令 ZRevrange

  

127.0.0.1:6379 zrevrange day:1 0 -1 withscores

 

  1) "post:1"

  2) "10"

  3) "post:2"

  4) "5"

  127.0.0.1:6379

  

 

  每周的评论排行榜记录。因为我只有三天的,所以只写了3天的

  

127.0.0.1:6379 zrevrange week:rank 0 -1 withscores

 

  1) "post:1"

  2) "30"

  3) "post:2"

  4) "15"

  127.0.0.1:6379

  

 

  上面的记录是没有错误的。上述的命令可以帮我们简单的实现了我们的想法,下面用代码来实现。上面还有一个小的 bug 就是当day:1这一天可能会出现就是不可能直接就过完了。可能会一条一条的增加,这个时候应该使用的是自增这个命令来解决这个问题。

  

#什么时候+1 什么时候-1 就是当你 添加一条评论的时候就添加1 删除的的时候就减1

 

  ZINCRBY day:1 10 post:1

  

 

  代码来进行实现

  目前前端的样式,这样的话我们就需要在项目一开始的时候就启动这个功能

  

@Component

 

  // 实现 启动类 ,还有 上下文的servlect

  public class ContextStartup implements ApplicationRunner, ServletContextAware {

   // 注入 categoryService

   @Autowired

   IMCategoryService categoryService;

   ServletContext servletContext;

   // 注入post 的服务类

   @Autowired

   IMPostService postService;

   @Override

   public void run(ApplicationArguments args) throws Exception {

   // 调用全查的方法

   List MCategory list = categoryService.list(new QueryWrapper MCategory ().eq("status", 0));

   servletContext.setAttribute("List", list);

   // 调用每周热评的方法

   postService.initweek();

   @Override

   public void setServletContext(ServletContext servletContext) {

   this.servletContext = servletContext;

  

 

  服务类serviceimpl类

  大概的思路

  
缓存文章的基本信息(id,标题,评论数,作者 ID )

  这样就可以避免的查库。可以直接用我们的缓存。

  
这里需要一个Redis 的工具类,我在网上找到的,不是我写的。网上一大堆。直接拿来用就可以了。

  推荐一个开源免费的 Spring Boot 最全教程:

  https://github.com/javastacks/spring-boot-best-practice

  

package com.example.springbootblog.util;

 

  import org.springframework.beans.factory.annotation.Autowired;

  import org.springframework.data.redis.core.RedisTemplate;

  import org.springframework.data.redis.core.ZSetOperations;

  import org.springframework.stereotype.Component;

  import org.springframework.util.CollectionUtils;

  import java.util.Collection;

  import java.util.List;

  import java.util.Map;

  import java.util.Set;

  import java.util.concurrent.TimeUnit;

  @Component

  public class RedisUtil {

   @Autowired

   private RedisTemplate redisTemplate;

   * 指定缓存失效时间

   * @param key 键

   * @param time 时间(秒)

   * @return

   public boolean expire(String key, long time) {

   try {

   if (time 0) {

   redisTemplate.expire(key, time, TimeUnit.SECONDS);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 根据key 获取过期时间

   * @param key 键 不能为null

   * @return 时间(秒) 返回0代表为永久有效

   public long getExpire(String key) {

   return redisTemplate.getExpire(key, TimeUnit.SECONDS);

   * 判断key是否存在

   * @param key 键

   * @return true 存在 false不存在

   public boolean hasKey(String key) {

   try {

   return redisTemplate.hasKey(key);

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 删除缓存

   * @param key 可以传一个值 或多个

   @SuppressWarnings("unchecked")

   public void del(String... key) {

   if (key != null key.length 0) {

   if (key.length == 1) {

   redisTemplate.delete(key[0]);

   } else {

   redisTemplate.delete(CollectionUtils.arrayToList(key));

   //============================String=============================

   * 普通缓存获取

   * @param key 键

   * @return 值

   public Object get(String key) {

   return key == null ? null : redisTemplate.opsForValue().get(key);

   * 普通缓存放入

   * @param key 键

   * @param value 值

   * @return true成功 false失败

   public boolean set(String key, Object value) {

   try {

   redisTemplate.opsForValue().set(key, value);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 普通缓存放入并设置时间

   * @param key 键

   * @param value 值

   * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期

   * @return true成功 false 失败

   public boolean set(String key, Object value, long time) {

   try {

   if (time 0) {

   redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);

   } else {

   set(key, value);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 递增

   * @param key 键

   * @param delta 要增加几(大于0)

   * @return

   public long incr(String key, long delta) {

   if (delta 0) {

   throw new RuntimeException("递增因子必须大于0");

   return redisTemplate.opsForValue().increment(key, delta);

   * 递减

   * @param key 键

   * @param delta 要减少几(小于0)

   * @return

   public long decr(String key, long delta) {

   if (delta 0) {

   throw new RuntimeException("递减因子必须大于0");

   return redisTemplate.opsForValue().increment(key, -delta);

   //================================Map=================================

   * HashGet

   * @param key 键 不能为null

   * @param item 项 不能为null

   * @return 值

   public Object hget(String key, String item) {

   return redisTemplate.opsForHash().get(key, item);

   * 获取hashKey对应的所有键值

   * @param key 键

   * @return 对应的多个键值

   public Map Object, Object hmget(String key) {

   return redisTemplate.opsForHash().entries(key);

   * HashSet

   * @param key 键

   * @param map 对应多个键值

   * @return true 成功 false 失败

   public boolean hmset(String key, Map String, Object map) {

   try {

   redisTemplate.opsForHash().putAll(key, map);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * HashSet 并设置时间

   * @param key 键

   * @param map 对应多个键值

   * @param time 时间(秒)

   * @return true成功 false失败

   public boolean hmset(String key, Map String, Object map, long time) {

   try {

   redisTemplate.opsForHash().putAll(key, map);

   if (time 0) {

   expire(key, time);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 向一张hash表中放入数据,如果不存在将创建

   * @param key 键

   * @param item 项

   * @param value 值

   * @return true 成功 false失败

   public boolean hset(String key, String item, Object value) {

   try {

   redisTemplate.opsForHash().put(key, item, value);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 向一张hash表中放入数据,如果不存在将创建

   * @param key 键

   * @param item 项

   * @param value 值

   * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间

   * @return true 成功 false失败

   public boolean hset(String key, String item, Object value, long time) {

   try {

   redisTemplate.opsForHash().put(key, item, value);

   if (time 0) {

   expire(key, time);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 删除hash表中的值

   * @param key 键 不能为null

   * @param item 项 可以使多个 不能为null

   public void hdel(String key, Object... item) {

   redisTemplate.opsForHash().delete(key, item);

   * 判断hash表中是否有该项的值

   * @param key 键 不能为null

   * @param item 项 不能为null

   * @return true 存在 false不存在

   public boolean hHasKey(String key, String item) {

   return redisTemplate.opsForHash().hasKey(key, item);

   * hash递增 如果不存在,就会创建一个 并把新增后的值返回

   * @param key 键

   * @param item 项

   * @param by 要增加几(大于0)

   * @return

   public double hincr(String key, String item, double by) {

   return redisTemplate.opsForHash().increment(key, item, by);

   * hash递减

   * @param key 键

   * @param item 项

   * @param by 要减少记(小于0)

   * @return

   public double hdecr(String key, String item, double by) {

   return redisTemplate.opsForHash().increment(key, item, -by);

   //============================set=============================

   * 根据key获取Set中的所有值

   * @param key 键

   * @return

   public Set Object sGet(String key) {

   try {

   return redisTemplate.opsForSet().members(key);

   } catch (Exception e) {

   e.printStackTrace();

   return null;

   * 根据value从一个set中查询,是否存在

   * @param key 键

   * @param value 值

   * @return true 存在 false不存在

   public boolean sHasKey(String key, Object value) {

   try {

   return redisTemplate.opsForSet().isMember(key, value);

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 将数据放入set缓存

   * @param key 键

   * @param values 值 可以是多个

   * @return 成功个数

   public long sSet(String key, Object... values) {

   try {

   return redisTemplate.opsForSet().add(key, values);

   } catch (Exception e) {

   e.printStackTrace();

   return 0;

   * 将set数据放入缓存

   * @param key 键

   * @param time 时间(秒)

   * @param values 值 可以是多个

   * @return 成功个数

   public long sSetAndTime(String key, long time, Object... values) {

   try {

   Long count = redisTemplate.opsForSet().add(key, values);

   if (time 0) expire(key, time);

   return count;

   } catch (Exception e) {

   e.printStackTrace();

   return 0;

   * 获取set缓存的长度

   * @param key 键

   * @return

   public long sGetSetSize(String key) {

   try {

   return redisTemplate.opsForSet().size(key);

   } catch (Exception e) {

   e.printStackTrace();

   return 0;

   * 移除值为value的

   * @param key 键

   * @param values 值 可以是多个

   * @return 移除的个数

   public long setRemove(String key, Object... values) {

   try {

   Long count = redisTemplate.opsForSet().remove(key, values);

   return count;

   } catch (Exception e) {

   e.printStackTrace();

   return 0;

   //===============================list=================================

   * 获取list缓存的内容

   * @param key 键

   * @param start 开始

   * @param end 结束 0 到 -1代表所有值

   * @return

   public List Object lGet(String key, long start, long end) {

   try {

   return redisTemplate.opsForList().range(key, start, end);

   } catch (Exception e) {

   e.printStackTrace();

   return null;

   * 获取list缓存的长度

   * @param key 键

   * @return

   public long lGetListSize(String key) {

   try {

   return redisTemplate.opsForList().size(key);

   } catch (Exception e) {

   e.printStackTrace();

   return 0;

   * 通过索引 获取list中的值

   * @param key 键

   * @param index 索引 index =0时, 0 表头,1 第二个元素,依次类推;index 0时,-1,表尾,-2倒数第二个元素,依次类推

   * @return

   public Object lGetIndex(String key, long index) {

   try {

   return redisTemplate.opsForList().index(key, index);

   } catch (Exception e) {

   e.printStackTrace();

   return null;

   * 将list放入缓存

   * @param key 键

   * @param value 值

   * @return

   public boolean lSet(String key, Object value) {

   try {

   redisTemplate.opsForList().rightPush(key, value);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 将list放入缓存

   * @param key 键

   * @param value 值

   * @param time 时间(秒)

   * @return

   public boolean lSet(String key, Object value, long time) {

   try {

   redisTemplate.opsForList().rightPush(key, value);

   if (time 0) expire(key, time);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 将list放入缓存

   * @param key 键

   * @param value 值

   * @return

   public boolean lSet(String key, List Object value) {

   try {

   redisTemplate.opsForList().rightPushAll(key, value);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 将list放入缓存

   * @param key 键

   * @param value 值

   * @param time 时间(秒)

   * @return

   public boolean lSet(String key, List Object value, long time) {

   try {

   redisTemplate.opsForList().rightPushAll(key, value);

   if (time 0) expire(key, time);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 根据索引修改list中的某条数据

   * @param key 键

   * @param index 索引

   * @param value 值

   * @return

   public boolean lUpdateIndex(String key, long index, Object value) {

   try {

   redisTemplate.opsForList().set(key, index, value);

   return true;

   } catch (Exception e) {

   e.printStackTrace();

   return false;

   * 移除N个值为value

   * @param key 键

   * @param count 移除多少个

   * @param value 值

   * @return 移除的个数

   public long lRemove(String key, long count, Object value) {

   try {

   Long remove = redisTemplate.opsForList().remove(key, count, value);

   return remove;

   } catch (Exception e) {

   e.printStackTrace();

   return 0;

   //================有序集合 sort set===================

   * 有序set添加元素

   * @param key

   * @param value

   * @param score

   * @return

   public boolean zSet(String key, Object value, double score) {

   return redisTemplate.opsForZSet().add(key, value, score);

   public long batchZSet(String key, Set ZSetOperations.TypedTuple typles) {

   return redisTemplate.opsForZSet().add(key, typles);

   public void zIncrementScore(String key, Object value, long delta) {

   redisTemplate.opsForZSet().incrementScore(key, value, delta);

   public void zUnionAndStore(String key, Collection otherKeys, String destKey) {

   redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);

   * 获取zset数量

   * @param key

   * @param value

   * @return

   public long getZsetScore(String key, Object value) {

   Double score = redisTemplate.opsForZSet().score(key, value);

   if(score==null){

   return 0;

   }else{

   return score.longValue();

   * 获取有序集 key 中成员 member 的排名 。

   * 其中有序集成员按 score 值递减 (从大到小) 排序。

   * @param key

   * @param start

   * @param end

   * @return

   public Set ZSetOperations.TypedTuple getZSetRank(String key, long start, long end) {

   return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);

  

 

  实现类的代码

  

// 每周热评的方法

 

  @Override

  public void initweek() {

   //获取 7天的文章

   List MPost posts = this.list(new QueryWrapper MPost ().ge("created", DateUtil.lastWeek())

   .select("id", "title", "user_id", "comment_count", "view_count", "created")

   );// 获取到7天前的以及按照这几个查询,不需要全部查询

   // 初始化文章的总评论

   for (MPost post : posts) {

   // 设置 key

   String key = "day:rank:" + DateUtil.format(post.getCreated(), DatePattern.PURE_DATE_FORMAT);

   // 缓存进去的评论数量

   redisUtil.zSet(key, post.getId(), post.getCommentCount());

   //设置自动过期 7天过期

   long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);

   long expireTime = (7 - between) * 24 * 60 * 60; // 有效 时间

   redisUtil.expire(key, expireTime);

   // 缓存文章的一些基本信息

   this.hashCachePost(post, expireTime);

   // 做并集

   this.zunionAndStore();

   * 文章每周评论数量并集操作

   private void zunionAndStore() {

   String destkey = "day:rank:" + DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT);

   // 设置并集后的新的 key

   String newkey = "week:rank";

   ArrayList String otherKeys = new ArrayList ();

   // 计算7天的

   for (int i = -6; i i++) {

   String temp = "day:rank:" + DateUtil.format(DateUtil.offsetDay(new Date(), i), DatePattern.PURE_DATE_FORMAT);

   otherKeys.add(temp);

   redisUtil.zUnionAndStore(destkey, otherKeys, newkey);

   * 文章作者缓存

   private void hashCachePost(MPost post, long expireTime) {

   // 设置 key

   String key = "rank:post:" + post.getId();

   // 判断存在不存在

   boolean hasKey = redisUtil.hasKey(key);

   if (!hasKey) {

   // 就存到缓存里面

   redisUtil.hset(key, "post:id", post.getId(), expireTime);

   redisUtil.hset(key, "post:title", post.getTitle(), expireTime);

   redisUtil.hset(key, "post:commentCount", post.getCommentCount(), expireTime);

  

 

  这样就可以把我们的命令行转换成代码的形式。就可以把我们的数据库的数据先存到缓存中去了。

  效果

  

127.0.0.1:6379 keys *

 

  1) "rank:post:4"

  2) "week:rank"

  3) "day:rank:20210724"

  4) "rank:post:3"

  5) "rank:post:2"

  6) "day:rank:20210726"

  #查看我们并集完后的数据 id 为 3 的有 1条评论。

  127.0.0.1:6379 zrevrange week:rank 0 -1 withscores

  1) "3"

  2) "1"

  3) "2"

  4) "1"

  5) "4"

  6) "0"

  127.0.0.1:6379

  

 

  数据库中id 为 3 的有 1条评论

  确实只有一条评论的

  前端展示出来

  这里的思路就比较简单了,把我们的数据从缓存中取出来就可以。用的freemarker可以自定义标签。我自定义了标签。

  Hosttemplate

  

/**

 

   * 本周热议

  @Component

  public class HotsTemplate extends TemplateDirective {

   @Autowired

   RedisUtil redisUtil;

   @Override

   public String getName() {

   return "hots";

   @Override

   public void execute(DirectiveHandler handler) throws Exception {

  // 设置 key

   String key ="week:rank";

   Set ZSetOperations.TypedTuple zSetRank = redisUtil.getZSetRank(key, 0, 6);

   ArrayList Map maps = new ArrayList ();

   // 便利

   for (ZSetOperations.TypedTuple typedTuple : zSetRank) {

   // 创建 Map

   HashMap String, Object map = new HashMap ();

   Object post_id = typedTuple.getValue();

   String PostHashKey = "rank:post:" +post_id;

   map.put("id",post_id);

   map.put("title",redisUtil.hget(PostHashKey,"post:title"));

   map.put("commentCount",typedTuple.getScore());

   maps.add(map);

   handler.put(RESULTS,maps).render();

  

 

  在FreemarkerConfig把我们写的便签注入就可以使用我们自定义的标签了

  

@Configuration

 

  public class FreemarkerConfig {

   @Autowired

   private freemarker.template.Configuration configuration;

   @Autowired

   PostsTemplate postsTemplate;

   @Autowired

   HotsTemplate hotsTemplate;

   @PostConstruct

   public void setUp() {

   configuration.setSharedVariable("timeAgo", new TimeAgoMethod());

   configuration.setSharedVariable("posts", postsTemplate);

   configuration.setSharedVariable("hosts", hotsTemplate);

  
前端的页面获取

  效果

  在做这个功能的时候。不够全面。虽然写完了,但是应该是获取7天内的评论。我获取了7天的文章。虽然是个 bug 但是我不想修复。就这样吧。现在能用就行。道理是一样的。到时候有时间出问题了在改吧。累了,无情的代码机器记录代码生活中。。。。。

  来源:blog.csdn.net/m0_46937429/article/details/119172118

  近期热文推荐:

  1.1,000+ 道 Java面试题及答案整理(2022最新版)

  2.劲爆!Java 协程要来了。。。

  3.Spring Boot 2.x 教程,太全了!

  4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

  5.《Java开发手册(嵩山版)》最新发布,速速下载!

  觉得不错,别忘了随手点赞+转发哦!

  以上就是面试官:Redis 如何实现每周热评功能?说说思路!(redis评论)的详细内容,想要了解更多 面试官:Redis 如何实现每周热评功能?说说思路!的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: