分佈式系列之緩存設計中常見的問題

 

    緩存這個東西相信你們工做中都接觸得比較多,相應的在不一樣場景下也會遇到各類各樣的問題。下面我列舉幾種可能會遇到的問題並提供一些解決建議。redis

 

一、如何把海量數據存放在緩存中並提供快速查詢算法

     現實中咱們的緩存一般都是以string,map,array,list,set,tree等具體的類型或者集合存放內存中,它們的共同點都在於把元素具體內容放到內存裏面。這種在元素數量小的時候是沒問題。但一旦數據量過大,消耗的內存也會呈現線性增加,最終達到瓶頸,而且查詢效率也可能隨着元素數量增加而降低。好比list與array,沒有數字下標的狀況下只能是0(n)遍歷,有人也許會說到map的效率不是很高嗎,查詢效率能夠達到O(1)。但這只是理想狀況而已,hash衝突大的狀況下map的查詢也會退化,而且map也並無解決內存消耗的問題。難道就沒有辦法解決這個問題嗎?固然有!答案就是Bit-map和布隆過濾器!數組

     

      什麼是Bit-map?緩存

       所謂的Bit-map就是用一個bit位來標記某個元素對應的Value, 而Key便是該元素或者元素通過轉換(好比hash)獲得的值。因爲採用了Bit爲單位來存儲數據,所以在存儲空間方面,能夠大大節省。原理以下圖:數據結構

  

 

      那1M的Bit-map能夠表示多少數據呢,1兆字節(mb)=8388608比特(bit),也就是指咱們能夠用1M的內存表示800W+的數據。。。。併發

      舉個情景好比運營方給了一批100W用戶ID,若是這批用戶在指定時間內購買了某個商品就給用戶派一張優惠劵,假設這100W的用戶的userId都是64位的長整型數。那如何把這100W的用戶存起來節省空間而後訪問的性能也不差?個人建議是放到redis緩存裏面利用redis的setbit,getbit的命令存儲起來和訪問,這樣要比你存db要節省更多的空間而且查詢速度也快~。若是這100W的用戶ID分佈範圍比較隨機,我建議是本地排好序,而後分紅幾段用不一樣Bit-map表示~,這樣就不會形成過多沒必要要的空間浪費。別外本地排序也要以用Bit-map實現哦。異步

     

    什麼又是布隆過濾器?函數

   布隆過濾器英語:Bloom Filter)是1970年由布隆提出的。它其實是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難。高併發

  基本概念和原理:性能

  若是想判斷一個元素是否是在一個集合裏,通常想到的是將集合中全部元素保存起來,而後經過比較肯定。鏈表散列表(又叫哈希表,Hash table)等等數據結構都是這種思路。可是隨着集合中元素的增長,咱們須要的存儲空間愈來愈大。同時檢索速度也愈來愈慢,上述三種結構的檢索時間複雜度分別爲O(n),O(logn),O(n/k)

    布隆過濾器的原理是,當一個元素被加入集合時,經過K個散列函數將這個元素映射成一個位數組中的K個點,把它們置爲1。檢索時,咱們只要看看這些點是否是都是1就(大約)知道集合中有沒有它了:若是這些點有任何一個0,則被檢元素必定不在;若是都是1,則被檢元素極可能在。這就是布隆過濾器的基本思想。

 因此說布隆過濾器底層仍是依賴Bit-map的存儲原理,由於是經過散列函數來進行映射就會有衝突的可能性,當元素a,b的hash出來index是同樣的時候就沒法判斷到底Bit-map裏面存的是a仍是b。因此通常布隆過濾器是不容許刪除元素的,由於真不知道刪除的是哪一個元素。。。。

  布隆過濾器hash衝突性與散列函數的設計和Bit-map的大小有關,若是Bit-map過小那必然不少元素都會落到同一個下標,而且後面數量越大沖突也就越大。不過不用太擔憂畢竟1M的Bit-map就能夠保存800W+數據~。

   當你存儲元素量不是很大的話,能夠優先考慮散列表(map),數量大時布隆過濾器會不錯的選擇~。

  

二、高併發時緩存如何更新

    在更新緩存時咱們一般會加鎖更新,爲了減小鎖住的資源,一般用分段鎖的設計,只鎖須要更新的資源。但在高併發的情景下大多數發請求都集中在一個key的時候也就是hot-key的情形下,對緩存進行更新。若是採用加鎖的形式,就只能有一個線程去更新,其它的線程就只能同步阻塞,瞬間就有會大量線程hold住,甚至有可能把線程打滿,這個時候系統的性能就會大打折扣。因此在高併發hot-key的情景下,加鎖更新很影響性能。若是把讀取和更新的操做隔離開來會怎樣,以下圖

 

   這種方案就是起額外的線程定時去定時去更新cache,這樣讀取線程就不會存在鎖爭的問題。這種一般cache不會設置超時時間,好比guava cache的refreshAfterWrite策略的cache就是永遠不會超時的,只是每次讀取的時候判斷是否到了刷新週期,若是到了選取其中一個線程去更新,其它線程仍然返回舊值。因此這異步更新cache也不失爲一個方法。只是要注意的控制好異步更新頻率,頻率過小那cache的實時性就會受影響。

 

 三、Redis OR Memcache?

      每當有人問我這個東西是用redis仍是memcache存起來時,我會建議他從下面幾個方面去考慮:

       一、緩存的更新設置是怎樣的?每次都get,set所有數據嗎仍是部分。

       二、除了get,set還有其它操做嗎?好比排序,獲取前面5個元素?

       三、預計緩存的qps有多少?

       四、緩存須要持久化嗎

       五、單個緩存有多大

       對於redis、memcache來講,我以爲若是是通常業務首先考慮的不該該是二者的性能問題,而是這二者提供的數據結構哪一個更適切合你當前的業務需求還有未來業務的發展。在這一點方面redis無疑是佔據了絕大優點,由於redis提供了String、Hash、List、Set和Sorted Set五種數據結構,而memcache只有key-value。因此通常我是優先考慮redis的。另外在單個緩存大小方面memcache的value存儲,最大爲1M,若是存儲的value很大,只能使用redis。剛提到爲何不優先考慮性能問題呢?由於這二者的性能都不算差,而且後面都是能夠橫向擴展的,甚至還能夠經過其它方式好比增長多層cache如local cache去提高。其實更多仍是考慮業務的維護與迭代。若是你是純k-v操做,而且數據量很是大,併發量很是大的業務,這個時候我建議你memcache會更適合你~,若是是其它redis可能會更適合你~。

相關文章
相關標籤/搜索