基礎知識
線程安全
由於單線程,故Redis天然規避線程安全問題。那麼,爲什麼單線程還能這麼高效呢?
這得益於其底層進行io操作時,採用了NIO的多路複用原則。(如有讀者感興趣,可以研究一下nio多路複用原理即可)
Redis官方是沒有windows版本的,因爲Redis底層做io操作是基於linux的epoll來進行NIO的io多路複用。
其主要通過socket收到消息後進行主動調用回調,去提升io輪詢效率。而Windows沒有epoll,只能通過selector去不停輪詢。
持久化機制
RDB
Redis默認採用rdb方式實現數據的持久化
即以快照的形式將數據持久化到磁盤,是一個二進制的文件dump.rdb
rdb方式會將Redis中的數據進行全量的備份,即覆蓋式更新,默認備份間隔時間是900s。
所以如果Redis出現故障無法備份數據時,如果採用rdb方式持久化數據,會導致這900s內數據的丟失。
但其優點是消耗服務器內存少
==================redis.conf==================
# rdb方式備份數據的文件名稱
dbfilename dump.rdb
# 在900秒(15分鐘)之後,如果至少有1個key發生變化,則dump內存快照
save 900 1
# 在300秒(5分鐘)之後,如果至少有10個key發生變化,則dump內存快照。
save 300 10
# 在60秒(1分鐘)之後,如果至少有10000個key發生變化,則dump內存快照。
save 60 10000
AOF
AOF是基於數據日誌操作實現的持久化,故屬於增量備份
在Redis中,aof有三種模式
# 每次有數據修改發生時都會寫入AOF文件,能夠保證數據不丟失,但是效率非常低
appendfsync always
# 每秒鐘同步一次,可能會丟失1s內的數據,但是效率非常高, 這也是推薦使用的方式
appendfsync everysec
# 從不同步。高效但是數據不會被持久化。
appendfsync no
==================redis.conf==================
# 開始aof持久化,默認採用everysec
appendonly yes
與MySQL保持一致性
Redis作爲緩存,是需要和數據庫保持一致的,當然這裏的一致不是強一致,而是最終一致。
常見的解決方法有以下
1.通過mq去監聽MySQL的binlog文件,將其變化的結果以消息隊列的方式傳輸至redis
2.有種比較low的方式,就是手動清空redis,讓其訪問數據庫,然後同步至Redis,這種方式風險高,容易雪崩
3.可採用阿里的canal去實現數據同步
淘汰策略
Redis中的內存並非無止境,故當其內容滿了再去存放數據會出問題,所以其內部維護了6種淘汰策略
noeviction:不淘汰,當內存滿了,直接報錯
allkeys-lru:指定了有效期的key中,淘汰不經常使用的key
volatile-lru:指定了有效期的key中,隨機淘汰
allkeys-random:所有key中,淘汰不經常使用的key
volatile-random:所有key中,隨機淘汰
volatile-ttl:指定了有效期的key中,淘汰具有更早過期時間的key
==================redis.conf==================
# 標識Redis內存大小的閾值,超過則會進行淘汰策略
maxmemory <bytes>
# 指明Redis要使用的淘汰策略爲哪一種
maxmemory-policy 淘汰策略名稱
key的自動過期
當我們的key失效時,可以執行我們的客戶端回調監聽的方法
==================redis.conf==================
notify-keyspace-events "Ex"
SpringBoot整合key失效監聽
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* Redis失效事件 key
*/
@Override
public void onMessage(Message message, byte[] pattern) {
String expiraKey = message.toString();
// 拿到失效的key做一些業務處理
}
}
事務
multi 開啓事務
exec 提交事務
discard 取消提交事務
watch keyName 可以監聽一個或者多個key,提交事務前判斷key是否變化,進而進行事務提交或取消,通過版本實現樂觀鎖
ps:Redis官方是沒有提供回滾方法, 只提供了取消事務。
multi只保證了事務的原子性,但沒有保證其隔離性,故一般配合watch使用
分佈式鎖
分佈式鎖的大體實現思路是相同的,即多個請求去創建同一個節點,誰成功誰就拿到鎖。
Redis也是一樣的,它維護了一個setnx操作,即帶返回結果的設置key,1表示設置成功,0表示失敗
所以當setnx返回1時,執行業務操作,超時則回滾數據,正常執行結束後刪除此key,這樣其他請求繼續搶奪鎖。
進階知識
主從複製
單個節點的Redis可用性不高,即當其宕機時,對外Redis不可用,故採用集羣的方式保證高可用。
主從模式遵循一主多從,讀寫分離(主寫從讀)。
==================redis.conf==================
# replicaof <masterip> <masterport>
slaveof 主節點的IP 主節點的端口
masterauth 主節點的密碼
從節點啓動時讀取其配置,根據主節點地址信息與其建立socket長連接,定期保持數據同步的通訊。
在一主多從模式中,如果一個主節點對應的從節點特別多時,主節點的同步壓力非常大,所以往往採用樹狀結構進行集羣。
即: master-->s0-->s2
-->s3
-->s1-->s4
從節點首次同步採用讀取rdb方式全量備份數據,當有更新key操作時,會採用增量方式同步
在客戶端連接中通過info replication可以獲取當前Redis節點集羣的信息
哨兵機制
主從模式存在很大的弊端,即當主節點宕機,會使得集羣對外不可寫,需要人工維護。
故Redis採用哨兵機制很好的解決這個問題。
哨兵是一個獨立的進程,其主要用於監聽master節點(定時向master發送ping的命令)的活躍狀態。
一般來說,一個Redis對應一個哨兵,多個哨兵都是監聽集羣中同一個master節點,當哨兵啓動時,會向消息隊列中訂閱
關於master的通道channel,然後將自身的地址作爲消息投放到消息通道中,然後作爲消費者等待消費,其它哨兵也做此
操作,這樣一來,哨兵之間可以得到彼此的信息,然後建立socket長連接進行通訊。
當有哨兵發現master宕機後,會發送消息至channel,通知所有哨兵去訪問master,超過閾值(可配置)數量的哨兵得
到master宕機結果的話,就會認爲master宕機,然後進行選舉。
之前的master啓動後,發現集羣已經有了master,那麼他就會變成slave的從。
通過這種機制,實現Redis的故障轉移
哨兵通過監聽master,執行info replication獲取master下對應從節點信息,然後向下遞歸執行info replication
從而獲取整個集羣的節點信息,進行選舉
==================sentinel.conf==================
sentinel monitor mymaster master的IP master的端口 哨兵閾值(認爲master宕機的閾值)
sentinel auth-pass mymaster master的密碼
# sentinel心跳檢測主3秒內無響應,視爲掛掉,開始切換其他從爲主
sentinel down-after-milliseconds mymaster 3000
# 每次最多可以有1個從同步主。一個從同步結束,另一個從開始同步
sentinel parallel-syncs mymaster 1
# 主從切換超時時間
sentinel failover-timeout mymaster 18000
#啓動哨兵
./redis-sentinel sentinel.conf的路徑
安全控制
緩存穿透
大量Redis不存在的key訪問,導致Redis無法命中,感覺像是穿過了緩存,直接訪問數據庫,造成數據庫訪問壓力。
解決方法:
api限流
接口限制
用戶授權
IP檢查
網關黑白名單
布隆過濾器
緩存擊穿
在高併發的情況下,熱點key失效,導致大量請求訪問到了數據庫,如同某個點被擊穿一樣。
解決方法:
分佈式鎖
軟過期
緩存雪崩
Redis持久化文件丟失,使得Redis啓動時進行數據預熱,大量的查詢湧向數據庫,導致數據庫無法承受而宕機。
這樣一來,緩存拿不到數據,數據庫又起不來,形成雪崩效應
Cluster集羣
傳統的主從模式有很大弊端,即:
中心化 只有一個主在寫,宕機需要選舉,期間對外不可用
數據全量同步 佔用資源,內容冗餘
故Redis推出分片化管理數據
其原理是內部維護一個hash槽,默認槽的個數是16384個,然後支持多個master節點,每個master可以支持各自的主從。
根據槽的長度取模master節點數,將槽的下標進行均勻分配,即每個master都會有屬於自己的槽區間
通過對key進行crc16算法計算及其然後對16384取模獲取此key對應的槽下標
根據槽下標尋找相應的master節點,然後將其存放至對應的槽位,一個槽位類似於一張表,即可存放多個key
其原理近似MySQL的分庫分表和hashMap
RedisCluster集羣模式環境搭建
mkdir rediscluster
cd rediscluster/
mkdir redis7000
mkdir redis7001
mkdir redis7002
mkdir redis7003
mkdir redis7004
mkdir redis7005
每個配置文件內容(以7005爲例)
# 後臺啓動
daemonize yes
# 允許外部訪問
protected-mode no
# 修改端口號,從7000到7005
port 7005
# 開啓cluster,去掉註釋
cluster-enabled yes
# 自動生成
cluster-config-file 7000nodes.conf
# 節點通信時間
cluster-node-timeout 15000
logfile /usr/rediscluster/redis7005/redis.log
啓動我們的redis
/usr/redis/bin/redis-server /usr/rediscluster/redis7000/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7001/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7002/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7003/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7004/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7005/redis.conf
連接任意一個redis
/usr/redis/bin/redis-cli -h 192.168.212.163 -p 7000
(error) CLUSTERDOWN Hash slot not served 說明沒有分配hash槽
# 分配hash槽
終端執行
/usr/redis/bin/redis-cli --cluster create 192.168.212.163:7000 192.168.212.163:7001 192.168.212.163:7002 192.168.212.163:7003 192.168.212.163:7004 192.168.212.163:7005 --cluster-replicas 1
(建議最好使用服務器的ip地址搭建)
# -c 標識當key的槽下標不在此Redis時,會進行重定向
/usr/redis/bin/redis-cli -h 192.168.212.163 -p 7000 –c
動態擴容
擴容節點
/usr/redis/bin/redis-server /usr/rediscluster/redis7006/redis.conf
/usr/redis/bin/redis-server /usr/rediscluster/redis7007/redis.conf
新增一個主節點 爲7006
/usr/redis/bin/redis-cli --cluster add-node 192.168.212.163:7006 192.168.212.163:7000
新增一個從節點 爲7007
/usr/redis/bin/redis-cli --cluster add-node 192.168.212.163:7007 192.168.212.163:7000 --cluster-salve --cluster-master-id 5d94171eb34ed4396bf5b9db8efaab4d96d0cf10(7006的ID)
新增的7006 是沒有任何槽位 需分配Redis槽位擴容
cluster slots
/usr/redis/bin/redis-cli --cluster reshard 192.168.212.163:7000
動態縮容
/usr/redis/bin/redis-cli --cluster reshard 192.168.212.163:7000 --cluster-from 5d94171eb34ed4396bf5b9db8efaab4d96d0cf10 --cluster-to 511058958a3b80dd600e060c2500050c6c5a02ab --cluster-slots