1. 程式人生 > >redis 熱點資料 快取

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 適用場景

  1. 快取 將熱點資料放到記憶體中
  2. 訊息佇列 List 型別是雙向連結串列,很適合用於訊息佇列
  3. 計數器 快速、頻繁讀寫操作;string的單線性自增減 ++ --
  4. 共同好友關係 set 交集運算,很容易就可以知道使用者的共同好友
  5. 排名 zset有序集合

鍵的過期時間 作用:清理快取資料

為鍵設定過期時間,過期,自動刪除該鍵

對於散列表這種容器,只能為整個鍵設定過期時間(整個散列表),而不能為鍵裡面的單個元素設定過期時間。

事務

Redis最簡單的事務實現方式是使用MULTI和EXEC命令將事務操作包圍起來

MULTI 和 EXEC 中的操作將會一次性發送給伺服器,這種方式稱為流水線,減少客戶端與伺服器之間的網路通訊次數,提升效能

redis事務三階段:

  1. 開啟:以MULTI開始一個事務
  2. 入隊:將多個命令入隊到事務佇列中,接到這些命令並不會立即執行,而是放到等待執行的事務佇列裡面
  3. 執行:由EXEC命令觸發事務

redis事務三大特性:

  1. 單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端傳送來的命令請求所打斷。
  2. 沒有隔離級別的概念:佇列中的命令沒有提交之前都不會實際的被執行,因為事務提交前任何指令都不會被實際執行,也就不存在”事務內的查詢要看到事務裡的更新,在事務外查詢不能看到”這個讓人萬分頭痛的問題
  3. 不保證事務原子性:redis同一個事務中如果有一條命令執行失敗,其後的命令仍然會被執行,沒有回滾
    1. 官方認為,只有當被呼叫的Redis命令有語法錯誤時,這條命令才會執行失敗
    2. 保證生產環境的簡單、快速

通過WATCH命令實現CAS操作,實現樂觀鎖;(讀鎖和寫鎖屬於悲觀鎖)

Redis使用WATCH命令實現事務的“檢查再設定”(CAS)行為。

作為WATCH命令的引數的鍵會受到Redis的監控,Redis能夠檢測到它們的變化。在執行EXEC命令之前,如果Redis檢測到至少有一個鍵被修改了,那麼整個事務便會中止執行,然後EXEC命令會返回一個Null值,提醒使用者事務執行失敗

持久化

快照持久化

將某個時間點的所有資料都存放到硬碟上

可以將快照複製到其它伺服器從而建立具有相同資料的伺服器副本

缺點:故障可能丟失最後一次建立快照之後的資料;如果資料量很大,儲存快照的時間也會很長。

AOF 持久化 將寫命令新增到 AOF 檔案(Append Only File)的末尾

寫命令新增到 AOF 檔案時,有以下同步選項:

選項

同步頻率

always

每個寫命令都同步

everysec

每秒同步一次

no

讓作業系統來決定何時同步

  1. always: 嚴重減低伺服器的效能;
  2. everysec :比較合適,保證系統奔潰時只會丟失一秒左右的資料,並且 Redis 每秒執行一次同步對伺服器效能幾乎沒有任何影響;
  3. no :不能給效能帶來提升,且會增加奔潰時資料丟失量

隨著伺服器寫請求的增多,AOF 檔案會越來越大;Redis 提供了一種將 AOF 重寫的特性,能夠去除 AOF 檔案中的冗餘寫命令。

對硬碟的檔案進行寫入時,寫入的內容首先會被儲存到緩衝區,作業系統決定何時寫

使用者可以呼叫 file.flush() 方法請求儘快將緩衝區儲存的資料同步到硬碟

redis主從複製 分散式資料同步方式

slaveof host port 命令來讓一個伺服器成為另一個伺服器的從伺服器。

一個從伺服器只能有一個主伺服器

從伺服器連線主伺服器的過程

  1. 主伺服器建立快照檔案,傳送給從伺服器。同時記錄其間執行的寫命令,傳送完畢後,開始向從伺服器傳送寫命令;
  2. 從伺服器丟棄所有舊資料,載入主伺服器的快照檔案,然後開始接受主伺服器發來的寫命令;
  3. 主伺服器每執行一次寫命令,就向從伺服器傳送相同的寫命令

主從鏈 建立一箇中間層來分擔主伺服器的複製工作

  1. 隨著負載不斷上升,主伺服器可能無法很快地更新所有從伺服器
  2. 重新連線和重新同步從伺服器將導致系統超載
  3. 中間層的伺服器是最上層伺服器的從伺服器,又是最下層伺服器的主伺服器

redis 主伺服器 故障 處理

當主伺服器出現故障時,Redis 常用的做法是新開一臺伺服器作為主伺服器,具體步驟如下:假設 A 為主伺服器,B 為從伺服器,當 A 出現故障時,讓 B 生成一個快照檔案,將快照檔案傳送給 C,並讓 C 恢復快照檔案的資料。最後,讓 B 成為 C 的從伺服器。

分片 叢集 讀併發

資料劃分為多個部分,可以將資料儲存到多臺機器裡,作用:負載均衡、線性級別的效能提升

分片方式:

  1. 客戶端程式碼分片
  2. Redis Sharding,對Redis資料的key進行hash,相同的key到相同的節點上
  3. 一致性雜湊演算法
  4. 代理伺服器分片 輪詢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新資料定時同步到資料庫過程:

  1. 定時任務定時同步redis與資料庫的資料,
    1. 資料庫裡儲存著原始資料,通過資料庫的資料和redis對比,得出需要更新的資料

2.在更新過程中,redis的資料還在增長

    1. 需先讀redis的資料,記下時間;
    1. 再查詢指定時間段裡的資料庫的資料;
    2. 再用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"