1. 程式人生 > >高可用服務設計之如何應對快取穿透

高可用服務設計之如何應對快取穿透

背景

使用者中心是授權邏輯與使用者資訊相關邏輯構建的應用。分散式系統中,大多數業務都需要和使用者中心打交道,為了保證使用者中心服務的高可用,避免不了做快取、匯入搜尋引擎從而降低資料庫的壓力。然而有些不經過使用者中心授權的業務場景查詢使用者中心的資料,可能引發大量無效的查詢,發生快取穿透,直接對搜尋引擎和資料庫造成壓力。如何解決使用者中心快取穿透的問題呢?接下來就著重說一下布隆過濾器是怎麼“隔檔”這些無效查詢的。

快取穿透

快取穿透是指使用者查詢資料,在資料庫沒有,自然在快取中也不會有。這樣就導致使用者查詢的時候,在快取中找不到對應key的value,每次都要去資料庫再查詢一遍,然後返回空(相當於進行了兩次無用的查詢)。這樣請求就繞過快取直接查資料庫。

布隆過濾器

基本概念

  • 布隆過濾器(Bloom Filter)是1970年由布隆提出的。它實際上是一個很長的二進位制向量(點陣圖)和一系列隨機對映函式(雜湊函式)。
  • 布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的演算法,缺點是有一定的誤識別率和刪除困難。

特點

  • 空間效率高和查詢效率高的概率型資料結構。
  • 對於一個元素檢測是否存在的呼叫,BloomFilter會告訴呼叫者兩個結果之一:可能存在或者一定不存在。
  • 一個很長的二進位制向量 (位陣列)。
  • 一系列隨機函式(雜湊)。
  • 有一定的誤判率(雜湊表是精確匹配)。

原理

布隆過濾器(Bloom Filter)的核心實現是一個超大的位陣列和幾個雜湊函式。假設位陣列的長度為m,雜湊函式的個數為k。

(1) 新增元素過程

  • 將要新增的元素給k個雜湊函式。
  • 得到對應於位陣列上的k個位置。
  • 將這k個位置設為1。

(2) 查詢元素過程

  • 將要查詢的元素給k個雜湊函式。
  • 得到對應於位陣列上的k個位置。
  • 如果k個位置有一個為0,則肯定不在集合中。
  • 如果k個位置全部為1,則可能在集合中。

相關公式

很顯然,根據布隆過濾器的原理和特性,bit陣列大小和雜湊函式的個數都會影響誤判率。那麼布隆過濾器是如何權衡bit陣列大小和雜湊函式個數的呢?

假設布隆過濾器bit陣列大小為m,樣本數量為n,失誤率為p。

假設樣本容量n=5000W,誤判率是0.03,那麼所需要的記憶體空間大小是m = -5000W * -3.057 / (0.7)^2 ≈ 318,437,500 ≈ 39.8MB

演示

(1)參考地址

https://www.jasondavies.com/bloomfilter

(2)可能存在

 

 

 

 (3)一定不存在

Guava Bloom Filter

Guava中,布隆過濾器的實現主要涉及到2個類, BloomFilter和 BloomFilterStrategies。首先來看一下 BloomFilter的成員變數。需要注意的是不同Guava版本的 BloomFilter實現不同。

  • BitArrays 是定義在BloomFilterStrategies中的內部類,封裝了布隆過濾器底層bit陣列的操作。
  • numHashFunctions表示雜湊函式的個數,即上文公式提到的k。
  • Funnel主要是把任意型別的資料轉化成Java基本資料型別(primitive value,如char,byte,int……),預設用java.nio.ByteBuffer實現,最終均轉化為byte陣列。
  • Strategy是定義在BloomFilter類內部的介面,程式碼如下,有3個方法,put(插入元素),mightContain(判定元素是否存在)和ordinal方法(可以理解為列舉類中那個預設方法)。

 

 

BloomFilterStrategies類,首先它是實現了BloomFilter.Strategy 介面的一個列舉類,其次它有兩個2列舉值,MURMUR128_MITZ_32和MURMUR128_MITZ_64,分別對應了32位雜湊對映函式和64位雜湊對映函式,後者使用了murmur3 hash可生成128位雜湊值,具有更大的空間,不過原理是相通的。

MURMUR128_MITZ_64實現原理可以參考(http://rrd.me/gDkD5)。

 

BitArray是guava bloom filter底層bit陣列的一個實現類。Guava使用的是一個long型陣列實現了類似BitSet的資料結構。第一個建構函式傳入了一個bit位的位數bits,然後bits除以64並向上取整得到long型陣列的大小。get和set操作根據bit位的索引index,找到對應的操作物件data[index >>> 6](等價於data[index / 64]),分別跟(1L << index)與操作和或操作相應的結果。

Redis Bloom Filter

分散式系統直接使用guava bloom filter在某些業務場景下不是很方便,既然是分散式環境,最好還是通過分散式快取封裝一版布隆過濾器。

通過對guava bloom filter的分析,由單機版改造成分散式版,只需要重新實現三個guava bloom filter的三個類(BloomFilter,BloomFilterStrategies,BitArray)。

RedisBitArray改造不是很麻煩,只需要引入操作分散式快取的JedisCluster物件就好了。get和set操作對應JedisCluster物件的getbit和setbit操作(針對String型別的值,Redis通過 位操作 實現了BitMap資料結構)。

BloomFilter和BloomFilterStrategies的改造相對比較簡單,這裡就不詳細說明了。

Routing Bloom Filter

為什麼要有路由布隆過濾器?通過上面的公式可以知道,當要插入的樣本數量n越大,那麼需要分配的記憶體容量m也會越大。也就是布隆過濾器的不當使用極易產生大 Value,增加 記憶體溢位或者阻塞風險,因此生成環境中建議對體積龐大的布隆過濾器進行拆分,拆分的規則我們定義為按照一定的路由規則對應到不同的布隆過濾器。

(1) 設計方案

 

(2) 路由策略

  • routing方法根據樣本計算出路由key值。
  • exceptedInsertions方法根據樣本獲取到路由key值,然後計算期望插入的樣本數量。

 

(3) 成員變數

  • ROUTE_MAP是本地快取,儲存RoutingStrategy物件routing方法計算出的路由key值以及對應的RedisBloomFilter例項。
  • routingStrategy是路由策略RoutingStrategy例項。
  • bfRedisKeyPrefix是Redis布隆過濾器bit陣列在redis中對應的key值字首。
  • bfKeysMappingRedisKey儲存了所有Redis布隆過濾器bit陣列在redis中對應的key(即bfRedisKeyPrefix + 路由key)的集合。

 

(4) put操作

獲取當前樣本物件的routeKey,ROUTE_MAP的computeIfAbsent方法根據routeKey獲取對應的Redis Bloom Filter,如果不存在則建立一個新的Redis Bloom Filter物件例項並儲存到ROUTE_MAP中。變數bloomFilterRedisKey = bfRedisKeyPrefix + routeKey,也就是Redis Bloom Filter bit陣列在redis中儲存的key值,最後儲存在分散式快取的集合中(即bfKeysMappingRedisKey對應的集合)。

 

(5) mightContain操作

和put操作的流程基本一致,在獲取routeKey對應的Redis Bloom Filter例項的時候,如果不存在需要判斷分散式快取bfKeysMappingRedisKey對應的集合中是否存在bloomFilterRedisKey,如果不存在說明put操作沒有建立對應的Redis Bloom Filter例項,直接返回null。

 

(6) 監控資訊

  • approximateElementCount,布隆過濾器中可能存在的元素個數。
  • bitSize,布隆過濾器bit陣列大小。
  • bitCount,布隆過濾器bit陣列中bit位是1的數量。
  • keyLength,布隆過濾器bit陣列通過strlen統計的長度。

注:布隆過濾器的bit陣列在redis中對應的資料型別是String哦!

應用場景

  • 網頁爬蟲對URL的去重。
  • 黑名單,垃圾郵件過濾。
  • 解決資料庫快取擊穿。

實際應用

訊息中心給使用者推送訊息的時候,是按照先微信小程式使用者,否則公眾號使用者序列邏輯來執行的(大多數訊息都是按照使用者手機號推送的)。小程式的使用者體系相對公眾號的使用者體系是較少的,而且小程式使用者訂閱訊息的量級增長的緩慢。這就出現了很多不是小程式使用者的查詢請求,也就是出現了上面提到 快取穿透 現象,無形之中會增加搜尋引擎和資料庫壓力。

小程式使用者查詢服務集成了布隆過濾器,很優雅的解決了快取穿透的問題。業務上線初期,每天大約有200W到300W的請求,可以過濾掉90%以上的無效使用者查詢請求。看著這鮮明的效果,欣喜若狂,心想著這方案整合的太完美了,真香!

原始碼參考

請關注微信訂閱號(演算法和技術SHARING),回覆:bloomfilter, 便可檢視。

參考資料

https://www.jianshu.com/p/2104d11ee0a2

https://zhuanlan.zhihu.com/p/43263751

https://blog.csdn.net/u012422440/article/details/94088166

https://www.jianshu.com/p/44b4b429