重新認識Redis

前言

隨著互聯網科技的不斷髮展,我們以前單純直接操作數據庫的方式已經不能滿足現有的高性能和高併發的需求了,於是緩存技術應用而生。

重新認識Redis

當前比較成熟的緩存技術有:MongoDB、Redis、Memcache,那麼此文主要講的是其中的Redis,也許我們在平常的工作中也用到過,但大多數也僅限於簡單的使用了,可能很多的知識點我們並不知道,那麼就跟我重新來認識一下它吧。

Redis定義

Redis全稱:Remote Dictionary Server,本質上是一個 Key-Value 類型的內存數據庫。

Redis的特點

支持豐富的數據類型(string、hash、list、set,sorted set等等)、支持數據磁盤持久化存儲、支持主從、支持分片。

為什麼redis效率高,速度快?

1、redis是完全基於內存的操作,不會受到磁盤IO的限制。

2、redis是單線程操作,可以避免頻繁的上下文切換。

3、redis採用了非阻塞I/O多路複用機制,單個線程(一個快遞員),通過跟蹤每個I/O流的狀態(每個快遞的送達地點),來管理多個I/O流。

Redis高級用法

假設redis中有十億個Key,如何從這麼多Key中找到固定前綴的Key?

我們一般的方法可能就是直接使用Keys [pattern]來查找所有符合給定模式Pattern的Key,即keys test* //返回所有以test為前綴的key,但是當面對海量數據時,這個操作會對內存造成很大的消耗,因此我們可以用更高級的操作:使用SCAN cursor [MATCH pattern] [COUNT count],即SCAN 0 MATCH test* COUNT 10 //每次返回10條以test為前綴的key。

如何使用redis實現分佈式鎖?

從 Redis 2.6.12 版本開始,我們就可以使用 Set 操作,將 SETNX 和 EXPIRE 融合在一起執行:SET KEY value [EX seconds] [PX milliseconds] [NX|XX]

代碼如下:

RedisService redisService = SpringUtils.getBean(RedisService.class);

String result = redisService.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime);

if("OK.equals(result)"){

doOcuppiredWork();

}

Redis持久化

持久化,即將數據持久存儲,而不因斷電或其他各種複雜外部環境影響數據的完整性。

Redis 目前有兩種持久化方式,即 RDB 和 AOF,但是兩者都有優缺點:

RDB 優點:全量數據快照,文件小,恢復快。

RDB 缺點:無法保存最近一次快照之後的數據。

AOF 優點:可讀性高,適合保存增量數據,數據不易丟失。

AOF 缺點:文件體積大,恢復時間長。

因此,Redis 4.0 之後推出了此種持久化方式,RDB 作為全量備份,AOF 作為增量備份,並且將此種方式作為默認方式使用。在 RDB-AOF 方式下,持久化策略首先將緩存中數據以 RDB 方式全量寫入文件,再將寫入後新增的數據以 AOF 的方式追加在 RDB 數據的後面,在下一次做 RDB 持久化的時候將 AOF 的數據重新以 RDB 的形式寫入文件。這種方式既可以提高讀寫和恢復效率,也可以減少文件大小,同時可以保證數據的完整性。

RDB 和 AOF 文件共存情況下的恢復流程如下圖:

重新認識Redis

Redis的同步機制

1、主從同步模式(全同步和增量同步):Redis 一般是使用一個 Master 節點來進行寫操作,而若干個 Slave 節點進行讀操作,另外定期的數據備份操作也是單獨選擇一個Slave去完成,這樣可以最大程度發揮Redis的性能,為的是保證數據的弱一致性和最終一致性,但是也存在一定的弊端,當Master宕機後,Redis集群將不能對外提供寫入操作。

2、Redis Sentinel(哨兵):解決主從同步 Master 宕機後的主從切換問題,但沒有解決Master寫的壓力,哨兵主要有以下特點:

1)監控:檢查主從服務器是否運行正常。

2)提醒:通過 API 向管理員或者其它應用程序發送故障通知。

3)自動故障遷移:主從切換(在 Master 宕機後,將其中一個 Slave 轉為 Master,其他的 Slave 從該節點同步數據)。

Redis 集群方案

1、twemproxy,大概概念是,它類似於一個代理方式, 使用時在本需要連接 redis 的地方改為連接 twemproxy, 它會以一個代理的身份接收請求並使用一致性 hash 算法,將請求轉接到具體 redis,將結果再返回 twemproxy。

缺點: twemproxy 自身單端口實例的壓力,使用一致性 hash 後,對 redis 節點數量改變時候的計算值的改變,數據無法自動移動到新的節點。

2、codis, 目前用的最多的集群方案, 基本和 twemproxy 一致的效果, 但它支持在節點數量改變情況下,舊節點數據可恢復到新 hash 節點。

3、Redis cluster3.0 自帶的集群, 特點在於他的分佈式算法不是一致性 hash, 而是 hash槽的概念, 以及自身支持節點設置從節點,具體看官方文檔介紹。

Redis的過期策略以及內存淘汰機制

Redis採用的是定期刪除+惰性刪除策略。

定期刪除,redis默認每個100ms檢查,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每個100ms將所有的key檢查一次,而是隨機抽取進行檢查(如果每隔100ms,全部key進行檢查,redis豈不是卡死)。因此,如果只採用定期刪除策略,會導致很多key到時間沒有刪除。於是惰性刪除派上用場,也就是說在你獲取某個key的時候,redis會檢查一下,這個key如果設置了過期時間那麼是否過期了,如果過期了此時就會刪除。

採用定期刪除+惰性刪除就沒其他問題了麼?

不是的,如果定期刪除沒刪除key。然後你也沒即時去請求key,也就是說惰性刪除也沒生效。這樣,redis的內存會越來越高,那麼就應該採用內存淘汰機制

在redis.conf中有一行配置

# maxmemory-policy volatile-lru

該配置就是配內存淘汰策略的,例舉幾種。

1)noeviction:當內存不足以容納新寫入數據時,新寫入操作會報錯。

2)allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key。(推薦使用,目前項目在用這種)

3)volatile-lru:嘗試回收最少使用的鍵, 但僅限於在過期集合的鍵,使得新添加的數據有空間存放。

4)allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個key。

5)volatile-random:回收隨機的鍵使得新添加的數據有空間存放,但僅限於在過期集合的鍵。

6)volatile-ttl:回收在過期集合的鍵,並且優先回收存活時間(TTL) 較短的鍵,使得新添加的數據有空間存放。

Redis和數據庫雙寫一致性問題

一致性問題是分佈式常見問題,還可以再分為最終一致性和強一致性。如果對數據有強一致性要求,不能放緩存。另外採取的措施是,首先,採取正確更新策略,先更新數據庫,再刪緩存。其次,因為可能存在刪除緩存失敗的問題,提供一個補償措施即可,例如利用消息隊列。

如何應對緩存穿透、緩存擊穿和緩存雪崩問題

緩存穿透:即故意去查詢緩存不存在的key,導致所有的請求都懟到數據庫上,從而導致數據庫壓力倍增。

解決方案:對查詢結果為空的情況也進行緩存(再次訪問直接返回空);提供一個能迅速判斷請求是否有效的攔截機制(如:布隆過濾器)

緩存擊穿:即故意同一時間去查詢緩存中不存在的同一個key,導致所有請求都懟到數據庫上,從而導致數據庫連接異常。

解決方案:設置或自動檢測熱點Key(加大key的過期時間或設置為永不過期);加互斥鎖(只允許有鎖的操作去訪問數據庫,然後更新緩存,其它操作可從緩存取值)。

緩存雪崩:即緩存同一時間大面積的key失效,導致所有請求都懟到數據庫上,從而導致數據庫連接異常。

解決方案:讓Key的失效時間分散開,可以在統一的失效時間上再加一個隨機值,或者使用更高級的算法分散失效時間;構建多個redis實例,個別節點掛了還有別的可以用;多級緩存(比如增加本地緩存,減小redis壓力);對存儲層增加限流措施,當請求超出限制,提供降級服務(一般就是返回錯誤即可)。

如何解決Redis的併發競爭key問題

如果對這個key操作,不要求順序,則準備一個分佈式鎖,大家去搶鎖,搶到鎖就做set操作即可;如果對這個key操作,要求順序,這種時候我們在數據寫入數據庫的時候,需要保存一個時間戳,時間早的就不做set操作了。

Redis為什麼採用跳錶而不是紅黑樹?

1、在做範圍查找的時候,平衡樹比skiplist操作要複雜。在平衡樹上,我們找到指定範圍的小值之後,還需要以中序遍歷的順序繼續尋找其它不超過大值的節點。而在skiplist上進行範圍查找就非常簡單,只需要在找到小值之後,對第1層鏈表進行若干步的遍歷就可以實現。

2、平衡樹的插入和刪除操作可能引發子樹的調整,邏輯複雜,而skiplist的插入和刪除只需要修改相鄰節點的指針,操作簡單又快速。

3、從內存佔用上來說,skiplist比平衡樹更靈活一些。一般來說,平衡樹每個節點包含2個指針(分別指向左右子樹),而skiplist每個節點包含的指針數目平均為1/(1-p),具體取決於參數p的大小。如果像Redis裡的實現一樣,取p=1/4,那麼平均每個節點包含1.33個指針,比平衡樹更有優勢。

4、查找單個key,skiplist和平衡樹的時間複雜度都為O(log n),大體相當;而哈希表在保持較低的哈希值衝突概率的前提下,查找時間複雜度接近O(1),性能更高一些。所以我們平常使用的各種Map或dictionary結構,大都是基於哈希表實現的。

5、從算法實現難度上來比較,skiplist比平衡樹要簡單得多。

什麼是HyperLogLog?

HyperLogLog 是一種概率數據結構,用來估算數據的基數。基數就是指一個集合中不同值的數目,比如 a, b, c, d 的基數就是 4,a, b, c, d, a 的基數還是 4。雖然 a 出現兩次,只會被計算一次。

Redis 的 HyperLogLog 通過犧牲準確率來減少內存空間的消耗,只需要12K內存,在標準誤差0.81%的前提下,能夠統計2^64個數據。所以 HyperLogLog 是否適合在比如統計日活月活此類的對精度要求不高的場景。

以上只是對Redis的一些基本概念和知識點做一下說明,最好是能針對自己所從事的語言去動手操作一番,實踐出真知,紙上談兵往往還是心裡沒底。

參考鏈接:

https://www.toutiao.com/i6747926805744730627/

https://www.toutiao.com/i6749708288830472716/


分享到:


相關文章: