[TOC]redis
redis 集羣部署方式大部分採用類 Twemproxy 的方式進行部署。即經過 Twemproxy 對 redis key 進行分片計算,將 redis key 進行分片計算,分配到多個 redis 實例中的其中一個。tewmproxy 架構圖以下:
算法
因爲 Twemproxy 背後的多個 redis 實例在內存配置和 cpu 配置上都是一致的,因此一旦出現訪問量傾斜或者數據量傾斜,則可能會致使某個 redis 實例達到性能瓶頸,從而使整個集羣達到性能瓶頸。json
Hot key,即熱點 key,指的是在一段時間內,該 key 的訪問量遠遠高於其餘的 redis key, 致使大部分的訪問流量在通過 proxy 分片以後,都集中訪問到某一個 redis 實例上。hot key 一般在不一樣業務中,存儲着不一樣的熱點信息。好比緩存
……架構
在 client 端使用本地緩存,從而下降了redis集羣對hot key的訪問量,可是同時帶來兩個問題:一、若是對可能成爲 hot key 的 key 都進行本地緩存,那麼本地緩存是否會過大,從而影響應用程序自己所需的緩存開銷。二、如何保證本地緩存和redis集羣數據的有效期的一致性。
針對這兩個問題,先不展開講,先將第二個解決方案。併發
咱們知道 hot key 之因此是 hot key,是由於它只有一個key,落地到一個實例上。因此咱們能夠給hot key加上前綴或者後綴,把一個hotkey 的數量變成 redis 實例個數N的倍數M,從而由訪問一個 redis key 變成訪問 N * M 個redis key。
N*M 個 redis key 通過分片分佈到不一樣的實例上,將訪問量均攤到全部實例。運維
代碼以下:tcp
//redis 實例數 const M = 16 //redis 實例數倍數(按需設計,2^n倍,n通常爲1到4的整數) const N = 2 func main() { //獲取 redis 實例 c, err := redis.Dial("tcp", "127.0.0.1:6379") if err != nil { fmt.Println("Connect to redis error", err) return } defer c.Close() hotKey := "hotKey:abc" //隨機數 randNum := GenerateRangeNum(1, N*M) //獲得對 hot key 進行打散的 key tmpHotKey := hotKey + "_" + strconv.Itoa(randNum) //hot key 過時時間 expireTime := 50 //過時時間平緩化的一個時間隨機值 randExpireTime := GenerateRangeNum(0, 5) data, err := redis.String(c.Do("GET", tmpHotKey)) if err != nil { data, err = redis.String(c.Do("GET", hotKey)) if err != nil { data = GetDataFromDb() c.Do("SET", "hotKey", data, expireTime) c.Do("SET", tmpHotKey, data, expireTime + randExpireTime) } else { c.Do("SET", tmpHotKey, data, expireTime + randExpireTime) } } }
在這個代碼中,經過一個大於等於 1 小於 M * N 的隨機數,獲得一個 tmp key,程序會優先訪問tmp key,在得不到數據的狀況下,再訪問原來的 hot key,並將 hot key的內容寫回 tmp key。值得注意的是,tmp key的過時時間是 hot key 的過時時間加上一個較小的隨機正整數,保證在 hot key 過時時,全部 tmp key 不會同時過時而形成緩存雪崩。這是一種經過坡度過時的方式來避免雪崩的思路,同時也能夠利用原子鎖來寫入數據就更加的完美,減少db的壓力。高併發
另外還有一件事值得一提,默認狀況下,咱們在生成 tmp key的時候,會把隨機數做爲 hot key 的後綴,這樣符合redis的命名空間,方便 key 的收歸和管理。可是存在一種極端的狀況,就是hot key的長度很長,這個時候隨機數不能做爲後綴添加,緣由是 Twemproxy 的分片算法在計算過程當中,越靠前的字符權重越大,考後的字符權重則越小。也就是說對於key名,前面的字符差別越大,算出來的分片值差別也越大,更有可能分配到不一樣的實例(具體算法這裏不展開講)。因此,對於很長 key 名的 hot key,要對隨機數的放入作謹慎處理,好比放在在最後一個命令空間的最前面(eg:由原來的 space1:space2:space3_rand 改爲 space1:space2:rand_space3)。工具
big key ,即數據量大的 key ,因爲其數據大小遠大於其餘key,致使通過分片以後,某個具體存儲這個 big key 的實例內存使用量遠大於其餘實例,形成,內存不足,拖累整個集羣的使用。big key 在不一樣業務上,一般體現爲不一樣的數據,好比:
……
對 big key 存儲的數據 (big value)進行拆分,變成value1,value2… valueN,
//存 mset key1, vlaue1, key2, vlaue2 ... keyN, valueN //取 mget key1, key2 ... keyN
在開發過程當中,有些 key 不僅是訪問量大,數據量也很大,這個時候就要考慮這個 key 使用的場景,存儲在redis集羣中是不是合理的,是否使用其餘組件來存儲更合適;若是堅持要用 redis 來存儲,可能考慮遷移出集羣,採用一主一備(或1主多備)的架構來存儲。
在業務開發階段,就要對可能變成 hot key ,big key 的數據進行判斷,提早處理,這須要的是對產品業務的理解,對運營節奏的把握,對數據設計的經驗。
經過監控以後,程序能夠獲取 big key 和 hot key,再報警的同時,程序對 big key 和 hot key 進行自動處理。或者通知程序猿利用必定的工具進行定製化處理(在程序中對特定的key 執行前面提到的解決方案)
儘可能仍是不要過後了吧,都是血和淚的教訓,不展開講。
謝謝閱讀,歡迎交流。