日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

redis——HyperLogLog

發(fā)布時間:2023/12/13 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 redis——HyperLogLog 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

HyperLogLog 是一種概率數(shù)據(jù)結(jié)構(gòu),用來估算數(shù)據(jù)的基數(shù)。數(shù)據(jù)集可以是網(wǎng)站訪客的 IP 地址,E-mail 郵箱或者用戶 ID。

基數(shù)就是指一個集合中不同值的數(shù)目,比如 a, b, c, d 的基數(shù)就是 4,a, b, c, d, a 的基數(shù)還是 4。雖然 a 出現(xiàn)兩次,只會被計算一次。

使用 Redis 統(tǒng)計集合的基數(shù)一般有三種方法,分別是使用 Redis 的 HashMap,BitMap 和 HyperLogLog。前兩個數(shù)據(jù)結(jié)構(gòu)在集合的數(shù)量級增長時,所消耗的內(nèi)存會大大增加,但是 HyperLogLog 則不會。

Redis 的 HyperLogLog 通過犧牲準(zhǔn)確率來減少內(nèi)存空間的消耗,只需要12K內(nèi)存,在標(biāo)準(zhǔn)誤差0.81%的前提下,能夠統(tǒng)計2^64個數(shù)據(jù)。所以 HyperLogLog 是否適合在比如統(tǒng)計日活月活此類的對精度要不不高的場景。

這是一個很驚人的結(jié)果,以如此小的內(nèi)存來記錄如此大數(shù)量級的數(shù)據(jù)基數(shù)。下面我們就帶大家來深入了解一下 HyperLogLog 的使用,基礎(chǔ)原理,源碼實現(xiàn)和具體的試驗數(shù)據(jù)分析。

HyperLogLog 在 Redis 中的使用

Redis 提供了?PFADD?、?PFCOUNT?和?PFMERGE?三個命令來供用戶使用 HyperLogLog。

PFADD?用于向 HyperLogLog 添加元素。

> PFADD visitors alice bob carol(integer) 1> PFCOUNT visitors(integer) 3

如果 HyperLogLog 估計的近似基數(shù)在?PFADD?命令執(zhí)行之后出現(xiàn)了變化, 那么命令返回 1 , 否則返回 0 。 如果命令執(zhí)行時給定的鍵不存在, 那么程序?qū)⑾葎?chuàng)建一個空的 HyperLogLog 結(jié)構(gòu), 然后再執(zhí)行命令。

PFCOUNT?命令會給出 HyperLogLog 包含的近似基數(shù)。在計算出基數(shù)后,?PFCOUNT?會將值存儲在 HyperLogLog 中進(jìn)行緩存,知道下次?PFADD?執(zhí)行成功前,就都不需要再次進(jìn)行基數(shù)的計算。

PFMERGE?將多個 HyperLogLog 合并為一個 HyperLogLog , 合并后的 HyperLogLog 的基數(shù)接近于所有輸入 HyperLogLog 的并集基數(shù)。

> PFADD customers alice dan(integer) 1> PFMERGE everyone visitors customersOK> PFCOUNT everyone(integer) 4

內(nèi)存消耗對比實驗

我們下面就來通過實驗真實對比一下下面三種數(shù)據(jù)結(jié)構(gòu)的內(nèi)存消耗,HashMap、BitMap 和 HyperLogLog。

我們首先使用 Lua 腳本向 Redis 對應(yīng)的數(shù)據(jù)結(jié)構(gòu)中插入一定數(shù)量的數(shù),然后執(zhí)行 bgsave 命令,最后使用 redis-rdb-tools 的 rdb 的命令查看各個鍵所占的內(nèi)存大小。

下面是 Lua 的腳本

local key = KEYS[1]local size = tonumber(ARGV[1])local method = tonumber(ARGV[2])for i=1,size,1 doif (method == 0)thenredis.call('hset',key,i,1)elseif (method == 1)thenredis.call('pfadd',key, i)elseredis.call('setbit', key, i, 1)endend

我們在通過 redis-cli 的?script load?命令將 Lua 腳本加載到 Redis 中,然后使用?evalsha?命令分別向 HashMap、HyperLogLog 和 BitMap 三種數(shù)據(jù)結(jié)構(gòu)中插入了一千萬個數(shù),然后使用?rdb?命令查看各個結(jié)構(gòu)內(nèi)存消耗。

我們進(jìn)行了兩輪實驗,分別插入一萬數(shù)字和一千萬數(shù)字,三種數(shù)據(jù)結(jié)構(gòu)消耗的內(nèi)存統(tǒng)計如下所示。

從表中可以明顯看出,一萬數(shù)量級時 BitMap 消耗內(nèi)存最小, 一千萬數(shù)量級時 HyperLogLog 消耗內(nèi)存最小,但是總體來看,HyperLogLog 消耗的內(nèi)存都是 14392 字節(jié),可見 HyperLogLog 在內(nèi)存消耗方面有自己的獨到之處。

基本原理

HyperLogLog 是一種概率數(shù)據(jù)結(jié)構(gòu),它使用概率算法來統(tǒng)計集合的近似基數(shù)。而它算法的最本源則是伯努利過程。

伯努利過程就是一個拋硬幣實驗的過程。拋一枚正常硬幣,落地可能是正面,也可能是反面,二者的概率都是 1/2 。伯努利過程就是一直拋硬幣,直到落地時出現(xiàn)正面位置,并記錄下拋擲次數(shù)k。比如說,拋一次硬幣就出現(xiàn)正面了,此時 k 為 1; 第一次拋硬幣是反面,則繼續(xù)拋,直到第三次才出現(xiàn)正面,此時 k 為 3。

對于 n 次伯努利過程,我們會得到 n 個出現(xiàn)正面的投擲次數(shù)值 k1, k2 ... kn , 其中這里的最大值是k_max。

根據(jù)一頓數(shù)學(xué)推導(dǎo),我們可以得出一個結(jié)論: 2^{k_ max} 來作為n的估計值。也就是說你可以根據(jù)最大投擲次數(shù)近似的推算出進(jìn)行了幾次伯努利過程。

下面,我們就來講解一下 HyperLogLog 是如何模擬伯努利過程,并最終統(tǒng)計集合基數(shù)的。

HyperLogLog 在添加元素時,會通過Hash函數(shù),將元素轉(zhuǎn)為64位比特串,例如輸入5,便轉(zhuǎn)為101(省略前面的0,下同)。這些比特串就類似于一次拋硬幣的伯努利過程。比特串中,0 代表了拋硬幣落地是反面,1 代表拋硬幣落地是正面,如果一個數(shù)據(jù)最終被轉(zhuǎn)化了 10010000,那么從低位往高位看,我們可以認(rèn)為,這串比特串可以代表一次伯努利過程,首次出現(xiàn) 1 的位數(shù)為5,就是拋了5次才出現(xiàn)正面。

所以 HyperLogLog 的基本思想是利用集合中數(shù)字的比特串第一個 1 出現(xiàn)位置的最大值來預(yù)估整體基數(shù),但是這種預(yù)估方法存在較大誤差,為了改善誤差情況,HyperLogLog中引入分桶平均的概念,計算 m 個桶的調(diào)和平均值。

Redis 中 HyperLogLog 一共分了 2^14 個桶,也就是 16384 個桶。每個桶中是一個 6 bit 的數(shù)組。

HyperLogLog 將上文所說的 64 位比特串的低 14 位單獨拿出,它的值就對應(yīng)桶的序號,然后將剩下 50 位中第一次出現(xiàn) 1 的位置值設(shè)置到桶中。50位中出現(xiàn)1的位置值最大為50,所以每個桶中的 6 位數(shù)組正好可以表示該值。

在設(shè)置前,要設(shè)置進(jìn)桶的值是否大于桶中的舊值,如果大于才進(jìn)行設(shè)置,否則不進(jìn)行設(shè)置。

此時為了性能考慮,是不會去統(tǒng)計當(dāng)前的基數(shù)的,而是將 HyperLogLog 頭的 card 屬性中的標(biāo)志位置為 1,表示下次進(jìn)行 pfcount 操作的時候,當(dāng)前的緩存值已經(jīng)失效了,需要重新統(tǒng)計緩存值。在后面 pfcount 流程的時候,發(fā)現(xiàn)這個標(biāo)記為失效,就會去重新統(tǒng)計新的基數(shù),放入基數(shù)緩存。

在計算近似基數(shù)時,就分別計算每個桶中的值,帶入到上文的 DV 公式中,進(jìn)行調(diào)和平均和結(jié)果修正,就能得到估算的基數(shù)值。

HyperLogLog 具體對象

我們首先來看一下 HyperLogLog 對象的定義

struct hllhdr {char magic[4]; /* 魔法值 "HYLL" */uint8_t encoding; /* 密集結(jié)構(gòu)或者稀疏結(jié)構(gòu) HLL_DENSE or HLL_SPARSE. */uint8_t notused[3]; /* 保留位, 全為0. */uint8_t card[8]; /* 基數(shù)大小的緩存 */uint8_t registers[]; /* 數(shù)據(jù)字節(jié)數(shù)組 */};

HyperLogLog 對象中的?registers?數(shù)組就是桶,它有兩種存儲結(jié)構(gòu),分別為密集存儲結(jié)構(gòu)和稀疏存儲結(jié)構(gòu),兩種結(jié)構(gòu)只涉及存儲和桶的表現(xiàn)形式,從中我們可以看到 Redis 對節(jié)省內(nèi)存極致地追求。

我們先看相對簡單的密集存儲結(jié)構(gòu),它也是十分的簡單明了,既然要有 2^14 個 6 bit的桶,那么我就真使用足夠多的?uint8_t?字節(jié)去表示,只是此時會涉及到字節(jié)位置和桶的轉(zhuǎn)換,因為字節(jié)有 8 位,而桶只需要 6 位。

所以我們需要將桶的序號轉(zhuǎn)換成對應(yīng)的字節(jié)偏移量 offsetbytes 和其內(nèi)部的位數(shù)偏移量 offsetbits。需要注意的是小端字節(jié)序,高位在右側(cè),需要進(jìn)行倒轉(zhuǎn)。

當(dāng) offset_bits 小于等于2時,說明一個桶就在該字節(jié)內(nèi),只需要進(jìn)行倒轉(zhuǎn)就能得到桶的值。

?offset_bits 大于 2 ,則說明一個桶分布在兩個字節(jié)內(nèi),此時需要將兩個字節(jié)的內(nèi)容都進(jìn)行倒置,然后再進(jìn)行拼接得到桶的值。

Redis 為了方便表達(dá)稀疏存儲,它將上面三種字節(jié)表示形式分別賦予了一條指令。

  • ZERO : 一字節(jié),表示連續(xù)多少個桶計數(shù)為0,前兩位為標(biāo)志00,后6位表示有多少個桶,最大為64。

  • XZERO : 兩個字節(jié),表示連續(xù)多少個桶計數(shù)為0,前兩位為標(biāo)志01,后14位表示有多少個桶,最大為16384。

  • VAL : 一字節(jié),表示連續(xù)多少個桶的計數(shù)為多少,前一位為標(biāo)志1,四位表示連桶內(nèi)計數(shù),所以最大表示桶的計數(shù)為32。后兩位表示連續(xù)多少個桶。

?

Redis從稀疏存儲轉(zhuǎn)換到密集存儲的條件是:

  • 任意一個計數(shù)值從 32 變成 33,因為 VAL 指令已經(jīng)無法容納,它能表示的計數(shù)值最大為 32

  • 稀疏存儲占用的總字節(jié)數(shù)超過 3000 字節(jié),這個閾值可以通過 hllsparsemax_bytes 參數(shù)進(jìn)行調(diào)整。

總結(jié)

以上是生活随笔為你收集整理的redis——HyperLogLog的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。