SpringBoot+Redis Bitmap實(shí)現(xiàn)活躍用戶(hù)統(tǒng)計(jì)
前言
Redis的Bitmap數(shù)據(jù)結(jié)構(gòu)是一種緊湊的位圖,它可以用于實(shí)現(xiàn)各種場(chǎng)景,其中統(tǒng)計(jì)活躍用戶(hù)是一種經(jīng)典的業(yè)務(wù)場(chǎng)景。
實(shí)現(xiàn)原理是,通過(guò)將每個(gè)用戶(hù)表示為一個(gè)位,從而跟蹤用戶(hù)的活躍狀態(tài),使用位圖記錄用戶(hù)每天是否登錄,并計(jì)算月度或年度活躍用戶(hù)數(shù)。
案例代碼
以下是一個(gè)小例子,可以看到,使用SpringDataRedis,可以很輕松的實(shí)現(xiàn)BitMap的操作。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.BitSet; @Service public class UserLoginService { // 用戶(hù)登錄記錄的鍵前綴 private static final String LOGIN_KEY_PREFIX = "login:"; // 月份格式化器 private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM"); // 年份格式化器 private static final DateTimeFormatter YEAR_FORMATTER = DateTimeFormatter.ofPattern("yyyy"); @Autowired private RedisTemplate<String, Object> redisTemplate; /** * 記錄用戶(hù)登錄 * * @param userId 用戶(hù)ID */ public void recordLogin(String userId) { // 獲取存儲(chǔ)當(dāng)天用戶(hù)登錄信息的鍵 String loginKey = getLoginKey(); // 計(jì)算位圖偏移量,對(duì)應(yīng)用戶(hù)ID的哈希碼 int bitOffset = getUserIdHashCode(userId); // 將用戶(hù)ID對(duì)應(yīng)的位設(shè)置為1,表示用戶(hù)登錄 redisTemplate.opsForValue().setBit(loginKey, bitOffset, true); } /** * 獲取月度活躍用戶(hù)統(tǒng)計(jì)數(shù)據(jù) * * @return 月度活躍用戶(hù)統(tǒng)計(jì)數(shù)據(jù) */ public BitSet getMonthlyActiveUsers() { // 獲取存儲(chǔ)月度活躍用戶(hù)信息的鍵 String monthlyKey = getMonthlyKey(); // 從Redis中獲取位圖數(shù)據(jù) return retrieveBitSet(monthlyKey); } /** * 獲取年度活躍用戶(hù)統(tǒng)計(jì)數(shù)據(jù) * * @return 年度活躍用戶(hù)統(tǒng)計(jì)數(shù)據(jù) */ public BitSet getYearlyActiveUsers() { // 獲取存儲(chǔ)年度活躍用戶(hù)信息的鍵 String yearlyKey = getYearlyKey(); // 從Redis中獲取位圖數(shù)據(jù) return retrieveBitSet(yearlyKey); } /** * 獲取存儲(chǔ)當(dāng)天用戶(hù)登錄信息的鍵 */ private String getLoginKey() { LocalDate today = LocalDate.now(); String dateKey = today.format(DateTimeFormatter.ISO_DATE); return LOGIN_KEY_PREFIX + dateKey; } /** * 計(jì)算用戶(hù)ID的哈希碼,保證非負(fù)數(shù)并適應(yīng)位圖長(zhǎng)度 */ private int getUserIdHashCode(String userId) { int hashCode = userId.hashCode(); // 1073741823是Redis位圖最大支持長(zhǎng)度(2^30-1),可根據(jù)實(shí)際需求調(diào)整 return Math.abs(hashCode % 1073741823); } /** * 獲取存儲(chǔ)月度活躍用戶(hù)信息的鍵 */ private String getMonthlyKey() { LocalDate today = LocalDate.now(); String monthKey = today.format(MONTH_FORMATTER); return LOGIN_KEY_PREFIX + "monthly:" + monthKey; } /** * 獲取存儲(chǔ)年度活躍用戶(hù)信息的鍵 */ private String getYearlyKey() { LocalDate today = LocalDate.now(); String yearKey = today.format(YEAR_FORMATTER); return LOGIN_KEY_PREFIX + "yearly:" + yearKey; } /** * 從Redis中獲取位圖數(shù)據(jù) */ private BitSet retrieveBitSet(String key) { // 獲取存儲(chǔ)在Redis中的位圖數(shù)據(jù) byte[] bytes = (byte[]) redisTemplate.opsForValue().get(key); if (bytes != null) { // 將字節(jié)數(shù)組轉(zhuǎn)換為BitSet return BitSet.valueOf(bytes); } else { // 若不存在,則返回空的BitSet return new BitSet(); } } }
其中有幾個(gè)地方解釋一下:
1、recordLogin方法用于記錄用戶(hù)登錄情況。每天的登錄情況被保存在以"login:日期"為鍵的位圖中,用戶(hù)的登錄狀態(tài)由位圖中對(duì)應(yīng)的位表示;
2、countMonthlyActiveUsers方法用于計(jì)算月度活躍用戶(hù)數(shù)量。每個(gè)月的活躍用戶(hù)數(shù)保存在以"login:monthly:年月"為鍵的位圖中,通過(guò)Redis的bitCount方法統(tǒng)計(jì)位圖中置為1的位數(shù),即月度活躍用戶(hù)數(shù);
3、ountYearlyActiveUsers方法用于計(jì)算年度活躍用戶(hù)數(shù)量,原理同上,只是統(tǒng)計(jì)的鍵變?yōu)?quot;login:yearly:年份";
4、getLoginKey、getUserIdHashCode、getMonthlyKey和getYearlyKey是輔助方法,負(fù)責(zé)生成對(duì)應(yīng)的Redis鍵或計(jì)算用戶(hù)ID的哈希碼。
轉(zhuǎn)換
上面的例子最終返回的是BitSet對(duì)象,通過(guò)這個(gè)對(duì)象我們經(jīng)過(guò)轉(zhuǎn)換后可以獲取到許多經(jīng)典的統(tǒng)計(jì)數(shù)據(jù),這里列舉一些經(jīng)典常用的統(tǒng)計(jì)結(jié)果示例。
大家可以根據(jù)這里面列舉的統(tǒng)計(jì)數(shù)據(jù),針對(duì)上面的代碼進(jìn)行替換,得到自己想要的結(jié)果。
import java.util.BitSet; // 獲取月度活躍用戶(hù)統(tǒng)計(jì)數(shù)據(jù) BitSet monthlyActiveUsers = userLoginService.getMonthlyActiveUsers(); // 獲取年度活躍用戶(hù)統(tǒng)計(jì)數(shù)據(jù) BitSet yearlyActiveUsers = userLoginService.getYearlyActiveUsers(); // 統(tǒng)計(jì)月度活躍用戶(hù)數(shù)量 int monthlyActiveUserCount = monthlyActiveUsers.cardinality(); // 統(tǒng)計(jì)年度活躍用戶(hù)數(shù)量 int yearlyActiveUserCount = yearlyActiveUsers.cardinality(); // 判斷某個(gè)用戶(hù)是否為月度活躍用戶(hù) String userId = "user123"; boolean isMonthlyActiveUser = monthlyActiveUsers.get(userLoginService.getUserIdHashCode(userId)); // 判斷某個(gè)用戶(hù)是否為年度活躍用戶(hù) boolean isYearlyActiveUser = yearlyActiveUsers.get(userLoginService.getUserIdHashCode(userId)); // 輸出統(tǒng)計(jì)結(jié)果 System.out.println("月度活躍用戶(hù)數(shù)量: " + monthlyActiveUserCount); System.out.println("年度活躍用戶(hù)數(shù)量: " + yearlyActiveUserCount); System.out.println("用戶(hù)user123是否為月度活躍用戶(hù): " + isMonthlyActiveUser); System.out.println("用戶(hù)user123是否為年度活躍用戶(hù): " + isYearlyActiveUser);
總結(jié)
Redis的Bitmap數(shù)據(jù)結(jié)構(gòu)非常靈活,可以根據(jù)具體需求實(shí)現(xiàn)各種位操作,但平時(shí)在項(xiàng)目中很多人沒(méi)有機(jī)會(huì)使用到,這個(gè)案例非常簡(jiǎn)單,希望能讓大家對(duì)此有個(gè)認(rèn)識(shí),未來(lái)用到了不會(huì)感到陌生。
以上就是SpringBoot+Redis Bitmap實(shí)現(xiàn)活躍用戶(hù)統(tǒng)計(jì)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Redis Bitmap用戶(hù)統(tǒng)計(jì)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- springboot項(xiàng)目Redis統(tǒng)計(jì)在線(xiàn)用戶(hù)的實(shí)現(xiàn)示例
- SpringBoot+Redis?BitMap實(shí)現(xiàn)簽到與統(tǒng)計(jì)的項(xiàng)目實(shí)踐
- 微服務(wù)Spring Boot 整合 Redis 實(shí)現(xiàn)UV 數(shù)據(jù)統(tǒng)計(jì)的詳細(xì)過(guò)程
- 微服務(wù)?Spring?Boot?整合?Redis?BitMap?實(shí)現(xiàn)?簽到與統(tǒng)計(jì)功能
- SpringBoot整合Redis實(shí)現(xiàn)訪(fǎng)問(wèn)量統(tǒng)計(jì)的示例代碼
- SpringBoot使用Redis的zset統(tǒng)計(jì)在線(xiàn)用戶(hù)信息
- SpringBoot運(yùn)用Redis統(tǒng)計(jì)用戶(hù)在線(xiàn)數(shù)量的兩種方法實(shí)現(xiàn)
相關(guān)文章
通過(guò)實(shí)例了解java checked和unchecked異常
這篇文章主要介紹了通過(guò)實(shí)例了解checked和unchecked異常,Java異常分為兩種類(lèi)型,checked異常和unchecked異常,另一種叫法是異常和錯(cuò)誤。下面小編就帶大家來(lái)一起學(xué)習(xí)一下吧2019-06-06java實(shí)現(xiàn)微信支付結(jié)果通知
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)微信支付結(jié)果通知,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01Java中的List接口實(shí)現(xiàn)類(lèi)解析
這篇文章主要介紹了Java中的List接口實(shí)現(xiàn)類(lèi)解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02通過(guò)java記錄數(shù)據(jù)持續(xù)變化時(shí)間代碼解析
這篇文章主要介紹了通過(guò)java記錄數(shù)據(jù)持續(xù)變化時(shí)間代碼解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01springboot項(xiàng)目如何使用切面記錄用戶(hù)操作日志
這篇文章主要介紹了springboot項(xiàng)目如何使用切面記錄用戶(hù)操作日志,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10Java中的ReentrantLock、ReentrantReadWriteLock、StampedLock詳解
這篇文章主要介紹了Java中的ReentrantLock、ReentrantReadWriteLock、StampedLock詳解,讀寫(xiě)鎖:一個(gè)資源能夠被多個(gè)讀線(xiàn)程訪(fǎng)問(wèn),或者被一個(gè)寫(xiě)線(xiàn)程訪(fǎng)問(wèn)但是不能同時(shí)存在讀寫(xiě)線(xiàn)程,需要的朋友可以參考下2024-01-01