日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

后端学习 - Redis

發布時間:2023/12/4 数据库 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 后端学习 - Redis 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 一 Redis 概述
    • Redis 為什么是單線程,單線程為什么這么快?
    • 數據存儲結構
  • 二 常用數據類型
    • 1 String
    • 2 Hash
    • Hash 的擴容機制:漸進式 rehash*
    • 3 List
    • 4 Set
    • 5 Zset
  • 三 Redis 事務
    • 1 樂觀鎖與 watch 命令
    • 2 事務的三個特性
  • 四 Redis 持久化
    • 1 RDB(Redis Database)與寫時復制
    • 2 AOF(Append Only File)
  • 五 主從復制
    • 1 復制原理
    • 2 一主二從
    • 3 薪火相傳
    • 4 哨兵模式
  • 六 常見問題及解決
    • 1 緩存穿透
    • 2 緩存擊穿
    • 3 緩存雪崩
    • 4 解決緩存穿透:布隆過濾器


一 Redis 概述

  • 屬于一種 NoSQL(非關系型數據庫),另一種常用的非關系型數據庫是 MongoDB
  • Redis 的數據都在內存中,并且支持持久化,主要用作備份恢復
  • 除了支持簡單的 key-value 模式,還支持多種數據結構的存儲: string、list、set、hash、zset 等,這些數據類型都支持 push / pop、add / remove 及取交集并集和差集等操作,而且這些操作都是原子性的,但 Redis 事務不具有原子性
  • 一般是作為緩存數據庫輔助持久化的數據庫
  • Redis 不支持自定義數據庫的名字,每個數據庫都以編號命名,開發者必須自己記錄哪些數據庫存儲了哪些數據;Redis 也不支持為每個數據庫設置不同的訪問密碼,所以一個客戶端要么可以訪問全部數據庫,要么連一個數據庫也沒有權限訪問;多個數據庫之間并不是完全隔離的,這些數據庫更像是一種命名空間,而不適宜存儲不同應用程序的數據(比如可以使用0號數據庫存儲某個應用生產環境中的數據,使用1號數據庫存儲測試環境中的數據,但不適宜使用0號數據庫存儲A應用的數據而使用1號數據庫B應用的數據,不同的應用應該使用不同的Redis實例存儲數據)

Redis 為什么是單線程,單線程為什么這么快?

  • 因為 Redis 是基于內存的操作,CPU 不是 Redis 的瓶頸,Redis 的瓶頸最有可能是內存大小或者網絡帶寬。既然單線程容易實現,而且 CPU 不會成為瓶頸,那就順理成章地采用單線程的方案了
  • 在單線程的情況下,處理邏輯更簡單,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗,不存在多進程或者多線程導致的切換而消耗 CPU
  • Redis 采用網絡 I/O 多路復用技術,來保證在多連接的時候系統的高吞吐量

數據存儲結構

  • Redis 的 Db 默認情況下有16個,每個 redisDb 內部包含一個 dict 的數據結構
  • dict 內部包含 ht 的數組,數組個數為2,元素類型為 dictht,主要用于 hash 擴容使用(參考下面的 Hash 擴容)
  • dictht 內部包含 dictEntry 的數組,可以理解就是 hash 桶,然后使用拉鏈法解決沖突
  • dictEntry 當中的 key 和 v 的指針指向的是 redisObject,redisObject 是 Redis server 存儲最原子數據的數據結構,其中的void *ptr 會指向真正的存儲數據結構
typedef struct redisDb {//數據字典,保存著數據庫中的所有鍵值對dict *dict; //過期字典,字典的值為鍵的過期時間,是一個UNIX時間戳dict *expires; //正處于阻塞狀態的鍵dict *blocking_keys;//可以解除阻塞的鍵dict *ready_keys;//正在被 WATCH 命令監視的鍵dict *watched_keys;//失效池,根據對象lru時間戳保存要被淘汰的對象struct evictionPoolEntry *eviction_pool;int id;//數據庫的鍵的平均 TTL ,統計信息long long avg_ttl; } redisDb;typedef struct dict {//類型特定函數dictType *type;//私有數據void *privdata;//哈希表dictht ht[2];//rehash 索引,當rehash不在進行時,值為 -1int rehashidx;//目前正在運行的安全迭代器的數量int iterators; } dict;typedef struct dictht {//哈希表數組dictEntry **table;//哈希表大小unsigned long size;//哈希表大小掩碼,用于計算索引值,總是等于 size - 1unsigned long sizemask;// 該哈希表已有節點的數量unsigned long used; } dictht;typedef struct dictEntry{//鍵void *key;//值union {void *val;uint64_t u64;int64_t s64;} v;//指向下個哈希表節點的指針(拉鏈法解決哈希沖突)struct dictEntry *next; } dictEntry;typedef struct redisObject {// 類型unsigned type:4;// 編碼unsigned encoding:4;// 對象最后一次被訪問的時間unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */// 引用計數int refcount;// 指向實際值的指針void *ptr; } robj;

二 常用數據類型

1 String

參考鏈接

  • 具有二進制安全(binary safe)特性,這意味著它的長度是已知的,不由任何其他終止字符決定的,一個字符串類型的值最多能夠存儲 512 MB 的內容。所有 SDS API 都會以處理二進制的方式來處理 SDS 存放在 buf 數組里的數據,程序不會對其中的數據做任何限制、過濾、或者假設(保證數據在寫入時是什么樣的, 它被讀取時就是什么樣),這也是 SDS 的 buf 屬性稱為字節數組的原因,Redis 不是用這個數組來保存字符, 而是用它來保存一系列二進制數據
  • Redis 實現了SDS(Simple Dynamic String,簡單動態字符串)的抽象類型,它通過存儲額外數據能簡單地得到自身信息:總長度、可用長度等
  • SDS 遵循 C 字符串以空字符結尾的慣例, 保存空字符的 1 字節空間不計算在 SDS 的 len 屬性里面, 并且為空字符分配額外的 1 字節空間(遵循空字符結尾這一慣例的好處是, SDS 可以直接重用一部分 C 字符串函數庫里面的函數)
  • SDS 還被用作緩沖區,比如 AOF 模塊中的 AOF 緩沖區
struct sdshdr {// 記錄 buf 數組中已使用字節的數量// 等于 SDS 所保存字符串的長度int len;// 記錄 buf 數組中未使用字節的數量int free;// 字節數組,用于保存字符串char buf[];};

2 Hash

  • 底層存儲使用 ziplist 或 hashtable(再底層是字典)

當一個哈希對象可以滿足以下兩個條件時,哈希對象會選擇使用ziplist編碼來進行存儲:

1 哈希對象中的所有鍵值對總長度(包括鍵和值)小于64字節(這個閾值可以通過參數hash-max-ziplist-value 來進行控制)
2 哈希對象中的鍵值對數量小于512個(這個閾值可以通過參數hash-max-ziplist-entries 來進行控制)

一旦不滿足這兩個條件中的任意一個,哈希對象就會選擇使用hashtable來存儲

  • 使用拉鏈法解決哈希沖突
  • 可以理解成一個包含了多個鍵值對的集合,一般用于存儲對象

Hash 的擴容機制:漸進式 rehash*

將 rehash 的操作分攤在每一個的訪問中,避免集中式 rehash 可能會導致服務器在一段時間內停止服務
參考鏈接

  • Hash 底層有兩個數組 ht[0] 和 ht[1] ,還有一個 rehashidx 用來控制 rehash 過程

  • 初始默認長度為4,當元素個數與 Hash 表長度一致時(負載因子為1)發生擴容,長度變為原來的二倍;同時 rehashindex 的值設置為0,表示 rehash 工作正式開始

  • 在 rehash 期間,每次對字典執行增刪改查時,還會順帶將 ht[0] 哈希表在rehashindex 索引上的所有鍵值對 rehash 到 ht[1] ,當 rehash 工作完成以后,rehashindex 的值 +1

  • 隨著字典操作的不斷執行,最終會在某一時間段上 ht[0] 的所有鍵值對都會被 rehash 到 ht[1],這時將 rehashindex 的值設置為 -1,表示 rehash 操作結束

  • 在漸進式 rehash 的過程中,如果有增刪改查操作,index 大于 rehashindex,訪問 ht[0] ,否則訪問 ht[1]

  • 3 List

    • 單鍵多值,內容按照插入的順序排序(lpush 和 rpush 的順序不同),可以向頭部或尾部添加數據
    • 底層是快速鏈表 QuickList,每個部分是壓縮鏈表 ZipList(內部是一段連續的內存),所以只需要額外存儲 ZipList 之間的指針

    4 Set

    • 單鍵多值,集合中的元素無序不重復
    • 底層實現為 intset 或 hashtable
    • intset 實現為數組,這個數組以有序、無重復的方式保存集合元素,在有需要時,程序會根據新添加元素的類型,改變這個數組的類型

    5 Zset

    • ZSet 基于跳表實現,是一種有序的數據結構,支持平均 O(logn) 的查詢效率
    • 每個元素都關聯了一個 score,被用來按照從最低分到最高分的方式排序集合中的成員;集合里的成員是唯一的,但是 score 可以重復
    • 底層實現為 ziplist 或 跳表:
      跳表的作用是根據 score 給元素排序,方便獲取指定 score 范圍的元素列表
      每次創建一個新跳表節點的時候,程序都根據冪次定律(power law,越大的數出現的概率越小)隨機生成一個1和32之間的值作為新節點所在的層的高度
      跳表的插入/刪除首先需要執行查找,平均時間復雜度 O(logn)
    • 跳表詳解

    三 Redis 事務

    • Redis 事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。主要作用就是串聯多個命令防止別的命令插隊。
    • 事務操作通過命令 multi - discard / exec 執行
      multi 過程中某個命令出現錯誤,執行時整個的所有命令都會被取消
      exec 階段某個命令出現錯誤,則只有報錯的命令不會被執行

    1 樂觀鎖與 watch 命令

    • 在讀數據時不認為該數據會被更新,不會上鎖
    • 在更新的時候會判斷:在此期間該數據是否被更新,如果被更新則不能執行,需要獲取最新的版本。可以使用版本號等機制
    • 樂觀鎖適用于多讀的應用類型,可以提高吞吐量
    • 如果在事務執行之前被 watch 的 key 被其他命令改動,那么事務將被打斷

    2 事務的三個特性

    • 單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷
    • 沒有隔離級別的概念:隊列中的命令沒有提交之前都不會實際被執行
    • 不保證原子性:事務中除了執行失敗的命令,其它的命令仍然會被執行

    四 Redis 持久化

    1 RDB(Redis Database)與寫時復制

    • 在指定的時間間隔內將內存中的數據集快照寫入磁盤,恢復時將快照文件直接讀到內存里
    • RDB 持久化的流程:主進程不進行任何IO操作,而是創建子進程,子進程將快照先寫入臨時文件,再用這個臨時文件替換上次持久化好的文件
    • 優點:
      適合大規模的數據恢復
      節省磁盤空間,恢復速度快
      對數據完整性和一致性要求不高時,更適合使用
    • 缺點:
      如果 Redis 意外停止,會丟失最后一次快照后的數據

    Redis持久化時可以進行寫操作嗎?

    • BGSAVE 命令的保存工作是由子進程執行的,所以在子進程下創建RDB文件的過程中,Redis 服務器仍然可以繼續處理客戶端的命令請求
    • 子進程是通過 fork 系統調用創建的,剛創建時由于 CopyOnWrite 機制會與父進程共享同一塊地址空間,此時如果父進程收到寫請求,CopyOnWrite 機制就會創建新頁面存放修改的數據,不影響持久化的 RDB
    • 寫時復制,通俗來說是多個調用者同時去請求一個資源數據的時候,有一個調用者需要對當前的數據源進行修改,這個時候系統將會復制一個當前數據源的副本給調用者修改
    • 寫時復制的優勢是,在并發的場景下進行讀操作不需要加鎖

    2 AOF(Append Only File)

    • AOF 的策略是增量保存,以日志的形式來記錄每個寫操作(不記錄讀操作)
    • 對于 AOF 文件,只允許追加但不可以改寫
    • AOF 的持久化流程:
      客戶端的請求寫命令會被追加到 AOF 緩沖區內
      AOF 緩沖區根據 AOF 同步頻率的設置 always / everysec / no 將操作同步到磁盤的 AOF 文件中
      AOF 文件大小超過重寫策略或手動重寫時,對 AOF 文件 Rewrite ,壓縮 AOF 文件容量
      Redis 服務重啟時,會重新加載 AOF 文件中的寫操作,達到數據恢復的目的
    • AOF 同步頻率:
    always始終同步,每次 Redis 的寫入都會立刻記入日志
    everysec每秒同步,每秒記入日志一次
    no不主動進行同步,把同步時機交給操作系統
    • Rewrite:AOF 文件的大小超過所設定的閾值時,Redis 就會啟動 AOF 文件的內容壓縮, 只保留可以恢復數據的 最小指令集。重寫也是 fork 子進程完成的,類似 RDB
    • AOF 優點:
      丟失數據概率更低
    • AOF 缺點:
      比起 RDB 占用更多的磁盤空間,恢復備份速度更慢

    五 主從復制

    • 可以實現:讀寫分離(Master 以寫為主,Slave 以讀為主)、容災恢復

    1 復制原理

  • Slave 啟動成功,連接到 Master 后會發送一個 sync 命令
  • Master 接收到命令后執行持久化,并將持久化的文件發送給 Slave(全量復制)
  • Slave 接收到持久化文件,存盤并加載到內存
  • 后續 Master 繼續將新的所有收集到的修改命令依次傳給 Slave(增量復制)
  • 如果 Slave 重啟,則會從步驟1開始執行
  • 2 一主二從

    • 如果 Master 停止,Slave 不做任何事,仍然承認 Master 但會標記狀態為 down
    • 如果 Slave 停止,重啟后成為另一個 Master,除非再次命令其成為原 Master 的 Slave

    3 薪火相傳

    • 上一個 Slave 可以是下一個 Slave 的 Master,Slave 同樣可以接收其他 Slaves 的連接和同步請求,形成鏈式結構
    • 可以有效減輕 Master 的寫壓力,去中心化降低風險
    • 如果 Master 停止:
      Slave 不做任何事,仍然承認 Master 但會標記狀態為 down
      或者用 slaveof no one 將從機變為主機,“反客為主”成為新的 Master

    4 哨兵模式

    • “反客為主”的自動版:當 Master 停機,從 Slave 中選出新的 Master(根據優先級別:slave-priority 等因素)
    • 需要有 n 個哨兵都檢測到 Master 停機才會執行 Master 更新,n 需要手動設定
    • 原 Master 重啟后會變為 Slave

    六 常見問題及解決

    1 緩存穿透

    • 問題:
      key 對應的數據在數據源并不存在,每次針對此 key 的請求從 Redis 緩存獲取不到,請求都會傳遞到數據源,從而可能壓垮數據源
    • 解決:
      1、對空值緩存,如果一個查詢返回的數據為空(不管是數據是否不存在),仍然把空結果進行緩存,設置過期時間相對短
      2、白名單、布隆過濾器
      3、實時監控,當發現 Redis 的命中率開始急速降低,排查訪問對象和訪問的數據,設置黑名單限制服務

    2 緩存擊穿

    • 問題:
      Redis 中的 某個 key 過期了,但這個 key 又被超高并發地訪問,從后端 DB 加載數據并返回到緩存,這個時候大并發的請求可能會瞬間把后端 DB 壓垮
    • 解決:
      1、實時監控
      2、預先設置熱門數據,在 Redis 高峰訪問之前,把一些熱門數據提前存入到 Redis 里面,加大這些熱門數據 key 的時長
      3、使用鎖,保證 DB 不會承受過高的訪問壓力,但是了降低效率

    3 緩存雪崩

    • 問題:
      和緩存擊穿類似,區別在于,緩存雪崩針對很多 key 緩存,緩存擊穿則是某一個 key
    • 解決:
      1、構建多級緩存架構:nginx 緩存 + redis 緩存 +其他緩存(ehcache 等)
      2、使用鎖或隊列降低 DB 壓力,但降低效率,不適于高并發的情況
      3、記錄緩存數據是否過期(可以在此設置提前量),如果過期(或快過期)會觸發通知另外的線程,后臺更新 key 的緩存
      4、將緩存失效時間分散開

    4 解決緩存穿透:布隆過濾器

    • 一個 二進制向量(或者說位數組)和 一系列 隨機映射函數(哈希函數)兩部分組成的數據結構
    • 對于一個字符串,用所有的哈希函數對其進行計算,將得到的一系列值作為下標,并將位數組對應位置的值設置為1;如果該字符串再次插入,用相同的方法驗證出位數組的對應位置都是1,則說明重復加入
    • 理論情況下添加到集合中的元素越多,誤報的可能性就越大
    • 不同的字符串可能哈希出來的位置相同,這種情況可以適當增加位數組大小,或者調整哈希函數
    • 布隆過濾器認為某個元素存在,小概率會誤判。布隆過濾器認為某個元素不在,那么這個元素一定不在

    總結

    以上是生活随笔為你收集整理的后端学习 - Redis的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。