最近有碰到布隆過濾器,發現redis本身沒有實現它。如果使用需要再安裝這個模。
有興趣的可以自己學習下
先看下介紹
The RedisBloom module provides four data structures: a scalable Bloom filter, a cuckoo filter, a count-min sketch, and a top-k. These data structures trade perfect accuracy for extreme memory efficiency, so they're especially useful for big data and streaming applications.
Bloom and cuckoo filters are used to determine, with a high degree of certainty, whether an element is a member of a set.
A count-min sketch is generally used to determine the frequency of events in a stream. You can query the count-min sketch get an estimate of the frequency of any given event.
A top-k maintains a list of k most frequently seen items.
簡單意思就是:
RedisBloom模塊提供了四種數據結構:可伸縮的Bloom filter、cuckoo filter、count-min sketch和top-k。這些數據結構以完美的準確性換取了極高的內存效率,因此它們對於大數據和流媒體應用程序特別有用。
Bloom和cuckoo過濾器用於高度精確地確定一個元素是否屬於一個集合的成員。
count-min sketch 通常用於確定流中事件的頻率。您可以查詢count-min sketch,以獲得對任何給定事件的頻率的估計。
top-k維護一個包含k個最常見項的列表。
簡單來說就是,利用少量內存完成大量數據處理。
下面逐個瞭解下
Bloom filter
使用場景
判斷一個元素在不在一個集合中,我們可以用hashset,複雜度O(1)即可判斷。
但是當數據量很大時,需要的存儲很大。
可以考慮使用bit位來判斷,但是又會有hash衝突的問題。爲了降低衝突率,可以採用多個hash 來實現。
基本原理
Bloom filter 本質是一個bit數組。每個元素都只佔用 1 bit,每個元素只能是 0 或者 1。
對於一個key,利用 k 個hash 函數進行映射得到 n1, n2, n3。然後標記數組下標 n1, n2, n3 的位置爲 1。
因爲存在哈希衝突,即某個bit位已經因爲其他key變成1。因此當一個key 映射的所有位置都已經是1,此時無法判斷當前key是否存在。當然,如果一個key 映射的所有位置不全是1, 則說明當前key 不存在。即所謂的假正
例如,如果 "hello" 的映射爲{1,4,9},此時發現這三個位置都是1,則不一定能準確判斷出 "hello" 存在集合中。
公式
當hash函數個數 k = (ln2) * (m/n) 時錯誤率最小。
在錯誤率不大於e的情況下,則 m >= n*log2(1/e)*log2e
具體可以參考論文
使用方法
bf.reserve your_key 0.01 100
bf.reserve 有三個參數,分別是 key, error_rate 和 initial_size 。
從上邊的公式不難看出:error_rate 錯誤率越低,需要的空間越大。
initial_size 參數表示預計放 入的元素數量,當實際數量超出這個數值時,誤判率會上升。
所以需要提前設置一個較大的數值避免超出導致誤判率升高。
默認情況下 error_rate 是 0.01,默認的 initial_size 是 100。
代碼實現
google的guava包中提供了BloomFilter類,直接用的是服務器內存
待續
優缺點
優點:存儲數據量小,節省存儲及計算空間
缺點:只能對集合添加元素,無法刪除(也並非完全不能,可以使用 bloom filter 的變種 CounterBloom Filte,它將Bloom Filter每一個Bit改爲一個計數器);存在誤報存在的可能,即false positive rate (假正率)。
cuckoo filter
使用場景
適合大併發的讀, 少量的寫的應用場景。 而且空間利用率可以做到很高。cuckoo filter裏不會存儲原信息,但會存儲指紋信息。因此,相比於Bloom filter,它既可以確保該元素存在的必然性,又可以在不違背此前提下刪除任意元素,僅僅比bitmap犧牲了微量空間效率。
基本原理
一個 key 如果哈希到某個位置,發現該位置上已經有元素 key_1 了,則把 key_1 移到 另一個位置,然後把key 放入騰出的位置上,並記錄碰撞次數。如果另一個位置上還有元素,再進行移動,循環進行。
如果碰撞次數達到某個閾值則認爲過濾器容量不足,需要對其進行 rehash 擴容。
這個過程類似於布穀鳥下蛋的過程,所以稱其爲布穀鳥過濾器。
問題:因爲桶中沒有存原有信息,因此計算備選桶就成了問題,解決方法如下:
即利用當前桶的位置和當前指紋的哈希值,進行異或。
公式
在保證內存利用率高的情況下,存儲n個元素,則需要 Ω(logn/b) bit位存指紋,其中 b 爲桶中的槽位數
實現
這裏引用論文文章裏給出的代碼實現
count-min sketch
使用場景
主要用於實時統計數據流中元素出現的頻率,能判斷某個元素出現的頻率,近似值。
在數據量小的時候,可以用hashmap,但是當數據量非常大時,就需要很大的存儲了,不可行。而count-min sketch只存儲它們Sketch的計數,因此可行。
基本原理
一個長度爲 x 的數組,用來計數,初始每個元素的計數值爲 0。當某個元素出現時,hash 到某個索引 i, 則對 x[i] 加 1 。
考慮存在哈希衝突,定義 k 個長度爲 x 的數組,和 k 個哈希函數。
更新時,對 k 個哈希函數算的位置上的值加 1。 即圖中行的Ct 都加 1
查詢頻率時,對k個哈希函數算出的位置上的值,取最小。即 min( Ct1, Ct2, ... Ctk)
公式
理論及 w 和 k 的取值,參考論文
前面提到的 redisBloom 代碼裏,實現如下:
CMSketch *NewCMSketch(size_t width, size_t depth) {
assert(width > 0);
assert(depth > 0);
CMSketch *cms = CMS_CALLOC(1, sizeof(CMSketch));
cms->width = width;
cms->depth = depth;
cms->counter = 0;
cms->array = CMS_CALLOC(width * depth, sizeof(uint32_t));
return cms;
}
根據參數 width, depth 申請一個長度爲 width * depth 的數組,來代替上邊說的二維哈希結構。
插入元素時,循環 hash 函數個數,即depth 次
size_t CMS_IncrBy(CMSketch *cms, const char *item, size_t itemlen, size_t value) {
assert(cms);
assert(item);
size_t maxCount = 0;
for (size_t i = 0; i < cms->depth; ++i) {
uint32_t hash = CMS_HASH(item, itemlen, i);
cms->array[(hash % cms->width) + (i * cms->width)] += value;
maxCount = max(maxCount, cms->array[(hash % cms->width) + (i * cms->width)]);
}
cms->counter += value;
return maxCount;
}
查詢時,則循環 depth 次,最後取 最小值;
size_t CMS_Query(CMSketch *cms, const char *item, size_t itemlen) {
assert(cms);
assert(item);
size_t temp = 0, res = (size_t)-1;
for (size_t i = 0; i < cms->depth; ++i) {
uint32_t hash = CMS_HASH(item, itemlen, i);
temp = cms->array[(hash % cms->width) + (i * cms->width)];
if (temp < res) {
res = temp;
}
}
return res;
}
優點:只需要固定大小的內存和計算時間,和需要統計的元素多少無關;
缺點:只會估算偏大,永遠不會偏小;對於低頻的元素,估算值相對的錯誤可能會很大。
個人感覺它是 HyperLogLog 的一個補充,HyperLogLog 只能 統計去重的數據量,但是不能計算頻率。
對此,文章《New estimation algorithms for streaming data: Count-min can do more》提出了 Count-Mean-Min Sketch。在查詢時利用降噪來提高準確率。
具體:
插入:和Count-min Sketch一樣;
查詢:假設查詢到 d 個hash函數在 d * w 的數組中,查到的數量分別時 Ct1,Ct2,.. Ctd,對每行,除去Ctd, 剩下的元素均勻分佈,則噪音爲該行剩餘元素的平均值,因此Ctd 降噪後應該是 Ctd - mean(該行剩餘元素)。
然後取 d 個 降噪後的中位數,即頻次爲 median(Ctd - mean(該行剩餘元素))
當然還有其他變種的Sketch,可以參考文章
top-k
使用場景
top-k 有基本的top-k 和 top-k 頻繁項之分
基本的top-k顧名思義,即海量數據取最大或最小的K個值。
可能想到的方案:
1.直接排序後取前 k 個。複雜度 O(n logn) ,當一億條數據時,就不可行了。
2.構建 k 個元素的大頂堆(小頂堆),遍歷所有元素,最後堆裏的元素排序輸出。O(k * logk) + O(n * logk)。如果不需要排序即O(n * logk)
3.使用快排中的partition,即BFPRT。利用隨機某個元素key 對n個元素分組,大於等於 key 的集合爲SL,小於 key 的爲SM。若SL 大於k個元素,則在 SL中遞歸找,否則在 SM 中找 k - len(SL) 個元素。同過遞歸將問題分解。複雜度O(n)
這就是平常所說的Top-K問題
而標題的Top-K 頻繁項,則是尋找數據流中出現最頻繁的k個元素,即 find top k frequent items in a data stream。
方案:
1.用一個容量爲k的小頂堆,維護目前最頻繁的前 k 個元素,同時用一個HashMap 維護元素與出現次數的映射。
2.元素出現,則把 HashMap 的計數加 1(不存在則初始爲 1)
3.同時在堆裏查找該元素:a)找到則把堆裏的計數加 1,並調整堆。b)找不到,則將當前元素出現次數與堆頂比較,如果比堆頂大,則替換,並調整堆。
空間複雜度:O(n)
時間複雜度:每次查找堆 O(k),調整堆 O(logk)。n個元素下來,複雜度 O(n*(k+logk))。k比較小,即 O(n)
海量數據時,就不可行了,除非利用多臺機器分解。
基本原理
Top-K 則是對上邊方案的改進,將 HashMap 維護計數 改成 Count-Min Sketch 維護計數。
空間複雜度:只需要一個非常小的數組,加 O(k) 的堆
時間複雜度:同上
具體理論可以參考論文《HeavyKeeper: An Accurate Algorithm for Finding Top-k Elephant Flows》
優缺點
參考:
《流數據庫 概率計算概念 - PipelineDB-Probabilistic Data Structures & Algorithms》
《Probabilistic Data Structures for Web Analytics and Data Mining》
《Approximating Data with the Count-Min Data Structure》