# Bloom Filter 學習筆記 ## Bloom Filter 簡介 Bloom Filter(布隆過濾器)由 Burton Howard Bloom 在 1970 構思出來,用來測試一個元素是否存在特定集合中。hash table 也可以做到,那為什麼要使用 Bloom Filter 呢?原因在於比起 hash table ,Bloom Filter 的空間複雜度有巨大的優勢,但強大的力量必然伴隨副作用,隨之而來的就是有機率會發生 false positive,幸好 false positive rate 是可以透過調整 bit array 的大小和 hash function 的數量來控制的。你可能有些疑問:到底 Bloom Filter 是怎麼運作的、為什麼 Bloom Filter 空間複雜度比較好、false positive 是什麼、我們要怎麼調整參數來控制 false positive rate?就讓我們繼續看下去吧! ## Bloom Filter 原理 Bloom Filter 有兩個要素:長度為 n 的 bit array 和 m 個獨立的 hash function,當要寫入資料(x)的時候,用所有的 hash function 對 x 進行 hash 後 mod n 得到 m 個位置,把 bit array 這些位置的 bit 設為 1,就完成了一次寫入。 圖片中的例子是 m = 3 X, Y, Z 都是 int,作為 index 去設定 bit array 的值 ![](https://hackmd.io/_uploads/BJ3slKHU2.png) 查詢也是同樣的流程在得到 m 個位置之後,去 bit array 取出相對應的值,如果全都是 1 的話就代表集合中有這個元素,就可以確認這個資料之前曾經出現過。 ## Bloom Filter 的代價 在簡介的時候有說到: > 越是強大的能力,必然伴隨更高的風險或是代價 Bloom Filter 有機會發生 false positive,那什麼是 false positive 呢? ![](https://hackmd.io/_uploads/rkCaeFH83.png) 指的是: * **如果 Bloom Filter 回傳沒有(negative):代表資料 一定沒有 在 Bloom Filter 中** * **如果 Bloom Filter 回傳有(positive):代表資料 可能有 在 Bloom Filter 中,並不是一定有在 Bloom Filter 中** 所以在使用 Bloom Filter 情境,是要可以忍受 false positive 的發生,且發生之後不會造成太大的影響,那我們怎麼評估或是計算 false positive 發生的機率呢?在 wiki 上有相關的數學推導,可以直接套用計算,或是想要更方便更懶人的話推薦這個網站:https://hur.st/bloomfilter 只要輸入直接幫你算好,還順便畫好圖表。 除了 false positive 有可能發生之外,第二個缺點就是無法刪除已經加入的資料,關於這點可以參考 bloom filter 的各種變形版(Counting Filter / Cuckoo Filter…)。 ## Bloom Filter 使用案例 講完原理了,讓我們來看看有什麼使用案例吧 1. 搶票流量控制: 在搶票的大流量的狀況下,我們必須限制進來 server 的流量,但不能擋已經成功 reserve ticket 的 user 繼續進行後續付款取票的流程, 這時候就可以用 bloom filter 快速辨別該 request 的 jwt 是否在 bloom filter 中,有的話代表有訂到票,就可以直接放行,進行後續付款的步驟。記得控制好 false positive rate 跟處理流程,如果 false positive rate 太高,放行太多應該要擋掉的 request,後端流量還是會衝很高。 2. Yahoo Email 確認收件者在不在連絡清單: 在進入 Yahoo Email 的頁面後會把 user 的聯絡清單放到 client 端的 bloom filter,在 user 寄信的時候,可以快速檢查收件人是不是已經在聯絡清單中,沒有的話就可以詢問要不要新增,這樣做的好處是,在確認時不用在呼叫後端,也可以快速地得到結果。 3. Tinder Suggestion: 這個是猜測,Tinder 會搜尋離 user 一定範圍內的用戶來作為給 user 配對的對象,但怎麼避免已經配對或是拒絕過的呢,一樣也是把 user 配對或是拒絕過的對象放進 bloom filter,在搜尋時確認是否在 bloom filter 中,如果有就不要回傳給 client,但如果 false positive 發生,可能就這樣錯過真命天子 / 天女了呢,只能說一切都是緣分啊。 from: https://www.quora.com/What-are-the-best-applications-of-Bloom-filters ## Bloom Filter 實驗設計 * **設計Bloom Filter** * **收集PCAP文件用作測試Bloom Filter的數據** * **列出BloomFilter長度N,Hash函數個數K,數據流個體M之間的關係** * **測試使用Bitarray與使用list在空間利用上的差異** * **比較 bloom filter / set / dict 之間的大小** ### 1. Bloom Filter 代碼 ```python3= import hashlib import bitarray import random import string import time import sys class BloomFilter: def __init__(self, size): self.size = size self.bitarray = bitarray.bitarray(size) self.list = [0] * size self.bitarray.setall(False) self.hash_functions = [hashlib.md5, hashlib.sha1, hashlib.sha256] def add_data(self, data): for hash_func in self.hash_functions: index = int(hash_func(data).hexdigest(), 16) % self.size self.bitarray[index] = 1 self.list[index] = 1 def is_data_exist(self, data): result = bitarray.bitarray([True]) for hash_func in self.hash_functions: index = int(hash_func(data).hexdigest(), 16) % self.size result &= self.bitarray[index:index+1] if result[0]: return True else: return False ``` ### 代碼解釋 為了可以直接操縱 bit 所以特別找了 bitarray 這個套件來使用,要不然使用一般的 python list 的話 size 就會比較大。 首先我們初始化一個 class,這次我們使用 3 個 hash function: md5, sha1, sha256,在 python 的 hashlib 已經實作好了,可以直接使用。 ```python= def __init__(self, size): self.size = size self.bitarray = bitarray.bitarray(size) self.list = [0] * size self.bitarray.setall(False) self.hash_functions = [hashlib.md5, hashlib.sha1, hashlib.sha256] ``` 接下來就是實作 add_data 這個 function,hash 之後 mod 就可拿到 index,再去改值就好了,為了看到底差多少,這邊連 list 也要一起改值喔。 ```python= def add_data(self, data): for hash_func in self.hash_functions: index = int(hash_func(data).hexdigest(), 16) % self.size self.bitarray[index] = 1 self.list[index] = 1 ``` 再來就是確認該筆 data 有沒有出現過的 is_data_exist function,只要把拿到的 bit 全部 AND 起來看是不是 1 就可以了。 ```python= def is_data_exist(self, data): result = bitarray.bitarray([True]) for hash_func in self.hash_functions: index = int(hash_func(data).hexdigest(), 16) % self.size result &= self.bitarray[index:index+1] if result[0]: return True else: return False ``` ### Bloom Filter 數據測試 > 根據實驗要求,數據采用PCAP格式。 1. 引入庫 ```python3= from scapy.all import * from scapy.layers.inet import IP, TCP, UDP from BloomFilter import BloomFilter ``` 2. 讀取PCAP文件內容 ```python3= def readData(path: str) -> (list, set): pcap = rdpcap(path) data_list = list() dataSet = set() for pkt in pcap: # 获取时间 time = pkt.time try: # 获取IP地址 srcIP = pkt[IP].src dstIP = pkt[IP].dst # 获取端口号 if pkt.haslayer(TCP): srcPort = pkt[TCP].sport dstPort = pkt[TCP].dport protocol = "TCP" size = len(pkt[TCP]) elif pkt.haslayer(UDP): srcPort = pkt[UDP].sport dstPort = pkt[UDP].dport protocol = "UDP" size = len(pkt[UDP]) else: continue x = srcIP + dstIP + str(srcPort) + str(dstPort) + protocol + str(size) data_list.append(str.encode(x)) dataSet.add(x) except Exception as e: print(e) break return data_list, dataSet ``` 3. 主函數 ```python3= if __name__ == "__main__": data_list, dataSet = readData("test.pcap") test_list, _ = readData("now.pcap") contain_data_size = len(data_list) test_case_amount = len(test_list) size = 20000 print("Number of items in the filter: ", contain_data_size) print("Number of bits in the filter: ", size) print("Number of hash functions: ", 3) false_positive_amount = 0 bloomFilter = BloomFilter(size) # Add data on BloomFilter for data in data_list: bloomFilter.add_data(data) # Test BloomFilter's false positive for data in test_list: if bloomFilter.is_data_exist(data) and (data not in dataSet): false_positive_amount += 1 fail_rate = round((false_positive_amount / test_case_amount) * 100, 2) print("{:20} {}\n".format("success case amount:", test_case_amount - false_positive_amount)) print("{:20} {}\n".format("fail case amount:", false_positive_amount)) print("{:20} {}%\n".format("fail rate:", fail_rate)) ``` 4. 實驗結果 設 N = 11140, M = 20000, K = 3 則 ![](https://hackmd.io/_uploads/H1LYz_j8h.png) ![](https://hackmd.io/_uploads/BkTl9_i8h.png) 可以看見,最終的結果很接近理論分析的公式。 設 N = 1000, M = 10000, K = 3 則 ![](https://hackmd.io/_uploads/BkkLxHnLh.png) ![](https://hackmd.io/_uploads/rkoUlS2Ih.png) 可以看見,最終的結果很接近理論分析的公式。