1、Redis 簡介
- 是一個用 C 語言開發的,高性能的鍵值對數據庫。
- 數據存在於內存,讀寫速度快。
- 可用來做緩存、分布式鎖、消息隊列。
- 提供多種數據類型來支持不同的業務場景。
- 支持事務、持久化、Lua 腳本、多種集群方案。
2、Redis 與 Memcached 對比
共同點:
- 都是基於內存的數據庫,常用來做緩存。
- 都有過期策略。
- 性能都非常高。
區別:
- Redis 支持多種數據類型;而 Memcached 只支持 string。
- Redis 支持數據持久化;而 Memcached 不支持。
- Redis 有災難恢復機制(通過加載持久化數據實現);而 Memcached 沒有。
- Redis 內存不足時,可以將不用的數據放到磁盤上;而 Memcached 會直接報異常。
- Redis 原生支持集群模式;而 Memcached 沒有原生的集群模式,需要客戶端實現。
- Redis 使用單線程、IO 多路復用的網絡模型(Redis 6.0 引入了多線程 IO);而 Memcached 使用多線程、非阻塞 IO 復用的網絡模式。
- Redis 支持發布訂閱模型、Lua 腳本、事務等功能;而 Memcached 不支持。
- Redis 過期數據的刪除策略使用了惰性刪除和定期刪除;而 Memcached 只用了惰性刪除。
3、為什么要用 Redis
- 高性能:Redis 的數據存在於內存,讀寫速度快。
- 高並發:單機情況下,MySQL 這類數據庫 QPS 在 1w 左右(4 核 8G),而 Redis 可達到 10w+,甚至 30w+。
4、Redis 數據類型
- string:簡單動態字符串(SDS)。
- 常用命令:set、get、strlen、decr、incr、setex 等。
- 應用場景:需要計數的場景,比如用戶訪問量、文章轉發量等。
- list:雙向鏈表。
- 常用命令:lpush、rpop、rpush、lpop、lrange、llen 等。
- 應用場景:發布訂閱(即消息隊列)。
- hash:類似 Java 的 HashMap,底層為數組 + 鏈表。
- 常用命令:hset、hget、hexists、hkeys、hvals 等
- 應用場景:存儲對象數據。
- set:類似 Java 的 HashSet,無序、去重,可實現交集、並集、差集的操作。
- 常用命令:sadd、spop、smembers、sismember、scard、sunion 等。
- 應用場景:需要存放的數據不能重復、需要獲取多個數據源的交集、並集、差集。
- sorted set(zset):和 set 相比,增加了一個權重參數 score,能夠按 score 進行排序。類似 Java 的 TreeSet,有序、去重。
- 常用命令:zadd、zcard、zscore、zrange、zrevrange 等。
- 應用場景:需要根據某個權重進行排序的場景,比如直播系統中的禮物排行榜、消息彈幕等。
5、Redis 單線程模型
- Redis 基於 Reactor 模式來開發自己的網絡事件處理器,這個處理器被稱為文件事件處理器。
- 由於文件事件處理器是單線程的,所以我們一般都說 Redis 是單線程模型。
- Redis 通過 IO 多路復用來監聽多個客戶端連接(或者說監聽多個 socket)。
- 文件事件處理器主要包含四個部分:
- 多個 socket:客戶端連接。
- IO 多路復用程序:支持多個客戶端連接的關鍵。
- 文件事件分派器:將 socket 關聯到相應的事件處理器。
- 事件處理器:連接應答處理器、命令請求處理器、命令回復處理器。
6、Redis 單線程為什么那么快
- 數據存在於內存,讀寫速度快。
- 單線程操作,避免了死鎖、上下文切換帶來的性能開銷。
- 采用非阻塞 IO 多路復用機制。
7、Redis 6.0 之前為什么使用單線程
- 單線程編程容易,並且更容易維護。
- Redis 的性能瓶頸不在 CPU,主要在內存和網絡。
- 多線程存在死鎖、線程上下文切換等問題,甚至會影響性能。
8、Redis 6.0 之后為何引入了多線程
- 為了提高網絡 IO 讀寫性能。
- Redis 只在網絡數據讀寫這類耗時操作上使用了多線程,執行命令仍然是單線程順序執行,因此不需要擔心線程安全問題。
9、Redis 如何判斷數據是否過期
- 通過過期字典(hash 表)保存數據過期時間。
- 過期字典的鍵指向 Redis 中的某個鍵,過期字典的值保存了數據的過期時間(毫秒精度的時間戳)。
10、Redis 過期數據的刪除策略
- 定時刪除:設置過期時間時,同時創建一個定時器,定時器會在過期時間到來時自動刪除過期數據。節約內存,但 CPU 壓力大。
- 定期刪除:每隔一段時間抽取一批數據進行過期檢查。內存、CPU 壓力都不是很大。
- 惰性刪除:在取出數據時進行過期檢查。節約 CPU 性能、但內存壓力大。
Redis 采用定期刪除 + 惰性刪除。但仍會漏掉某些過期數據,可能導致大量過期數據堆積在內存,從而導致內存溢出,Redis 通過內存淘汰機制來解決這個問題。
11、Redis 內存淘汰機制
- volatile-lru(least recently used):從已設置過期時間的數據中,淘汰最近最少使用的數據。
- volatile-ttl:從已設置過期時間的數據中,淘汰將要過期的數據。
- volatile-random:從已設置過期時間的數據中,隨機淘汰數據。
- allkeys-lru(least recently used):從所有數據中,淘汰最近最少使用的數據。
- allkeys-random:從所有數據中,隨機淘汰數據。
- noeviction:禁止淘汰數據,內存不足時直接報錯。
4.0 版本后增加兩種:
- volatile-lfu(least frequently used):從已設置過期時間的數據中,淘汰最不經常使用的數據。
- allkeys-lfu(least frequently used):從所有數據中,淘汰最不經常使用的數據。
12、Redis 持久化機制
- RDB 持久化:默認方式,將某個時刻內存中的數據以快照的形式保存在磁盤上。文件小、恢復速度快,對性能影響小,但是實時性差。
- AOF 持久化:以日志的方式將每一條寫命令保存到磁盤的文件上。文件大、恢復速度慢,對性能影響大,但是實時性高。目前 Redis 持久化的主流方式。
- 一般設置為每秒同步一次 AOF 文件:appendfsync 選項設置為 everysec。
- AOF 重寫(bgrewriteaof 命令):將內存中的數據以命令的方式保存到新的 AOF 文件中,然后用新的 AOF 文件替換舊的 AOF 文件。
13、Redis 事務
- 相關命令:
- MULTI:開啟事務。
- EXEC:執行事務中的所有命令。
- DISCARD:取消事務,放棄執行事務中的所有命令。
- WATCH:監聽一個或多個 key,如果事務在執行前,監聽的 key 被其他命令修改,則中斷事務,不會執行事務中的任何命令。
- UNWATCH:取消 WATCH 對所有 key 的監聽。
- 使用 MULTI 命令后可輸入多個命令,Redis 不會立即執行這些命令,而是會放到隊列中,等調用 EXEC 命令后再原子化執行這些命令。
- Redis 不支持回滾,因此不滿足原子性。
- 當 Redis 運行在 AOF 持久化模式下,並且 appendfsync 選項設為 always 時,具有持久性。
14、Redis 集群(多機)
- 主從模式:主庫(master)負責讀寫,從庫(slave)負責讀。不具備高可用。
- master 掛了,不影響 slave,只是不再提供寫服務。
- master 掛了,不會在 slave 中選一個 master。
- 哨兵(Sentinel)模式:通過 sentinel 進程監視 master、slave 的運行狀態。
- 哨兵模式是建立在主從模式的基礎上。
- 為了避免 sentinel 掛掉,一般會做 sentinel 集群。並且不和 master、slave 部署在同一台機器。
- sentinel 會每秒向 master、slave 以及其他 sentinel 實例發送一個 PING 命令,以監視其運行狀態。
- master 掛了,sentinel 會在 slave 中選一個 master,並修改所有實例的配置。
- master 重啟后,將作為 slave 運行。
- 集群(Cluster)模式:Redis 提供的分布式數據庫方案。該模式是為了解決單機 Redis 容量上限的問題,它通過分片來進行數據共享。
- 由多個節點組成,每個節點包含一主一從(或一主多從),從庫僅作為備用。
- 客戶端可以連接任一節點的主庫進行讀寫。
- 集群的整個數據庫被分為 16384 個槽(slot),當存儲一個 key-value 時,會被分配在某個槽上。
15、緩存穿透
- 大量請求不存在緩存中的 key,導致請求落在數據庫上。
- 解決辦法:
- 攔截非法 key:攔截所有一定不存在數據的 key。一般采用布隆過濾器。
- 緩存空值:即使查詢結果為空,也將這個空結果緩存,但設置一個較短的過期時間。
16、緩存雪崩
- 大量緩存在同一時間失效,或者一些被大量訪問的緩存(熱點緩存)在某一時刻失效,導致大量請求落在數據庫上。
- 解決辦法:
- 設置不同的過期時間。
- 限流,避免同時處理大量請求。
- 設置熱點緩存永不失效。
交流區
微信公眾號:驚卻一目
個人博客:驚卻一目
Reference
[1] https://snailclimb.gitee.io/javaguide-interview/#/./docs/d-2-redis
[2] https://blog.csdn.net/miss1181248983/article/details/90056960
[3] 《Redis 設計與實現》