自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何使用 Redis 完成 PV,UV 統(tǒng)計?

開發(fā)
本文我們分析了如何使用 Redis 統(tǒng)計 PV 和 UV,通過 Redis 的 INCR? 和 HyperLogLog 數據結構,可以高效地實現 PV 和 UV 的統(tǒng)計。

面試中,我們經常會被問題 PV,UV,那么,什么是 PV?什么又是UV?如何使用 Redis 統(tǒng)計 PV 和 UV?這篇文章,我們將詳細介紹如何在 Java 中使用 Redis 實現 PV 和 UV 的統(tǒng)計。

1. 什么是 PV 和 UV?

  • PV(Page Views):指頁面被訪問的總次數。每一次頁面加載或刷新都會增加一次 PV,無論訪問者是誰。
  • UV(Unique Visitors):指獨立訪客數。通常通過用戶的唯一標識(如用戶 ID、IP 地址、Cookie 等)來統(tǒng)計同一用戶在一定時間范圍內的訪問次數,確保每個獨立訪客只計數一次。

2. Redis 如何統(tǒng)計 PV 和 UV?

(1) 統(tǒng)計 PV

統(tǒng)計 PV 可以通過 Redis 的 INCR 命令實現。這是一個原子操作,可以確保在高并發(fā)情況下準確計數。

(2) 統(tǒng)計 UV

統(tǒng)計 UV 可以使用 Redis 的 HyperLogLog 或 Bitmap 數據結構:

  • HyperLogLog:適合大規(guī)模去重統(tǒng)計,占用內存小,但只能估算基數,誤差約為 0.81%。
  • Bitmap:通過位圖記錄用戶訪問情況,適合用戶 ID 范圍固定且不大的場景。

本示例中將使用 HyperLogLog 來統(tǒng)計 UV,因為它適用于大規(guī)模和動態(tài)用戶場景,且實現簡單。

(3) 數據結構設計

假設我們要統(tǒng)計某個頁面(例如 /home)每日的 PV 和 UV,可以設計如下 Redis 鍵:

  • pv:home:20250301 — 存儲 /home 頁面在 2025年3月1日的 PV 計數。
  • uv:home:20250301 — 存儲 /home 頁面在 2025年3月1日的 UV 計數。

3. 示例代碼

為了更好地理解如何使用 Redis統(tǒng)計 PV,UV,確保在項目中添加 Jedis 依賴。

(1) 示例代碼:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

publicclass RedisPvUvCounter {
    // Redis 服務器配置
    privatestaticfinal String REDIS_HOST = "localhost";
    privatestaticfinalint REDIS_PORT = 6379;
    privatestaticfinal String PAGE_NAME = "home"; // 頁面名稱
    privatestaticfinal DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");

    private JedisPool jedisPool;

    // 構造方法,初始化 Jedis 連接池
    public RedisPvUvCounter() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(128); // 最大連接數,可根據需要調整
        this.jedisPool = new JedisPool(poolConfig, REDIS_HOST, REDIS_PORT);
    }

    /**
     * 統(tǒng)計 PV
     * @param pageName 頁面名稱
     */
    public void incrementPv(String pageName) {
        String date = LocalDate.now().format(DATE_FORMATTER);
        String pvKey = String.format("pv:%s:%s", pageName, date);
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.incr(pvKey);
        }
    }

    /**
     * 統(tǒng)計 UV
     * @param pageName 頁面名稱
     * @param userId   用戶唯一標識
     */
    public void addUv(String pageName, String userId) {
        String date = LocalDate.now().format(DATE_FORMATTER);
        String uvKey = String.format("uv:%s:%s", pageName, date);
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.pfadd(uvKey, userId);
        }
    }

    /**
     * 獲取 PV 統(tǒng)計
     * @param pageName 頁面名稱
     * @return PV 數量
     */
    public long getPv(String pageName) {
        String date = LocalDate.now().format(DATE_FORMATTER);
        String pvKey = String.format("pv:%s:%s", pageName, date);
        try (Jedis jedis = jedisPool.getResource()) {
            String pvStr = jedis.get(pvKey);
            return pvStr != null ? Long.parseLong(pvStr) : 0;
        }
    }

    /**
     * 獲取 UV 統(tǒng)計
     * @param pageName 頁面名稱
     * @return UV 數量
     */
    public long getUv(String pageName) {
        String date = LocalDate.now().format(DATE_FORMATTER);
        String uvKey = String.format("uv:%s:%s", pageName, date);
        try (Jedis jedis = jedisPool.getResource()) {
            return jedis.pfcount(uvKey);
        }
    }

    /**
     * 設置鍵的過期時間(例如 2 天后過期)
     * @param key キー
     * @param seconds 秒數
     */
    public void setExpire(String key, int seconds) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.expire(key, seconds);
        }
    }

    /**
     * 關閉 Jedis 連接池
     */
    public void close() {
        if (jedisPool != null) {
            jedisPool.close();
        }
    }

    public static void main(String[] args) {
        RedisPvUvCounter counter = new RedisPvUvCounter();

        String page = "home";
        String user1 = "user_001";
        String user2 = "user_002";

        // 模擬 PV 和 UV 統(tǒng)計
        counter.incrementPv(page);
        counter.addUv(page, user1);

        counter.incrementPv(page);
        counter.addUv(page, user1); // 重復訪問,不增加 UV

        counter.incrementPv(page);
        counter.addUv(page, user2);

        // 設置鍵的過期時間(可選,根據實際需求)
        String date = LocalDate.now().format(DATE_FORMATTER);
        String pvKey = String.format("pv:%s:%s", page, date);
        String uvKey = String.format("uv:%s:%s", page, date);
        counter.setExpire(pvKey, 2 * 24 * 60 * 60); // PV 鍵 2 天后過期
        counter.setExpire(uvKey, 2 * 24 * 60 * 60); // UV 鍵 2 天后過期

        // 獲取統(tǒng)計結果
        long pv = counter.getPv(page);
        long uv = counter.getUv(page);

        System.out.println("PV 總數: " + pv); // 輸出: PV 總數: 3
        System.out.println("UV 總數: " + uv); // 輸出: UV 總數: 2

        // 關閉連接池
        counter.close();
    }
}

(2) 代碼詳解

①連接 Redis

使用 JedisPool 來管理 Redis 連接池,提升性能和資源利用率。通過配置 JedisPoolConfig 可以調整連接池的相關參數,如最大連接數等。

②統(tǒng)計 PV

  • 使用 INCR 命令對 PV 鍵進行自增。
  • 鍵的命名規(guī)范為 pv:{pageName}:{date}(例如 pv:home:20250301)。
  • 每訪問一次頁面,調用 incrementPv 方法即可增加 PV 計數。

③統(tǒng)計 UV

  • 使用 PFADD 命令將用戶的唯一標識添加到 HyperLogLog 結構中。
  • 鍵的命名規(guī)范為 uv:{pageName}:{date}(例如 uv:home:20250301)。
  • userId 可以是用戶的登錄 ID、IP 地址或其他唯一標識。
  • HyperLogLog 會自動去重,因此即使同一個用戶多次訪問,也只會計數一次。

④獲取 PV 和 UV 數量

  • PV 使用 GET 命令獲取鍵的值,并轉換為 long 類型。如果鍵不存在,則返回 0。
  • UV 使用 PFCOUNT 命令獲取 HyperLogLog 的估算基數。

⑤設置鍵的過期時間

為了避免 Redis 中存儲過多歷史數據,可以為 PV 和 UV 鍵設置過期時間。本示例中設置為 2 天后過期。可以根據實際需求調整。

⑥關閉連接池

使用完畢后,調用 close 方法關閉 JedisPool,釋放資源。

(3) 運行示例

運行 main 方法后,將模擬以下操作:

  • 用戶 user_001 訪問 /home 頁面,PV 增加 1,UV 增加 1。
  • 用戶 user_001 再次訪問 /home 頁面,PV 增加 1,UV 不變。
  • 用戶 user_002 訪問 /home 頁面,PV 增加 1,UV 增加 1。

最終輸出:

PV 總數: 3
UV 總數: 2

4. 擴展與優(yōu)化

(1) 設置鍵的過期時間

可以在 incrementPv 和 addUv 方法中設置鍵的過期時間,以自動刪除過期數據,避免 Redis 內存不斷增長。

public void incrementPv(String pageName) {
    String date = LocalDate.now().format(DATE_FORMATTER);
    String pvKey = String.format("pv:%s:%s", pageName, date);
    try (Jedis jedis = jedisPool.getResource()) {
        jedis.incr(pvKey);
        jedis.expire(pvKey, 2 * 24 * 60 * 60); // 設置過期時間為2天
    }
}

public void addUv(String pageName, String userId) {
    String date = LocalDate.now().format(DATE_FORMATTER);
    String uvKey = String.format("uv:%s:%s", pageName, date);
    try (Jedis jedis = jedisPool.getResource()) {
        jedis.pfadd(uvKey, userId);
        jedis.expire(uvKey, 2 * 24 * 60 * 60); // 設置過期時間為2天
    }
}

(2) 使用 Lua 腳本優(yōu)化

為了減少 Redis 交互次數,可以使用 Lua 腳本將多個命令合并為一個原子操作。例如,可以在一次 Lua 腳本中同時對 PV 和 UV 進行操作。

(3) 分布式環(huán)境下的 Redis 集群

在分布式系統(tǒng)中,可以使用 Redis 集群來提高可用性和擴展性。Jedis 提供了 JedisCluster 類來支持 Redis 集群。

(4) 選擇合適的唯一標識

為了準確統(tǒng)計 UV,選擇唯一標識非常關鍵。常見的方式包括:

  • 用戶登錄 ID:最可靠,但僅適用于已認證用戶。
  • IP 地址:簡單但可能不夠準確,受 NAT 和代理影響。
  • Cookie:通過生成唯一的 Cookie 標識符,即使用戶未登錄也可以追蹤。

根據業(yè)務需求選擇合適的方式,并注意隱私和數據保護。

(5) 持久化與備份

確保 Redis 的持久化機制(RDB 或 AOF)已正確配置,以防止數據丟失。

5. 總結

本文,我們分析了如何使用 Redis 統(tǒng)計 PV 和 UV,通過 Redis 的 INCR 和 HyperLogLog 數據結構,可以高效地實現 PV 和 UV 的統(tǒng)計。另外,實際工作中,我們可以根據實際業(yè)務需求,可以進一步優(yōu)化和擴展,如設置鍵過期時間、使用 Lua 腳本、部署 Redis 集群等。

責任編輯:趙寧寧 來源: 猿java
相關推薦

2019-10-17 09:25:56

Spark StreaPVUV

2021-08-08 22:08:41

Redis開發(fā)網頁

2021-11-01 13:11:45

FlinkPvUv

2016-10-16 13:48:54

多維分析 UVPV

2021-06-03 08:10:30

SparkStream項目Uv

2021-06-06 13:10:12

FlinkPvUv

2015-12-14 14:26:56

Linux命令pv

2015-12-23 17:08:25

H5

2025-02-13 11:11:53

Redis哨兵代碼

2024-10-06 12:50:25

2023-03-08 08:13:33

Pv工具

2017-12-22 10:34:02

大數據AI存儲

2018-12-05 09:00:00

RedisRedis Strea數據庫

2021-08-04 17:55:38

keysRedis數據庫

2017-12-18 09:02:42

Red Hat SysAnsible集成

2025-03-03 10:25:10

2012-03-28 14:06:43

軟件系統(tǒng)系統(tǒng)測試

2024-12-17 15:39:33

2024-03-22 12:10:39

Redis消息隊列數據庫

2013-04-08 10:31:38

微信公眾平臺數據統(tǒng)計
點贊
收藏

51CTO技術棧公眾號