一、非關系型數據庫
1. 主要針對的是鍵值、文檔以及圖形類型數據存儲。
2. 特點:
特點 | 說明 |
---|---|
靈活的數據模型 | 支持多種數據模型(文檔、鍵值、列族、圖),無需預定義固定的表結構,能夠處理各種類型的數據。 |
高擴展性 | 設計為水平擴展,能夠輕松地通過增加更多節點來處理大量的數據和高并發請求。 |
高性能 | 通過優化特定類型的查詢和數據操作,通常比關系型數據庫在大規模數據處理時表現更好。 |
分布式架構 | 天生支持分布式存儲和計算,能夠跨多個節點和數據中心實現數據的分布和冗余。 |
弱一致性 | 為了提高性能和可用性,通常采用最終一致性模型,而不是關系型數據庫的強一致性模型。 |
靈活的事務支持 | 事務支持通常較為靈活,有些NoSQL數據庫提供有限的事務支持,有些則支持ACID事務。 |
易于使用 | 簡單的API接口和查詢語言,使開發者能夠快速上手和使用。 |
豐富的類型支持 | 能夠存儲和處理多種數據類型,包括JSON、XML、二進制數據等。 |
高可用性 | 通過數據復制和分區,實現高可用性和數據冗余,保證系統在部分節點失效時仍能正常運行。 |
適應多種應用場景 | 特別適合于大數據分析、實時應用、社交網絡、物聯網等需要處理大量非結構化數據的場景。 |
3. 代表:HBase、Cassandra、MongoDB、Redis
二、Redis
Redis是一個基于 C 語言開發的開源 NoSQL 數據庫,使用key-value鍵值對存儲數據,且由于其數據存儲在內存中,速度很快,在開發中使用廣泛。
(一)數據類型
1. 五種基礎數據類型
五種基礎數據類型包括:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)
- String:是一種二進制安全的數據類型,常用于緩存 Session、Token、圖片地址、序列化后的對象,用戶單位時間的請求數(簡單限流可以用到)、頁面單位時間的訪問數
- List:使用一個雙向鏈表實現,常用于實現最新文章、最新動態、消息隊列
- Hash:是一個 String 類型的 field-value(鍵值對) 的映射表,內部實現:數組 + 鏈表,常用于存儲對象
- Set:是一種無序集合,實現了求交集、并集、差集等操作,常用于網站 UV 統計、文章點贊、動態點贊等場景;共同好友(交集)、共同粉絲(交集)、共同關注(交集)、好友推薦(差集)、音樂推薦(差集)、訂閱號推薦(差集+交集) 等場景;抽獎系統、隨機點名等場景
- Sorted Set:增加了一個權重參數 score,底層使用跳表實現,使得集合中的元素能夠按 score 進行有序排列,常用于各種排行榜,優先級任務隊列
2. 三種特殊數據類型
包括:HyperLogLog(基數統計)、Bitmap (位圖)、Geospatial (地理位置)
- Bitmap:存儲的是連續的二進制數字(0 和 1),常用于用戶簽到情況、活躍用戶情況、用戶行為統計(比如是否點贊過某個視頻)。
- HyperLogLog:是一種有名的基數計數概率算法,常用于數據量巨大的統計場景:熱門網站每日/每周/每月訪問 ip 數統計、熱門帖子 uv 統計等
- Geospatial index(地理空間索引,簡稱 GEO):基于 Sorted Set 實現,主要用于存儲地理位置信息。
3. 其他數據類型
包括: Bloom filter(布隆過濾器)、Bitfield(位域)
- Bloom filter(布隆過濾器):由一個初始值為零的bit數組和多個哈希函數構成,用來快速判斷集合中是否存在某個元素,常用于解決緩存穿透問題
- Bitfield(位域):是一種對Redis中的字符串類型進行擴展的數據類型,用于對字符串中任意偏移進行修改等操作。
(二)應用
(三)常見面試問題
1. 為什么快
- Redis 基于內存存儲數據,內存的訪問速度比磁盤快很多
- Redis 基于 Reactor 模式設計開發了一套高效的事件處理模型,使用IO多路復用+事件派發來處理多個socket
- Redis是單線程的,避免線程間的切換(Redis6.0之后命令回復處理器、命令請求處理器使用了多線程,命令執行還是使用的單線程)
- Redis 內置了多種優化過后的數據類型/結構實現,性能非常高
- Redis 通信協議實現簡單且解析高效
2. 緩存讀寫策略
(1)Cache Aside Pattern(旁路緩存模式)
- 讀數據:從 cache 中讀取數據,讀取到就直接返回;否則從 db 中讀取數據返回,再把數據放到 cache 中
- 寫數據:先更新db,再刪除緩存
(2)Read/Write Through Pattern(讀寫穿透)(以cache服務器為主) - 讀數據:從 cache 中讀取數據,讀取到就直接返回 ;否則先從 db 加載,寫入到 cache 后返回響應
- 寫數據:先查 cache,cache 中不存在,直接更新 db;cache 中存在,則先更新 cache,然后 cache 服務自己更新 db
(3)Write Behind Pattern(異步緩存寫入) - 只同步更新緩存,不直接更新 db,而是改為異步批量的方式來更新 db
- db 的寫性能非常高,非常適合一些數據經常變化又對數據一致性要求沒那么高的場景,比如瀏覽量、點贊量
3. key過期刪除策略
- 惰性刪除:使用時才檢查刪除(內存消耗大)
- 定期刪除:周期性地隨機從設置了過期時間的 key 中抽查一批,然后逐個檢查這些 key 是否過期,過期就刪除 key(周期時間確定較難)
- 延遲隊列:把設置過期時間的 key 放到一個延遲隊列里,到期之后就刪除 key(需要額外的資源維護隊列)
- 定時刪除:每個設置了過期時間的 key 都會在設置的時間到達時立即被刪除(每個key都要維護一個定時器,資源消耗大)
注:Redis采用惰性+定期刪除的方式
4. Redis 的內存淘汰策略(內存不足時觸發)
- volatile-lru(least recently used):從已設置過期時間的數據集中挑選最近最少使用的數據淘汰。
- volatile-ttl:從已設置過期時間的數據集中挑選將要過期的數據淘汰
- volatile-random:從已設置過期時間的數據集中任意選擇數據淘汰。
- volatile-lfu(least frequently used):從已設置過期時間的數據集中挑選最不經常使用的數據淘汰
- allkeys-lru(least recently used):從數據集中移除最近最少使用的數據
- allkeys-random:從數據集中任意選擇數據淘汰。
- allkeys-lfu(least frequently used):從數據集中移除最不經常使用的數據淘汰
- no-eviction(默認內存淘汰策略):不淘汰數據,當內存不足以容納新寫入數據時,新寫入操作會報錯
5. 生產問題(緩存三兄弟)
(1)緩存穿透
- 請求的 key 不存在于緩存中,會導致大量查詢請求直接到了數據庫,導致數據庫崩潰
- 解決方式:
① 緩存無效值(內存不友好)
② 布隆過濾器(使用一個較大的 bit 數組來保存所有可能請求的key的哈希值,每次請求時計算key對應的哈希值,如果哈希值對應的位置為true才去緩存中查找)
(2)緩存擊穿 - 請求的key對應的是熱點數據 ,但緩存中的那份數據已經過期,導致大量請求落在數據庫上
- 解決方法:
① 設置熱點數據永不過期或者過期時間比較長(內存不友好)
② 提前預熱(推薦):針對熱點數據提前預熱,將其存入緩存中并設置合理的過期時間比如秒殺場景下的數據在秒殺結束之前不過期
③ 加鎖:在緩存失效后,通過設置互斥鎖確保只有一個請求去查詢數據庫并更新緩存
(3)緩存雪崩 - 緩存中的key在同一時間大量失效,導致大量的請求都直接落到了數據庫上,對數據庫造成了巨大的壓力/Redis服務器宕機
- 解決方式:
① key設置隨機失效時間
② 提前預熱
③ 多級緩存:設置多級緩存,例如本地緩存+Redis 緩存的二級緩存組合
④ Redis 集群:采用 Redis 集群,避免單機出現問題整個緩存服務都沒辦法使用
6. Redis集群
(1)主從復制
- 主節點寫,多個從節點讀
- 主從數據同步原理
① 全量同步:初始同步都采用全量同步
② 增量同步:一般是slave重啟或者后期數據變化 - 實現高并發
(2)哨兵模式 - 使用哨兵檢測集群中各個服務器的狀態(心跳機制),并在主節點宕機后重新選擇主節點
- 哨兵選主規則:優先級、與主節點斷開時間(小)、offset值(大)、id(小)
- 可能會出現腦裂問題(網絡問題導致),解決方法是:設置最少的從節點數量/縮短主從數據同步的延遲時間/達不到要求就拒絕請求
- 實現高可用
(3)分片集群 - 多個master、多個slave,多個master之間通過ping檢測彼此健康狀態;客戶端請求可以訪問任意節點,最終會根據Redis中的路由轉發到正確節點
- 實現海量數據存儲,以及高并發寫
7. 持久化機制
(1)快照RDB
- 通過創建快照來獲得存儲在內存里面的數據在某個時間點上的副本
- 優點:
① RDB 文件存儲的內容是經過壓縮的二進制數據,文件很小,適合做數據的備份,災難恢復
② 使用 RDB 文件恢復數據,直接解析還原數據即可,不需要一條一條地執行命令,速度非常快 - 實現原理:bgsave命令開始時主進程會fork一個子進程,子進程復制主進程的頁表,將對應的內存數據寫入磁盤
- 對數據丟失容忍度更高,追求啟動速度
(2)只追加文件AOF - 將每一條Redis執行命令寫入到 AOF 緩沖區中,然后再寫入到 AOF 文件中,最后根據持久化方式( fsync策略)的將系統內核緩存區的數據同步到硬盤中
- 優點
① 實時性更好
② AOF 以一種易于理解和解析的格式包含所有操作的日志,可以直接進行操作和分析 - 對數據的安全性、完整性要求更高
- 持久化策略
① appendfsync always:主線程調用 write 執行寫操作后,后臺線程( aof_fsync 線程)立即會調用 fsync 函數同步 AOF 文件(刷盤),fsync 完成后線程返回
② appendfsync everysec:主線程調用 write 執行寫操作后立即返回,由后臺線程( aof_fsync 線程)每秒鐘調用 fsync 函數(系統調用)同步一次 AOF 文件
③ appendfsync no:主線程調用 write 執行寫操作后立即返回,讓操作系統決定何時進行同步刷盤 - AOF文件重寫(當文件較大時):由一個子進程將數據庫狀態寫入新的AOF文件中,重寫期間,Redis 還會維護一個 AOF 重寫緩沖區,該緩沖區會在子進程創建新 AOF 文件期間,記錄服務器執行的所有寫命令。當子進程完成創建新 AOF 文件的工作之后,服務器會將重寫緩沖區中的所有內容追加到新 AOF 文件的末尾。
注:該操作不不需要對原有AOF文件進行任何的讀取,寫入,分析
(3)混合方式 - AOF 重寫的時候就直接把 RDB 的內容寫到 AOF 文件開頭
- 優點:可以結合 RDB 和 AOF 的優點, 快速加載同時避免丟失過多的數據
- 缺點:AOF 里面的 RDB 部分是壓縮格式不再是 AOF 格式,可讀性較差
8. Redis實現延時
(1)Redis 過期事件監聽
- 原理:在pub/sub 模式下,監聽 key 的過期事件 channel,就可以拿到過期的 key 的消息,進而實現了延時任務功能
- 存在問題
① 時效性差,key過期后的刪除是惰性刪除+定期刪除結合,而這個發布者是要在key刪除時才發布消息到channel
② 丟消息:當沒有訂閱者時,消息會被直接丟棄,在 Redis 中不會存儲該消息
③ 多服務實例下消息重復消費
(2)Redisson 內置的延時隊列 - 原理:基于 Redis 的 SortedSet 來實現,將需要延遲執行的任務插入到 SortedSet 中,并給它們設置相應的過期時間作為分數,Redisson 使用 zrangebyscore 命令掃描 SortedSet 中過期的元素,然后將這些過期元素從 SortedSet 中移除,并將它們加入到就緒消息列表中
9. Redis實現分布式鎖
(1)SETNX(SET if Not eXists)命令實現分布式鎖
(2)使用Redisson實現的分布式鎖
- 基于lua腳本完成加鎖、設置過期時間等操作,使用watch-dog給鎖續期
- 不能實現主從一致性,使用紅鎖可以,但性能極差