1. 程式人生 > 資料庫 >redis知識升級

redis知識升級

學會了Redis的基本操作還不夠,再來看看升級部分

1. 資料刪除策略

惰性刪除+定期刪除(預設)

定期刪除:預設是每隔 100ms 就輪詢各個庫隨機抽取一些設定了過期時間的key,檢查其是否過期,如果過期就刪除。每隔100ms就遍歷所有的設定過期時間的 key 的話,是個損耗。

惰性刪除:定期刪除會導致很多過期 key 到了時間並沒有被刪除掉。除非系統去查詢才會刪除。如果靠定期刪除,和沒有走惰性刪除的話會導致一大部分過期資料沒有刪除,這時候就出現了記憶體淘汰機制

2. 記憶體淘汰機制

在資料進入記憶體的時候發現記憶體不夠了,就採用記憶體淘汰機制,不一定淘汰過期的

其配置有:

  • maxmemory:最大可用記憶體

  • maxmemory-samples:每次選取刪除資料的個數

  • maxmemory-policy:刪除策略

    • volatile-lru:從已設定過期時間的資料集(server.db[i].expires)最近最久未使用
    • volatile-lfu:從已設定過期時間的資料集(server.db[i].expires)最近最少使用
    • volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)將要過期的資料淘汰
    • volatile-random:從已設定過期時間的資料集(server.db[i].expires)隨機淘汰
    • allkeys-lru:在全庫資料中(server.db[i].dict),最近最久未使用(這個是最常用的)
    • allkeys-lfu:在全庫資料中(server.db[i].dict),最近最少使用
    • allkeys-random:在全庫資料中(server.db[i].dict)隨機淘汰
    • no-eviction:禁止驅逐資料,也就是說當記憶體不足以容納新寫入資料時,新寫入操作會報錯。這個應該沒人使用

3.限制登入次數功能

判斷使用者是否被限制登入

  • 有:做相應的提示
  • 沒有
    • 登入成功:清除失敗錯誤次數
    • 登入不成功(查詢key是否存在,即是否第一次 錯誤)
      • 第一次錯誤:設次數為1,user:loginCount:fail:使用者名稱進行賦值,同時設定失效期
      • 不是第一次
        • (判斷是否4次,是的話這次加1等於5,限制1小時),user:loginCount:fail:使用者名稱+1
        • 小於第四次,失敗次數加1
// 這裡筆者用了不規範的返回值,返回數值大於10表示被限制登入
public long login(String username, String password) {

    // 先判斷是否被限制,
    if (jedis.exists("user:loginCount:limit:" + username)) {
        return jedis.ttl("user:loginCount:limit:" + username);
    }

    // 然後查詢資料庫返回結果,這裡模擬查詢設資料庫,密碼輸入錯誤
    if (!username.equals(password)) {
        long count = 0;
        // 第一次錯誤,鍵不存在,設定過期時間,10秒內可以錯誤5次
        if (!jedis.exists("user:loginCount:fail:" + username)) {
            jedis.setex("user:loginCount:fail:" + username, 10, "1");
            return 1;
        } else {
            count = jedis.incr("user:loginCount:fail:" + username);
            if (count == 5) {
                jedis.setex("user:loginCount:limit:" + username, 10, ""); // 設定登入限制時間
                jedis.del("user:loginCount:fail:" + username);  // 刪除登入次數,因為被登入限制
            }
            return count; // 返回已嘗試次數
        }
    }
    // 密碼正確,清除錯誤次數
    jedis.del("user:loginCount:fail:" + username);
    return 0L; // 表示密碼正確
}

4.訊息訂閱

subscribe channel[channel]  訂閱頻道
psubscribe pattern[pattern]  訂閱匹配的頻道

publish channel message  將訊息傳送到指定頻道

unsubscribe [channel | channel]  退訂頻道
punsubscribe [pattern | pattern]  退訂匹配的頻道

應用場景:

  • 構建實時訊息系統
    • 普通的即時聊天,群聊
    • 粉絲訂閱之後,釋出新文章的訊息推送,公眾號模式

5. 快取雪崩

Redis過期是惰性刪除+定期刪除,如果快取資料設定的過期時間相同,那麼當這些資料全部過期時,就會在這段時間全部請求走資料庫中。簡單就是Redis某段時間,或直接掛了,請求全走資料庫,那麼導致資料庫支援不住而宕機。

解決方法:

  • 給過期時間加上一個隨機值(資料分類過期),減少大幅度同一時間過期問題
  • 事前:可以用叢集或高可用來儘量避免
  • 事發中:使用本地快取+限流(比如驗證碼)
  • 事發後:redis的持久化,從硬碟上恢復資料

6. 快取穿透

大量查詢不存在的資料,導致每次返回空,Redis不起作用,相當於直接訪問資料庫。

解決方法:

  • 請求引數的校驗,使之不能進入到redis,更不要說資料庫了
  • 查詢不存在資料時,也將這個資料放入Redis,下次訪問可以從裡面獲取,當然要設定過期時間
  • 布隆過濾器、限流演算法、令牌桶

7. 快取與資料庫的讀寫一致

讀:

  • 如果查詢資料快取裡有,直接返回
  • 快取裡沒有,去資料庫查詢,將查詢結果放入快取,並返回給客戶端
    對於更新時:會導致快取資料和資料庫不一致,可以先修改資料庫,再修改快取。或者先修改快取,再修改資料庫,重點在於我們要是這兩個操作突顯原子性,這樣資料才不會出錯

操作快取:可以選擇更新和刪除,但一般採取刪除操作。因為刪除相對比更新更直接簡單,如果每次更新資料庫都要更新快取,如果頻繁更新的話,會頻繁修改一定程度損耗效能,不如直接刪除,再次讀取時快取沒有就到資料庫查詢

先更新資料庫再刪除快取:也有概率出錯但很低,比如快取失效,執行緒A查詢資料庫得到舊值,期間執行緒B將新值寫入資料庫,執行緒B刪除快取,然後執行緒A才將舊址寫入快取。刪除快取失敗策略是,不斷重試刪除,直到成功。

先刪除快取,再更新資料庫:如果原子性被破壞了,第一步成功刪除快取,第二步更新資料庫失敗,那麼資料庫資料是一致的,如果第一步刪除快取失敗了,可以直接返回錯誤,資料庫資料和快取還是一致。

但是:執行緒A刪除了快取,期間執行緒B查詢會走資料庫得到舊值,並把舊值寫入快取,然後執行緒A才將新值寫入資料庫,導致資料不一致,解決方法:將刪除快取,修改資料庫,讀取快取等操作擠壓到佇列裡,實現序列化。

二者對比:

前者:高併發下表現優異,原子性破壞時不好

後者:高併發下序列,原子性破壞時優異

8. 持久化

Redis是基於記憶體的,萬一遇到宕機那麼記憶體中的資料則會丟失,而持久化則是將記憶體中的資料儲存到硬碟防止丟失。Redis支援兩種方式的持久化方式:RDB、AOF

RDB

建立記憶體中資料的二進位制快照來實現持久化,可對快照備份或把快照複製到其他伺服器使之成為伺服器副本,還可以將快照留在原地以便重啟伺服器載入使用,預設持久化檔案為dump.rdb

save命令執行一次就儲存一次,若資料量過大,加入單執行緒任務執行會阻塞任務,所以不建議使用

bgsave命令後臺執行,fork子程序來進行持久化,成功後記錄到日誌中

自動執行持久化:需在redis.conf中配置,執行多少次非查詢操作就儲存

  • save 900 1
  • save 300 10
  • save 60 10000

優點:

  • 緊湊壓縮的二進位制檔案,儲存效率高
  • 儲存的是某個時間點的快照,適合資料備份,全量複製
  • 恢復資料速度比AOF快很多
  • 應用:每隔一段時間執行bgsave備份,用於災難恢復

缺點:

  • 不能實時持久化,間隔時間段的資料可能丟失
  • fork子程序,記憶體額外消耗
  • 資料量大時,持久化速度慢,全部資料持久化
AOF

將除查詢外的命令追加儲存到AOF檔案中,重啟時重新執行AOF檔案中的命令達到恢復資料的目的,是主流的持久化方式,預設沒有開啟,持久化檔案為appendonly.aof

持久化資料的三種策略(寫命令重新整理到aof命令緩衝區)

  • always 每次
  • everysec 每秒
  • no 系統控制

配置檔案

  • appendonly yes|no
  • appendfsync always|everysec|no
    AOF重寫機制

將Redis程序內的資料轉化為寫命令同步到新的AOF檔案的過程,即將對同一個資料的若干命令的執行結果合併成一條操作指令(忽略超時資料,忽略無效指令刪除等,合併重複指令),可降低檔案大小,提高持久化與恢復效率,其也有重寫緩衝區,下面是重寫命令:

  • bgrewriteaof 手動重寫
  • auto-aof-rewrite-min-size size 配置自動重寫(當aof快取了多少)
  • auto-aof-rewrite-percentage percentage 配置自動重寫(%)
    在這裡插入圖片描述
    優點:
  • AOF持久化的實時性更好
  • 持久化速度快,追加命令

缺點:

  • 因為記錄命令,持久化檔案大
  • 恢復資料慢,要執行命令

9. 事務

Redis 通過 MULTI、EXEC命令來實現事務(transaction)功能,其事務實質是將多個命令打包後一次性地按順序執行,期間不會執行其他客戶端的命令請求,簡單來說是命令序列化執行功能,沒有回滾功能。關係型資料庫用 ACID 檢驗事務功能的可靠性和安全性。而 Redis 中,事務總是具有原子性、一致性、隔離性,當持久化時,事務也具有永續性

MULTI:開啟事務,建立佇列,命令來了加入佇列
EXEC:執行事務,佇列中執行命令,完後銷燬佇列
DISCARD:取消事務,銷燬佇列

流程:

  • 開始事務
  • 命令入隊,命令不會立即執行
  • 執行事務,按上面入隊順序執行

舉例轉賬:multi開始事務,exec執行事務

set account:a 100
set account:b 100

multi

get account:a
"QUEUED"
get account:b
"QUEUED"
decrby account:a 10
"QUEUED"
incrby account:b 10
"QUEUED"

exec
 1)  "100"
 2)  "100"
 3)  "90"
 4)  "110"

Redis的事務是沒有回滾功能的,在進行事務的時候,只有報錯的命令不會執行(例外:語法錯誤整個佇列都不會執行,型別錯誤會執行),其他命令都會執行。只是單純的執行事務的時候不會有其他命令加塞

場景:動物園給熊貓投喂竹子,這裡有很多個飼養員,只要其中一個投餵了,其他飼養員就不用再投喂,使用watch解決

WATCH:執行事務前,監視Key是否被修改,若有則取消事務,返回nil(針對同時修改用處大)
UNWATCH:取消監視
watch eat
// 中間可以執行其他命令,必須在開啟事務前watch
multi
set panda 1
exec

10. 主從複製

  • 建立連線

在這裡插入圖片描述

方式1:
客戶端傳送 slaveof <masterip> <masterport>
		  auth <password>

方式2:
啟動式伺服器引數 redis-server -slavveof <masterip> <masterport>

方式3
slave配置檔案:slaveof <masterip> <masterport>
masterauth 123456

主從複製低版本不能複製高版本的資料,筆者在這裡花了挺久時間才找出問題所在

  • 資料同步
    在這裡插入圖片描述
  • 命令傳播
    在這裡插入圖片描述
  • 心跳機制
    進入命令傳播階段時,master和slave的資訊交換使用心跳機制維護,實現雙方連線保持線上

主從複製的作用

  • master寫,slave讀,提高讀寫負載能力
  • 負載均衡,基於主從結構,配合讀寫分離
  • 故障恢復,當master故障時,由slave提供服務,實現快速恢復
  • 資料冗餘,實現資料熱備份
  • 高可用基礎

11. 哨兵模式Sentinel(主備切換)

哨兵是一個分散式系統,也是一臺redis伺服器,對於主從結構中的每臺伺服器進行監控,出現故障時投票機制選擇新的master並將所有slave連線到新的master,演示搭建三個哨兵和1主2從

sentinel.conf的配置檔案

monitor mymaster 127.0.0.1 6379 2  // 監聽主伺服器,自定義名字,後面2表示多少個哨兵認為宕機才有效
down-after-millisecoinds mymaster 30000  // 多久才認為宕機
parallel-syncs mymaster 1  // 命令傳播
failover-timeout mymaster 180000  // 複製超時時間

先啟動1主2從,再啟動哨兵

redis-sentinel sentinel-26379.conf
redis-sentinel sentinel-26380.conf
redis-sentinel sentinel-26381.conf

啟動哨兵後,每臺伺服器的配置都會有對應的修改

哨兵模式的流程:

  • 1.監控階段
    • 獲取各sentinel的狀態(是否線上)
    • 獲取master的狀態
      • master屬性
        • runId
        • role:master
      • 各個slave的詳細資訊
    • 獲取所有slave的狀態(根據master中的slave資訊)
    • slave屬性
      • runId
      • role:slave
      • master_host、master_port
      • offset
  • 2.通知階段
    • 不停地用ping去測試
  • 3.故障轉移
    • 發現問題
    • 競選負責人
    • 優選新master
      • 線上的
      • 響應快的
      • 與原master斷開時間最短的
      • 優先原則:優先順序、offset、runId
    • 新master上線,其他slave切換master,原master作為slave故障恢復後連線
      在這裡插入圖片描述
      在這裡插入圖片描述

12. 叢集

分散單臺伺服器的訪問壓力,即負載均衡

其底層儲存原理:

  • 將key進行兩次演算法運算得key應該儲存的位置(CRC16(key) % 16384)
  • 將所有Redis伺服器的總儲存空間計劃切割成16384份,每臺主機儲存一部分
  • 加Redis伺服器的話,原本伺服器將槽分給新的伺服器、刪除伺服器則相反
  • 叢集內部通訊:記錄各伺服器槽範圍,一次命中OK,否則伺服器查詢通訊錄讓請求去對應槽伺服器(最多2次命中)
  • 內部通訊這樣就不用虛擬IP了

配置3主3從(官方自帶,每個伺服器都要配置)

cluster-enabled yes  // 開啟叢集節點
cluster-config-file nodes-6379 // 叢集配置檔案
cluster-node-timeout 10000  // 宕機時間

src下有redis-trib.rb(需要Ruby、Gem支援)

./redis-trib.rb create --replicas 1 // 其中1表示1主拖1從
127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381
127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384

客戶端啟動

redis-cli -c
// 不然key和伺服器沒有對應上會報錯,讓你去連對應的伺服器。加了配置會幫你重定向

故障處理:

  • 從伺服器下線,各個節點能收到通知,對應master節點會標記一下宕機從伺服器
  • 主伺服器下線,對應從伺服器重試,失敗就執行上面的主從切換,切換的從頂替了主叢集。原主上線變成slave

13. 併發競爭Key問題

所謂 Redis 的併發競爭 Key 的問題也就是多個系統同時對一個 key 進行操作,但是最後執行的順序和我們期望的順序不同,這樣也就導致了結果的不同

推薦一種方案:分散式鎖(zookeeper 和 redis 都可以實現分散式鎖)。(如果不存在 Redis 的併發競爭 Key 問題,不要使用分散式鎖,這樣會影響效能)

參考: https://www.bilibili.com/video/BV1CJ411m7Gc?p=101