生日悖論是啥?我用它省了上百G的內存

生日悖論: 是指在不少於 23 個人中至少有兩人生日相同的概率大於 50%。例如在一個 30 人的小學班級中,存在兩人生日相同的概率爲 70%。對於 60 人的大班,這種概率要大於 99%。從引起邏輯矛盾的角度來說,生日悖論並不是一種 “悖論”。但這個數學事實十分反直覺,故稱之爲一個悖論。

生日悖論是有個有趣的概念,但這和我省上百G的內存有什麼關係?

背景

首先介紹下背景,工作中我負責了一個廣告數據系統,其中一個功能就是對同一次請求的廣告曝光去重,因爲我們只需要知道這次請求這個廣告的一次曝光就行了,那些同一次請求產生的重複曝光記錄下來沒有意義,而且還耗會增加我們的存儲成本。所以這裏就需要有個邏輯去判斷每條新到的曝光是否只之前已經記錄過的,舊的方案是在redis中存儲請求唯一標識(uuid)和廣告ID(adid),每次數據過來我們就看redis裏有沒有uuid+adid這個key,有就過濾掉,沒有就不過濾並在redis記錄下來已出現。

問題就來了,redis記錄的這份數很大(兩天數據超過400G),而且隨着我們業務的增長,我們的Redis集羣快盛不下了…… 當然花錢加機器是最簡單的方式,畢竟能用錢解決的問題都不是問題。而優秀的我,爲了替公司省錢,走了優化的路。

如何優化?

首先可以肯定的是數據條數不會少,因爲業務量就在那裏,所以減少數據量的這條路肯定行不通。那是否可以減少每條數據的長度呢?
我們再來看下redis存儲的設計,如下圖:
在這裏插入圖片描述
這樣下來一條記錄總共用了45個字節,這個長度能不能縮短? 當然能,我們可以截取部分UUID,但這樣又帶來一個新的問題,截取UUID會增加重複的概率,所以首先搞清楚怎麼截取,截多少?

這裏我們用的是隨機UUID,這個版本中有效二進制位是122個,所以總共有$2^{122}$個有效的UUID。 因爲是隨機產生的所以肯定有重複的概率,UUID重複的概率有多少? 要算這個重複概率,光有$2^{122}$這個總數還不行,還得知道你擁有的UUID個數。 我把這個問題具體下,求:在$2^{36}$個UUID中有重複的概率是多少?
$$
p(n) \approx 1-e{-\frac{n{2}}{2 x}}
$$
這不就是生日悖論的數據放大版嗎? 當然這個概率可以根據上面公式計算,其中x是UUID的總數$2{122}$,n是$2{36}$,引用百度百科的數據,概率爲$4 *10^{-16}$ 這比你出門被隕石撞的概率還小很多。

n 機率
2^36 4 x 10^-16
2^41 4 x 10^-13
2^46 4 x 10^-10

另外,從上面的公式也可以看出,在n固定的時候,隨着有效二進制位的減少,概率p就會增加。 回到我們廣告去重的場景下,每天最大請求數n是基本固定的,而且我們也可以忍受一個較小的概率p(小於萬分之一),然後就可以反推出這個x有多大。

其實只要$\frac{n^{2}}{2 x} < 0.0001$,p就會小於萬分之一。我可以從歷史數中統計出n的大小,然後計算出x,再留一定的buff,然後根據n的大小重新設計了redis的key。(因爲涉及公司數據,這裏不公佈中間計算過程)

新設計

最終有效位我選取了40個有效二進制位(10個16進制位),但我並沒有直接截取UUID的前10位,因爲UUID的前幾位和時間有關,隨機性並不強。我選擇將整個UUID重新md5散列,然後截取md5的前10位,然後拼接adId形成最終的key,如下圖:
在這裏插入圖片描述
明顯看出,key的長度縮小了一半,總體上能節省至少50%的存儲空間。備註:但其實我們redis的具體存儲實現和上文描述略有差異,爲了不喧賓奪主上文特意對實際實現做了簡化描述,所以最終實際沒有省一半以上的內存,只省了35%左右。

如何進一步優化?

實際優化就到這了,但其實還是不夠極致,其實adId中也包含大量的冗餘信息也可以截取,其實我們可以承受更高的重複率,其實我們可以把redis數據存儲時間設的更短一些……

上面幾種方法都可以進一步優化,但存儲空間不會有量級級別的減少,而下面一種方式,可以將存儲空間減小99%以上。

布隆過濾器(BloomFilter)

關於布隆過濾器的原理,可以參考我之前寫的一篇文章布隆過濾器(BloomFilter)原理 實現和性能測試。 布隆過濾器完全就是爲了去重場景設計的,保守估計我們廣告去重的場景切到布隆過濾器,至少節省90%的內存。

那爲什麼我沒有用布隆過濾器,其實還是因爲實現複雜。redis在4.0後支持模塊,其中有人就開發設計了布隆過濾器的模塊RedisBloom,但無奈我們用的redis 還是3.x版本 !我也考慮過應用端基於redis去實現布隆過濾器,但我們應用端是個集羣,需要解決一些分佈式數據一致性的問題,作罷。

結語

其實我們redis的具體存儲實現和上文描述略有差異,爲了不喧賓奪主上文特意對實際實現做了簡化描述,所以最終實際沒有省一半以上的內存,只省了35%左右。

最終400G+優化後能減少100多G的內存,其實也就是一臺服務器,即便放到未來也就是少擴容幾臺服務器。對公司而言就是每個月節省幾千的成本,我司這種大廠其實是不會在乎這點錢的。不過即便這幾千的成本最終不會轉化成我的工資或者獎金,但像這種優化該做還是得做。如果每個人都本着 用最低的成本做同樣事 的原則去做好每一件事,就我司這體量,一個月上千萬的成本還是能省下來的。

參考資料

  1. 百度百科 生日悖論
  2. 百度百科 UUID
  3. 布隆過濾器(BloomFilter)原理 實現和性能測試
  4. RedisBloom 基於redis的布隆過濾器實現

本文來自https://blog.csdn.net/xindoo

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