用Redis的zset有序集合實現一個本週熱議功能

本週熱議,本週發表並且評論最多的文章排行,如果直接查詢數據庫的話很快就可以實現,只需要限定一下文章創建時間,然後更加評論數量倒敘取前幾篇即可搞定。

但這裡我們使用redis來完成。之前上課時候我們說過,排行榜功能,我們可以使用redis的有序集合zset來完成。現在我們就這個數據結構來完成本週熱議的功能。

在編碼之前,我們需要先來回顧一下zset的幾個基本命令。

  • zrange key start stop [WITHSCORES]

withscores代表的是否顯示順序號 start和stop代表所在的位置的索引。可以這樣理解:將集合元素依照順序值升序排序再輸出,start和stop限制遍歷的限制範圍

  • zincrby key increment member

為有序集 key 的成員 member 的 score 值加上增量 increment 。

  • ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]

計算給定的一個或多個有序集的並集,其中給定 key 的數量必須以 numkeys 參數指定,並將該並集(結果集)儲存到 destination 。

默認情況下,結果集中某個成員的 score 值是所有給定集下該成員 score 值之 和 。

其他命令可以參考這裡:http://doc.redisfans.com/

以下是我做的實驗:

用Redis的zset有序集合實現一個本週熱議功能

我們來分析一下我們的需求。我們想用緩存來完成這本週熱議排行榜功能,不依賴數據庫(除了初始化數據)。有人發表評論之後,直接使用命令加一,並重新計算並集得到排行榜。

項目啟動時候我們先初始化最近文章的評論數量。基本邏輯如下:

  1. 查庫獲取最近7天的所有文章(或者加多一個條件:評論數量大於0)
  2. 然後把文章的評論數量作為有序集合的分數,文章id作為ID存儲到zset中。
  3. 本週熱議上有標題和評論數量,因此,我們還需要把文章的基本信息存儲到redis總。這樣得到文章的id之後,我們再從緩存中得到標題等信息,這裡我們可以使用hash的結構來存儲文章的信息。
  4. 另外,因為是本週熱議,如果文章發表超過7天了之後就沒啥用了,所以我們可以給文章的有序集合一個有效時間。超過7天之後就自定刪除緩存。

具體代碼如下:

  • com.example.service.impl.PostServiceImpl#initIndexWeekRank
<code>/** * 初始化首頁的周評論排行榜 */@Overridepublic void initIndexWeekRank() {    //緩存最近7天的文章評論數量    List<post> last7DayPosts = this.list(new QueryWrapper<post>()            .ge("created", DateUtil.offsetDay(new Date(), -7).toJdkDate())            .select("id, title, user_id, comment_count, view_count, created"));    for (Post post : last7DayPosts) {        String key = "day_rank:" + DateUtil.format(post.getCreated(), DatePattern.PURE_DATE_PATTERN);        //設置有效期        long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);        long expireTime = (7 - between) * 24 * 60 * 60;        //緩存文章到set中,評論數量作為排行標準        redisUtil.zSet(key, post.getId(), post.getCommentCount());        //設置有效期        redisUtil.expire(key, expireTime);        //緩存文章基本信息(hash結構)        this.hashCachePostIdAndTitle(post);    }    //7天閱讀相加。    this.zUnionAndStoreLast7DaysForLastWeekRank();}/<post>/<post>/<code>
  • 對應的緩存文章信息的方法如下:
<code>/** * hash結構緩存文章標題和id * @param post */private void hashCachePostIdAndTitle(Post post) {    boolean isExist = redisUtil.hasKey("rank_post_" + post.getId());    if(!isExist) {        long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);        long expireTime = (7 - between) * 24 * 60 * 60;        //緩存文章基本信息(hash結構)        redisUtil.hset("rank_post_" + post.getId(), "post:id", post.getId(), expireTime);        redisUtil.hset("rank_post_" + post.getId(), "post:title", post.getTitle(), expireTime);        //redisUtil.hset("rank_post_" + post.getId(), "post:comment_count", post.getCommentCount(), expireTime);    }}/<code>
  • 統計7天的文章集合交集數量:
<code>/** * 把最近7天的文章評論數量統計一下 * 用於首頁的7天評論排行榜 */public void zUnionAndStoreLast7DaysForLastWeekRank() {    String prifix = "day_rank:";    List<string> keys  = new ArrayList<>();    String key = prifix + DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);    for(int i = -7 ; i < 0; i++) {        Date date = DateUtil.offsetDay(new Date(), i).toJdkDate();        keys.add(prifix + DateUtil.format(date, DatePattern.PURE_DATE_PATTERN));    }    redisUtil.zUnionAndStore(key, keys, "last_week_rank");}/<string>/<code>

寫好了之後,我們再我們的項目啟動類中調用一下即可完成了初始化。

<code>@Slf4j@Order(1000)@Componentpublic class ContextStartup implements ApplicationRunner, ServletContextAware {    private ServletContext servletContext;    @Autowired    PostService postService;    @Override    public void setServletContext(ServletContext servletContext) {        this.servletContext = servletContext;    }    @Override    public void run(ApplicationArguments args) throws Exception {        servletContext.setAttribute("base", servletContext.getContextPath());        //初始化首頁的周評論排行榜        postService.initIndexWeekRank();    }}/<code>

以上就完成了初始化。這時候我們在本週熱議模塊已經可以看到效果了。緩存中已經有我們想要的數據,接下我們在controller中獲取出來,然後返回給個我們的頁面,頁面用異步加載的模式,所以這裡定義一個異步接口:

  • com.example.controller.PostController
<code>@ResponseBody@GetMapping("/post/hots")public Result hotPost() {    Set<zsetoperations.typedtuple> lastWeekRank = redisUtil.getZSetRank("last_week_rank", 0, 6);    List> hotPosts = new ArrayList<>();    for (ZSetOperations.TypedTuple typedTuple : lastWeekRank) {        Map<string> map = new HashMap<>();        map.put("comment_count", typedTuple.getScore());        map.put("id", redisUtil.hget("rank_post_" + typedTuple.getValue(), "post:id"));        map.put("title", redisUtil.hget("rank_post_" + typedTuple.getValue(), "post:title"));        hotPosts.add(map);    }    return Result.succ(hotPosts);/<string>/<zsetoperations.typedtuple>/<code>

}

測試結果:

用Redis的zset有序集合實現一個本週熱議功能

致此,我們已經完成了獲取本週熱議的數據,但是,只是一個初始化而已,當有評論的時候還應該添加數據到我們的緩存中,還有頁面的內容我們也應該寫一些ajax加載數據,這些我們先留著,先到這裡,以上是我們之前課程講過的內容,大家先行消化一下。


分享到:


相關文章: