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


1. RDB

創建內存中數據的二進制快照來實現持久化,可對快照備份或把快照復制到其他服務器使之成為服務器副本,還可以將快照留在原地以便重啟服務器加載使用,默認持久化文件為dump.rdb


save命令執行一次就保存一次,若數據量過大,加入單線程任務執行會阻塞任務,所以不建議使用

bgsave命令后台運行,fork子進程來進行持久化,成功后記錄到日志中

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

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

優點:

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

缺點:

  • 不能實時持久化,間隔時間段的數據可能丟失
  • fork子進程,內存額外消耗
  • 數據量大時,持久化速度慢,全部數據持久化


2. 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. 主從復制

repl-backlog-size 設置指令緩沖區
slave-server-stale-data yes|no  slave關閉寫功能

  • 建立連接

方式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




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM