【Redis】Redis 入門歷險

目錄

(1)  爲什麼要用 redis 而不用 map/guava 做緩存

(2) redis 的線程模型

(3) redis 和memcached 的區別

(4) redis 常見數據結構以及使用場景分析

(5) redis 設置過期時間

(6) redis 的內存淘汰機制

(7) redis 持久化機制

(8) 緩存雪崩和緩存穿透問題解決方案

(9)  如何解決 Redis 的併發競爭 Key 問題

(10) 如何保證緩存與數據庫雙寫時的數據一致性?


學習Redis

(1)  爲什麼要用 redis 而不用 map/guava 做緩存

  •  緩存分爲本地緩存分佈式緩存。以 Java 爲例,使用自帶的 map 或者 guava 實現的是本地緩存,最主要的特點是輕量以及快速,生命週期隨着 jvm 的銷燬而結束,並且在多實例的情況下,每個實例都需要各自保存一份緩存,緩存不具有一致性。
  • 使用 redis 或 memcached 之類的稱爲分佈式緩存,在多實例的情況下,各實例共用一份緩存數據,緩存具有一致性。缺點是需要保持 redis 或 memcached服務的高可用,整個程序架構上較爲複雜。

(2) redis 的線程模型

redis 內部使用文件事件處理器 file event handler,這個文件事件處理器是單線程的,所以 redis 才叫做單線程的模型。它採用 IO 多路複用機制同時監聽多個 socket,根據 socket 上的事件來選擇對應的事件處理器進行處理。

文件事件處理器的結構包含 4 個部分:

  • 多個 socket
  • IO 多路複用程序
  • 文件事件分派器
  • 事件處理器(連接應答處理器、命令請求處理器、命令回覆處理器)

多個 socket 可能會併發產生不同的操作,每個操作對應不同的文件事件,但是 IO 多路複用程序會監聽多個 socket,會將 socket 產生的事件放入隊列中排隊,事件分派器每次從隊列中取出一個事件,把該事件交給對應的事件處理器進行處理。

(3) redis 和memcached 的區別

對於 redis 和 memcached 我總結了下面四點。現在公司一般都是用 redis 來實現緩存,而且 redis 自身也越來越強大了!

  1. redis支持更豐富的數據類型(支持更復雜的應用場景):Redis不僅僅支持簡單的k/v類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。memcache支持簡單的數據類型,String。
  2. Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啓的時候可以再次加載進行使用,而Memecache把數據全部存在內存之中。
  3. 集羣模式:memcached沒有原生的集羣模式,需要依靠客戶端來實現往集羣中分片寫入數據;但是 redis 目前是原生支持 cluster 模式的.
  4. Memcached是多線程,非阻塞IO複用的網絡模型;Redis使用單線程的多路 IO 複用模型。

來自網絡上的一張圖,這裏分享給大家!

             

(4) redis 常見數據結構以及使用場景分析

  • String數據結構是簡單的key-value類型,value其實不僅可以是String,也可以是數字。 常規key-value緩存應用; 常規計數:微博數,粉絲數等。
  • hash 是一個 string 類型的 field 和 value 的映射表,hash 特別適合用於存儲對象,後續操作的時候,你可以直接僅僅修改這個對象中的某個字段的值。 比如我們可以 hash 數據結構來存儲用戶信息,商品信息等等。比如下面我就用 hash 類型存放了我本人的一些信息:
key=JavaUser293847
value={
  “id”: 1,
  “name”: “SnailClimb”,
  “age”: 22,
  “location”: “Wuhan, Hubei”
}
  • list 就是鏈表,Redis list 的應用場景非常多,也是Redis最重要的數據結構之一,比如微博的關注列表,粉絲列表,消息列表等功能都可以用Redis的 list 結構來實現。

    Redis list 的實現爲一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內存開銷。

    另外可以通過 lrange 命令,就是從某個元素開始讀取多少個元素,可以基於 list 實現分頁查詢,這個很棒的一個功能,基於 redis 實現簡單的高性能分頁,可以做類似微博那種下拉不斷分頁的東西(一頁一頁的往下走),性能高。

  • set 對外提供的功能與list類似是一個列表的功能,特殊之處在於 set 是可以自動排重的。

    當你需要存儲一個列表數據,又不希望出現重複數據時,set是一個很好的選擇,並且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。可以基於 set 輕易實現交集、並集、差集的操作。

    比如:在微博應用中,可以將一個用戶所有的關注人存在一個集合中,將其所有粉絲存在一個集合。Redis可以非常方便的實現如共同關注、共同粉絲、共同喜好等功能。這個過程也就是求交集的過程,具體命令如下:

  • zset :sorted set增加了一個權重參數score,使得集合中的元素能夠按score進行有序排列。

舉例: 在直播系統中,實時排行信息包含直播間在線用戶列表,各種禮物排行榜,彈幕消息(可以理解爲按消息維度的消息排行榜)等信息,適合使用 Redis 中的 Sorted Set 結構進行存儲。

(5) redis 設置過期時間

Redis中有個設置時間過期的功能,即對存儲在 redis 數據庫中的值可以設置一個過期時間。作爲一個緩存數據庫,這是非常實用的。如我們一般項目中的 token 或者一些登錄信息,尤其是短信驗證碼都是有時間限制的,按照傳統的數據庫處理方式,一般都是自己判斷過期,這樣無疑會嚴重影響項目性能。

我們 set key 的時候,都可以給一個 expire time,就是過期時間,通過過期時間我們可以指定這個 key 可以存活的時間。

如果假設你設置了一批 key 只能存活1個小時,那麼接下來1小時後,redis是怎麼對這批key進行刪除的?

定期刪除+惰性刪除。

通過名字大概就能猜出這兩個刪除方式的意思了。

  • 定期刪除:redis默認是每隔 100ms 就隨機抽取一些設置了過期時間的key,檢查其是否過期,如果過期就刪除。注意這裏是隨機抽取的。爲什麼要隨機呢?你想一想假如 redis 存了幾十萬個 key ,每隔100ms就遍歷所有的設置過期時間的 key 的話,就會給 CPU 帶來很大的負載!
  • 惰性刪除 :定期刪除可能會導致很多過期 key 到了時間並沒有被刪除掉。所以就有了惰性刪除。假如你的過期 key,靠定期刪除沒有被刪除掉,還停留在內存裏,除非你的系統去查一下那個 key,纔會被redis給刪除掉。這就是所謂的惰性刪除,也是夠懶的哈!

但是僅僅通過設置過期時間還是有問題的。我們想一下:如果定期刪除漏掉了很多過期 key,然後你也沒及時去查,也就沒走惰性刪除,此時會怎麼樣?如果大量過期key堆積在內存裏,導致redis內存塊耗盡了。怎麼解決這個問題呢? redis 內存淘汰機制。

(6) redis 的內存淘汰機制

redis 內存淘汰機制(MySQL裏有2000w數據,Redis中只存20w的數據,如何保證Redis中的數據都是熱點數據?)

redis 配置文件 redis.conf 中有相關注釋: http://download.redis.io/redis-stable/redis.conf

redis 提供 6種數據淘汰策略:

  1. volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
  2. volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
  3. volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
  4. allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key(這個是最常用的)
  5. allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
  6. no-eviction:禁止驅逐數據,也就是說當內存不足以容納新寫入數據時,新寫入操作會報錯。這個應該沒人使用吧!

4.0版本後增加以下兩種:

  1. volatile-lfu:從已設置過期時間的數據集(server.db[i].expires)中挑選最不經常使用的數據淘汰
  2. allkeys-lfu:當內存不足以容納新寫入數據時,在鍵空間中,移除最不經常使用的key

(7) redis 持久化機制

redis 持久化機制(怎麼保證 redis 掛掉之後再重啓數據可以進行恢復)

很多時候我們需要持久化數據也就是將內存中的數據寫入到硬盤裏面,大部分原因是爲了之後重用數據(比如重啓機器、機器故障之後恢復數據),或者是爲了防止系統故障而將數據備份到一個遠程位置。

Redis不同於Memcached的很重一點就是,Redis支持持久化,而且支持兩種不同的持久化操作。Redis的一種持久化方式叫快照(snapshotting,RDB),另一種方式是隻追加文件(append-only file,AOF)。這兩種方法各有千秋,下面我會詳細這兩種持久化方法是什麼,怎麼用,如何選擇適合自己的持久化方法。

快照(snapshotting)持久化(RDB)

Redis可以通過創建快照來獲得存儲在內存裏面的數據在某個時間點上的副本。Redis創建快照之後,可以對快照進行備份,可以將快照複製到其他服務器從而創建具有相同數據的服務器副本(Redis主從結構,主要用來提高Redis性能),還可以將快照留在原地以便重啓服務器的時候使用。

快照持久化是Redis默認採用的持久化方式,在redis.conf配置文件中默認有此下配置:

save 900 1           #在900秒(15分鐘)之後,如果至少有1個key發生變化,Redis就會自動觸發BGSAVE命令創建快照。
save 300 10          #在300秒(5分鐘)之後,如果至少有10個key發生變化,Redis就會自動觸發BGSAVE命令創建快照。
save 60 10000        #在60秒(1分鐘)之後,如果至少有10000個key發生變化,Redis就會自動觸發BGSAVE命令創建快照。

AOF(append-only file)持久化

與快照持久化相比,AOF持久化 的實時性更好,因此已成爲主流的持久化方案。默認情況下Redis沒有開啓AOF(append only file)方式的持久化,可以通過appendonly參數開啓:

appendonly yes

開啓AOF持久化後每執行一條會更改Redis中的數據的命令,Redis就會將該命令寫入硬盤中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數設置的,默認的文件名是appendonly.aof。

在Redis的配置文件中存在三種不同的 AOF 持久化方式,它們分別是:

appendfsync always    #每次有數據修改發生時都會寫入AOF文件,這樣會嚴重降低Redis的速度
appendfsync everysec  #每秒鐘同步一次,顯示地將多個寫命令同步到硬盤
appendfsync no        #讓操作系統決定何時進行同步

爲了兼顧數據和寫入性能,用戶可以考慮 appendfsync everysec選項 ,讓Redis每秒同步一次AOF文件,Redis性能幾乎沒受到任何影響。而且這樣即使出現系統崩潰,用戶最多隻會丟失一秒之內產生的數據。當硬盤忙於執行寫入操作的時候,Redis還會優雅的放慢自己的速度以便適應硬盤的最大寫入速度。

(8) 緩存雪崩和緩存穿透問題解決方案

擊穿比較形象,擊打後穿透,是有一個過程,說明是有緩存的。 穿透就比較直接,相當於直接就過了,緩存沒生效。

緩存雪崩

簡介:緩存同一時間大面積的失效,所以,後面的請求都會落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。

解決辦法

  • 事前:儘量保證整個 redis 集羣的高可用性,發現機器宕機儘快補上。選擇合適的內存淘汰策略。
  • 事中:本地ehcache緩存 + hystrix限流&降級,避免MySQL崩掉
  • 事後:利用 redis 持久化機制保存的數據儘快恢復緩存

      

 

緩存穿透

簡介:一般是黑客故意去請求緩存中不存在的數據,導致所有的請求都落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。

解決辦法: 有很多種方法可以有效地解決緩存穿透問題,最常見的則是採用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被 這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。另外也有一個更爲簡單粗暴的方法(我們採用的就是這種),如果一個查詢返回的數據爲空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘

參考:

(9)  如何解決 Redis 的併發競爭 Key 問題

所謂 Redis 的併發競爭 Key 的問題也就是多個系統同時對一個 key 進行操作,但是最後執行的順序和我們期望的順序不同,這樣也就導致了結果的不同!

  • 分佈式鎖(zookeeper 和 redis 都可以實現分佈式鎖)。(如果不存在 Redis 的併發競爭 Key 問題,不要使用分佈式鎖,這樣會影響性能)

  • 基於zookeeper臨時有序節點可以實現的分佈式鎖。大致思想爲:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。 判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。 當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。完成業務流程後,刪除對應的子節點釋放鎖。

  • 在實踐中,當然是從以可靠性爲主。所以首推Zookeeper。

參考:

(10) 如何保證緩存與數據庫雙寫時的數據一致性?

你只要用緩存,就可能會涉及到緩存與數據庫雙存儲雙寫,你只要是雙寫,就一定會有數據一致性的問題,那麼你如何解決一致性問題?

一般來說,就是如果你的系統不是嚴格要求緩存+數據庫必須一致性的話,緩存可以稍微的跟數據庫偶爾有不一致的情況,最好不要做這個方案,讀請求和寫請求串行化,串到一個內存隊列裏去,這樣就可以保證一定不會出現不一致的情況

串行化之後,就會導致系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。

   

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章