一個項目中,遇到了搜索熱詞統計的需求,我使用了 Redis 的五大數據類型之一 Sorted Set 實現。目前有兩項數據須要統計:「當日搜索熱詞 top10」和「當週搜索熱詞 top10」。html
關於這兩項數據的統計方法,目前想到了兩種實現方法:java
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
如上圖,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("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;
}
}
複製代碼
@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:
使用rdm查看reids的存儲狀況,搜索熱詞已經存在redis一個名爲searchHotWord的Sorted Set中:
查詢結果:
zrevrange方法無需擔憂用於指定範圍的 start
和 end
出現越界報錯問題。
測試用的Sorted Set總共有8個數據,故意指定 start
和 end
在此長度範圍以外:
經測試,在保證 start
和 end
均 >=0 的前提下,start
和 end
均無越界報錯問題。