Redis是什么
redis是一個基于記憶體的高性能的 key-value 資料庫,
Redis與Memcached的區別
- memcached所有的值都是簡單的字串,redis支持更多的資料結構(string,list,hash,sorted set,set)
- redis的速度比memcached快
- redis可以持久化其資料(AOF,RDB)
為什么要用Redis
高性能
操作快取就是直接操作記憶體,所以速度相當快,

高并發
直接操作快取能夠承受的請求是遠遠大于直接訪問資料庫的,所以我們可以考慮把資料庫中的部分資料轉移到快取中去,這樣用戶的一部分請求會直接到快取這里而不用經過資料庫,

為什么Redis這么快
- Redis是純記憶體資料庫,
- Redis是單執行緒資料庫,利用佇列技術將并發訪問變為串行訪問,
- Redis采用了多路復用IO技術:“多路”指多個網路連接;"復用"指復用一個執行緒;多路復用IO技術可以讓單執行緒高效的處理多個連接請求,
Redis常用的資料結構
Redis底層的資料結構包括:簡單動態陣列SDS、鏈表、字典、跳表、整數集合、壓縮串列、物件,壓縮串列是一種為了節約記憶體而開發的且經過特殊編碼之后的連續記憶體塊順序型資料結構,
redisObject
redisObject是Redis型別系統的核心,資料庫中的每個鍵、值,以及Redis本身處理的引數,都表示為這種資料型別,
//Redis 物件
typedef struct redisObject {
// 型別,記錄了物件所保存的值的型別
unsigned type:4
// 對齊位
unsigned notused:2;
// 編碼方式,記錄了物件所保存的值的編碼
unsigned encoding:4;
// LRU 時間(相對于 server.lruclock)
unsigned lru:22;
// 參考計數
int refcount;
// 指向物件的值,指向實際保存值的資料結構, 這個資料結構由type屬性和encoding屬性決定
void *ptr;
} robj;
下圖展示了 redisObject 、Redis 所有資料型別、以及 Redis 所有編碼方式(底層實作)三者之間的關系:

Redis的字串(SDS)和C語言的字串區別
struct sdshdr {
int len; // 記錄buf陣列中已使用位元組的數量,等于SDS所保存字串的長度
int free; // 記錄buf陣列中未使用位元組的數量
char buf[];// 位元組陣列,用于保存字串
};
| C字串 | SDS |
|---|---|
| 獲取字串長度的復雜度為O(N) | 獲取字串長度的復雜度為O(1) |
| API是不安全的,可能會造成緩沖區溢位 | API是安全的,不會造成緩沖區溢位 |
| 修改字串長度N次必然需要執行N次記憶體重分配 | 修改字串長度N次最多需要執行N次記憶體重分配 |
| 只能保存文本資料 | 可以保存文本資料或者二進制資料 |
| 可以使用所有的<string.h>庫中的函式 | 可以使用一部分<string.h>庫中的函式 |
skiplist跳表原理
- https://cloud.tencent.com/developer/article/1539038
- https://blog.csdn.net/pcwl1206/article/details/83512600
- https://blog.csdn.net/gloomysnow/article/details/51510203
String:key -value快取應用,最常規的set/get操作,value可以是String也可以是數字,一般做一些復雜的計數功能的快取,短信驗證碼
Hash:field-value映射表,存盤用戶資訊和商品資訊,單點登錄;
List:list分頁查詢,訊息佇列,粉絲串列、文章的評論串列功能;
Set:實作差,并,交集操作,全域去重的功能;
Sorted set:用戶串列,禮物排行榜,彈幕訊息的功能;
Redis高級資料結構
HyperLogLog:通常用于基數統計,使用少量固定大小的記憶體,來統計集合中唯一元素的資料,統計結果不是精確值,而是一個帶有0.81%標準差(standard error)的近似值,所以,HyperLogLog適用于一些對于統計結果精確度要求不是特別高的場景,例如網站的UV統計,
Geo:Redis 3.2版本的新特性,可以將用戶給定的地理位置資訊儲存起來,并對這些資訊進行操作,
獲取2個位置的距離,根據給定地地理位置坐標獲取指定范圍內的地址位置集合,
BitMap:位圖,
Stream:主要用于訊息佇列,類似于 Kafka,可以認為是 pub/sub 的進階版,提供了訊息的持久化和主從復制功能,可以讓任何客戶端訪問任何時刻的資料,并且能記住每一個客戶端的訪問位置,還能保證訊息不丟失,
Redis應用場景
快取、分布式鎖、排行榜(zset)、計數(incrby)、訊息佇列(stream)、地理位置(geo)、訪客統計(hyperloglog)等,
Redis做異步佇列
一般使用list結構作為佇列,rpush生產訊息,lpop消費訊息,
缺點:在消費者下線的情況下,生產的訊息會丟失,得使用專業的訊息佇列如rabbitmq等
問題:能不能生產一次消費多次呢?
使用pub/sub主題訂閱者模式,可以實作1:N的訊息佇列,
Redis和資料庫雙寫一致性問題
更新策略:采用正確更新策略,先更新資料庫,再洗掉快取,其次,因為可能存在洗掉快取失敗的問題,提供一個補償措施即可,例如利用訊息佇列,
采用延時雙刪策略:
- 先淘汰快取
- 再寫資料庫
- 休眠一會,再次淘汰快取
注:如果對資料有強一致性要求,不能放快取,
Redis持久化
為什么Redis需要持久化?
由于Redis是一種記憶體型資料庫,即服務器在運行時,系統為其分配了一部分記憶體存盤資料,一旦服務器掛了或宕機了,那么資料庫里面的資料將會丟失,為了使服務器即使突然關機也能保存資料,必須通過持久化的方式將資料從記憶體保存到磁盤中,
持久化就是把記憶體的資料寫到磁盤中,防止服務器宕機了,導致記憶體資料待久,
Redis的持久化機制
Redis提供兩種持久化機制,分別是RDB和AOF,Redis服務器默認開啟RDB,關閉AOF;
RDB持久化
RDB(Redis DataBase):快照,按照一定的時間將記憶體的資料以快照的形式保存到硬碟中,對應產生的資料檔案為dump.rdb,通過組態檔中的save引數來定義快照的周期,
優點:RDB檔案緊湊,體積小,網路傳輸快,適合全量復制;恢復速度比AOF快,與AOF相比,RDB最重要的優點之一是對性能的影響相對較小,
缺點:RDB檔案的致命缺點在于其資料快照的持久化方式決定了必然做不到實時持久化,而在資料越來越重要的今天,資料的大量丟失很多時候是無法接受的,因此AOF持久化成為主流,此外,RDB檔案需要滿足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB檔案),
AOF持久化
AOF(Append Only File):將Redis執行的每次寫命令記錄到單獨的日志檔案中,當重啟Redis會重新將持久化的日志志中檔案恢復資料,
與RDB持久化相對應,AOF的優點在于支持秒級持久化、兼容性好,缺點是檔案大、恢復速度慢、對性能影響大,
持久化策略選擇
- 如果Redis中的資料完全丟棄也沒有關系,那么無論是單機,還是主從架構,都可以不進行任何持久化,
- 在單機環境下,如果可以接受十幾分鐘或更多的資料丟失,選擇RDB對Redis的性能更加有利;如果只能接受秒級別的資料丟失,應該選擇AOF,
- 但在多數情況下,我們都會配置主從環境,slave的存在既可以實作資料的熱備,也可以進行讀寫分離分擔Redis讀請求,以及在master宕掉后繼續提供服務,
Redis的淘汰策略
當Redis的記憶體(maxmemory引數配置)已滿時,它會根據淘汰策略(maxmemory-policy引數配置)進行相應的操作,
不洗掉策略(no-eviction)
no-eviction:不洗掉策略,Redis默認策略,達到最大記憶體限制時,若需要更多記憶體,直接回傳錯誤資訊,
最近最少使用策略(lru)
allkeys-lru:所有key通用;優先洗掉最近最少使用的key
volatile-lru:只限于設定了 expire 過期時間的部分;優先洗掉最近最少使用的key
隨機策略(random):
allkeys-random:所有key通用;隨機洗掉一部分key,
volatile-random:只限于設定 expire 的部分;隨機洗掉一部分key,
剩余時間短策略(ttl):
volatile-ttl:只限于設定 expire 的部分;優先洗掉剩余時間短的key,
最不經常使用策略(lfu):
volatile-lfu:只限于設定 expire 的部分;優先洗掉最不經常使用的key,
allkeys-lfu:所有key通用;優先洗掉最不經常使用的key,
volatile-*:從已過期時間的資料集中淘汰key,
allkeys-*:所有key,
Reids的洗掉策略
Redis是 key-value 資料庫,可以設定Redis快取的key的過期時間,Redis的過期洗掉策略就是指當Redis中的key過期了,Redis是如何進行處理的,
定時洗掉:在設定key的過期時間的同時,Redis會創建一個定時器,當key達到過期時間時,立即洗掉該鍵,
惰性洗掉:放任鍵過期不管,只有當獲取鍵時,才檢查獲取的鍵是否過期,若過期洗掉該鍵;若沒過期,就回傳值,
定期洗掉:每隔一段時間(默認100ms),程式就對資料庫進行一次檢查,洗掉過期鍵,
注:expires字典會保存所有設定了過期時間的key的過期時間資料,key是指向鍵空間中的某個鍵的指標,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間,
Redis采用的是定期洗掉和惰性洗掉策略,
Redis執行緒模型
Redis基于 Reactor 模式開發了自己網路事件處理器,它由四部分組成,分別是套接字、IO多路復用程式、檔案事件分派器、事件處理器,因為檔案事件分派器佇列的消費是單執行緒的,所以Redis才叫單執行緒模型,

套接字
IO多路復用
檔案事件分派器
事件處理器
IO多路復用技術
IO多路復用:"多路"是指多個TCP連接;"復用"是指復用一個或多個執行緒;可以讓單執行緒高效的處理多個連接請求,
IO多路復用使用兩個系統呼叫(select/poll/epoll和recvfrom),阻塞IO只呼叫了recvfrom;select/poll/epoll 核心是可以同時處理多個連接,而不是更快,所以連接數不高的話,性能不一定比多執行緒+阻塞IO好,多路復用模型中,每一個socket,設定為non-blocking,阻塞是被select這個函式阻塞的,而不是被socket阻塞的,
select機制
原理:客戶端操作服務器時會三種檔案描述符(簡稱fd):writefds(寫)、readfds(讀)、exceptfds(例外),select會阻塞監視3類檔案描述符,等有資料、可讀、可寫、出例外或超時,就會回傳;回傳后通過遍歷fdset整個陣列來找到就緒的描述符fd,然后進行對應的IO操作,
優點:所有平臺都支持,跨平臺性好,
缺點:
- 由于是采用輪詢方式全盤掃描,會隨著檔案描述符fd數量增多而性能下降,
- 每次呼叫 select(),需要把 fd 集合從用戶態拷貝到內核態,并進行遍歷(訊息傳遞都是從內核到用戶空間)
- 默認單個行程打開的fd有限制是1024個,可修改宏定義,但是效率仍然慢,
poll機制
原理:基本原理與select一致,也是輪詢+遍歷;唯一的區別就是poll沒有最大檔案描述符限制(使用鏈表的方式存盤fd),
epoll機制
原理:epoll也沒有fd個數限制,用戶態拷貝到內核態只需要一次,使用時間通知機制來觸發,通過epoll_ctl注冊fd,一旦fd就緒就會通過callback回呼機制來激活對應fd,進行相關的io操作,
優點:
- 沒有fd限制,所支持的fd上限是作業系統的最大檔案句柄數,1G記憶體大概支持10萬個句柄
- 效率提高,使用回呼通知而不是輪詢的方式,不會隨著FD數目的增加效率下降
- 內核和用戶空間mmap同一塊記憶體實作(mmap是一種記憶體映射檔案的方法,即將一個檔案或者其它物件映射到行程的地址空間)
例子:100萬個連接,里面有1萬個連接是活躍,我們可以對比 select、poll、epoll 的性能表現
select:不修改宏定義默認是1024,l則需要100w/1024=977個行程才可以支持 100萬連接,會使得CPU性能特別的差,
poll: 沒有最大檔案描述符限制,100萬個鏈接則需要100w個fd,遍歷都回應不過來了,還有空間的拷貝消耗大量的資源,
epoll: 請求進來時就創建fd并系結一個callback,主需要遍歷1w個活躍連接的callback即可,即高效又不用記憶體拷貝,
Redis例外問題
快取雪崩
定義:同一時間內大量鍵過期(失效),導致所有請求瞬間都落在了資料庫中導致連接例外而崩掉,
如何解決快取雪崩
給快取資料的過期時間設定隨機機,防止同一時間大量資料過期,給每一個快取資料增加相應的快取標記,記錄快取是否失效,若標記失效,則更新快取資料,并發量不大時,可以使用加鎖排隊,
對于"Redis掛掉了,請求全部走資料庫"這種情況,我們可以有以下的思路:
事發前:實作Redis的高可用(主從架構+Sentinel或者Redis集群),盡量避免Redis掛掉
事發中:設定本地快取(ehcache)+限流(hystrix),盡量避免資料掛掉,保證服務能正常作業
事發后:redis持久化,重啟后自動從磁盤上加載資料,快速恢復快取資料
快取穿透
定義:惡意請求快取中不存在的資料,導致所有請求都落在資料庫,造成短時間承受大量請求而崩掉
如何解決快取穿透
采用布隆過濾器,將所有可能存在的資料哈希到一個bitmap中,一個一定不存在的資料會被這個bitmap攔截掉,從而避免對底層存盤系統的查詢壓力,
快取擊穿
定義:快取擊穿是指快取中沒有但資料庫中有的資料,同時讀快取資料沒有讀到,導致所有請求都落在資料庫,造成過大壓力,
快取雪崩與快取擊穿的區別
與快取雪崩不同的是,快取擊穿指并發查同一條資料,快取雪崩是不同資料都過期,很多資料都查不到從而查資料庫,
如何解決快取擊穿
設定熱點資料永不過期,利用互斥鎖:在快取失效的時,先獲取鎖,得到鎖后再去請求資料庫,沒有得到鎖,則休眠一段時間在重試,
快取預熱
定義:快取預熱指系統上線后,將相關的快取資料直接加載到Redis中,這樣就可以避免在用戶請求時,先查詢資料庫,然后再將資料快取的問題,
如何解決快取預熱
- 直接寫個快取重繪頁面,系統上線時手動將快取資料加載;
- 定時重繪快取;
快取降級
當訪問量劇增、服務出現問題(如回應時間慢或不回應)或非核心服務影響到核心流程的性能時,仍然需要保證服務還是可用的,即使是有損服務,系統可以根據一些關鍵資料進行自動降級,也可以配置開關實作人工降級,
快取降級的最終目的是保證核心服務可用,即使是有損的,而且有些服務是無法降級的(如加入購物車、結算),
在進行降級之前要對系統進行梳理,看看系統是不是可以丟卒保帥;從而梳理出哪些必須誓死保護,哪些可降級;比如可以參考日志級別設定預案:
- 一般:比如有些服務偶爾因為網路抖動或者服務正在上線而超時,可以自動降級;
- 警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,并發送告警;
- 錯誤:比如可用率低于90%,或者資料庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;
- 嚴重錯誤:比如因為特殊原因資料錯誤了,此時需要緊急人工降級,
服務降級的目的,是為了防止Redis服務故障,導致資料庫跟著一起發生雪崩問題,因此,對于不重要的快取資料,可以采取服務降級策略,例如一個比較常見的做法就是,Redis出現問題,不去資料庫查詢,而是直接回傳默認值給用戶,
Redis事務
事務間相互獨立:事務中的所有命令都會序列化,按順序執行,事務在執行程序中,不會被其他客戶端請求的命令中斷,
注:
事務中的命令要么都執行,要么都不執行,
Redis事務相關命令
Redis事務功能是通過MULTI、EXEC、DISCARD、WATCH命令實作的,通過MULTI開啟事務,然后將請求的命令入隊,最后通過EXEC命令依次執行佇列中所有的命令,
Redis會將一個事務所有的命令序列化,然后按順序執行,
Redis不支持回滾,Redis在事務失敗時不進行回滾,而是繼續執行剩下的命令,若在一個事務中的命令出現錯誤,那么所有命令都不會執行,若在一個事務中出現運行錯誤,那么正確的命令會被執行,
WATCH命令:是一個樂觀鎖,可以為Redis提供CAS操作,可以監控一個或多個鍵,一旦其中有一個鍵被修改或洗掉,之后的事務就不執行,監控一直持續到EXEC命令,
MULTI命令:用于開啟事務,MULTI執行后,Client可以繼續向服務器發送任意多條命令,這些命令會存放到一個佇列中,當EXEC命令呼叫后,所有佇列中的命令才會被執行,
EXEC命令:執行所有事務塊的命令,可以理解為提交事務,按命令的執行順序,回傳事務中所有命令的回傳值,當操作被打斷時,回傳空值(nil),
DISCARD命令:用于清空事務佇列,并放棄執行事務,Client從事務狀態中退出,
UNWATCH命令:用于取消WATCH命令對所有key的監控,
注:
事務執行程序中,若服務器收到有EXEC、DISCARD、WATCH、MULTI之外的請求,將會把請求放入佇列中,
Redis事務的特性
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔離性(Isolation)
- 持久性(Durability)
Redis分布式問題
Redis的分布式鎖
Redis是單行程模式,佇列技術將并發訪問變為串行訪問,且多個客戶端對Redis的連接并不存在競爭關系,Redis可以使用setnx命令實作分布式鎖,
獲取鎖時呼叫setnx(setnx若設定值成功,回傳1;設定失敗,回傳0),鎖的value值會隨機生成一個UUID,在釋放鎖時,會通過UUID進行判斷是否為對應的鎖,若是該鎖,則釋放該鎖;可以使用 expire 命令為鎖添加一個超時時間,超過該時間則自動釋放鎖,
setnx key value:只有在 key 不存在時,才將key設定為value值,

Redis的并發競爭key問題
多個系統同時對一個key進行操作,最后執行的順序和我們期望的順序不同,導致結果不同,
問題:如何解決并發競爭問題?
答:可以通過Redis或Zookeeper實作分布式鎖,
- Redis實作分布式鎖:通過Redis中setnx命令可以實作分布式鎖,當獲取鎖時,呼叫setnx加鎖,鎖的value值會隨機生成一個UUID,在釋放鎖時,通過UUID進行判斷是否為對應的鎖,若是則釋放鎖,使用expire命令為鎖添加一個超時時間,若超過該時間則自動釋放鎖,
- Zookeeper實作分布式鎖:通過Zookeeper臨時有序節點可以實作分布式鎖,每個Client對某個方法加鎖時,在Zookeeper上的與該方法對應的指定節點的目錄下,生成一個唯一的瞬時有序節點,通過判斷有序節點中,序號是否為最小來獲取鎖;當釋放鎖時,只需要洗掉瞬時有序節點,
RedLock是什么?
Redis 官方站提出了一種權威的基于 Redis 實作分布式鎖的方式名叫 Redlock,此種方式比原先的單節點的方法更安全,
特性
安全性:互斥訪問,只會有一個Client能拿到鎖資源,
容錯性:只要大部分Redi節點可以存活,就可以正常提供服務,
避免死鎖:最終Client都可以拿到鎖,不會出現死鎖的情況,即使原本鎖住某資源的Client crash了或者出現了網路磁區
Redis集群
哨兵模式
哨兵(Sentinel) 是 Redis 的高可用性解決方案:由一個或多個 Sentinel 實體組成的 Sentinel 系統可以監視任意多個主服務器,以及這些主服務器屬下的所有從服務器,

哨兵的作用:
- 監控redis主、從資料庫是否正常運行
- 主資料庫出現故障自動將從資料庫轉換為主資料庫,
哨兵的核心知識
- 哨兵至少需要 3 個實體,來保證自己的健壯性,
- 哨兵 + redis 主從的部署架構,是不保證資料零丟失的,只能保證 redis 集群的高可用性,
- 對于哨兵 + redis 主從這種復雜的部署架構,盡量在測驗環境和生產環境,都進行充足的測驗和演練,
- 配置哨兵監控一個系統時,只需要配置其監控主資料庫即可,哨兵會自動發現所有復制該主資料庫的從資料庫,
Redis的主從復制
當從資料庫啟動時,會向主資料庫發送sync命令,主資料庫接收到sync后開始在后臺保存快照rdb,在保存快照期間收到的命令快取起來,當快照完成時,主資料庫會將快照和快取的命令一塊發送給從資料庫,復制初始化結束,之后,主資料庫每收到1個命令就同步發送給從資料庫, 當出現斷開重連后,2.8之后的版本會將斷線期間的命令傳給從資料庫,增量復制,
主從復制是樂觀復制,當客戶端發送寫執行給主資料庫,主資料庫執行完立即將結果回傳客戶端,并異步的把命令發送給從資料庫,從而不影響性能,
參考文章:
- Redis面試題(2020最新版)
- 5 分鐘搞懂布隆過濾器,億級資料過濾演算法你值得擁有!
- IO多路復用技術
- Redis redisObject資料結構
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/272861.html
標籤:其他
