redis——HyperLogLog

HyperLogLog 是一種概率數據結構,用來估算數據的基數。數據集可以是網站訪客的 IP 地址,E-mail 郵箱或者用戶 ID。

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

使用 Redis 統計集合的基數一般有三種方法,分別是使用 Redis 的 HashMap,BitMap 和 HyperLogLog。前兩個數據結構在集合的數量級增長時,所消耗的內存會大大增加,但是 HyperLogLog 則不會。

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

這是一個很驚人的結果,以如此小的內存來記錄如此大數量級的數據基數。下面我們就帶大家來深入了解一下 HyperLogLog 的使用,基礎原理,源碼實現和具體的試驗數據分析。

HyperLogLog 在 Redis 中的使用

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

PFADD?用于向 HyperLogLog 添加元素。

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

如果 HyperLogLog 估計的近似基數在?PFADD?命令執行之后出現了變化, 那么命令返回 1 , 否則返回 0 。 如果命令執行時給定的鍵不存在, 那么程序將先創建一個空的 HyperLogLog 結構, 然后再執行命令。

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

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

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

內存消耗對比實驗

我們下面就來通過實驗真實對比一下下面三種數據結構的內存消耗,HashMap、BitMap 和 HyperLogLog。

我們首先使用 Lua 腳本向 Redis 對應的數據結構中插入一定數量的數,然后執行 bgsave 命令,最后使用 redis-rdb-tools 的 rdb 的命令查看各個鍵所占的內存大小。

下面是 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 三種數據結構中插入了一千萬個數,然后使用?rdb?命令查看各個結構內存消耗。

我們進行了兩輪實驗,分別插入一萬數字和一千萬數字,三種數據結構消耗的內存統計如下所示。

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

基本原理

HyperLogLog 是一種概率數據結構,它使用概率算法來統計集合的近似基數。而它算法的最本源則是伯努利過程。

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

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

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

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

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

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

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

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

在設置前,要設置進桶的值是否大于桶中的舊值,如果大于才進行設置,否則不進行設置。

此時為了性能考慮,是不會去統計當前的基數的,而是將 HyperLogLog 頭的 card 屬性中的標志位置為 1,表示下次進行 pfcount 操作的時候,當前的緩存值已經失效了,需要重新統計緩存值。在后面 pfcount 流程的時候,發現這個標記為失效,就會去重新統計新的基數,放入基數緩存。

在計算近似基數時,就分別計算每個桶中的值,帶入到上文的 DV 公式中,進行調和平均和結果修正,就能得到估算的基數值。

HyperLogLog 具體對象

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

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

HyperLogLog 對象中的?registers?數組就是桶,它有兩種存儲結構,分別為密集存儲結構和稀疏存儲結構,兩種結構只涉及存儲和桶的表現形式,從中我們可以看到 Redis 對節省內存極致地追求。

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

所以我們需要將桶的序號轉換成對應的字節偏移量 offsetbytes 和其內部的位數偏移量 offsetbits。需要注意的是小端字節序,高位在右側,需要進行倒轉。

當 offset_bits 小于等于2時,說明一個桶就在該字節內,只需要進行倒轉就能得到桶的值。

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

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

  • ZERO : 一字節,表示連續多少個桶計數為0,前兩位為標志00,后6位表示有多少個桶,最大為64。

  • XZERO : 兩個字節,表示連續多少個桶計數為0,前兩位為標志01,后14位表示有多少個桶,最大為16384。

  • VAL : 一字節,表示連續多少個桶的計數為多少,前一位為標志1,四位表示連桶內計數,所以最大表示桶的計數為32。后兩位表示連續多少個桶。

?

Redis從稀疏存儲轉換到密集存儲的條件是:

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

  • 稀疏存儲占用的總字節數超過 3000 字節,這個閾值可以通過 hllsparsemax_bytes 參數進行調整。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/445274.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445274.shtml
英文地址,請注明出處:http://en.pswp.cn/news/445274.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

機器學習知識總結系列-機器學習中的優化算法總結(1-4)

文章目錄1.梯度下降1.1批量梯度下降(BGD)1.2隨機梯度下降(SGD)1.3 小批量隨機梯度下降(MSGD)1.4 比較:1.5 動量算法(momentum)1.6 Nestrov Momentum2. 自適應方法2.1 自適應學習率算法&#xff…

Python(19)-字符串、Unicode字符串

高級數據類型--字符串、Unicode字符串1.字符串的定義2.字符串的長度、計數、Index3.字符串常用方法3.1判斷類型3.2查找和替換3.3文本對齊3.4去除空白字符.strip()4.字符串的拆分和拼接5.字符串的切片6.跨行字符串7.包含轉義字符r8.字符串的分割與連接9.Unicode字符串字符串-不變…

機器學習中的距離和損失函數

文章目錄13.1 距離度量13.2 損失函數13.1 距離度量 距離函數種類:歐式距離、曼哈頓距離、明式距離(閔可夫斯基距離)、馬氏距離、切比雪夫距離、標準化歐式距離、漢明距離、夾角余弦等常用距離函數:歐式距離、馬氏距離、曼哈頓距離…

Python(20)-高級數據類型的公共方法

高級數據類型的公共方法1內置函數2高級數據類型切片3運算符,*,in4完整的for循環公共方法是列表,元組,字典,字符串都能使用的方法1內置函數 內置函數:不需要import導入模塊,就可以直接使用的函數…

redis——為什么選擇了跳表而不是紅黑樹?

跳表是個啥東西請看這個文章。 我們知道,節點插入時隨機出一個層數,僅僅依靠一個簡單的隨機數操作而構建出來的多層鏈表結構,能保證它有一個良好的查找性能嗎?為了回答這個疑問,我們需要分析skiplist的統計性能。 在…

機器學習公式推導

文章目錄線性回歸邏輯回歸線性判別分析PCAk-means決策樹svm隨機深林GBDTxgboost強化學習MapReduce線性回歸 邏輯回歸 對于分類問題:輸出0/1,超過[0,1]沒有意義,使用sigmoid函數 **代價函數:**使用L2平方差,由于模型函…

Python綜合應用(1)--名片管理系統開發

第一個綜合應用-名片管理系統1框架搭建2完善功能綜合應用,名片管理系統 歡迎界面,不同選項,1.新建名片,2.顯示全部,3 查詢名片(查到之后可以修改名片信息),0 退出系統 程序開發流程…

springboot1——spring相關入門

spring 隨著我們開發,發現了一個問題: A---->B---->C---->D 在A中創建B的對象調用B的資源 在B中創建C的對象調用C的資源 在C中創建D的對象調用…

大數據學習(06)-- 云數據庫

文章目錄目錄1.什么是云數據庫?1.1 云計算和云數據庫的關系1.2 云數據庫的概念1.3 云數據庫的特性1.4 云數據庫應用場景1.5 云數據庫和其他數據的關系2.云數據庫產品有哪些?2.1 云數據庫廠商概述2.2 亞馬遜云數據庫產品2.3 Google云數據庫產品2.4 微軟云…

Python(21)--變量進階

變量的進階使用1變量引用2可變、不可變數據類型3局部變量和全局變量4.Tips本系列博文來自學習《Python基礎視頻教程》筆記整理,視屏教程連接地址:http://yun.itheima.com/course/273.html在博文:https://blog.csdn.net/sinat_40624829/articl…

HTTP 響應代碼全集

HTTP 響應狀態代碼指示特定 http 請求是否已成功完成。響應分為五類:信息響應(100–199),成功響應(200–299),重定向(300–399),客戶端錯誤(400–499)和服務器錯誤 (500–599)。狀態代碼由 section 10 of RFC 2616定義 信息響應 …

機器學習知識總結系列-機器學習中的數學-矩陣(1-3-2)

矩陣 SVD 矩陣的乘法狀態轉移矩陣狀態轉移矩陣特征值和特征向量 對稱陣 正交陣 正定陣數據白化矩陣求導 向量對向量求導 標量對向量求導 標量對矩陣求導一.矩陣1.1 SVD奇異值分解(Singular Value Decomposition),假設A是一個mn階矩陣&#xf…

阿里Java編程規約(注釋)提煉

【強制】類、類屬性、類方法的注釋必須使用 Javadoc 規范,使用/**內容*/格式,不得使用 // xxx 方式。 說明:在 IDE 編輯窗口中,Javadoc 方式會提示相關注釋,生成 Javadoc 可以正確輸出相應注釋;在 IDE 中…

Python面試題-交換兩個數字的三種方法

Python實現兩個數字交換解法1解法2解法3a6 b100 解法1 使用其他變量,最通用的方法 ca ab bc 解法2 不使用其他變量,利算法節省內存空間 aab ba-b aa-b 解法3 python 專有 a,b(b,a) #等號右邊是一個元組 或者可以寫為: a,bb,a print(a,b)

面試中海量數據處理總結

教你如何迅速秒殺掉:99%的海量數據處理面試題 前言 一般而言,標題含有“秒殺”,“99%”,“史上最全/最強”等詞匯的往往都脫不了嘩眾取寵之嫌,但進一步來講,如果讀者讀罷此文,卻無任何收獲&…

redis——舊版復制

Redis 的復制功能分為同步(sync)和命令傳播(command propagate)兩個操作: 同步操作用于將從服務器的數據庫狀態更新至主服務器當前所處的數據庫狀態。命令傳播操作用于在主服務器的數據庫狀態被修改, 導致…

Linux(3)-網-ifconfig,ping,ssh

終端命令網-ping,ssh1. ifconfig -a2. ping3. ssh3.1安裝3.2 連接3.3 配置登入別名防火墻端口號,todo1. ifconfig -a 查看IP地址, 還可以用于配置網口。 ifconfig -a 2. ping ping命令: 檢測到IP地址的連接是否正常。命令開始后由本機發送數據包a&…

redis——相關問題匯總

什么是redis? Redis 本質上是一個 Key-Value 類型的內存數據庫, 整個數據庫加載在內存當中進行操作, 定期通過異步操作把數據庫數據 flush 到硬盤上進行保存。 因為是純內存操作, Redis 的性能非常出色, 每秒可以處理…

一文搞定面試中的二叉樹問題

一文搞定面試中的二叉樹問題 版權所有,轉載請注明出處,謝謝! http://blog.csdn.net/walkinginthewind/article/details/7518888 樹是一種比較重要的數據結構,尤其是二叉樹。二叉樹是一種特殊的樹,在二叉樹中每個節點…

無數踩坑系列(1)--Brightness Controller

Brightness Controller1.嘗試找回系統自帶亮度調節條1.1 配置grub文件,無效1.2 使用命令調節屏幕亮度,無效2.安裝應用程序Brightness Controller2.1許多博文都寫出了如下方案,無效:2.2 github 手動安裝https://github.com/LordAmi…