熱衷學習,熱衷生活!?
沉澱、分享、成長,讓自己和他人都能有所收穫!?
一、簡單的介紹一下Redis
簡單的說Redis
就是一個使用C語言
開發的一個資料庫,不過與傳統資料庫不同的是Redis
的資料是存在記憶體中的,它是記憶體資料庫,所以讀寫速度非常快,所以Redis
被廣泛應用於快取方向。
另外,Redis
除了做快取之外,也還經常用於做分散式鎖,甚至是訊息佇列。
Redis
提供了多種資料型別來支援不同的業務場景。Redis
還支援事務、持久化、Lua指令碼、多種叢集方案。
二、Redis持久化機制
Redis
是一個支援持久化的記憶體資料庫,透過持久化機制把記憶體中的資料同步到硬碟檔案來保證資料持久化。當Redis
重啟後透過把硬碟檔案重新載入到記憶體,就能達到恢復資料的目的。
實現:單獨建立一個fork()
子程式,將當前父執行緒的資料庫檔案複製到子程式的記憶體中,然後由子程式寫入到臨時檔案中,持久化的過程就結束了,再用這個臨時檔案替換上次的快照檔案,然後子程式退出,記憶體釋放。
RDB(Redis DataBase)
是Redis
預設的持久化方式。按照一定的時間週期策略把記憶體的資料以快照的形式儲存到硬碟的二進位制檔案。即Snapshot
快照儲存,對應產生的資料檔案為dump.rdb
,透過配置檔案中的save
引數來定義快照的週期。
AOF(Append Only Field)
方式:Redis
會將每一個寫命令都透過write
函式追加到檔案最後,類似MySQL的binlog
,當重啟Redis
會載入appendonly.aof
檔案恢復資料。
三、快取雪崩、快取穿透、快取預熱、快取更新、快取降級問題
快取雪崩
快取雪崩可以理解為:由於原有快取失效,新快取未到期間,而形成一些列連鎖反應,而造成整個系統崩潰。
舉個例子:
我們設定快取採用了相同的過期時間,在同一時刻出現大面積的快取過期,然後所有的請求都去訪問資料庫了,對資料庫CPU
和存在造成了巨大壓力,嚴重的時候造成資料庫當機。
解決方法:
大多數系統設計者考慮用加鎖或者佇列的方式保證不會有大量的執行緒對資料庫一次性進行讀寫,從而避免失效時大量的併發請求落到底層儲存系統上。還有一個簡單方案就時講快取失效時間分散開。
快取穿透
快取穿透是指使用者查詢資料庫,在資料庫沒有,自然在快取中也不會有,這樣就導致使用者在查詢的時候,在快取中找不到,每次都要去資料庫再查詢一遍,然後返回空,相當於兩次無用的查詢,這樣請求就直接繞過快取直接查詢資料庫,這也是經常說的快取命中問題。
解決方法:
最常用的就是採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的bitmap
中,不存的資料會被這個bitmap
攔截掉,從而避免了對底層儲存系統的查詢壓力。
另外一個更為簡單粗暴的方法,如果一個查詢返回的資料為空(不管是資料不存在,還是系統故障),我們仍然把這個空結果進行快取,但它的過期時間會很短,不會超過五分鐘,透過這個直接設定的預設值存放到快取,這樣子二次到快取中獲取就有值了。
快取預熱
快取預熱就是系統上線後,將相關的快取資料直接載入到快取系統。這樣子就可以避免在使用者請求的時候,先查詢資料庫,然後再將資料快取的問題!使用者直接查詢事先被預熱的快取資料!
解決思路:
- 直接寫個快取頁面,上線時手工操作下。
- 資料量不大,可以在專案啟動時自動進行載入。
- 定時重新整理快取。
快取更新
除了快取伺服器自帶的快取失效策略之外(Redis預設的有6中策略可供選擇),我們還可以根據具體的
業務需求進行自定義的快取淘汰,常見的策略有兩種:
(1)定時去清理過期的快取;
(2)當有使用者請求過來時,再判斷這個請求所用到的快取是否過期,過期的話就去底層系統得到新數
據並更新快取。
兩者各有優劣,第一種的缺點是維護大量快取的key是比較麻煩的,第二種的缺點就是每次使用者請求過
來都要判斷快取失效,邏輯相對比較複雜!具體用哪種方案,可以根據自己的應用場景來權衡。
快取降級
當訪問量劇增、服務出現問題或非核心服務影響到核心流程的效能時仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵資料進行自動降級,也可以配置開關實現人工降級。
降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結
算)。
以參考日誌級別設定預案:
(1)一般:比如有些服務偶爾因為網路抖動或者服務正在上線而超時,可以自動降級;
(2)警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,
併傳送告警;
(3)錯誤:比如可用率低於90%,或者資料庫連線池被打爆了,或者訪問量突然猛增到系統能承受的
最大閥值,此時可以根據情況自動降級或者人工降級;
(4)嚴重錯誤:比如因為特殊原因資料錯誤了,此時需要緊急人工降級。
服務降級的目的是為了防止Redis
服務故障,導致資料庫跟著一起發生雪崩問題。因此,對於不重要的快取資料,可以採取服務降級策略,例如一個比較常見的做法就是,Redis出現問題,不去資料庫查
詢,而是直接返回預設值給使用者。
四、單執行緒的Redis為什麼這麼快
- 純記憶體操作。
- 單執行緒操作,避免了頻繁的上下文切換。
- 採用非阻塞I/O多路複用機制。
五、Redis的資料型別,以及每種資料型別的使用場景
string
string
資料結構是簡單的key-value
型別,value
可以是字串也可以是數字。
一般用於做一下複雜的計數功能,比如使用者的訪問次數、點贊功能等等。
list
list
即是連結串列,實現了一個雙向連結串列,可以支援反向查詢、遍歷,更方便操作,但是帶來了額外的記憶體開銷。
常用於釋出和訂閱或者訊息佇列、慢查詢。
hash
hash
類似JDK1.8
前的HashMap
,記憶體實現也差不過是陣列+連結串列。特別適合用於儲存物件,後續操作的時候,你可以直接僅僅修改這個物件中的某個欄位的值。
常用於系統中物件資料的儲存。
set
set
類似於Java
中的HashSet
,是一個無序且不重複的集合。可以基於 set 輕易實現交集、並集、差集的操作。
常用於需要存放的資料不能重複以及需要獲取多個資料來源交集和並集等場景。
sorted set
和set
相比,增加了一個權重引數score
,是的集合中的元素能夠按照score
進行有序排列,還可以透過 score 的範圍來獲取元素的列表。
常用於需要對資料根據某個權重進行排序的場景。比如:各種排行榜、TOP N
。
bitmap
bitmap
儲存的是連續的二進位制數字(0和1),透過bitmap
,只需一個bit
位來表示某個元素對應的值或者狀態,key
就是對應元素本身。
常用於需要儲存狀態資訊(比如是否簽到、是否登陸)並需要進一步對這些資訊進行分析的場景。比如使用者簽到情況、活躍使用者情況、使用者行為統計。
六、Redis的過期策略以及記憶體淘汰機制
redis
採用的是定期刪除+惰性刪除策略。
為什麼不用定時刪除策略?
定時刪除用一個定時器來負責監視key
,過期則自動刪除。雖然記憶體及時釋放,但是十分消耗CPU
資源。
在大量併發請求下,CPU
要將時間用在處理請求,而不是用來刪除key
。
定期刪除+惰性刪除是怎麼工作的呢?
定期刪除,redis
預設每隔100ms
檢查是否有過期的key
,有的話就刪除。需要說明是redis
不是每隔100ms
將所有的key
檢查一次,而是隨機抽取進行檢查,因此如果只採用定期刪除策略會導致很多過期的key
沒有刪除,這個時候惰性刪除就派上用場了,獲取某個key
的時候,redis
會檢查一下這個key
是否過期了,如果過期了就刪除。
採用定期刪除+惰性刪除就沒其他問題了嗎?
不是的,如果定期刪除沒刪除的key
,也沒有請求去獲取,這個時候惰性刪除就不會生效,redis的記憶體會越來越高,這個時候就要用到記憶體淘汰機制了。
在redis.conf
中有一行配置:
maxmemory-policy volatile-lru
該配置就是配置記憶體淘汰策略,主要有以下策略:
volatile-re
:從已設定過期的資料集中挑選最近最少使用的資料淘汰。volatile-ttl
:從已設定過期的資料集中挑選將要過期的資料淘汰。volatile-random
:從已設定過期的資料集中隨機選擇資料淘汰。allkeys-lru
:從資料集中挑選最近最少使用的資料淘汰。allkeys-random
:從資料集中隨機挑選資料淘汰。no-enviction
:禁止淘汰資料,新寫入操作會報錯。
ps:如果沒有設定expire
的key
, 不滿足先決條件(prerequisites
); 那麼 volatile-lru
, volatile-random
和volatile-ttl
策略的行為, 和 noeviction(不刪除)
基本上一致。
七、Redis為什麼是單執行緒的
官方FAQ
表示,因為redis
是基於記憶體操作,CPU
不是redis
的瓶頸,Redis
的瓶頸最有可能是機器記憶體的大小或者網路寬頻。既然單執行緒容易實現,而且CPU
不會成為瓶頸。那就順理成章地採用單執行緒的方案了(畢竟採用多執行緒會有很多麻煩!)
Redis
利用佇列技術將併發訪問變為序列訪問。
- 絕大部分請求是純粹的記憶體操作。
- 採用單執行緒可以避免不必要的上下文切換和競爭條件。
Redis
採用了非阻塞I/O技術。
八、為什麼Redis的操作是原子性的,怎麼保證原子性的?
對Redis
而言,命令的原子性指的是:一個操作的不可以再分,操作要麼執行,要麼不執行。
Redis
的操作之所以是原子性的,是因為Redis
是單執行緒的。
Redis
本身提供的所有API
都是原子操作,Redis
中的事務其實是要保證批次操作的原子性。
多個命令在併發中也是原子性嗎?
這個不一定,將get
和set
改成單命令操作可能其中一個命令成功,一個失敗。可以使用Redis
事務或者使用Redis + Lua指令碼
的方式實現。
九、Redis事務
Redis
事務功能是透過MULTI、EXEC、DISCARD、WATCH
四個原語實現的。
Redis
會將一個事務中的所有命令序列化,然後按順序執行,並且不會被中途打斷。
Redis
是不支援 roll back
的,因而不滿足原子性的(而且不滿足永續性)。
Redis 官網也解釋了自己為啥不支援回滾。簡單來說就是 Redis 開發者們覺得沒必要支援回滾,這樣更簡單便捷並且效能更好。Redis 開發者覺得即使命令執行錯誤也應該在開發過程中就被發現而不是生產過程中。
四個原語功能如下:
MULTI
:用於開始一個事務,它總是返回OK
。MULTI
命令執行完畢之後,客戶端可以繼續向伺服器傳送任意多條命令,這些命令不會被立即執行,而是被放到一個佇列中,當EXEC
命令被執行時,所有佇列中的命令才會被執行。EXEC
:執行所有事務塊內的命令。返回事務塊內所有命令的返回值,按命令執行的先後順序排列,當操作被打斷時返回空值。- 透過呼叫
DISCARD
命令,客戶端可以清空事務佇列,並放棄執行事務,並且客戶端會從事務狀態中退出。 WATCH
: 命令用於監聽指定的鍵,當呼叫EXEC
命令執行事務時,如果一個被WATCH
命令監視的鍵被修改的話,整個事務都不會執行,直接返回失敗。