本篇文章為大家展示了大數(shù)據(jù)緩存擊穿以及如何解決緩存擊穿,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。
創(chuàng)新互聯(lián)主營荊州網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,手機(jī)APP定制開發(fā),荊州h5成都微信小程序搭建,荊州網(wǎng)站營銷推廣歡迎荊州等地區(qū)企業(yè)咨詢
寫在前沿
工作幾年,一直都沒有去體系化的學(xué)習(xí),很多東西沒有復(fù)雜的工作場景經(jīng)驗(yàn),最后還是決定報(bào)了拉勾的高薪訓(xùn)練營,在這里也是實(shí)實(shí)在在的學(xué)習(xí)到了很多,學(xué)完掌握程度也比之前深了很多,而且還有定期的內(nèi)推,多了更多的機(jī)會,真的對我有了很大的幫助提升。特別感謝溫柔可愛的小竹子班主任和認(rèn)真負(fù)責(zé)帥氣的老可樂導(dǎo)師給予我的幫助!
緩存在某個時(shí)間點(diǎn)過期的時(shí)候,恰好在這個時(shí)間點(diǎn)對這個Key有大量的并發(fā)請求過來,這些請求發(fā)現(xiàn)緩 存過期一般都會從后端DB加載數(shù)據(jù)并回設(shè)到緩存,這個時(shí)候大并發(fā)的請求可能會瞬間把后端DB壓垮。
當(dāng)數(shù)據(jù)庫和redis中都不存在key,在數(shù)據(jù)庫返回null時(shí),在redis中插入<key,null,expireTime>當(dāng)key再次請求時(shí),redis直接返回null,而不用再次請求數(shù)據(jù)庫。
可以設(shè)置一些過濾規(guī)則, 如布隆過濾器
將數(shù)據(jù)庫中所有的查詢條件,放入布隆過濾器中,
當(dāng)一個查詢請求過來時(shí),先經(jīng)過布隆過濾器進(jìn)行查,如果判斷請求查詢值存在,則繼續(xù)查;如果判斷請求查詢不存在,直接丟棄。
布隆過濾器(Bloom Filter,下文簡稱BF)由Burton Howard Bloom在1970年提出,是一種空間效率高的概率型數(shù)據(jù)結(jié)構(gòu)。**它專門用來檢測集合中是否存在特定的元素。**聽起來是很稀松平常的需求,為什么要使用BF這種數(shù)據(jù)結(jié)構(gòu)呢?
優(yōu)點(diǎn):
不需要存儲數(shù)據(jù)本身,只用比特表示,因此空間占用相對于傳統(tǒng)方式有巨大的優(yōu)勢,并且能夠保密數(shù)據(jù);
時(shí)間效率也較高,插入和查詢的時(shí)間復(fù)雜度均為O(k);
哈希函數(shù)之間相互獨(dú)立,可以在硬件指令層面并行計(jì)算。
缺點(diǎn):
存在假陽性的概率,不適用于任何要求100%準(zhǔn)確率的情境;
只能插入和查詢元素,不能刪除元素,這與產(chǎn)生假陽性的原因是相同的。我們可以簡單地想到通過計(jì)數(shù)(即將一個比特?cái)U(kuò)展為計(jì)數(shù)值)來記錄元素?cái)?shù),但仍然無法保證刪除的元素一定在集合中。
所以,BF在對查準(zhǔn)度要求沒有那么苛刻,而對時(shí)間、空間效率要求較高的場合非常合適,另外,由于它不存在假陰性問題,所以用作“不存在”邏輯的處理時(shí)有奇效,比如可以用來作為緩存系統(tǒng)(如Redis)的緩沖,防止緩存穿透。
BF是由一個**長度為m比特的位數(shù)組(bit array)與k個哈希函數(shù)(hash function)**組成的數(shù)據(jù)結(jié)構(gòu)。位數(shù)組均初始化為0,所有哈希函數(shù)都可以分別把輸入數(shù)據(jù)盡量均勻地散列。
它本身是一個很長的二進(jìn)制向量,既然是二進(jìn)制的向量,那么顯而易見的,存放的不是0,就是1。
現(xiàn)在我們新建一個長度為16的布隆過濾器,默認(rèn)值都是0,就像下面這樣:
現(xiàn)在需要添加一個數(shù)據(jù):
我們通過某種計(jì)算方式,比如Hash2,計(jì)算出了Hash2(數(shù)據(jù))=5,我們就把下標(biāo)為5的格子改成1,就像下面這樣:
我們又通過某種計(jì)算方式,比如Hash3,計(jì)算出了Hash3(數(shù)據(jù))=9,我們就把下標(biāo)為9的格子改成1,就像下面這樣:
還是通過某種計(jì)算方式,比如Hash4,計(jì)算出了Hash4(數(shù)據(jù))=2,我們就把下標(biāo)為2的格子改成1,就像下面這樣:
這樣,剛才添加的數(shù)據(jù)就占據(jù)了布隆過濾器“5”,“9”,“2”三個格子。
可以看出,僅僅從布隆過濾器本身而言,根本沒有存放完整的數(shù)據(jù),只是運(yùn)用一系列隨機(jī)映射函數(shù)計(jì)算出位置,然后填充二進(jìn)制向量。
這有什么用呢?比如現(xiàn)在再給你一個數(shù)據(jù),你要判斷這個數(shù)據(jù)是否重復(fù),你怎么做?
你只需利用上面的三種固定的計(jì)算方式,計(jì)算出這個數(shù)據(jù)占據(jù)哪些格子,然后看看這些格子里面放置的是否都是1,如果有一個格子不為1,那么就代表這個數(shù)字不在其中。這很好理解吧,比如現(xiàn)在又給你了剛才你添加進(jìn)去的數(shù)據(jù),你通過三種固定的計(jì)算方式,算出的結(jié)果肯定和上面的是一模一樣的,也是占據(jù)了布隆過濾器“5”,“9”,“2”三個格子。
但是有一個問題需要注意,如果這些格子里面放置的都是1,不一定代表給定的數(shù)據(jù)一定重復(fù),也許其他數(shù)據(jù)經(jīng)過三種固定的計(jì)算方式算出來的結(jié)果也是相同的。這也很好理解吧,比如我們需要判斷對象是否相等,是不可以僅僅判斷他們的哈希值是否相等的。
也就是說布隆過濾器只能判斷數(shù)據(jù)是否一定不存在,而無法判斷數(shù)據(jù)是否一定存在。如圖:
按理來說,介紹完了新增、查詢的流程,就要介紹刪除的流程了,但是很遺憾的是布隆過濾器是很難做到刪除數(shù)據(jù)的,為什么?你想想,比如你要刪除剛才給你的數(shù)據(jù),你把“5”,“9”,“2”三個格子都改成了0,但是可能其他的數(shù)據(jù)也映射到了“5”,“9”,“2”三個格子啊,這不就亂套了嗎?
布隆過濾器有許多實(shí)現(xiàn)與優(yōu)化,Guava中就提供了一種Bloom Filter的實(shí)現(xiàn)。
在使用bloom filter時(shí),繞不過的兩點(diǎn)是預(yù)估數(shù)據(jù)量n以及期望的誤判率fpp,
在實(shí)現(xiàn)bloom filter時(shí),繞不過的兩點(diǎn)就是hash函數(shù)的選取以及bit數(shù)組的大小。
對于一個確定的場景,我們預(yù)估要存的數(shù)據(jù)量為n,期望的誤判率為fpp,然后需要計(jì)算我們需要的Bit數(shù)組的大小m,以及hash函數(shù)的個數(shù)k,并選擇hash函數(shù)
根據(jù)預(yù)估數(shù)據(jù)量n以及誤判率fpp,bit數(shù)組大小的m的計(jì)算方式:
由預(yù)估數(shù)據(jù)量n以及bit數(shù)組長度m,可以得到一個hash函數(shù)的個數(shù)k:
哈希函數(shù)的選擇對性能的影響應(yīng)該是很大的,一個好的哈希函數(shù)要能近似等概率的將字符串映射到各個Bit。
選擇k個不同的哈希函數(shù)比較麻煩,一種簡單的方法是選擇一個哈希函數(shù),然后送入k個不同的參數(shù)。
看看Guava中BloomFilter中對于m和k值計(jì)算的實(shí)現(xiàn),在com.google.common.hash.BloomFilter類中:
/** * 計(jì)算 Bloom Filter的bit位數(shù)m * * <p>See http://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives for the * formula. * * @param n 預(yù)期數(shù)據(jù)量 * @param p 誤判率 (must be 0 < p < 1) */ @VisibleForTesting static long optimalNumOfBits(long n, double p) { if (p == 0) { p = Double.MIN_VALUE; } return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2))); } /** * 計(jì)算最佳k值,即在Bloom過濾器中插入的每個元素的哈希數(shù) * * <p>See http://en.wikipedia.org/wiki/File:Bloom_filter_fp_probability.svg for the formula. * * @param n 預(yù)期數(shù)據(jù)量 * @param m bloom filter中總的bit位數(shù) (must be positive) */ @VisibleForTesting static int optimalNumOfHashFunctions(long n, long m) { // (m / n) * log(2), but avoid truncation due to division! return Math.max(1, (int) Math.round((double) m / n * Math.log(2))); }
BloomFilter實(shí)現(xiàn)的另一個重點(diǎn)就是怎么利用hash函數(shù)把數(shù)據(jù)映射到bit數(shù)組中。Guava的實(shí)現(xiàn)是對元素通過MurmurHash4計(jì)算hash值,將得到的hash值取高8個字節(jié)以及低8個字節(jié)進(jìn)行計(jì)算,以得當(dāng)前元素在bit數(shù)組中對應(yīng)的多個位置。MurmurHash4算法詳見:Murmur哈希,于2008年被發(fā)明。這個算法hbase,redis,kafka都在使用。
這個過程的實(shí)現(xiàn)在兩個地方:
將數(shù)據(jù)放入bloom filter中
判斷數(shù)據(jù)是否已在bloom filter中
這兩個地方的實(shí)現(xiàn)大同小異,區(qū)別只是,前者是put數(shù)據(jù),后者是查數(shù)據(jù)。
這里看一下put的過程,hash策略以MURMUR128_MITZ_64為例:
public <T> boolean put( T object, Funnel<? super T> funnel, int numHashFunctions, LockFreeBitArray bits) { long bitSize = bits.bitSize(); //利用MurmurHash4得到數(shù)據(jù)的hash值對應(yīng)的字節(jié)數(shù)組 byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).getBytesInternal(); //取低8個字節(jié)、高8個字節(jié),轉(zhuǎn)成long類型 long hash2 = lowerEight(bytes); long hash3 = upperEight(bytes); boolean bitsChanged = false; //這里的combinedHash = hash2 + i * hash3 long combinedHash = hash2; //根據(jù)combinedHash,得到放入的元素在bit數(shù)組中的k個位置,將其置1 for (int i = 0; i < numHashFunctions; i++) { bitsChanged |= bits.set((combinedHash & Long.MAX_VALUE) % bitSize); combinedHash += hash3; } return bitsChanged; }
判斷元素是否在bloom filter中的方法mightContain與上面的實(shí)現(xiàn)基本一致,不再贅述。
簡單寫個demo,用法很簡單
package com.qunar.sage.wang.common.bloom.filter; import com.google.common.base.Charsets; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnel; import com.google.common.hash.Funnels; import com.google.common.hash.PrimitiveSink; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.ToString; /** * BloomFilterTest * */ public class BloomFilterTest { public static void main(String[] args) { long expectedInsertions = 10000000; double fpp = 0.00001; BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), expectedInsertions, fpp); bloomFilter.put("aaa"); bloomFilter.put("bbb"); boolean containsString = bloomFilter.mightContain("aaa"); System.out.println(containsString); BloomFilter<Email> emailBloomFilter = BloomFilter .create((Funnel<Email>) (from, into) -> into.putString(from.getDomain(), Charsets.UTF_8), expectedInsertions, fpp); emailBloomFilter.put(new Email("sage.wang", "quanr.com")); boolean containsEmail = emailBloomFilter.mightContain(new Email("sage.wangaaa", "quanr.com")); System.out.println(containsEmail); } @Data @Builder @ToString @AllArgsConstructor public static class Email { private String userName; private String domain; } }
上述內(nèi)容就是大數(shù)據(jù)緩存擊穿以及如何解決緩存擊穿,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
標(biāo)題名稱:大數(shù)據(jù)緩存擊穿以及如何解決緩存擊穿
網(wǎng)站路徑:http://chinadenli.net/article14/ppedge.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)公司、外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)、營銷型網(wǎng)站建設(shè)、標(biāo)簽優(yōu)化、品牌網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)