手把手教你使用redis實(shí)現(xiàn)排行榜功能
一、需求背景
最近項(xiàng)目需要做排行榜功能,實(shí)現(xiàn)員工邀請(qǐng)用戶注冊(cè)排行榜,要求是實(shí)時(shí)更新,查詢要快。員工所屬支行、二級(jí)行、省行,界面要根據(jù)條件顯示排名數(shù)據(jù)。效果如下圖所示:
原型圖展示比較隨意,用excel隨便寫了一下,湊合著看。
二、實(shí)現(xiàn)思路
1、利用數(shù)據(jù)庫(kù)
建一張統(tǒng)計(jì)表,字段包括:邀請(qǐng)人、邀請(qǐng)人所屬支行、邀請(qǐng)人所屬二級(jí)行、被邀請(qǐng)人、注冊(cè)時(shí)間等關(guān)鍵信息,用于sql統(tǒng)計(jì)排名,根據(jù)條件使用group by相關(guān)字段,比較簡(jiǎn)單,這個(gè)大家應(yīng)該清楚。
數(shù)據(jù)量小,統(tǒng)計(jì)效率還可以。但是支行下有十幾萬(wàn)員工,一個(gè)員工邀請(qǐng)10個(gè)就百萬(wàn)數(shù)據(jù),如果更多,數(shù)據(jù)就更大了,統(tǒng)計(jì)效率不高。下面重點(diǎn)討論用第二種方式實(shí)現(xiàn)。
2、利用redis
我們都知道redis基于內(nèi)存實(shí)現(xiàn)的,查詢效率極高,且支持多種數(shù)據(jù)類型,其中zset是本次實(shí)現(xiàn)功能的關(guān)鍵。
- ZSet也是String類型元素的集合,且不允許重復(fù)的成員;
- 不同的是每個(gè)元素都會(huì)關(guān)聯(lián)一個(gè)double類型的分?jǐn)?shù),剛好也是我們需要的邀請(qǐng)用戶數(shù);
- 通過(guò)分?jǐn)?shù)來(lái)為集合中的成員進(jìn)行排序。ZSet的成員是唯一的,但分?jǐn)?shù)(score)卻可以重復(fù);
基于上面的特性,滿足我們本次的需求。好了,說(shuō)了一大堆廢話,下面將進(jìn)入正題。
首先,捋一下查詢條件,根據(jù)前面的效果圖,可以看出有以下幾種情況:
- 二級(jí)行的全部排名以及日周月榜排名
- 支行在省行的全部排名以及日周月榜排名
- 支行在二級(jí)行的全部排名以及日周月榜排名
- 員工在省行的全部排名以及日周月榜排名
- 員工在二級(jí)行的全部排名以及日周月榜排名
- 員工在支行的全部排名以及日周月榜排名
基于redis的Zset函數(shù)incrementScore,我們很快就能發(fā)現(xiàn),其實(shí)實(shí)現(xiàn)各個(gè)排名,只要把key規(guī)定好即可,例如:
- 員工在省行的全部排名key,可以設(shè)置為 rank:employee:省行
- 員工在省行的日排行榜key,可以設(shè)置為 rank:emploee:省行:當(dāng)天日期
下面我們來(lái)實(shí)現(xiàn)其中的上面的兩個(gè)排行:
String key = "rank:employee:廣東省"; redisTemplate.opsForZSet().incrementScore(key, "張三", 10); redisTemplate.opsForZSet().incrementScore(key, "李四", 8); redisTemplate.opsForZSet().incrementScore(key, "王五", 5);
執(zhí)行完后,redis會(huì)保存為如下數(shù)據(jù):
這樣的話,有了上面的數(shù)據(jù),可以用redis的提供的函數(shù)把排行榜查出來(lái),代碼如下:
String key = "rank:employee:廣東省"; Set<ZSetOperations.TypedTuple<String>> set = redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, -1); JSONArray jsonArray = JSONObject.parseArray(JSONObject.toJSONString(set)); for(int i = 0, size = jsonArray.size(); i < size; i++) { JSONObject o = JSONObject.parseObject(jsonArray.get(i).toString()); System.out.println("員工:" + o.getString("value") + ", 邀請(qǐng)人數(shù):" + o.getLongValue("score")); }
reverseRangeWithScores方法接收三個(gè)參數(shù),第一個(gè)是key,后面兩個(gè)是分頁(yè)查詢,起始是從0開(kāi)始,-1表示全部,如果設(shè)置為0,4,那么就是查詢前5條記錄,查出結(jié)果如下:
以上就實(shí)現(xiàn)了員工在省行的排名。類似的,員工要實(shí)現(xiàn)在省行的日榜,代碼如下:
String key = "rank:employee:廣東省:2022-09-01"; redisTemplate.opsForZSet().incrementScore(key, "張三", 10); redisTemplate.opsForZSet().incrementScore(key, "李四", 8); redisTemplate.opsForZSet().incrementScore(key, "王五", 5);
執(zhí)行完后,redis會(huì)保存為如下數(shù)據(jù)(這里我多設(shè)置了前一天的數(shù)據(jù)):
一樣的,用 reverseRangeWithScores方法可以把上面的結(jié)果查出來(lái)。
至于周榜、月榜,可以把每一天的數(shù)據(jù)累加起來(lái),再做個(gè)排名,redis已經(jīng)幫我們實(shí)現(xiàn)了這個(gè)功能,代碼如下:
Date date = DateUtil.date(); //獲取本周的第一天 DateTime beginOfWeek = DateUtil.beginOfWeek(date); //到今天一共有幾天 long diffDay = DateUtil.between(date, beginOfWeek, DateUnit.DAY) + 1; List<String> keys = new ArrayList<>(); for(int i = 0; i < diffDay; i++) { //把需要查詢的天數(shù)放一起 keys.add("rank:employee:廣東省:" + DateUtil.formatDate(DateUtil.offsetDay(beginOfWeek, i))); } //redis使用unionAndStore做合并,將結(jié)果集放在另一個(gè)的key,也就是第三個(gè)參數(shù) redisTemplate.opsForZSet().unionAndStore("weekRank", keys, "employeeRankWeek"); //查詢結(jié)果集用employeeRankWeek這個(gè)key Set<ZSetOperations.TypedTuple<String>> set = redisTemplate.opsForZSet().reverseRangeWithScores("employeeRankWeek", 0, -1); JSONArray jsonArray = JSONObject.parseArray(JSONObject.toJSONString(set)); for(int i = 0, size = jsonArray.size(); i < size; i++) { JSONObject o = JSONObject.parseObject(jsonArray.get(i).toString()); System.out.println("員工:" + o.getString("value") + ", 本周邀請(qǐng)人數(shù):" + o.getLongValue("score")); }
注意代碼里面說(shuō)的,redis會(huì)把結(jié)果合并到另一個(gè)key,在redis上也可以看到,如下圖:
查出來(lái)的結(jié)果如下圖:
其實(shí)我們會(huì)發(fā)現(xiàn),本周榜、本月榜無(wú)需保存每一天的數(shù)據(jù),只要把key設(shè)置為本周或本月的第一天就可以,因?yàn)樘砑訑?shù)據(jù)的那一刻就知道是哪一周或哪月了。
例如:key = rank:employee:廣東省:2022-08-29,8月29日是本周的第一天,無(wú)論你在接下來(lái)一周內(nèi)邀請(qǐng)多少人,都是在本周內(nèi)完成的,在這個(gè)基礎(chǔ)上累加邀請(qǐng)數(shù)量即可。本月榜的邏輯也是一樣。
查詢的時(shí)候,獲取當(dāng)前時(shí)間本周或本月的第一天,就可以實(shí)現(xiàn)本周、本月排行了。
String key = "rank:employee:廣東省"; Date date = DateUtil.date(); String week = DateUtil.formatDate(DateUtil.beginOfWeek(date)); String month = DateUtil.formatDate(DateUtil.beginOfMonth(date)); //周榜 redisTemplate.opsForZSet().incrementScore(key+":week:"+week, "張三", 17); //月榜 redisTemplate.opsForZSet().incrementScore(key+":month:"+month, "張三", 17);
當(dāng)然了,如果要查詢歷史的排行,這種設(shè)計(jì)就滿足不了了,還是要保存每天的數(shù)據(jù)才行。
總結(jié)
到此這篇關(guān)于使用redis實(shí)現(xiàn)排行榜功能的文章就介紹到這了,更多相關(guān)redis實(shí)現(xiàn)排行榜功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis實(shí)現(xiàn)限流器的三種方法(小結(jié))
本文主要介紹了Redis實(shí)現(xiàn)限流器的三種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05Redis生成分布式系統(tǒng)全局唯一ID的實(shí)現(xiàn)
在互聯(lián)網(wǎng)系統(tǒng)中,并發(fā)越大的系統(tǒng),數(shù)據(jù)就越大,數(shù)據(jù)越大就越需要分布式,本文主要介紹了Redis生成分布式系統(tǒng)全局唯一ID的實(shí)現(xiàn),感興趣的可以了解一下2021-10-10Redis全文搜索教程之創(chuàng)建索引并關(guān)聯(lián)源數(shù)據(jù)的教程
RediSearch提供了一種簡(jiǎn)單快速的方法對(duì) hash 或者 json 類型數(shù)據(jù)的任何字段建立二級(jí)索引,然后就可以對(duì)被索引的 hash 或者 json 類型數(shù)據(jù)字段進(jìn)行搜索和聚合操作,這篇文章主要介紹了Redis全文搜索教程之創(chuàng)建索引并關(guān)聯(lián)源數(shù)據(jù),需要的朋友可以參考下2023-12-12Redis中一些最常見(jiàn)的面試問(wèn)題總結(jié)
Redis在互聯(lián)網(wǎng)技術(shù)存儲(chǔ)方面使用如此廣泛,幾乎所有的后端技術(shù)面試官都要在Redis的使用和原理方面對(duì)小伙伴們進(jìn)行各種刁難。下面這篇文章主要給大家總結(jié)介紹了關(guān)于Redis中一些最常見(jiàn)的面試問(wèn)題,需要的朋友可以參考下2018-09-09詳解redis desktop manager安裝及連接方式
這篇文章主要介紹了redis desktop manager安裝及連接方式,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09redis不能訪問(wèn)本機(jī)真實(shí)ip地址的解決方案
這篇文章主要介紹了redis不能訪問(wèn)本機(jī)真實(shí)ip地址的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07如何使用Redis實(shí)現(xiàn)電商系統(tǒng)的庫(kù)存扣減
在日常開(kāi)發(fā)中有很多地方都有類似扣減庫(kù)存的操作,本文主要介紹了如何使用Redis實(shí)現(xiàn)電商系統(tǒng)的庫(kù)存扣減,具有一定的參考價(jià)值,感興趣的可以了解一下2022-01-01