1. 簡單介紹一下 Redis 唄!
簡單來說 Redis 就是一個使用 C 語言開發的資料庫,不過與傳統資料庫不同的是 Redis 的資料是存在記憶體中的 ,也就是它是記憶體資料庫,所以讀寫速度非常快,因此 Redis 被廣泛應用于快取方向,
另外,Redis 除了做快取之外,Redis 也經常用來做分布式鎖,甚至是訊息佇列,
Redis 提供了多種資料型別來支持不同的業務場景,Redis 還支持事務 、持久化、Lua 腳本、多種集群方案,
2. 分布式快取常見的技術選型方案有哪些?
分布式快取的話,使用的比較多的主要是 Memcached 和 Redis,不過,現在基本沒有看過還有專案使用 Memcached 來做快取,都是直接用 Redis,
Memcached 是分布式快取最開始興起的那會,比較常用的,后來,隨著 Redis 的發展,大家慢慢都轉而使用更加強大的 Redis 了,
分布式快取主要解決的是單機快取的容量受服務器限制并且無法保存通用的資訊,因為,本地快取只在當前服務里有效,比如如果你部署了兩個相同的服務,他們兩者之間的快取資料是無法共同的,
3. 說一下 Redis 和 Memcached 的區別和共同點
現在公司一般都是用 Redis 來實作快取,而且 Redis 自身也越來越強大了!不過,了解 Redis 和 Memcached 的區別和共同點,有助于我們在做相應的技術選型的時候,能夠做到有理有據!
共同點 :
- 都是基于記憶體的資料庫,一般都用來當做快取使用,
- 都有過期策略,
- 兩者的性能都非常高,
區別 :
- Redis 支持更豐富的資料型別(支持更復雜的應用場景),Redis 不僅僅支持簡單的 k/v 型別的資料,同時還提供 list,set,zset,hash 等資料結構的存盤,Memcached 只支持最簡單的 k/v 資料型別,
- Redis 支持資料的持久化,可以將記憶體中的資料保持在磁盤中,重啟的時候可以再次加載進行使用,而 Memecache 把資料全部存在記憶體之中,
- Redis 有災難恢復機制, 因為可以把快取中的資料持久化到磁盤上,
- Redis 在服務器記憶體使用完之后,可以將不用的資料放到磁盤上,但是,Memcached 在服務器記憶體使用完之后,就會直接報例外,
- Memcached 沒有原生的集群模式,需要依靠客戶端來實作往集群中分片寫入資料;但是 Redis 目前是原生支持 cluster 模式的.
- Memcached 是多執行緒,非阻塞 IO 復用的網路模型;Redis 使用單執行緒的多路 IO 復用模型, (Redis 6.0 引入了多執行緒 IO )
- Redis 支持發布訂閱模型、Lua 腳本、事務等功能,而 Memcached 不支持,并且,Redis 支持更多的編程語言,
- Memcached過期資料的洗掉策略只用了惰性洗掉,而 Redis 同時使用了惰性洗掉與定期洗掉,
相信看了上面的對比之后,我們已經沒有什么理由可以選擇使用 Memcached 來作為自己專案的分布式快取了,
4. 快取資料的處理流程是怎樣的?
作為暖男一號,我給大家畫了一個草圖,

簡單來說就是:
- 如果用戶請求的資料在快取中就直接回傳,
- 快取中不存在的話就看資料庫中是否存在,
- 資料庫中存在的話就更新快取中的資料,
- 資料庫中不存在的話就回傳空資料,
5. 為什么要用 Redis/為什么要用快取?
簡單,來說使用快取主要是為了提升用戶體驗以及應對更多的用戶,
下面我們主要從“高性能”和“高并發”這兩點來看待這個問題,

高性能 :
對照上面我畫的圖,我們設想這樣的場景:
假如用戶第一次訪問資料庫中的某些資料的話,這個程序是比較慢,畢竟是從硬碟中讀取的,但是,如果說,用戶訪問的資料屬于高頻資料并且不會經常改變的話,那么我們就可以很放心地將該用戶訪問的資料存在快取中,
這樣有什么好處呢? 那就是保證用戶下一次再訪問這些資料的時候就可以直接從快取中獲取了,操作快取就是直接操作記憶體,所以速度相當快,
不過,要保持資料庫和快取中的資料的一致性, 如果資料庫中的對應資料改變的之后,同步改變快取中相應的資料即可!
高并發:
一般像 MySQL 這類的資料庫的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 快取之后很容易達到 10w+,甚至最高能達到 30w+(就單機 redis 的情況,redis 集群的話會更高),
QPS(Query Per Second):服務器每秒可以執行的查詢次數;
所以,直接操作快取能夠承受的資料庫請求數量是遠遠大于直接訪問資料庫的,所以我們可以考慮把資料庫中的部分資料轉移到快取中去,這樣用戶的一部分請求會直接到快取這里而不用經過資料庫,進而,我們也就提高的系統整體的并發,
6. Redis 常見資料結構以及使用場景分析
你可以自己本機安裝 redis 或者通過 redis 官網提供的在線 redis 環境,

6.1. string
- 介紹 :string 資料結構是簡單的 key-value 型別,雖然 Redis 是用 C 語言寫的,但是 Redis 并沒有使用 C 的字串表示,而是自己構建了一種 簡單動態字串(simple dynamic string,SDS),相比于 C 的原生字串,Redis 的 SDS 不光可以保存文本資料還可以保存二進制資料,并且獲取字串長度復雜度為 O(1)(C 字串為 O(N)),除此之外,Redis 的 SDS API 是安全的,不會造成緩沖區溢位,
- 常用命令:
set,get,strlen,exists,dect,incr,setex等等, - 應用場景 :一般常用在需要計數的場景,比如用戶的訪問次數、熱點文章的點贊轉發數量等等,
下面我們簡單看看它的使用!
普通字串的基本操作:
127.0.0.1:6379> set key value #設定 key-value 型別的值
OK
127.0.0.1:6379> get key # 根據 key 獲得對應的 value
"value"
127.0.0.1:6379> exists key # 判斷某個 key 是否存在
(integer) 1
127.0.0.1:6379> strlen key # 回傳 key 所儲存的字串值的長度,
(integer) 5
127.0.0.1:6379> del key # 洗掉某個 key 對應的值
(integer) 1
127.0.0.1:6379> get key
(nil)
批量設定 :
127.0.0.1:6379> mset key1 value1 key2 value2 # 批量設定 key-value 型別的值
OK
127.0.0.1:6379> mget key1 key2 # 批量獲取多個 key 對應的 value
1) "value1"
2) "value2"
計數器(字串的內容為整數的時候可以使用):
127.0.0.1:6379> set number 1
OK
127.0.0.1:6379> incr number # 將 key 中儲存的數字值增一
(integer) 2
127.0.0.1:6379> get number
"2"
127.0.0.1:6379> decr number # 將 key 中儲存的數字值減一
(integer) 1
127.0.0.1:6379> get number
"1"
過期:
127.0.0.1:6379> expire key 60 # 資料在 60s 后過期
(integer) 1
127.0.0.1:6379> setex key 60 value # 資料在 60s 后過期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看資料還有多久過期
(integer) 56
6.2. list
- 介紹 :list 即是 鏈表,鏈表是一種非常常見的資料結構,特點是易于資料元素的插入和洗掉并且且可以靈活調整鏈表長度,但是鏈表的隨機訪問困難,許多高級編程語言都內置了鏈表的實作比如 Java 中的 LinkedList,但是 C 語言并沒有實作鏈表,所以 Redis 實作了自己的鏈表資料結構,Redis 的 list 的實作為一個 雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的記憶體開銷,
- 常用命令:
rpush,lpop,lpush,rpop,lrange、llen等, - 應用場景: 發布與訂閱或者說訊息佇列、慢查詢,
下面我們簡單看看它的使用!
通過 rpush/lpop 實作佇列:
127.0.0.1:6379> rpush myList value1 # 向 list 的頭部(右邊)添加元素
(integer) 1
127.0.0.1:6379> rpush myList value2 value3 # 向list的頭部(最右邊)添加多個元素
(integer) 3
127.0.0.1:6379> lpop myList # 將 list的尾部(最左邊)元素取出
"value1"
127.0.0.1:6379> lrange myList 0 1 # 查看對應下標的list串列, 0 為 start,1為 end
1) "value2"
2) "value3"
127.0.0.1:6379> lrange myList 0 -1 # 查看串列中的所有元素,-1表示倒數第一
1) "value2"
2) "value3"
通過 rpush/rpop 實作堆疊:
127.0.0.1:6379> rpush myList2 value1 value2 value3
(integer) 3
127.0.0.1:6379> rpop myList2 # 將 list的頭部(最右邊)元素取出
"value3"
我專門花了一個圖方便小伙伴們來理解:

通過 lrange 查看對應下標范圍的串列元素:
127.0.0.1:6379> rpush myList value1 value2 value3
(integer) 3
127.0.0.1:6379> lrange myList 0 1 # 查看對應下標的list串列, 0 為 start,1為 end
1) "value1"
2) "value2"
127.0.0.1:6379> lrange myList 0 -1 # 查看串列中的所有元素,-1表示倒數第一
1) "value1"
2) "value2"
3) "value3"
通過 lrange 命令,你可以基于 list 實作分頁查詢,性能非常高!
通過 llen 查看鏈表長度:
127.0.0.1:6379> llen myList
(integer) 3
6.3. hash
- 介紹 :hash 類似于 JDK1.8 前的 HashMap,內部實作也差不多(陣列 + 鏈表),不過,Redis 的 hash 做了更多優化,另外,hash 是一個 string 型別的 field 和 value 的映射表,特別適合用于存盤物件,后續操作的時候,你可以直接僅僅修改這個物件中的某個欄位的值, 比如我們可以 hash 資料結構來存盤用戶資訊,商品資訊等等,
- 常用命令:
hset,hmset,hexists,hget,hgetall,hkeys,hvals等, - 應用場景: 系統中物件資料的存盤,
下面我們簡單看看它的使用!
127.0.0.1:6379> hset userInfoKey name "guide" description "dev" age "24"
OK
127.0.0.1:6379> hexists userInfoKey name # 查看 key 對應的 value中指定的欄位是否存在,
(integer) 1
127.0.0.1:6379> hget userInfoKey name # 獲取存盤在哈希表中指定欄位的值,
"guide"
127.0.0.1:6379> hget userInfoKey age
"24"
127.0.0.1:6379> hgetall userInfoKey # 獲取在哈希表中指定 key 的所有欄位和值
1) "name"
2) "guide"
3) "description"
4) "dev"
5) "age"
6) "24"
127.0.0.1:6379> hkeys userInfoKey # 獲取 key 串列
1) "name"
2) "description"
3) "age"
127.0.0.1:6379> hvals userInfoKey # 獲取 value 串列
1) "guide"
2) "dev"
3) "24"
127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某個欄位對應的值
127.0.0.1:6379> hget userInfoKey name
"GuideGeGe"
6.4. set
- 介紹 : set 類似于 Java 中的
HashSet,Redis 中的 set 型別是一種無序集合,集合中的元素沒有先后順序,當你需要存盤一個串列資料,又不希望出現重復資料時,set 是一個很好的選擇,并且 set 提供了判斷某個成員是否在一個 set 集合內的重要介面,這個也是 list 所不能提供的,可以基于 set 輕易實作交集、并集、差集的操作,比如:你可以將一個用戶所有的關注人存在一個集合中,將其所有粉絲存在一個集合,Redis 可以非常方便的實作如共同關注、共同粉絲、共同喜好等功能,這個程序也就是求交集的程序, - 常用命令:
sadd,spop,smembers,sismember,scard,sinterstore,sunion等, - 應用場景: 需要存放的資料不能重復以及需要獲取多個資料源交集和并集等場景
下面我們簡單看看它的使用!
127.0.0.1:6379> sadd mySet value1 value2 # 添加元素進去
(integer) 2
127.0.0.1:6379> sadd mySet value1 # 不允許有重復元素
(integer) 0
127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素
1) "value1"
2) "value2"
127.0.0.1:6379> scard mySet # 查看 set 的長度
(integer) 2
127.0.0.1:6379> sismember mySet value1 # 檢查某個元素是否存在set 中,只能接收單個元素
(integer) 1
127.0.0.1:6379> sadd mySet2 value2 value3
(integer) 2
127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 獲取 mySet 和 mySet2 的交集并存放在 mySet3 中
(integer) 1
127.0.0.1:6379> smembers mySet3
1) "value2"
6.5. sorted set
- 介紹: 和 set 相比,sorted set 增加了一個權重引數 score,使得集合中的元素能夠按 score 進行有序排列,還可以通過 score 的范圍來獲取元素的串列,有點像是 Java 中 HashMap 和 TreeSet 的結合體,
- 常用命令:
zadd,zcard,zscore,zrange,zrevrange,zrem等, - 應用場景: 需要對資料根據某個權重進行排序的場景,比如在直播系統中,實時排行資訊包含直播間在線用戶串列,各種禮物排行榜,彈幕訊息(可以理解為按訊息維度的訊息排行榜)等資訊,
127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 為權重
(integer) 1
127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多個元素
(integer) 2
127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素數量
(integer) 3
127.0.0.1:6379> zscore myZset value1 # 查看某個 value 的權重
"3"
127.0.0.1:6379> zrange myZset 0 -1 # 順序輸出某個范圍區間的元素,0 -1 表示輸出所有元素
1) "value3"
2) "value2"
3) "value1"
127.0.0.1:6379> zrange myZset 0 1 # 順序輸出某個范圍區間的元素,0 為 start 1 為 stop
1) "value3"
2) "value2"
127.0.0.1:6379> zrevrange myZset 0 1 # 逆序輸出某個范圍區間的元素,0 為 start 1 為 stop
1) "value1"
2) "value2"
7. Redis 單執行緒模型詳解
Redis 基于 Reactor 模式來設計開發了自己的一套高效的事件處理模型 (Netty 的執行緒模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),這套事件處理模型對應的是 Redis 中的檔案事件處理器(file event handler),由于檔案事件處理器(file event handler)是單執行緒方式運行的,所以我們一般都說 Redis 是單執行緒模型,
既然是單執行緒,那怎么監聽大量的客戶端連接呢?
Redis 通過IO 多路復用程式 來監聽來自客戶端的大量連接(或者說是監聽多個 socket),它會將感興趣的事件及型別(讀、寫)注冊到內核中并監聽每個事件是否發生,
這樣的好處非常明顯: I/O 多路復用技術的使用讓 Redis 不需要額外創建多余的執行緒來監聽客戶端的大量連接,降低了資源的消耗(和 NIO 中的 Selector 組件很像),
另外, Redis 服務器是一個事件驅動程式,服務器需要處理兩類事件: 1. 檔案事件; 2. 時間事件,
時間事件不需要多花時間了解,我們接觸最多的還是 檔案事件(客戶端進行讀取寫入等操作,涉及一系列網路通信),
《Redis 設計與實作》有一段話是如是介紹檔案事件的,我覺得寫得挺不錯,
Redis 基于 Reactor 模式開發了自己的網路事件處理器:這個處理器被稱為檔案事件處理器(file event handler),檔案事件處理器使用 I/O 多路復用(multiplexing)程式來同時監聽多個套接字,并根據 套接字目前執行的任務來為套接字關聯不同的事件處理器,
當被監聽的套接字準備好執行連接應答(accept)、讀取(read)、寫入(write)、關 閉(close)等操作時,與操作相對應的檔案事件就會產生,這時檔案事件處理器就會呼叫套接字之前關聯好的事件處理器來處理這些事件,
雖然檔案事件處理器以單執行緒方式運行,但通過使用 I/O 多路復用程式來監聽多個套接字,檔案事件處理器既實作了高性能的網路通信模型,又可以很好地與 Redis 服務器中其他同樣以單執行緒方式運行的模塊進行對接,這保持了 Redis 內部單執行緒設計的簡單性,
可以看出,檔案事件處理器(file event handler)主要是包含 4 個部分:
- 多個 socket(客戶端連接)
- IO 多路復用程式(支持多個客戶端連接的關鍵)
- 檔案事件分派器(將 socket 關聯到相應的事件處理器)
- 事件處理器(連接應答處理器、命令請求處理器、命令回復處理器)

《Redis設計與實作:12章》
8. Redis 沒有使用多執行緒?為什么不使用多執行緒?
雖然說 Redis 是單執行緒模型,但是, 實際上,Redis 在 4.0 之后的版本中就已經加入了對多執行緒的支持,

不過,Redis 4.0 增加的多執行緒主要是針對一些大鍵值對的洗掉操作的命令,使用這些命令就會使用主處理之外的其他執行緒來“異步處理”,
大體上來說,Redis 6.0 之前主要還是單執行緒處理,
那,Redis6.0 之前 為什么不使用多執行緒?
我覺得主要原因有下面 3 個:
- 單執行緒編程容易并且更容易維護;
- Redis 的性能瓶頸不再 CPU ,主要在記憶體和網路;
- 多執行緒就會存在死鎖、執行緒背景關系切換等問題,甚至會影響性能,
9. Redis6.0 之后為何引入了多執行緒?
Redis6.0 引入多執行緒主要是為了提高網路 IO 讀寫性能,因為這個算是 Redis 中的一個性能瓶頸(Redis 的瓶頸主要受限于記憶體和網路),
雖然,Redis6.0 引入了多執行緒,但是 Redis 的多執行緒只是在網路資料的讀寫這類耗時操作上使用了, 執行命令仍然是單執行緒順序執行,因此,你也不需要擔心執行緒安全問題,
Redis6.0 的多執行緒默認是禁用的,只使用主執行緒,如需開啟需要修改 redis 組態檔 redis.conf :
io-threads-do-reads yes
開啟多執行緒后,還需要設定執行緒數,否則是不生效的,同樣需要修改 redis 組態檔 redis.conf :
io-threads 4 #官網建議4核的機器建議設定為2或3個執行緒,8核的建議設定為6個執行緒
推薦閱讀:
- Redis 6.0 新特性-多執行緒連環 13 問!
- 為什么 Redis 選擇單執行緒模型
10. Redis 給快取資料設定過期時間有啥用?
一般情況下,我們設定保存的快取資料的時候都會設定一個過期時間,為什么呢?
因為記憶體是有限的,如果快取中的所有資料都是一直保存的話,分分鐘直接Out of memory,
Redis 自帶了給快取資料設定過期時間的功能,比如:
127.0.0.1:6379> exp key 60 # 資料在 60s 后過期
(integer) 1
127.0.0.1:6379> setex key 60 value # 資料在 60s 后過期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看資料還有多久過期
(integer) 56
注意:**Redis中除了字串型別有自己獨有設定過期時間的命令 setex 外,其他方法都需要依靠 expire 命令來設定過期時間 ,另外, persist 命令可以移除一個鍵的過期時間: **
過期時間除了有助于緩解記憶體的消耗,還有什么其他用么?
很多時候,我們的業務場景就是需要某個資料只在某一時間段記憶體在,比如我們的短信驗證碼可能只在1分鐘內有效,用戶登錄的 token 可能只在 1 天內有效,
如果使用傳統的資料庫來處理的話,一般都是自己判斷過期,這樣更麻煩并且性能要差很多,
11. Redis是如何判斷資料是否過期的呢?
Redis 通過一個叫做過期字典(可以看作是hash表)來保存資料過期的時間,過期字典的鍵指向Redis資料庫中的某個key(鍵),過期字典的值是一個long long型別的整數,這個整數保存了key所指向的資料庫鍵的過期時間(毫秒精度的UNIX時間戳),

過期字典是存盤在redisDb這個結構里的:
typedef struct redisDb {
...
dict *dict; //資料庫鍵空間,保存著資料庫中所有鍵值對
dict *expires // 過期字典,保存著鍵的過期時間
...
} redisDb;
12. 過期的資料的洗掉策略了解么?
如果假設你設定了一批 key 只能存活 1 分鐘,那么 1 分鐘后,Redis 是怎么對這批 key 進行洗掉的呢?
常用的過期資料的洗掉策略就兩個(重要!自己造快取輪子的時候需要格外考慮的東西):
- 惰性洗掉 :只會在取出key的時候才對資料進行過期檢查,這樣對CPU最友好,但是可能會造成太多過期 key 沒有被洗掉,
- 定期洗掉 : 每隔一段時間抽取一批 key 執行洗掉過期key操作,并且,Redis 底層會通過限制洗掉操作執行的時長和頻率來減少洗掉操作對CPU時間的影響,
定期洗掉對記憶體更加友好,惰性洗掉對CPU更加友好,兩者各有千秋,所以Redis 采用的是 定期洗掉+惰性/懶漢式洗掉 ,
但是,僅僅通過給 key 設定過期時間還是有問題的,因為還是可能存在定期洗掉和惰性洗掉漏掉了很多過期 key 的情況,這樣就導致大量過期 key 堆積在記憶體里,然后就Out of memory了,
怎么解決這個問題呢?答案就是: Redis 記憶體淘汰機制,
13. Redis 記憶體淘汰機制了解么?
相關問題:MySQL 里有 2000w 資料,Redis 中只存 20w 的資料,如何保證 Redis 中的資料都是熱點資料?
Redis 提供 6 種資料淘汰策略:
- volatile-lru(least recently used):從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰
- volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)中挑選將要過期的資料淘汰
- volatile-random:從已設定過期時間的資料集(server.db[i].expires)中任意選擇資料淘汰
- allkeys-lru(least recently used):當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的 key(這個是最常用的)
- allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰
- no-eviction:禁止驅逐資料,也就是說當記憶體不足以容納新寫入資料時,新寫入操作會報錯,這個應該沒人使用吧!
4.0 版本后增加以下兩種:
- volatile-lfu(least frequently used):從已設定過期時間的資料集(server.db[i].expires)中挑選最不經常使用的資料淘汰
- allkeys-lfu(least frequently used):當記憶體不足以容納新寫入資料時,在鍵空間中,移除最不經常使用的 key
14. 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 還會優雅的放慢自己的速度以便適應硬碟的最大寫入速度,
相關 issue :783:Redis 的 AOF 方式
拓展:Redis 4.0 對于持久化機制的優化
Redis 4.0 開始支持 RDB 和 AOF 的混合持久化(默認關閉,可以通過配置項 aof-use-rdb-preamble 開啟),
如果把混合持久化打開,AOF 重寫的時候就直接把 RDB 的內容寫到 AOF 檔案開頭,這樣做的好處是可以結合 RDB 和 AOF 的優點, 快速加載同時避免丟失過多的資料,當然缺點也是有的, AOF 里面的 RDB 部分是壓縮格式不再是 AOF 格式,可讀性較差,
補充內容:AOF 重寫
AOF 重寫可以產生一個新的 AOF 檔案,這個新的 AOF 檔案和原有的 AOF 檔案所保存的資料庫狀態一樣,但體積更小,
AOF 重寫是一個有歧義的名字,該功能是通過讀取資料庫中的鍵值對來實作的,程式無須對現有 AOF 檔案進行任何讀入、分析或者寫入操作,
在執行 BGREWRITEAOF 命令時,Redis 服務器會維護一個 AOF 重寫緩沖區,該緩沖區會在子行程創建新 AOF 檔案期間,記錄服務器執行的所有寫命令,當子行程完成創建新 AOF 檔案的作業之后,服務器會將重寫緩沖區中的所有內容追加到新 AOF 檔案的末尾,使得新舊兩個 AOF 檔案所保存的資料庫狀態一致,最后,服務器用新的 AOF 檔案替換舊的 AOF 檔案,以此來完成 AOF 檔案重寫操作
15. Redis 事務
Redis 可以通過 MULTI,EXEC,DISCARD 和 WATCH 等命令來實作事務(transaction)功能,
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
使用 MULTI命令后可以輸入多個命令,Redis不會立即執行這些命令,而是將它們放到佇列,當呼叫了EXEC命令將執行所有命令,
Redis官網相關介紹 https://redis.io/topics/transactions 如下:

但是,Redis 的事務和我們平時理解的關系型資料庫的事務不同,我們知道事務具有四大特性: 1. 原子性,2. 隔離性,3. 持久性,4. 一致性,
- 原子性(Atomicity): 事務是最小的執行單位,不允許分割,事務的原子性確保動作要么全部完成,要么完全不起作用;
- 隔離性(Isolation): 并發訪問資料庫時,一個用戶的事務不被其他事務所干擾,各并發事務之間資料庫是獨立的;
- 持久性(Durability): 一個事務被提交之后,它對資料庫中資料的改變是持久的,即使資料庫發生故障也不應該對其有任何影響,
- 一致性(Consistency): 執行事務前后,資料保持一致,多個事務對同一個資料讀取的結果是相同的;
Redis 是不支持 roll back 的,因而不滿足原子性的(而且不滿足持久性),
Redis官網也解釋了自己為啥不支持回滾,簡單來說就是Redis開發者們覺得沒必要支持回滾,這樣更簡單便捷并且性能更好,Redis開發者覺得即使命令執行錯誤也應該在開發程序中就被發現而不是生產程序中,

你可以將Redis中的事務就理解為 :Redis事務提供了一種將多個命令請求打包的功能,然后,再按順序執行打包的所有命令,并且不會被中途打斷,
相關issue :issue452: 關于 Redis 事務不滿足原子性的問題 ,
推薦閱讀:https://zhuanlan.zhihu.com/p/43897838 ,
16. 快取穿透
16.1. 什么是快取穿透?
快取穿透說簡單點就是大量請求的 key 根本不存在于快取中,導致請求直接到了資料庫上,根本沒有經過快取這一層,舉個例子:某個黑客故意制造我們快取中不存在的 key 發起大量請求,導致大量請求落到資料庫,
16.2. 快取穿透情況的處理流程是怎樣的?
如下圖所示,用戶的請求最終都要跑到資料庫中查詢一遍,

16.3. 有哪些解決辦法?
最基本的就是首先做好引數校驗,一些不合法的引數請求直接拋出例外資訊回傳給客戶端,比如查詢的資料庫 id 不能小于 0、傳入的郵箱格式不對的時候直接回傳錯誤訊息給客戶端等等,
1)快取無效 key
如果快取和資料庫都查不到某個 key 的資料就寫一個到 Redis 中去并設定過期時間,具體命令如下: SET key value EX 10086 ,這種方式可以解決請求的 key 變化不頻繁的情況,如果黑客惡意攻擊,每次構建不同的請求 key,會導致 Redis 中快取大量無效的 key ,很明顯,這種方案并不能從根本上解決此問題,如果非要用這種方式來解決穿透問題的話,盡量將無效的 key 的過期時間設定短一點比如 1 分鐘,
另外,這里多說一嘴,一般情況下我們是這樣設計 key 的: 表名:列名:主鍵名:主鍵值 ,
如果用 Java 代碼展示的話,差不多是下面這樣的:
public Object getObjectInclNullById(Integer id) {
// 從快取中獲取資料
Object cacheValue = https://www.cnblogs.com/xzsj/archive/2020/11/24/cache.get(id);
// 快取為空
if (cacheValue == null) {
// 從資料庫中獲取
Object storageValue = storage.get(key);
// 快取空物件
cache.set(key, storageValue);
// 如果存盤資料為空,需要設定一個過期時間(300秒)
if (storageValue == null) {
// 必須設定過期時間,否則有被攻擊的風險
cache.expire(key, 60 * 5);
}
return storageValue;
}
return cacheValue;
}
2)布隆過濾器
布隆過濾器是一個非常神奇的資料結構,通過它我們可以非常方便地判斷一個給定資料是否存在于海量資料中,我們需要的就是判斷 key 是否合法,有沒有感覺布隆過濾器就是我們想要找的那個“人”,
具體是這樣做的:把所有可能存在的請求的值都存放在布隆過濾器中,當用戶請求過來,先判斷用戶發來的請求的值是否存在于布隆過濾器中,不存在的話,直接回傳請求引數錯誤資訊給客戶端,存在的話才會走下面的流程,
加入布隆過濾器之后的快取處理流程圖如下,

但是,需要注意的是布隆過濾器可能會存在誤判的情況,總結來說就是: 布隆過濾器說某個元素存在,小概率會誤判,布隆過濾器說某個元素不在,那么這個元素一定不在,
為什么會出現誤判的情況呢? 我們還要從布隆過濾器的原理來說!
我們先來看一下,當一個元素加入布隆過濾器中的時候,會進行哪些操作:
- 使用布隆過濾器中的哈希函式對元素值進行計算,得到哈希值(有幾個哈希函式得到幾個哈希值),
- 根據得到的哈希值,在位陣列中把對應下標的值置為 1,
我們再來看一下,當我們需要判斷一個元素是否存在于布隆過濾器的時候,會進行哪些操作:
- 對給定元素再次進行相同的哈希計算;
- 得到值之后判斷位陣列中的每個元素是否都為 1,如果值都為 1,那么說明這個值在布隆過濾器中,如果存在一個值不為 1,說明該元素不在布隆過濾器中,
然后,一定會出現這樣一種情況:不同的字串可能哈希出來的位置相同, (可以適當增加位陣列大小或者調整我們的哈希函式來降低概率)
更多關于布隆過濾器的內容可以看我的這篇原創:《不了解布隆過濾器?一文給你整的明明白白!》 ,強烈推薦,個人感覺網上應該找不到總結的這么明明白白的文章了,
17. 快取雪崩
17.1. 什么是快取雪崩?
我發現快取雪崩這名字起的有點意思,哈哈,
實際上,快取雪崩描述的就是這樣一個簡單的場景:快取在同一時間大面積的失效,后面的請求都直接落到了資料庫上,造成資料庫短時間內承受大量請求, 這就好比雪崩一樣,摧枯拉朽之勢,資料庫的壓力可想而知,可能直接就被這么多請求弄宕機了,
舉個例子:系統的快取模塊出了問題比如宕機導致不可用,造成系統的所有訪問,都要走資料庫,
還有一種快取雪崩的場景是:有一些被大量訪問資料(熱點快取)在某一時刻大面積失效,導致對應的請求直接落到了資料庫上, 這樣的情況,有下面幾種解決辦法:
舉個例子 :秒殺開始 12 個小時之前,我們統一存放了一批商品到 Redis 中,設定的快取過期時間也是 12 個小時,那么秒殺開始的時候,這些秒殺的商品的訪問直接就失效了,導致的情況就是,相應的請求直接就落到了資料庫上,就像雪崩一樣可怕,
17.2. 有哪些解決辦法?
針對 Redis 服務不可用的情況:
- 采用 Redis 集群,避免單機出現問題整個快取服務都沒辦法使用,
- 限流,避免同時處理大量的請求,
針對熱點快取失效的情況:
- 設定不同的失效時間比如隨機設定快取的失效時間,
- 快取永不失效,
18. 如何保證快取和資料庫資料的一致性?
細說的話可以扯很多,但是我覺得其實沒太大必要(小聲BB:很多解決方案我也沒太弄明白),我個人覺得引入快取之后,如果為了短時間的不一致性問題,選擇讓系統設計變得更加復雜的話,完全沒必要,
下面單獨對 Cache Aside Pattern(旁路快取模式) 來聊聊,
Cache Aside Pattern 中遇到寫請求是這樣的:更新 DB,然后直接洗掉 cache ,
如果更新資料庫成功,而洗掉快取這一步失敗的情況的話,簡單說兩個解決方案:
- 快取失效時間變短(不推薦,治標不治本) :我們讓快取資料的過期時間變短,這樣的話快取就會從資料庫中加載資料,另外,這種解決辦法對于先操作快取后操作資料庫的場景不適用,
- 增加cache更新重試機制(常用): 如果 cache 服務當前不可用導致快取洗掉失敗的話,我們就隔一段時間進行重試,重試次數可以自己定,如果多次重試還是失敗的話,我們可以把當前更新失敗的 key 存入佇列中,等快取服務可用之后,再將 快取中對應的 key 洗掉即可,
19. 參考
- 《Redis 開發與運維》
- 《Redis 設計與實作》
- Redis 命令總結:http://Redisdoc.com/string/set.html
- 通俗易懂的 Redis 資料結構基礎教程:https://juejin.im/post/5b53ee7e5188251aaa2d2e16
- WHY Redis choose single thread (vs multi threads): https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153
- 《2020最新Java基礎精講視頻教程和學習路線!》
作者:Snailclimb
鏈接:Redis 常見問題總結
來源:github
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/227149.html
標籤:其他
