用戶中心是受權邏輯與用戶信息相關邏輯構建的應用。分佈式系統中,大多數業務都須要和用戶中心打交道,爲了保證用戶中心服務的高可用,避免不了作緩存、導入搜索引擎從而下降數據庫的壓力。然而有些不通過用戶中心受權的業務場景查詢用戶中心的數據,可能引起大量無效的查詢,發生緩存穿透,直接對搜索引擎和數據庫形成壓力。如何解決用戶中心緩存穿透的問題呢?接下來就着重說一下布隆過濾器是怎麼「隔檔」這些無效查詢的。java
緩存穿透是指用戶查詢數據,在數據庫沒有,天然在緩存中也不會有。這樣就致使用戶查詢的時候,在緩存中找不到對應key的value,每次都要去數據庫再查詢一遍,而後返回空(至關於進行了兩次無用的查詢)。這樣請求就繞過緩存直接查數據庫。redis
布隆過濾器(Bloom Filter)的核心實現是一個超大的位數組和幾個哈希函數。假設位數組的長度爲m,哈希函數的個數爲k。算法
(1) 添加元素過程
數據庫
(2) 查詢元素過程
網頁爬蟲
很顯然,根據布隆過濾器的原理和特性,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中,布隆過濾器的實現主要涉及到2個類, BloomFilter和 BloomFilterStrategies。首先來看一下 BloomFilter的成員變量。須要注意的是不一樣Guava版本的 BloomFilter實現不一樣。
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)與操做和或操做相應的結果。
分佈式系統直接使用guava bloom filter在某些業務場景下不是很方便,既然是分佈式環境,最好仍是經過分佈式緩存封裝一版布隆過濾器。
經過對guava bloom filter的分析,由單機版改形成分佈式版,只須要從新實現三個guava bloom filter的三個類(BloomFilter,BloomFilterStrategies,BitArray)。
RedisBitArray改造不是很麻煩,只須要引入操做分佈式緩存的JedisCluster對象就行了。get和set操做對應JedisCluster對象的getbit和setbit操做(針對String類型的值,Redis經過 位操做 實現了BitMap數據結構)。
BloomFilter和BloomFilterStrategies的改造相對比較簡單,這裏就不詳細說明了。
爲何要有路由布隆過濾器?經過上面的公式能夠知道,當要插入的樣本數量n越大,那麼須要分配的內存容量m也會越大。也就是布隆過濾器的不當使用極易產生大 Value,增長 內存溢出或者阻塞風險,所以生成環境中建議對體積龐大的布隆過濾器進行拆分,拆分的規則咱們定義爲按照必定的路由規則對應到不一樣的布隆過濾器。
(1) 設計方案
(2) 路由策略
(3) 成員變量
(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) 監控信息
注:布隆過濾器的bit數組在redis中對應的數據類型是String哦!
消息中心給用戶推送消息的時候,是按照先微信小程序用戶,不然公衆號用戶串行邏輯來執行的(大多數消息都是按照用戶手機號推送的)。小程序的用戶體系相對公衆號的用戶體系是較少的,並且小程序用戶訂閱消息的量級增加的緩慢。這就出現了不少不是小程序用戶的查詢請求,也就是出現了上面提到 緩存穿透 現象,無形之中會增長搜索引擎和數據庫壓力。
小程序用戶查詢服務集成了布隆過濾器,很優雅的解決了緩存穿透的問題。業務上線初期,天天大約有200W到300W的請求,能夠過濾掉90%以上的無效用戶查詢請求。看着這鮮明的效果,欣喜若狂,心想着這方案集成的太完美了,真香!
請關注微信訂閱號(算法和技術SHARING),回覆:bloomfilter, 即可查看。
https://www.jianshu.com/p/2104d11ee0a2
https://zhuanlan.zhihu.com/p/43263751