[C++] 海量數據專講:抖音、快手每日幾億用戶活躍數據如何處理?(位圖 與 布隆過濾器)


小引

本篇所講的位圖布隆過濾器,其實都是哈希表的具體應用~
這兩個結構對於處理海量數據都有很好的功效!

區別:

  • 位圖:適合處理整型數據。
  • 布隆:適合處理字符型非整型數據

位圖

例如小視頻服務器每天有海量的整型數據到達需要處理,使用位圖就十分高效,不僅記錄方便,存儲更是十分輕量。

所謂位圖,就是用每一位來存放某種狀態,適用於海量數據,數據無重複的場景。通常是用來判斷某個數據存不存在的。

來看一個小問題:

40億個不重複的無符號整數,沒排過序。給一個無符號整數,如何快速判斷一個數是否在這40億個數中?

解法:

  1. 遍歷,時間複雜度O(N),所以這種方法很低效。
  2. 排序,時間複雜度O(N * logN),如果利用二分查找: logN
  3. 位圖解決:
    數據是否在給定的整型數據中,結果是在或者不在,剛好是2種狀態,那麼可以使用一個二進制比
    特位來代表數據是否存在的信息
    ,如果二進制比特位爲1,代表存在,爲0代表不存在。

實現一個位圖類

class bitset{
public:
	//構造函數
	bitset(size_t bitCount)
		: _bit((bitCount>>5)+1)
		, _bitCount(bitCount)
	{}
	
	// 將which比特位置1
	void set(size_t which){
		if(which > _bitCount)
			return;
		size_t index = (which >> 5);
		size_t pos = which % 32;
		_bit[index] |= (1 << pos);
	}
	
	// 將which比特位置0
	void reset(size_t which){
		if(which > _bitCount)
			return;
		size_t index = (which >> 5);
		size_t pos = which % 32;
		_bit[index] &= ~(1<<pos);
	}
	
	// 檢測位圖中which是否爲1
	bool test(size_t which){
		if(which > _bitCount)
			return false;
		size_t index = (which >> 5);
		size_t pos = which % 32;
		return _bit[index] & (1<<pos);
	}
	
	// 獲取位圖中比特位的總個數
	size_t size()const{ 
		return _bitCount;
	}
	
	// 位圖中比特爲1的個數
	size_t Count()const{
		int bitCnttable[256] = {
		0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2,
		3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3,
		3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3,
		4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4,
		3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5,
		6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4,
		4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5,
		6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5,
		3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3,
		4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6,
		6, 7, 6, 7, 7, 8
		};

		size_t size = _bit.size();
		size_t count = 0;
		for(size_t i = 0; i < size; ++i){
			int value = _bit[i];
			int j = 0;
			while(j < sizeof(_bit[0])){
				unsigned char c = value;
				count += bitCntTable[c];
				++j;
				value >>= 8;
			}
		}
		return count;
	}
private:
	vector<int> _bit;
	size_t _bitCount;
};

應用

  1. 快速查找某個數據是否在一個集合中。
  2. 排序
  3. 求兩個集合的交集、並集等
  4. 操作系統中磁盤塊標記。(報文頭部標誌位等)

布隆過濾器

當今最爲人們樂道的無非就是後起之秀的小視頻軟件了。

我們在使用新聞或者小視頻軟件客戶端時,它會給我們不停地推薦新的內容,它每次推薦時要去重,去掉那些已經看過的內容。
問題來了,新聞客戶端推薦系統如何實現推送去重的?

  • 猜想:用服務器記錄了用戶看過的所有歷史記錄,當推薦系統推薦新聞時會從每個用戶的歷史記錄裏進行篩選,過濾掉那些已經存在的記錄?

其實不然!這樣的方式雖然降低重複率,但是例如抖音、快手等軟件擁有世界級的幾億數據,基於當今存儲技術,在每日的海量數據面前,吉比特級的存儲器也頂不住啊~

  • 另求他法:
方法 分析
1. 用哈希表存儲用戶記錄 缺點:浪費空間
2. 用位圖存儲用戶記錄 缺點:不能處理哈希衝突
3. 將哈希位圖結合 布隆過濾器,取其精華,棄其糟粕。

概念

布隆過濾器是由布隆(Burton Howard Bloom)在1970年提出的一種緊湊型的、比較巧妙的概率型數據結構,特點是高效地插入和查詢,可以用來告訴你 “某樣東西一定不存在或者可能存在”。

它是用多個哈希函數,將一個數據映射到位圖結構中。
此種方式不僅可以提升查詢效率,也可以節省大量的內存空間


接下來我們來探究它的實現

布隆過濾器的插入

// 假設布隆過濾器中元素類型爲K,每個元素對應5個哈希函數
template<class K, 
	class KToInt1 = KeyToInt1,
	class KToInt2 = KeyToInt2,
	class KToInt3 = KeyToInt3,
	class KToInt4 = KeyToInt4,
	class KToInt5 = KeyToInt5
	>
class BloomFilter{
public:
	BloomFilter(size_t size) // 布隆過濾器中元素個數
		: _bmp(5*size), _size(0)
	{}
	bool Insert(const K& key){
		size_t bitCount = _bmp.Size();
		size_t index1 = KToInt1()(key)%bitCount;
		size_t index2 = KToInt2()(key)%bitCount;
		size_t index3 = KToInt3()(key)%bitCount;
		size_t index4 = KToInt4()(key)%bitCount;
		size_t index5 = KToInt5()(key)%bitCount;
		_bmp.Set(index1);
		_bmp.Set(index2);
		_bmp.Set(index3);
		_bmp.Set(index4);
		_bmp.Set(index5);
		
		_size++;
	}
private:
	bitset _bmp;
	size_t _size; // 實際元素的個數
}

布隆過濾器的查找

布隆過濾器的思想是將一個元素用多個哈希函數映射到一個位圖中,降低哈希衝突率,因此被映射到的位置的比特位一定爲1

所以可以按照以下方式進行查找
分別計算每個哈希值對應的比特位置存儲的是否爲0,只要有一個爲0,代表該元素一定不在哈希表中,否則可能在哈希表中。

bool IsInBloomFilter(const K& key){
	size_t bitCount = _bmp.Size();
	
	//所有的false:一定不在
	size_t index1 = KToInt1()(key)%bitCount;
	if(!_bmp.Test(index1))
		return false;
	
	size_t index2 = KToInt2()(key)%bitCount;
	if(!_bmp.Test(index2))
		return false;
	
	size_t index3 = KToInt3()(key)%bitCount;
	if(!_bmp.Test(index3))
		return false;
	
	size_t index4 = KToInt4()(key)%bitCount;
	if(!_bmp.Test(index4))
		return false;
	
	size_t index5 = KToInt5()(key)%bitCount;
	if(!_bmp.Test(index5))
		return false;
	
	return true; // 有可能在
}

【注】:

  • 布隆過濾器如果說某個元素不存在時,該元素一定不存在
  • 如果該元素存在時,該元素可能存在,因爲有些哈希函數存在一定的誤判

比如:在布隆過濾器中查找"alibaba"時,假設5個哈希函數計算的哈希值爲:13756,剛好和其他元素的比特位重疊,此時布隆過濾器告訴該元素存在,但實該元素是不存在的。


布隆過濾器的刪除

布隆過濾器不能直接支持刪除工作,因爲在刪除一個元素時,可能會影響其他元素

比如:刪除一個元素,如果直接將該元素所對應的二進制比特位置0,該位對於其他元素的映射也被刪除/替換了,因爲這兩個元素在多個哈希函數計算出的比特位上剛好有重疊

  • 一種支持刪除的方法:
    將布隆過濾器中的每個比特位擴展成一個小的計數器,插入元素時給k個計數器(k個哈希函數計算出的哈希地址) 加一,刪除元素時,給k個計數器減一,通過多佔用幾倍存儲空間的代價來增加刪除操作。

缺陷:

  1. 無法確認元素是否真正在布隆過濾器中。
  2. 存在計數迴繞,產生額外開銷,甚者影響結果。

【小結】:

優點

  1. 增加和查詢元素的時間複雜度爲O(K), (K爲哈希函數的個數,一般比較小),與數據量大小無關。
  2. 哈希函數相互之間沒有關係,方便硬件並行運算。
  3. 布隆過濾器不需要存儲元素本身,在某些對保密要求比較嚴格的場合有很大優勢。
  4. 在能夠承受一定的誤判時,布隆過濾器比其他數據結構有這很大的空間優勢。
  5. 數據量很大時,布隆過濾器可以表示全集,其他數據結構不能。
  6. 使用同一組散列函數的布隆過濾器可以進行交、並、差運算。

缺陷

  1. 誤判率,即存在假陽性(False Position),即不能準確判斷元素是否在集合中。
    ( 補救方法:再建立一個白名單,存儲可能會誤判的數據 )
  2. 不能獲取元素本身,只能表示在不在。
  3. 一般情況下不能從布隆過濾器中刪除元素。
  4. 如果採用計數方式刪除,可能會存在計數迴繞問題。

海量問題擴展

  • 哈希切割
    給一個超過100G大小的log filelog中存着IP地址:
  1. 設計算法找到出現次數最多的IP地址?
  2. 如何找到top KIP
  3. 如何直接用Linux系統命令實現?
  • 位圖應用
  1. 給定100億個整數,設計算法找到只出現一次的整數?
  2. 給兩個文件,分別有100億個整數,我們只有1G內存,如何找到兩個文件交集?
  3. 1個文件有100億個int1G內存,設計算法找到出現次數不超過2次的整數
  • 布隆過濾器
  1. 給兩個文件,分別有100億個query,我們只有1G內存,如何找到兩個文件交集?分別給出精確算法和近似算法
  2. 如何擴展BloomFilter使得它支持刪除元素的操作
  • 倒排索引
    給上千個文件,每個文件大小爲1K100M。給n個詞,設計算法對每個詞找到所有包含它的文件,你只有100K內存

所以對這兩個結構有所瞭解之後,讀者就大致有了四兩撥千斤的思想。

不要懼怕“海量”二字,當今信息時代,大數據分析給人們帶來便利的同時,相應的處理也要更加巧妙與靈活。讓科技,爲人所用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章