redis設計統計使用者訪問量

橘子味芬达水發表於2024-10-03

需求:實現某個介面每天呼叫了多少次,每個使用者只記錄一次。

(例如,統計刷題模組,練題模組,模擬面試模組每天訪問量,利於後續針對功能訪問量做出其他最佳化設計。貼子的瀏覽量)
先分析幾種不同的方案:

方案一:使用Hash雜湊結構

實現方法:當使用者訪問網站時,我們可以使用使用者的ID作為標識(若使用者未登入,則生成一個隨機標識)。透過Redis的HSET命令,以URI和日期拼接作為key,使用者ID或隨機標識作為field,將value設定為1。統計訪問量時,使用HLEN命令獲取結果。

優點:

  • 實現簡單,易於理解。

  • 查詢方便,資料準確性高。

缺點:

  • 隨著key的增多,記憶體佔用過大,效能可能下降。

  • 對於訪問量巨大的網站(如拼多多),此方案可能無法承受。

方案二:使用Bitset

實現方法:利用Bitset對使用者ID進行壓縮儲存。透過SETBIT命令標記使用者訪問,使用GETBIT查詢使用者是否訪問,最後透過BITCOUNT統計訪問量。

優點:

  • 佔用記憶體更小,適用於大規模使用者資料。

  • 查詢方便,可指定查詢某個使用者。

缺點:

  • 使用者稀疏時,記憶體佔用可能比方案一更大。

  • 對於未登入使用者,可能需要額外的對映開銷。

方案三:使用機率演算法

實現方法:採用Redis中的HyperLogLog演算法,這是一種基數評估演算法。使用PFADD命令記錄使用者訪問,透過PFCOUNT命令計算訪問量。

優點:

  • 佔用記憶體極小,每個key僅需要12KB。

  • 非常適合超大規模使用者訪問量的網站。

缺點:

  • 查詢指定使用者時可能存在誤差。

  • 總數統計存在一定的誤差(約0.81%)。

以上三種方案各有優劣,具體選擇應根據實際業務需求和場景來決定:

  • 若對資料準確性要求較高,且訪問量適中,可以選擇方案一。

  • 若需要處理大規模使用者資料,且對記憶體佔用有要求,可以選擇方案二。

  • 若對資料精度要求不高,但需要處理超大規模使用者訪問量,可以選擇方案三。

以上是針對Redis提供的解決方案,在專案中對前後端日誌埋點資料,透過流式計算以及大資料分析,也是常用的解決方案。

首先最簡單的做法就是當使用者訪問某個功能時,在進行查詢時自動傳遞一個攜帶使用者id,日期與功能欄位的資料傳入mysql中,當第二次進入先查詢,當日已存在便不再儲存該資料,但是該方案存在問題便是,每個使用者都在頻繁的與資料庫做互動,是否針對具體業務情況將其存入單獨的庫儲存也需要權衡,而且我們系統已經引入的redis,不妨直接用redis來做。首次先查詢redis,redis存在直接切斷,不存在存入則存入redis,後續透過定時任務排程在流量穩定時遷入資料庫。

redis的HyperLogLog
Redis 在 2.8.9 版本新增了 HyperLogLog 結構。Redis HyperLogLog 是用來做基數統計的演算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定 的、並且是很小的。在 Redis 裡面,每個 HyperLogLog 鍵只需要花費 12 KB 記憶體,就可以計算接近 2^64 個不同元素的基 數。這和計算基數時,元素越多耗費記憶體就越多的集合形成鮮明對比。
但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以HyperLogLog 不能像集合那樣,返回輸入的各個元素。比如資料集 {1, 3, 5, 7, 5, 7, 8}, 那麼這個資料集的基數集為 {1, 3, 5 ,7, 8}, 基數(不重複元素)為5。 基數估計就是在誤差可接受的範圍內,快速計算基數。
簡單的說HyperLogLog透過機率學統計訪問數量,統計的時候存在誤差,但是誤差很小。所以適合特殊場景,例如日訪問量統計。
操作指令如下:
使用者001訪問。如果 HyperLogLog 的內部被修改了,那麼返回 1,否則返回 0 .

PFADD visit_{data} “001”

使用者002訪問。

PFADD visit_{data} “002”

檢視訪問數量。

PFCOUNT visit_{data}

整合spring

    public boolean add(String key, String obj) {
        return redisTemplate.opsForHyperLogLog().add(key, obj) > 0;
    }
    public long count(String key) {
        // pfcount 非精準統計 key的計數
        return redisTemplate.opsForHyperLogLog().size(key);
    }

我們可以抽象出單獨介面方法,需要統計日活量的直接呼叫介面即可

功能介面

public interface UniqueVisitor {
    Boolean add(String id, String loginid);
    Boolean addMonth(String id, String loginid);
    Long getCount(String id);
}

  

@Service
public class UniqueVisitorImpl implements UniqueVisitor {
    @Resource
    private RedisUtil redisUtil;
    private static final String Unique_Visitor = "user_Unique_Visitor:";

    @Override
    public Boolean add(String id, String loginid) {
        LocalDate today = LocalDate.now();
        long dayOfYear = today.getDayOfYear();
        return redisUtil.add(Unique_Visitor + id+dayOfYear, loginid);
    }
    @Override
    public Boolean addMonth(String id, String loginid) {
        LocalDate today = LocalDate.now();
        long Month = today.getMonthValue();
        return redisUtil.add(Unique_Visitor + id+Month, loginid);
    }
    @Override
    public Long getCount(String id) {
        LocalDate today = LocalDate.now();
        long dayOfYear = today.getDayOfYear();
        return redisUtil.count(Unique_Visitor + id+dayOfYear);
    }
}

呼叫計入日活量

    @GetMapping("/uniquevisitor")
    public Result<Boolean> uniqueVisitor(@RequestParam("id") String id) {
        try {
            return Result.ok(uniqueVisitor.getCount(id));
        } catch (Exception e) {
            log.error("SubjectCategoryController.add.error:{}", e.getMessage(), e);
            return Result.fail("查詢瀏覽量失敗");
        }
    }

  

   /**
         * 統計使用者今日訪問新增題目
         */
        uniqueVisitor.add("addSubject",LoginUtil.getLoginId());

  實現效果

我們測試了兩個賬號,可以看到今日新增題目的日活量為2次。

相關文章