redis 熱點資料 快取
Redis 是什麼、特點
非關係型(NoSQL)記憶體鍵值資料庫
五種型別資料型別為:字串、列表、散列表,集合、有序集合
記憶體中資料持久化
使用複製來擴充套件讀效能:複製到多臺伺服器、提高讀效能和可用性
使用分割槽來擴充套件寫效能【hash一致性演算法】:當資料量大的時候,把資料分散存入多個數據庫中,減少單節點的連線壓力
特點
- 完全基於記憶體
- 資料結構簡單,對資料操作也簡單
- 使用多路 I/O 複用模型
多路 I/O 複用模型是利用select、poll、epoll可以同時監察多個流的 I/O 事件的能力,在空閒的時候,會把當前執行緒阻塞掉,當有一個或多個流有I/O事件時,就從阻塞態中喚醒,於是程式就會輪詢一遍所有的流(epoll是隻輪詢那些真正發出了事件的流),並且只依次順序的處理就緒的流,這種做法就避免了大量的無用操作
Redis 的五種基本型別
資料型別 |
可以儲存的值 |
操作 |
STRING |
字串、整數或者浮點數 |
字串操作 對整數和浮點數執行自增、自減操作 |
LIST |
連結串列,維護順序 |
兩端壓入、彈出元素;保持順序 讀取單個或者多個元素 刪除、保留範圍內元素 |
SET |
無序集合 |
新增、隨機獲取、移除單個元素 檢查一個元素是否存在於集合中 計算交集、並集、差集 |
HASH |
包含鍵值對的無序散列表 |
新增、獲取、移除單個鍵值對 獲取所有鍵值對 檢查某個鍵是否存在 |
ZSET |
有序集合,增加了一個權重引數score,集合中的元素能按score進行有序排列 |
新增、獲取、刪除元素個元素 根據分值範圍或者成員來獲取元素 計算一個鍵的排名 |
Redis 適用場景
- 快取 將熱點資料放到記憶體中
- 訊息佇列 List 型別是雙向連結串列,很適合用於訊息佇列
- 計數器 快速、頻繁讀寫操作;string的單線性自增減 ++ --
- 共同好友關係 set 交集運算,很容易就可以知道使用者的共同好友
- 排名 zset有序集合
鍵的過期時間 作用:清理快取資料
為鍵設定過期時間,過期,自動刪除該鍵
對於散列表這種容器,只能為整個鍵設定過期時間(整個散列表),而不能為鍵裡面的單個元素設定過期時間。
事務
Redis最簡單的事務實現方式是使用MULTI和EXEC命令將事務操作包圍起來
MULTI 和 EXEC 中的操作將會一次性發送給伺服器,這種方式稱為流水線,減少客戶端與伺服器之間的網路通訊次數,提升效能
redis事務三階段:
- 開啟:以MULTI開始一個事務
- 入隊:將多個命令入隊到事務佇列中,接到這些命令並不會立即執行,而是放到等待執行的事務佇列裡面
- 執行:由EXEC命令觸發事務
redis事務三大特性:
- 單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端傳送來的命令請求所打斷。
- 沒有隔離級別的概念:佇列中的命令沒有提交之前都不會實際的被執行,因為事務提交前任何指令都不會被實際執行,也就不存在”事務內的查詢要看到事務裡的更新,在事務外查詢不能看到”這個讓人萬分頭痛的問題
- 不保證事務原子性:redis同一個事務中如果有一條命令執行失敗,其後的命令仍然會被執行,沒有回滾
- 官方認為,只有當被呼叫的Redis命令有語法錯誤時,這條命令才會執行失敗
- 保證生產環境的簡單、快速
通過WATCH命令實現CAS操作,實現樂觀鎖;(讀鎖和寫鎖屬於悲觀鎖)
Redis使用WATCH命令實現事務的“檢查再設定”(CAS)行為。
作為WATCH命令的引數的鍵會受到Redis的監控,Redis能夠檢測到它們的變化。在執行EXEC命令之前,如果Redis檢測到至少有一個鍵被修改了,那麼整個事務便會中止執行,然後EXEC命令會返回一個Null值,提醒使用者事務執行失敗
持久化
快照持久化
將某個時間點的所有資料都存放到硬碟上
可以將快照複製到其它伺服器從而建立具有相同資料的伺服器副本
缺點:故障可能丟失最後一次建立快照之後的資料;如果資料量很大,儲存快照的時間也會很長。
AOF 持久化 將寫命令新增到 AOF 檔案(Append Only File)的末尾
寫命令新增到 AOF 檔案時,有以下同步選項:
選項 |
同步頻率 |
always |
每個寫命令都同步 |
everysec |
每秒同步一次 |
no |
讓作業系統來決定何時同步 |
- always: 嚴重減低伺服器的效能;
- everysec :比較合適,保證系統奔潰時只會丟失一秒左右的資料,並且 Redis 每秒執行一次同步對伺服器效能幾乎沒有任何影響;
- no :不能給效能帶來提升,且會增加奔潰時資料丟失量
隨著伺服器寫請求的增多,AOF 檔案會越來越大;Redis 提供了一種將 AOF 重寫的特性,能夠去除 AOF 檔案中的冗餘寫命令。
對硬碟的檔案進行寫入時,寫入的內容首先會被儲存到緩衝區,作業系統決定何時寫
使用者可以呼叫 file.flush() 方法請求儘快將緩衝區儲存的資料同步到硬碟
redis主從複製 分散式資料同步方式
slaveof host port 命令來讓一個伺服器成為另一個伺服器的從伺服器。
一個從伺服器只能有一個主伺服器
從伺服器連線主伺服器的過程
- 主伺服器建立快照檔案,傳送給從伺服器。同時記錄其間執行的寫命令,傳送完畢後,開始向從伺服器傳送寫命令;
- 從伺服器丟棄所有舊資料,載入主伺服器的快照檔案,然後開始接受主伺服器發來的寫命令;
- 主伺服器每執行一次寫命令,就向從伺服器傳送相同的寫命令
主從鏈 建立一箇中間層來分擔主伺服器的複製工作
- 隨著負載不斷上升,主伺服器可能無法很快地更新所有從伺服器
- 重新連線和重新同步從伺服器將導致系統超載
- 中間層的伺服器是最上層伺服器的從伺服器,又是最下層伺服器的主伺服器
redis 主伺服器 故障 處理
當主伺服器出現故障時,Redis 常用的做法是新開一臺伺服器作為主伺服器,具體步驟如下:假設 A 為主伺服器,B 為從伺服器,當 A 出現故障時,讓 B 生成一個快照檔案,將快照檔案傳送給 C,並讓 C 恢復快照檔案的資料。最後,讓 B 成為 C 的從伺服器。
分片 叢集 讀併發
資料劃分為多個部分,可以將資料儲存到多臺機器裡,作用:負載均衡、線性級別的效能提升
分片方式:
- 客戶端程式碼分片
- Redis Sharding,對Redis資料的key進行hash,相同的key到相同的節點上
- 一致性雜湊演算法
- 代理伺服器分片 輪詢round-bin
資料淘汰策略 6 種
可設定記憶體最大使用量,超出時淘汰, 淘汰策略。
策略 |
描述 |
volatile-lru |
從已設定過期時間的資料集中挑選最近最少使用的資料淘汰 |
volatile-ttl |
從已設定過期時間的資料集中挑選將要過期的資料淘汰 |
volatile-random |
從已設定過期時間的資料集中任意選擇資料淘汰 |
allkeys-lru |
從所有資料集中挑選最近最少使用的資料淘汰;最常用的熱點資料快取策略 |
allkeys-random |
從所有資料集中任意選擇資料進行淘汰 |
no-envicition |
禁止驅逐資料 |
快取熱點資料,啟用 allkeys-lru 淘汰策略,
一個簡單的論壇系統分析
該論壇系統功能如下:
- 可以釋出文章;
- 可以對文章進行點贊;
- 在首頁可以按文章的釋出時間或者文章的點贊數進行排序顯示;
文章資訊 HASH 來儲存
文章包括標題、作者、贊數等資訊,在 Redis 中使用 HASH 來儲存每種資訊以及其對應的值的對映
Redis 使用名稱空間的方式來實現類似表的功能、名稱空間可以擴充套件樹的深度 set test1:test2:test3 123 類似json
鍵名的前面部分儲存空間名,後面部分儲存空間 ID,整個組成Hash的健名
使用【冒號 : 】分隔。例如下面的 HASH 的鍵名為 article:92617,其中 article 為名稱空間,ID 為 92617。
點贊功能
建立文章的已投票使用者集合,set交集操作檢查是否已點過贊
點贊 votes 欄位進行加 1 操作
設定一週的過期時間,過後就不能再點贊
對文章進行排序 zset
為建立一個文章釋出時間的有序集合和一個文章點贊數的有序集合
redis與資料庫的同步 資料一致
一、一致性要求高場景,實時同步方案,即查詢redis,若查詢不到再從DB查詢,儲存到redis;
更新redis時,先更新資料庫,再將redis內容設定為過期(建議不要去更新快取內容,直接設定快取過期),再用ZINCRBY增量修正redis資料
二、併發程度高的,採用非同步佇列的方式,採用kafka等訊息中介軟體處理訊息生產和消費
三、阿里的同步工具canal,實現方式是模擬mysql slave和master的同步機制,監控DB bitlog的日誌更新來觸發redis的更新,解放程式設計師雙手,減少工作量
四、利用mysql觸發器的API進行程式設計,c/c++語言實現,學習成本高。
redis新資料定時同步到資料庫過程:
- 定時任務定時同步redis與資料庫的資料,
- 資料庫裡儲存著原始資料,通過資料庫的資料和redis對比,得出需要更新的資料
2.在更新過程中,redis的資料還在增長
-
- 需先讀redis的資料,記下時間;
-
- 再查詢指定時間段裡的資料庫的資料;
- 再用ZINCRBY增量修正redis資料,而不是直接用ZADD覆蓋redis資料
熱資料與Mysql的同步編碼實現 資料庫上鎖
熱點資料(經常會被查詢,但是不經常被修改或者刪除的資料),首選是使用redis快取
用spring的AOP來構建redis快取的自動生產和清除,過程如下:
- Select 資料庫前查詢redis,有的話使用redis資料,放棄select 資料庫,沒有的話,select 資料庫,然後將資料插入redis
- update或者delete 資料庫資料
- 高併發的情況下:先對資料庫加鎖,再刪除redis
- 查詢redis是否存在該資料,若存在則先對資料庫加行鎖,再刪除redis,再update或者delete資料庫中資料
- update或者delete redis,先更新資料庫,再將redis內容設定為過期(建議不要去更新快取內容,直接設定快取過期)
出錯場景:update先刪掉了redis中的該資料,這時另一個執行緒執行查詢,發現redis中沒有,瞬間執行了查詢SQL,並且插入到redis
使用案例
1.計數器 string
單執行緒,避免併發問題,保證不會出錯,毫秒級效能
命令:INCRBY incrby
2.佇列 list 簡單訊息佇列、使用者第幾個訪問、新聞列表排序
由於redis把資料新增到佇列是返回新增元素在佇列的第幾位,所以可以做判斷使用者是第幾個訪問這種業務
新聞列表頁面最新的新聞列表,redis的 LPUSH命令構建List
3.線上狀態、簽到(大資料處理)
幾億使用者系統的簽到,去重登入次數統計,使用者是否線上狀態
setbit、getbit、bitcount命令
原理是:
redis內構建一個足夠長的陣列,每個陣列元素只能是0和1兩個值
陣列的下標index用來表示我們上面例子裡面的使用者id
4.hash實現冪等性請求
- (hash實現冪等性請求)驗證前端的重複請求,通過redis進行過濾:每次請求將request ip、引數、介面等hash作為key儲存redis,設定多長時間有效期,然後下次請求過來的時候先在redis中檢索有沒有這個key,進而驗證是不是一定時間內過來的重複提交
5.秒殺系統(防止超賣),單執行緒特徵,自增,無併發問題 string
6.全域性增量ID生成 生成全域性唯一商品序列號、插入資料重複問題
7.排行榜 zrevrank 檢視前n名 ZRANGE 檢視所有排名 O(log(N))
誰得分高誰排名往上。命令:ZADD(有序集)
給Alice投票 redis> zincrby vote_activity 1 Alice "1"
給Bob投票 redis> zincrby vote_activity 1 Bob "1"
給Alice投票 redis> zincrby vote_activity 1 Alice "2"
檢視Alice投票數 redis> zscore vote_activity Alice ----"2"
獲取Alice排名(從高到低,zero-based ) redis> zrevrank vote_activity Alice (integer) 0
獲取前10名(從高到低) redis> zrevrange vote_activity 0 9 1) "Alice" 2) "Bob"
獲取前10名及對應的分數(從高到低) redis> zrevrange vote_activity 0 9 withscores "Alice" "2" "Bob" "1"
獲取總參與選手數 redis> zcard vote_activity (integer) 2
score相同,排序邏輯是按照key的字母序排序,同分數情況下按時間排序,key加上時間戳字首
通過ZRANK可以快速得到使用者的排名
通過ZRANGE可以快速得到TOP N的使用者列表,它們的複雜度都是O(log(N)),
STRING
> set hello world OK > get hello "world" > del hello (integer) 1 > get hello (nil)
LIST
> rpush list-key item (integer) 1 > rpush list-key item2 (integer) 2 > rpush list-key item (integer) 3 > lrange list-key 0 -1 1) "item" 2) "item2" 3) "item" > lindex list-key 1 "item2" > lpop list-key "item" > lrange list-key 0 -1 1) "item2" 2) "item"
SET
> sadd set-key item (integer) 1 > sadd set-key item2 (integer) 1 > sadd set-key item3 (integer) 1 > sadd set-key item (integer) 0 > smembers set-key 1) "item" 2) "item2" 3) "item3" > sismember set-key item4 (integer) 0 > sismember set-key item (integer) 1 > srem set-key item2 (integer) 1 > srem set-key item2 (integer) 0 > smembers set-key 1) "item" 2) "item3"
HASH
> hset hash-key sub-key1 value1 (integer) 1 返回是否存在該鍵值 > hset hash-key sub-key2 value2 (integer) 1 > hset hash-key sub-key1 value1 (integer) 0 查詢不到該鍵值 > hgetall hash-key //查詢所有鍵值 1) "sub-key1" 2) "value1" 3) "sub-key2" 4) "value2" > hdel hash-key sub-key2 //刪除鍵 (integer) 1 > hdel hash-key sub-key2 (integer) 0 > hget hash-key sub-key1 //根據鍵,查詢值 "value1"
ZSET Sorted Set
SkipList + HashTable
> zadd zset-key 728 member1 (integer) 1 > zadd zset-key 982 member0 (integer) 1 > zadd zset-key 982 member0 (integer) 0 > zrange zset-key 0 -1 withscores 1) "member1" 2) "728" 3) "member0" 4) "982" > zrangebyscore zset-key 0 800 withscores 1) "member1" 2) "728" > zrem zset-key member1 (integer) 1 > zrem zset-key member1 (integer) 0 > zrange zset-key 0 -1 withscores 1) "member0" 2) "982"