系統命令
shutdown 正常關閉伺服器
redis-server 啟動伺服器
redis-cli 客戶端連線伺服器
flushall 刪庫跑路,一般不這麼做
REDIS 持久化 RDB AOF區別
RDB:[Redis Database] 在指定時間間隔把記憶體中的資料快照寫入磁碟,之後可以備份快照,或者複製到其他伺服器建立相同副本,或者伺服器重啟也會用到這個快照恢復資料,預設持久化方式
觸發時機
手動執行save和bgsave時
配置檔案 設定 save <seconds> <changes>,自動間隔執行
主從複製時
執行flushall時
執行shutdown時
save
阻塞redis伺服器程式,完成之前伺服器都是炸的
bgsave
主程式會fork一個子程式出來建立RDB,先把資料寫入臨時檔案,再用二進位制壓縮並替換之前的RDB檔案,除了fork的時候會堵塞一下下,其餘全程主程式不受影響繼續工作
RDB檔案是採用LZF演算法壓縮的二進位制檔案,耗時但是體積小,可以關閉
config set rdbcompression no
##redis.conf配置檔案 save <seconds> <changes>
`save 900 1` 當時間到900秒時,如果至少有1個key發生變化,就會自動觸發`bgsave`命令建立快照
`save 300 10` 當時間到300秒時,如果至少有10個key發生變化,就會自動觸發`bgsave`命令建立快照
`save 60 10000` 當時間到60秒時,如果至少有10000個key發生變化,就會自動觸發`bgsave`命令建立快照
----------------
AOF:[Append Only File] 預設沒有開啟,開啟後每執行一條寫命令就會記錄到aof_buf快取,隨後寫入AOF檔案末尾,且根據配置定時重寫壓縮檔案,多條合併成一條
##redis.conf配置檔案
appendonly yes //是否開啟
appendfilename "appendonly.aof" //日誌名稱
appendfsync always/everysec/no 每次/每秒/作業系統自行決定,預設是everysec
no-appendfsync-on-rewrite no //重寫期間是否同步
auto-aof-rewrite-percentage 100 //日誌檔案如果增長100%觸發重寫
auto-aof-rewrite-min-size 64mb//日誌檔案大於64mb時,才會觸發重寫,權重比上面大
AOF重寫
手動觸發bgrewriteaof 和 bgsave 同一個原理,這裡按下不表
重寫aof檔案的操作,並沒有讀取舊的aof檔案,而是將整個記憶體中的資料庫內容用命令的方式重寫了一個新的aof檔案,跟RDB相似,RDB存的是二進位制壓縮資料,AOF存的是日誌
混合持久化
aof-use-rdb-preamble yes //開啟,預設關閉
bgrewriteaof時fork出的子程式先將資料以RDB方式寫入aof檔案,然後在將重寫緩衝區的增量命令以AOF方式寫入到檔案,寫入完成後通知主程式更新統計資訊,並將新的含有RDB格式和AOF格式的AOF檔案替換舊的的AOF檔案
重啟的時候如果開啟了AOF模式,則載入AOF檔案,否則載入RDB
RESP 協議 [REdis Serialization Protocol]
redis客戶端 和 redis-server 之間的通訊協議,這個瞭解一下就好
"+OK\\r\\n"//simple string
"-ERR unknown command 'foobar'"//error msg
":1000\\r\\n"//integer
"$12\\r\\nHello World!\\r\\n"//bulk string
"$0\\r\\n\\r\\n" //空字串
"$-1\\r\\n" //nil
"*2\\r\\n$5\\r\\nhello\\r\\n$5\\r\\nworld\\r\\n"//陣列
架構
單機模式
優點:部署簡單,成本低,高效能,不需要同步資料
缺點:有當機風險,單執行緒受限於CPU處理能力,單機承載QPS大概在幾萬左右,如果QPS達到10萬+,單機模式會直接掛掉
主從複製
slaveof 192.168.1.1 6379 //Version<5.0
replicaof 192.168.249.20 6379 //Version>=5.0
成功配置一個從伺服器, 從伺服器向主伺服器傳送一個SYNC命令,主伺服器BGSAVE,完成後傳送RDB檔案到從伺服器,從伺服器載入RDB檔案啟動,第一次連線是全量重同步
之後主伺服器每執行一次寫命令都會順便發一份給從伺服器,如果主從斷開,重連的時候會根據offset複製偏移量部分重同步,比全量重同步塊一些
優點:增加從伺服器,QPS增加,降低MASTER讀壓力
缺點:主伺服器寫壓力沒有解決,當機無法繼續寫入,從節點晉升主節點需要改程式碼的配置,和從伺服器的配置,及其麻煩
哨兵模式 Sentinel
相當於主從模式下,給每臺(主從)伺服器新增一個程式,根據配置定時每秒,每十秒去判斷伺服器是否當機,如果是主伺服器當機,則當有足夠數量的哨兵程式確定主伺服器當機之後,將投票從從伺服器中選舉出新的主伺服器,程式碼方面需要執行SENTINEL相關命令獲取當前的主伺服器是哪臺,再例項化操作
優點:主伺服器當機的時候,不用半夜跑起來一頓操作,全自動化
缺點:已然沒有解決主庫寫壓力,QPS大的時候,該當機還得當機
叢集模式 REDIS CLUSTER version>3.0
採用無中心結構,至少6個節點(1主1從 乘以3)才能保證高可用叢集,3個主伺服器採用虛擬雜湊槽分割槽,公式為
hash_slot = crc16(key) % 16384 ;
雜湊槽的區間為[0,16383],擴容和縮容都是對槽的重新分配,服務不需要下線
某一組主節點伺服器當機,他的從節點會升級成主節點,宕掉的主節點重啟之後會變成新主節點的從節點
也就是說每個主節點只儲存有部分資料,不是全部,不過如果某一組主節點和他的從節點都當機的話,整個叢集會掛掉
優點:有條件的話肯定用這個方案啦
缺點:搭建發雜,用docker搭建會好點
一致性雜湊演算法
傳統雜湊取模演算法 N表示伺服器的總數
hash(key) % N 每個鍵都根據雜湊演算法分佈到N臺伺服器中,但是當N變化時(當機/新增)會導致鍵和伺服器的對映關係發生變化導致快取失效,新的鍵值不影響,主要是舊的鍵值,hash(key1)%3=1突然變成hash(key1)%2=2,到第2臺伺服器發現沒有key1,如果同一時刻發生大量快取失效,會導致快取雪崩
一致性雜湊演算法
透過一個雜湊環資料結構實現,環的取值範圍[0-2^32-1]
將伺服器的主機名或者IP進行雜湊運算對映到雜湊環上
hash(server.name/server.ip)
將鍵值透過雜湊運算也對映到雜湊環上,在雜湊環上順時針尋找最近雜湊值的伺服器,把值存進去
伺服器增加的時候同樣雜湊運算對映到雜湊環,只會影響當前雜湊值逆時針上一個伺服器到當前雜湊值之間的區間物件需要重新分配伺服器,其他伺服器沒影響
伺服器減少的時候,也只是需要把當前減少的伺服器雜湊值到逆時針上一個伺服器雜湊值之間的區間物件重新分配,其他伺服器沒影響
虛擬節點
可以把一臺伺服器虛擬成多個節點,使資料分佈更加分散,但是節點越多,新增和減少伺服器時重新分配的物件就越多,可以權衡一下
分散式鎖
單機分散式鎖
set lock_resource_id 1 nx ex 10 //原子操作,搶鎖10秒自動釋放
正常情況:A獲得鎖,A執行邏輯,成功之後,A在10秒內釋放鎖
異常情況:A獲得鎖,A執行邏輯,超過10秒,redis主動釋放鎖
B獲得鎖,A完成業務,然後把B的鎖給釋放了,
拉都拉不住,此時B的心態逐漸發生了一些變化
解決方案:
避免在可能會超時的場景使用鎖,加鎖時可以把value設定成一個自己知道的數字,釋放的時候判斷一下是否仍是自己的鎖再刪除,判斷和刪除是兩個原子性操作,需要用到LUA指令碼才能實現,這並不是一個完美解決方案,這裡沒有解決A超時鎖被提前釋放,B乘虛而入的問題,不過這也是一個無解的問題
set lock_resource_id thread_id nx ex 10
##LUA指令碼##
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
##LUA指令碼##
Redisson分散式鎖
開啟一個定時的守護執行緒/程式,判斷是否要給當前鎖新增過期時間,也就是再給你10秒鐘
Redlock
單節點不需要用到redlock
多節點,主從複製的時候,會發生A從主節點獲取鎖,沒同步到從節點發生故障轉移,從節點被選舉成主節點,B獲得相同的鎖,就變成兩個客戶端都獲取到同一把鎖的狀態
redlock原理:客戶端向所有主節點獲取鎖,當且僅當從一半以上的節點取到鎖,且使用的時間小於鎖失效的時間,才算獲得成功,釋放的時候向全部節點釋放
php有現成的包(暴露身份了),使用簡單
https://github.com/ronnylt/redlock-php
這個演算法不是100可靠的,詳情可以去研究
https://redis.io/topics/distlock
使用REDLOCK需要3臺以上的Redis例項,運維成本高,另外REDIS發生主從切換的機率並不高,即使發生了主從切換出現鎖丟失的機率也很低,因為主從切換會有一個過程,這個過程往往超過鎖的超時時間,所以用第一種單機分散式鎖可以解決很多問題
非同步佇列
List 常用命令
lpush,lpop,blpop(blocking 堵塞)
rpush,rpop,brpop(blocking 堵塞)
rpoplpush,brpoplpush(blocking 堵塞)
llen,lrange
可以用(b)rpoplpush把佇列的值pop出來push到另一個佇列裡面,業務處理完再刪除,有問題就回滾,實現訊息確認機制,原子性操作
rpoplpush myqueue queuebak
缺點:不能生產一次消費多次,其實不算缺點,設計如此
PUB/SUB
釋出/訂閱 可以實現生產一次消費多次,但是不能實現訊息持久化,客戶端下線之後,就會丟失資訊,設計如此
stream Version>=5.0
就目前來說,stream還不能當做主流MQ使用,慎用在生產環境
stream實現了消費組消費和應答機制,簡單使用如下
xadd mystream * k1 v1 k2 v2 k3 v3//新增元素
xadd mystream 1609404470049-1 k4 v4//新增元素
xrange mystream - + //檢視全部元素
xdel mystream 1609404470049-1//刪除元素
xdel mystream 刪除key
xlen mystream//容量
##獨立消費
xread count 2 streams mystream 0//從頭開始讀2條
xread count 2 streams mystream $//從尾開始讀最新
xread block 0 streams mysteam $//永久堵塞讀最新
xread streams mystream1 mystream2 $ $//同時讀兩個
##消費組消費 每個組狀態獨立,互不影響,同一個組內多個消費者是競爭關係,每個消費組都有一個遊標last_delivered_id在陣列上往前移動,表示消費組消費到哪條資訊,消費未應答的id儲存在pending_ids裡面.
xgroup create mystream mygroup $//建立組讀最新資料
xinfo stream mystream//檢視stream和消費者組的資訊
xreadgroup group mygroup c1 count1 streams mystream //消費組mygroup的消費者c1消費1條資料
延時佇列
非同步佇列如何和分散式鎖一起使用,會出現死鎖,導致非同步任務集體睡眠,佇列長度爆表
當加鎖失敗的時候,可以把訊息丟到延時佇列裡,過一會再處理
透過zset的score來實現
zadd key 100 member1
zadd key 110 member2
zadd key 120 member3
zrangebyscore key 100 100 //member1
zrem key member1
上述寫法不是原子性的,可能zrangebyscore拿出來,zrem發現不見了,使用LUA指令碼
###LUA指令碼###
local res = nil
local tasks = redis.pcall("zrangebyscore", KEYS[1], ARGV[1], 0, "LIMIT", 0, 1)
if #tasks > 0 then
local ok = redis.pcall("zrem", KEYS[1], tasks[1])
if ok > 0 then
res = tasks[1]
end
end
return res
###LUA指令碼###
布隆過濾器
原理:使用多個不同的雜湊函式把元素雜湊到一個BIT向量表中,二向箔打擊,非常節省空間和成本,查詢也快,缺點就是判斷元素是否已經存在會有誤差,很小,但是無法完全消除,不過判斷元素是否不存在準確率是100%,另外不支援刪除操作,REDIS並不自帶布隆過濾器的實現,需要應用端實現多個雜湊函式和利用REIDS的SETBIT,GETBIT命令實現
應用:
1.大量的郵件,URL去重,過濾,識別
2.記錄使用者已經讀過的文章
3.解決快取穿透的問題
快取穿透
一般的業務邏輯是先去REDIS查詢有沒快取KEY,如果沒有就去資料庫查,但是一些惡意請求會故意查詢不存在的key,導致每個請求直達資料庫
解決方案:
1.查詢結果為空的情況也快取進REDIS,過期時間設定稍微短些
2.將所有可能存在的KEY雜湊到一個足夠大的bitmap中,對一定不存在的key進行過濾,用到上面說的布隆過濾器
快取雪崩
REDIS重啟或者大量快取同一個時間點失效,這樣請求又直達資料庫,引起連鎖反應
解決方案:
1.不同的KEY,設定不同的過期時間,可以帶個隨機數
HYPERLOGLOG 基數統計
本來想去研究一下原理的,知道我看到伯努利方程...
這個功能主要是用來模糊計數的,底層是點陣圖,跟布隆過濾器差不多,誤差在0.81%,佔用空間很小,最多隻佔用12k的儲存空間,可以用來統計日活月活
pfadd sign_uv_20210722 uid1
pfadd sign_uv_20210722 uid2
pfcount sign_uv_20210722 // 返回2
GEOSPATIAL 地理空間索引
將地理空間位置(經緯度)新增到key中,然後進行相關地址位置計算
GEOADD keyname 13.361389 38.115556 "Peter" 15.087269 37.502669 "Lily"
GEODIST keyname Peter Lily距離 //計算 Peter Lily距離
GEORADIUS keyname 15 37 100 km //列出(15,37)附近100公里的人
GEORADIUSBYMEMBER keyname Peter 100 km//列出Peter附近100公里的人
淘汰策略
config get maxmemory-policy //獲取記憶體淘汰策略
config set maxmemory-policy allkeys-lru //設定淘汰策略
config get maxmemory //獲取REDIS能使用的最大記憶體
config set maxmemory 20gb //設定REDIS最大記憶體
1.noeviction(預設策略):對於寫請求直接返回錯誤,刪除清除除外
2.allkeys-lru:所有keys中淘汰最久沒有使用的鍵 //推薦使用
3.allkeys-random:所有keys中隨機淘汰 //不推薦使用
4.allkeys-lfu:所有keys中淘汰使用頻率最少的鍵 //推薦使用
5.volatile-lru:從設定了過期時間的keys中淘汰最久沒有使用的鍵//推薦使用
6.volatile-random:從設定了過期時間的keys中隨機淘汰//不推薦使用
7.volatile-ttl:從設定了過期時間的keys中淘汰剩餘時間剩餘最短的//推薦使用
8.volatile-lfu:從設定了過期時間的keys中淘汰使用頻率最少的鍵 //推薦使用
5,6,7,8這三種如果沒有key符合策略,則退化成第1種
開發建議和效能最佳化
1.儲存的KEY一定要設定超時時間
2.大文字資料壓縮後儲存,SIZE>500位元組
3.禁止KEYS操作,可以用SCAN替代
4.使用HASH,SET時,注意FIELD不要太多
5.禁止MONITOR操作
6.禁止大STRING,消耗頻寬
7.單機記憶體建議在10-20GB,鍵的個數控制在1000萬內,太多會影響回收
8.使用連線池和定時監控redis健康資訊
本作品採用《CC 協議》,轉載必須註明作者和本文連結