redis 實現搜索熱詞統計

核心需求

一個項目中,遇到了搜索熱詞統計的需求,我使用了 Redis 的五大數據類型之一 Sorted Set 實現。目前有兩項數據須要統計:「當日搜索熱詞 top10」和「當週搜索熱詞 top10」。html

關於這兩項數據的統計方法,目前想到了兩種實現方法:java

  1. 兩個 Redis 的 Sorted Set 實現,一個 Sorted Set A 統計當天,0 點 top10 記錄進 MySQL,Sorted Set 清零。一個 Sorted Set B 統計當週,每週日 top10 記錄進 MySQL,Sorted Set B 清零。
  2. 只使用用一個 Sorted Set 記錄當天搜索熱詞,0 點 top10 記錄進 MySQL,Sorted Set 清零。到週日時,會有 7 * 10 行記錄。把這 7 * 10 行遍歷,每次便利都記錄進 Sorted Set,所有遍歷結束後,再從 Sorted Set 中取出 top10 記錄進 MySQL 的周熱詞統計表中。

Sorted Set 是 Redis 的數據結構,方法 1 會佔用兩分內存,一份當天的,一份當週的。方法 2 會提升系統的複雜度,而且在統計周表時,可能會出現短期內大量的計算(固然可使用定時任務,把周表的統計放到凌晨進行)。redis

最後選擇了方案1,分開維護清晰明瞭。數據結構

至於內存佔用問題,1MB = 1048576 字節,按兩個字節存一個字算,理論上1MB 能存 1048576/2/8 = 65,536 個不重複的搜索關鍵詞(固然使用Sorted Set確定比純字更多佔用一些空間)。多投入一些內存,能存下的數量仍是很大的,一般能夠撐到每週結束清理內存。通常的 CRUD 項目不用怎麼考慮內存佔用。app

問題中涉及的相關知識

一個項目中,遇到了搜索熱詞統計的需求。我使用了 Redis 的五大數據類型之一 Sorted Set 實現。ide

Redis 有序集合(sorted set)post

Redis 有序集合和集合同樣也是 string 類型元素的集合,且不容許重複的成員。測試

不一樣的是每一個元素都會關聯一個 double 類型的分數。Redis 正是經過分數來爲集合中的成員進行從小到大的排序。大數據

有序集合的成員是惟一的,但分數(score)卻能夠重複。 集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是 O(1)。 集合中最大的成員數爲 232 - 1 (4294967295, 每一個集合可存儲 40 多億個成員)。 定義出自:菜鳥教程spa

01 Sorted Set展現

如上圖,Redis 的 Sorted Set 自帶排序功能。

操做方法也比較簡單,在本項目中,核心是兩個方法:

zincrby,對於一個 Sorted Set,存在的就把分數加 x (x 可自行設定),不存在就建立一個分數爲 1 的成員。

zrevrange,查詢Sorted Set中指定範圍的值。返回的有序集合中,score 大的在前面。此方法無需擔憂用於指定範圍的start和end出現越界報錯問題。

在StringRedisTemplate中,Sorted Set被稱爲ZSet。更多redis (java客戶端) 的Soeted Set使用方法請見:Redis之ZSet數據結構使用姿式

代碼

service示例代碼:

@Service("redisService")
public class RedisServiceImpl implements RedisService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /** * 使用Sorted Set記錄keyword * zincrby命令,對於一個Sorted Set,存在的就把分數加x(x可自行設定),不存在就建立一個分數爲1的成員 * * @param keyword 搜索關鍵詞 */
    @Override
    public void searchZincrby(String keyword) {
        // 名爲sortedSetName的Sorted Set不用預先建立,不存在會自動建立,存在則向裏添加數據
        String sortedSetName = "searchHotWord";
        // x 的含義請見本方法的註釋
        double x = 1.0;
        redisTemplate.opsForZSet().incrementScore(sortedSetName, keyword, x);
    }

    /** * zrevrange命令, 查詢Sorted Set中指定範圍的值 * 返回的有序集合中,score大的在前面 * zrevrange方法無需擔憂用於指定範圍的start和end出現越界報錯問題 * * @param start 查詢範圍開始位置 * @param end 查詢範圍結束位置 * @return */
    @Override
    public Set<ZSetOperations.TypedTuple<String>> queryTopSearchHotWord(Integer start, Integer end) {
        String sortedSetName = "searchHotWord";
        Set<ZSetOperations.TypedTuple<String>> resultSet =  redisTemplate.opsForZSet().reverseRange(sortedSetName, start, end);
        return resultSet;
    }
}
複製代碼

controller示例代碼

@RestController
@RequestMapping("/redis")
public class RedisController {
    @Autowired
    private RedisService redisService;
    
    /** * 測試redis記錄keyword * * @param keyword 搜索關鍵詞 * @return */
    @GetMapping("/test_search")
    public ResultVO testSearch(@RequestParam("keyword") String keyword) {
        redisService.searchZincrby(keyword);
        // ResultVO和ResultVOUtil是自定義的class,爲了方便展現結果,閱讀時忽略便可
        return ResultVOUtil.success(1, "test-return");
    }

    /** * 測試redis查詢指定範圍的熱詞 * * @param start 查詢範圍開始位置 * @param end 查詢範圍結束位置 * @return */
    @GetMapping("/test_query_top_search_hot_word")
    public ResultVO testQueryTopSearchHotWord(@RequestParam("start") Integer start, @RequestParam("end") Integer end) {
        Set<ZSetOperations.TypedTuple<String>> resultSet =  redisService.queryTopSearchHotWord(start, end);
        // ResultVO和ResultVOUtil是自定義的class,爲了方便展現結果,閱讀時忽略便可
        return ResultVOUtil.success(1, "success", resultSet);
    }
}
複製代碼

測試代碼的運行效果

模擬搜索一些keyword:

02 模擬搜索一些keyword

使用rdm查看reids的存儲狀況,搜索熱詞已經存在redis一個名爲searchHotWord的Sorted Set中:

01 使用rdm查看reids的存儲狀況,搜索熱詞已經存在redis一個名爲searchHotWord的Sorted Set中

查詢結果:

03 查詢結果

One more thing

zrevrange方法無需擔憂用於指定範圍的 startend 出現越界報錯問題。

測試用的Sorted Set總共有8個數據,故意指定 startend 在此長度範圍以外:

04 end無越界問題

05 start無越界問題

經測試,在保證 startend 均 >=0 的前提下,startend 均無越界報錯問題。

相關文章
相關標籤/搜索