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

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

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

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

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

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

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

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

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

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

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

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

二、 HyperLogLog 的優(yōu)勢(shì)

2.1 算法原理與核心特性

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

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

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

2.2 Redis HyperLogLog 核心命令

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

命令詳解

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

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

3.1 單元測(cè)試壓測(cè)代碼

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

import redis
import time
def test_hyperloglog():
    # 連接 Redis
    r = redis.Redis(host='127.0.0.1', port=6379, db=0, decode_responses=True)
    # 清空測(cè)試 Key
    r.delete('hll:uv:daily')
    # 準(zhǔn)備批量添加
    batch_size = 1000
    total_users = 1000000
    start_time = time.time()
    # 批量添加 100 萬(wàn)用戶
    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)計(jì)數(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)計(jì)結(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"耗時(shí): {elapsed_time:.4f} 秒")
    print(f"吞吐量: {total_users / elapsed_time:.0f} ops/sec")
if __name__ == "__main__":
    test_hyperloglog()

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

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

結(jié)論

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

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

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

# 日級(jí) UV 統(tǒng)計(jì)
uv:daily:{YYYY-MM-DD}  # 例如: uv:daily:2024-01-15
# 月級(jí) UV 統(tǒng)計(jì)(通過(guò) PFCOUNT 合并日級(jí)數(shù)據(jù))
uv:monthly:{YYYY-MM}    # 例如: uv:monthly:2024-01
# 全站歷史 UV(通過(guò) PFMERGE 合并月級(jí)數(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):
        """
        記錄用戶訪問(wèn)
        :param user_id: 用戶 ID
        :param visit_date: 訪問(wèn)日期(默認(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è)置過(guò)期時(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)計(jì))
        :param start_date: 開(kāi)始日期
        :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 計(jì)算并集基數(shù)
        return self.redis.pfcount(*keys)
    def merge_monthly_uv(self, year: int, month: int):
        """
        合并月度 UV 統(tǒng)計(jì)
        :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è)置過(guò)期時(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)計(jì)數(shù)據(jù)
    :param date: 查詢?nèi)掌冢╕YYY-MM-DD 格式,默認(rèn)當(dāng)天)
    :return: 日、周、月 UV 統(tǒng)計(jì)
    """
    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))
):
    """
    記錄用戶訪問(wèn)
    :param user_id: 用戶 ID
    """
    uv_service.record_visit(user_id)
    return {"status": "success", "message": "Visit recorded"}

四、 HyperLogLog 與 Bitmap、Set 的對(duì)比選型

數(shù)據(jù)結(jié)構(gòu)內(nèi)存占用精確度適用場(chǎng)景核心命令
HyperLogLog< 16KB(固定)誤差 < 0.81%海量 UV 統(tǒng)計(jì)、去重計(jì)數(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)計(jì)(千萬(wàn)級(jí)以上):HyperLogLog,內(nèi)存占用極低,誤差可接受。
  • 用戶簽到(連續(xù)日期):Bitmap,按日期偏移量存儲(chǔ),支持連續(xù)簽到統(tǒng)計(jì)。
  • 共同關(guān)注/好友列表(萬(wàn)級(jí)以下):Set,支持交集、并集等集合運(yùn)算,精確度高。

五、 總結(jié)

5.1 核心收益

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

5.2 優(yōu)化建議

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

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

相關(guān)文章

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

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

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

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

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

    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-05
  • Redis源碼分析之set?和?sorted?set?使用

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

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

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

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

    Redis過(guò)期監(jiān)聽(tīng)機(jī)制,訂單超時(shí)自動(dòng)取消方式

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

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

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

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

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

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

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

    Redis高可用集群redis-cluster詳解

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

最新評(píng)論

常德市| 康马县| 本溪市| 甘泉县| 鸡东县| 安塞县| 神池县| 隆尧县| 溧水县| 荥经县| 民乐县| 新蔡县| 黄梅县| 萨嘎县| 邹城市| 武冈市| 高台县| 榆中县| 英德市| 哈尔滨市| 桦川县| 崇左市| 莎车县| 宁国市| 奉贤区| 卢氏县| 安康市| 台东县| 武冈市| 临湘市| 吉木乃县| 贡山| 三门峡市| 陵水| 公主岭市| 遂平县| 灵璧县| 凤庆县| 体育| 东平县| 靖州|