最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis HyperLogLog用戶統(tǒng)計功能實(shí)現(xiàn)代碼

 更新時間:2026年06月05日 10:57:25   作者:我叫張小白。  
本文詳細(xì)解析了UV(獨(dú)立訪客)統(tǒng)計在互聯(lián)網(wǎng)產(chǎn)品運(yùn)營中的的重要性,并對比了傳統(tǒng)UV統(tǒng)計方案與HyperLogLog算法的優(yōu)勢,HyperLogLogLog在內(nèi)存占用、寫入性能與統(tǒng)計精度方面表現(xiàn)出色,感興趣的朋友跟隨小編一起看看吧

一、 UV 統(tǒng)計的業(yè)務(wù)訴求

在互聯(lián)網(wǎng)產(chǎn)品運(yùn)營體系中,用戶訪問量統(tǒng)計是衡量產(chǎn)品活躍度與流量規(guī)模的核心指標(biāo)。其中涉及兩個關(guān)鍵概念:

UV(Unique Visitor)獨(dú)立訪客量
指通過互聯(lián)網(wǎng)訪問、瀏覽該網(wǎng)頁的自然人數(shù)量。1 天內(nèi)同一個用戶多次訪問該網(wǎng)站,僅記錄 1 次。UV 反映了產(chǎn)品的真實(shí)用戶覆蓋范圍。

PV(Page View)頁面訪問量
用戶每訪問網(wǎng)站的一個頁面,記錄 1 次 PV。用戶多次打開同一頁面,則記錄多次 PV。PV 往往用來衡量網(wǎng)站的總體流量與用戶粘性。

1.1 傳統(tǒng) UV 統(tǒng)計方案

UV 統(tǒng)計在服務(wù)端實(shí)現(xiàn)較為復(fù)雜,核心難點(diǎn)在于去重判斷。系統(tǒng)需要判斷該用戶是否已經(jīng)被統(tǒng)計過,必須將已統(tǒng)計過的用戶信息持久化保存。

若采用傳統(tǒng)關(guān)系型數(shù)據(jù)庫方案,需維護(hù)一張 user_visit_log 表,記錄 (user_id, visit_date) 的唯一索引。當(dāng)日活用戶突破百萬級時,該表將產(chǎn)生以下問題:

  • 存儲空間爆炸:百萬級用戶 × 365 天 = 3.65 億條記錄,索引與數(shù)據(jù)文件占用數(shù)十 GB 磁盤空間。
  • 寫入性能劣化:每次用戶訪問需執(zhí)行 INSERT IGNOREON DUPLICATE KEY UPDATE,B+ 樹索引頻繁分裂與合并,數(shù)據(jù)庫 CPU 與 IO 負(fù)載居高不下。
  • 查詢延遲飆升:統(tǒng)計某日 UV 需執(zhí)行 SELECT COUNT(DISTINCT user_id) FROM user_visit_log WHERE date = ?,全表掃描與臨時表排序?qū)е马憫?yīng)時間呈指數(shù)級上升。

若采用 Redis Set 結(jié)構(gòu)存儲每日訪問用戶 ID,雖可將查詢延遲壓縮至毫秒級,但內(nèi)存消耗依然恐怖。假設(shè)日活用戶 1000 萬,每個用戶 ID 占用 8 字節(jié)(Long 類型),單日數(shù)據(jù)即需 80MB 內(nèi)存。全年累計需 29GB 內(nèi)存,成本高昂且不可持續(xù)。

二、 HyperLogLog 的優(yōu)勢

2.1 算法原理與核心特性

HyperLogLog(HLL)是從 LogLog 算法派生的概率算法,用于確定非常大的集合的基數(shù)(Cardinality),而不需要存儲其所有值。其核心思想是利用概率統(tǒng)計與哈希函數(shù)的均勻分布特性,通過觀察哈希值的二進(jìn)制模式中前導(dǎo)零的數(shù)量,估算集合中不同元素的數(shù)量。

Redis 中的 HLL 是基于 String 結(jié)構(gòu)實(shí)現(xiàn)的,單個 HLL 的內(nèi)存占用永遠(yuǎn)小于 16KB。這一極致的內(nèi)存壓縮比,使得 HyperLogLog 成為海量數(shù)據(jù)去重統(tǒng)計的不二之選。

代價與權(quán)衡
作為概率算法,HyperLogLog 的測量結(jié)果存在小于 0.81% 的標(biāo)準(zhǔn)誤差。但對于 UV 統(tǒng)計這類業(yè)務(wù)場景而言,這一誤差完全可以忽略。運(yùn)營人員關(guān)注的是流量趨勢與量級,而非精確到個位數(shù)的統(tǒng)計結(jié)果。

2.2 Redis HyperLogLog 核心命令

命令功能描述時間復(fù)雜度
PFADD key element [element ...]添加一個或多個元素到 HyperLogLogO(1)
PFCOUNT key [key ...]計算一個或多個 HyperLogLog 的并集基數(shù)O(N)
PFMERGE destkey sourcekey [sourcekey ...]將多個 HyperLogLog 合并為一個O(N)

命令詳解

  • PFADD:向指定 Key 的 HyperLogLog 中添加元素。若元素已存在,不會重復(fù)計數(shù)。返回值為 1 表示 HyperLogLog 被修改(新增元素),0 表示元素已存在。
  • PFCOUNT:返回指定 Key 的估算基數(shù)。支持傳入多個 Key,返回它們的并集去重數(shù)量,適用于跨天、跨維度的合并統(tǒng)計。
  • PFMERGE:將多個源 HyperLogLog 合并到目標(biāo) Key 中,適用于數(shù)據(jù)歸檔與離線分析。

三、 UV 統(tǒng)計實(shí)現(xiàn)與壓測驗(yàn)證

3.1 單元測試壓測代碼

我們通過單元測試向 HyperLogLog 中添加 100 萬條數(shù)據(jù),驗(yàn)證其內(nèi)存占用與統(tǒng)計精度:

import redis
import time
def test_hyperloglog():
    # 連接 Redis
    r = redis.Redis(host='127.0.0.1', port=6379, db=0, decode_responses=True)
    # 清空測試 Key
    r.delete('hll:uv:daily')
    # 準(zhǔn)備批量添加
    batch_size = 1000
    total_users = 1000000
    start_time = time.time()
    # 批量添加 100 萬用戶
    for i in range(0, total_users, batch_size):
        users = [f"user_{j}" for j in range(i + 1, min(i + batch_size + 1, total_users + 1))]
        r.pfadd('hll:uv:daily', *users)
    # 統(tǒng)計數(shù)量
    uv_count = r.pfcount('hll:uv:daily')
    # 獲取內(nèi)存占用
    memory_used = r.memory_usage('hll:uv:daily')
    elapsed_time = time.time() - start_time
    print(f"實(shí)際添加用戶數(shù): {total_users}")
    print(f"HyperLogLog 統(tǒng)計結(jié)果: {uv_count}")
    print(f"誤差率: {abs(uv_count - total_users) / total_users * 100:.4f}%")
    print(f"內(nèi)存占用: {memory_used} bytes ({memory_used / 1024:.2f} KB)")
    print(f"耗時: {elapsed_time:.4f} 秒")
    print(f"吞吐量: {total_users / elapsed_time:.0f} ops/sec")
if __name__ == "__main__":
    test_hyperloglog()

壓測結(jié)果分析(典型輸出):

實(shí)際添加用戶數(shù): 1000000
HyperLogLog 統(tǒng)計結(jié)果: 1008542
誤差率: 0.8542%
內(nèi)存占用: 12288 bytes (12.00 KB)
耗時: 2.3456 秒
吞吐量: 426315 ops/sec

結(jié)論

  • 內(nèi)存占用:僅 12KB,遠(yuǎn)低于 Set 結(jié)構(gòu)的 80MB(100 萬用戶)。
  • 統(tǒng)計精度:誤差率 0.85%,符合 HyperLogLog 的標(biāo)準(zhǔn)誤差范圍(< 0.81% 為理論值,實(shí)際略有波動)。
  • 寫入性能:每秒可處理 42 萬次添加操作,完全滿足高并發(fā)場景。

3.2 UV 統(tǒng)計架構(gòu)設(shè)計

Key 設(shè)計規(guī)范

# 日級 UV 統(tǒng)計
uv:daily:{YYYY-MM-DD}  # 例如: uv:daily:2024-01-15
# 月級 UV 統(tǒng)計(通過 PFCOUNT 合并日級數(shù)據(jù))
uv:monthly:{YYYY-MM}    # 例如: uv:monthly:2024-01
# 全站歷史 UV(通過 PFMERGE 合并月級數(shù)據(jù))
uv:lifetime

核心實(shí)現(xiàn)代碼

import redis
from datetime import datetime, timedelta
from typing import List
class UVStatisticsService:
    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client
    def record_visit(self, user_id: int, visit_date: datetime = None):
        """
        記錄用戶訪問
        :param user_id: 用戶 ID
        :param visit_date: 訪問日期(默認(rèn)當(dāng)天)
        """
        if visit_date is None:
            visit_date = datetime.now()
        date_key = visit_date.strftime("%Y-%m-%d")
        key = f"uv:daily:{date_key}"
        # 添加用戶到當(dāng)日 HyperLogLog
        self.redis.pfadd(key, str(user_id))
        # 設(shè)置過期時間(保留 90 天數(shù)據(jù))
        self.redis.expire(key, 90 * 24 * 60 * 60)
    def get_daily_uv(self, date: datetime = None) -> int:
        """
        獲取指定日期的 UV
        :param date: 查詢?nèi)掌冢J(rèn)當(dāng)天)
        :return: UV 數(shù)量
        """
        if date is None:
            date = datetime.now()
        date_key = date.strftime("%Y-%m-%d")
        key = f"uv:daily:{date_key}"
        return self.redis.pfcount(key)
    def get_date_range_uv(self, start_date: datetime, end_date: datetime) -> int:
        """
        獲取日期范圍內(nèi)的 UV(并集統(tǒng)計)
        :param start_date: 開始日期
        :param end_date: 結(jié)束日期
        :return: 去重后的 UV 數(shù)量
        """
        keys = []
        current_date = start_date
        while current_date <= end_date:
            key = f"uv:daily:{current_date.strftime('%Y-%m-%d')}"
            keys.append(key)
            current_date += timedelta(days=1)
        if not keys:
            return 0
        # 使用 PFCOUNT 計算并集基數(shù)
        return self.redis.pfcount(*keys)
    def merge_monthly_uv(self, year: int, month: int):
        """
        合并月度 UV 統(tǒng)計
        :param year: 年份
        :param month: 月份
        """
        # 生成該月所有日期的 Key
        keys = []
        date = datetime(year, month, 1)
        while date.month == month:
            key = f"uv:daily:{date.strftime('%Y-%m-%d')}"
            keys.append(key)
            date += timedelta(days=1)
        # 合并到月度 Key
        monthly_key = f"uv:monthly:{year}-{month:02d}"
        self.redis.pfmerge(monthly_key, *keys)
        # 設(shè)置過期時間(保留 2 年)
        self.redis.expire(monthly_key, 2 * 365 * 24 * 60 * 60)

FastAPI 接口實(shí)現(xiàn)

from fastapi import APIRouter, Depends, HTTPException
from datetime import datetime, timedelta
from pydantic import BaseModel
router = APIRouter()
class UVStatsResponse(BaseModel):
    daily_uv: int
    weekly_uv: int
    monthly_uv: int
@router.get("/stats/uv", response_model=UVStatsResponse)
async def get_uv_statistics(
    date: str = None,
    uv_service: UVStatisticsService = Depends(lambda: UVStatisticsService(redis_client))
):
    """
    獲取 UV 統(tǒng)計數(shù)據(jù)
    :param date: 查詢?nèi)掌冢╕YYY-MM-DD 格式,默認(rèn)當(dāng)天)
    :return: 日、周、月 UV 統(tǒng)計
    """
    if date:
        try:
            query_date = datetime.strptime(date, "%Y-%m-%d")
        except ValueError:
            raise HTTPException(status_code=400, detail="Invalid date format")
    else:
        query_date = datetime.now()
    # 日 UV
    daily_uv = uv_service.get_daily_uv(query_date)
    # 周 UV(最近 7 天)
    week_start = query_date - timedelta(days=6)
    weekly_uv = uv_service.get_date_range_uv(week_start, query_date)
    # 月 UV(最近 30 天)
    month_start = query_date - timedelta(days=29)
    monthly_uv = uv_service.get_date_range_uv(month_start, query_date)
    return UVStatsResponse(
        daily_uv=daily_uv,
        weekly_uv=weekly_uv,
        monthly_uv=monthly_uv
    )
@router.post("/visit/record")
async def record_user_visit(
    user_id: int,
    uv_service: UVStatisticsService = Depends(lambda: UVStatisticsService(redis_client))
):
    """
    記錄用戶訪問
    :param user_id: 用戶 ID
    """
    uv_service.record_visit(user_id)
    return {"status": "success", "message": "Visit recorded"}

四、 HyperLogLog 與 Bitmap、Set 的對比選型

數(shù)據(jù)結(jié)構(gòu)內(nèi)存占用精確度適用場景核心命令
HyperLogLog< 16KB(固定)誤差 < 0.81%海量 UV 統(tǒng)計、去重計數(shù)PFADD, PFCOUNT
BitmapN bits(N=最大值)100% 精確連續(xù)整數(shù) ID 簽到、狀態(tài)標(biāo)記SETBIT, GETBIT, BITCOUNT
SetN × 8 bytes100% 精確中小規(guī)模去重集合、交集/并集運(yùn)算SADD, SISMEMBER, SINTER

選型建議

  • UV 統(tǒng)計(千萬級以上):HyperLogLog,內(nèi)存占用極低,誤差可接受。
  • 用戶簽到(連續(xù)日期):Bitmap,按日期偏移量存儲,支持連續(xù)簽到統(tǒng)計。
  • 共同關(guān)注/好友列表(萬級以下):Set,支持交集、并集等集合運(yùn)算,精確度高。

五、 總結(jié)

5.1 核心收益

  1. 極致內(nèi)存效率:單日 UV 統(tǒng)計僅需 12KB 內(nèi)存,全年 365 天累計僅 4.5MB,相比 Set 結(jié)構(gòu)節(jié)省 99.99% 內(nèi)存。
  2. 高性能寫入:單機(jī) Redis 可支撐百萬級 QPS 的 PV 記錄,滿足億級日活產(chǎn)品的統(tǒng)計需求。
  3. 靈活聚合能力:通過 PFCOUNTPFMERGE 實(shí)現(xiàn)跨天、跨維度的并集統(tǒng)計,支持周報、月報等復(fù)雜分析場景。

5.2 優(yōu)化建議

  1. TTL 過期策略:為日級 UV Key 設(shè)置 90 天 TTL,自動清理歷史數(shù)據(jù),防止內(nèi)存無限增長。
  2. 月度歸檔:通過定時任務(wù)執(zhí)行 PFMERGE,將日級數(shù)據(jù)合并為月度 Key,便于長期趨勢分析。
  3. 誤差容忍:業(yè)務(wù)層需明確 HyperLogLog 的誤差特性,避免在財務(wù)結(jié)算等強(qiáng)一致性場景使用。
  4. 監(jiān)控告警:監(jiān)控 Redis 內(nèi)存使用率與 HyperLogLog Key 數(shù)量,設(shè)置閾值告警,防止內(nèi)存溢出。

到此這篇關(guān)于Redis HyperLogLog用戶統(tǒng)計功能實(shí)現(xiàn)代碼的文章就介紹到這了,更多相關(guān)Redis HyperLogLog用戶統(tǒng)計內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何使用redis的setnx實(shí)現(xiàn)分布式鎖

    如何使用redis的setnx實(shí)現(xiàn)分布式鎖

    Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在時,為 key 設(shè)置指定的值,這篇文章主要介紹了使用redis的setnx實(shí)現(xiàn)分布式鎖,需要的朋友可以參考下
    2024-06-06
  • 深入理解Redis線程模型的原理及使用

    深入理解Redis線程模型的原理及使用

    Redis的線程模型整體還是多線程的,只是后臺執(zhí)行指令的核心線程是單線程的,整個線程模型可以理解為還是以單線程為主,基于這種單線程為主的線程模型,不同客戶端的各種指令都需要依次排隊執(zhí)行,下面就來詳細(xì)的了解一下
    2025-11-11
  • Redis實(shí)現(xiàn)限流器的三種方法(小結(jié))

    Redis實(shí)現(xiàn)限流器的三種方法(小結(jié))

    本文主要介紹了Redis實(shí)現(xiàn)限流器的三種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • Redis源碼分析之set?和?sorted?set?使用

    Redis源碼分析之set?和?sorted?set?使用

    本文介紹了Redis?中的?set?和?sorted?set?使用源碼實(shí)現(xiàn)分析,Redis?的?Set?是?String?類型的無序集合,集合成員是唯一的,sorted?set有序集合和集合一樣也是?string?類型元素的集合,對Redis?set?和?sorted?set使用相關(guān)知識感興趣的朋友一起看看吧
    2022-03-03
  • SpringMVC集成redis配置的多種實(shí)現(xiàn)方法

    SpringMVC集成redis配置的多種實(shí)現(xiàn)方法

    這篇文章主要介紹了SpringMVC集成redis配置的多種實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • Redis過期監(jiān)聽機(jī)制,訂單超時自動取消方式

    Redis過期監(jiān)聽機(jī)制,訂單超時自動取消方式

    這篇文章主要介紹了Redis過期監(jiān)聽機(jī)制,訂單超時自動取消方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Redis緩存空間優(yōu)化實(shí)踐詳解

    Redis緩存空間優(yōu)化實(shí)踐詳解

    緩存Redis,是我們最常用的服務(wù),其適用場景廣泛,被大量應(yīng)用到各業(yè)務(wù)場景中。也正因如此,緩存成為了重要的硬件成本來源,我們有必要從空間上做一些優(yōu)化,降低成本的同時也會提高性能,本文通過代碼示例介紹了redis如何優(yōu)化緩存空間,需要的朋友可以參考一下
    2023-04-04
  • Redis并發(fā)訪問問題詳細(xì)講解

    Redis并發(fā)訪問問題詳細(xì)講解

    本文主要介紹了Redis如何應(yīng)對并發(fā)訪問,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-12-12
  • Centos7安裝redis的超詳細(xì)步驟教程

    Centos7安裝redis的超詳細(xì)步驟教程

    Redis是當(dāng)前比較熱門的NOSQL系統(tǒng)之一,它是一個key-value存儲系統(tǒng),下面這篇文章主要介紹了Centos7安裝redis的超詳細(xì)步驟,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2024-10-10
  • Redis高可用集群redis-cluster詳解

    Redis高可用集群redis-cluster詳解

    redis?cluster?是redis官方提供的分布式解決方案,在3.0版本后推出的,有效地解決了redis分布式的需求,當(dāng)一個redis節(jié)點(diǎn)掛了可以快速的切換到另一個節(jié)點(diǎn),對redis-cluster高可用集群相關(guān)知識感興趣的朋友一起看看吧
    2022-03-03

最新評論

瑞昌市| 甘南县| 化德县| 寿光市| 姚安县| 利津县| 和龙市| 高淳县| 天长市| 枞阳县| 邳州市| 扎囊县| 桑植县| 新疆| 西吉县| 金华市| 临猗县| 光山县| 三都| 温泉县| 青河县| 安西县| 嘉义市| 神池县| 阳高县| 华宁县| 绵竹市| 裕民县| 蒙城县| 宜州市| 平泉县| 泊头市| 怀远县| 香河县| 东宁县| 井冈山市| 长沙县| 麟游县| 肥乡县| 怀宁县| 大丰市|