Redis Cluster
數據分區
# 順序分區
例如,100份數據,分爲三份,那麼第1份是1-33,第二份34-66,第三份是67-100
# 哈希分區
把數據進行hash(key)%3,餘數爲0的在第一份,餘數爲1的在第二份,餘數爲2的在第三份
第一份(餘數爲0) 第二份(餘數爲1) 第三份(餘數爲2)
0,6.. 1,4... 2,5...
節點取餘
客戶端分片:哈希 + 取餘
節點伸縮:數據節點關係變化,導致數據遷移
遷移數量和添加節點數量有關:建議翻倍擴容
一致性哈希
客戶端分片:哈希 + 順時針(優化取餘)
節點伸縮:隻影響鄰近節點,但是還是有數據遷移
翻倍伸縮:保證最小遷移數據和負載均衡
Redis Cluster架構
# 節點
配置cluster-enabled:yes,表示以集羣的方式啓動
# meet(節點直接相互通信)
所有節點,都有聯繫,例如,三個節點A B C
A meet B, A meet C, B meet C
# 指派槽,共16383個slot,數據存儲在哪個節點
節點 槽
A 0~5460
B 5461~10922
c 10923~16383
客戶端:
1.keyhash=hash(key)
2.slot=keyhash%16383
# 複製
# Redis Cluster特性
複製,高可用,分片
原生命令安裝-理解架構
# 三主三從的搭建
1.配置開啓節點
port ${port}
daemonize yes
dir "/opt/redis/data"
dbfilename "dump-${port}.rdb"
logfile "${port}.log"
cluster-enabled yes
cluster-config-file nodes-${port}.conf
# 開啓節點
redis-server redis-7000.conf # 主
2.meet(節點之間通信)
cluster meet ip port
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7001
...
這幾個就互相有通信了
# Cluster節點主要配置
cluster-enabled yes
cluster-node-timeout 15000 # 15s
cluster-config-file "nodes.conf"
cluster-require-full-coverage yes # 一般配爲no,表示一個節點出問題,也可以繼續使用
3.指派槽(數據訪問)共16383個slot
分配槽
cluster addslots slot [slot...]
redis-cli -h 127.0.0.1 -p 7000 cluster addslots {0...5461}
redis-cli -h 127.0.0.1 -p 7001 cluster addslots {5462...10922}
redis-cli -h 127.0.0.1 -p 7002 cluster addslots {10923...16383}
4.主從(故障自動轉移)
cluster replicate node-id
redis-cli -h 127.0.0.1 -p 7003 cluster replicate ${node-id-7000}
redis-cli -h 127.0.0.1 -p 7004 cluster replicate ${node-id-7001}
redis-cli -h 127.0.0.1 -p 7005 cluster replicate ${node-id-7002}
配置三主三從[原生命令]
# 配置redis-7000.conf,主7000端口的redis
port 7000
daemonize yes
dir "/home/zby/redis-4.0.6/data"
dbfilename "dump-7000.rdb"
logfile "7000.log"
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-require-full-coverage no
# 在配置主7001,和主7002,從7003,7004,7005
# 將redis-7000.conf文件中的7000改爲7001,並寫到redis-7001.conf文件中
sed 's/7000/7001/g' redis-7000.conf > redis-7001.conf
# meet操作,搭建集羣,互通
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7001
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7002
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7003
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7004
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7005
# 給主分配槽共16383,分配三個主,即7000,7001,7002
7000 0-5461
7001 5462-10922
7002 10923-16383
編寫腳本來進行執行addslots.sh
start=$1
end=$2
host=$3
port=$4
for slot in `seq ${start} ${end}`
do
echo "slot:${slot}"
redis-cli -h ${host} -p ${port} cluster addslots ${slot}
done
執行腳本
bash addslots.sh 0 5461 127.0.0.1 7000
bash addslots.sh 5462 10922 127.0.0.1 7001
bash addslots.sh 10923 16383 127.0.0.1 7002
# 主從,前面是從,後面id對應的節點爲主
redis-cli -h 127.0.0.1 -p 7003 cluster replicate 8f2130460c33790666236e9fd338cc83ec427c5b
redis-cli -h 127.0.0.1 -p 7004 cluster replicate 34fd90ab7c42fc1d9db9ecfe27b0ffc2105e9c1c
redis-cli -h 127.0.0.1 -p 7005 cluster replicate d319c8083f8149f95ad8984cd87cbdfd6c543d2e
# 連接,以中這種方式進行連接帶上-c
redis-cli -c -p 7000
redis-cli -c -p 7001
Ruby搭建redis集羣
安裝ruby
# 下載ruby的包
tar -azvf ruby-2.6.3.tar.gz
./configure --prefix=/usr/local/ruby
make
make install
cd /usr/local/ruby
cp bin/ruby /usr/local/bin
cp bin/gem /usr/local/bin
安裝rubygem redis
# 下載
http://rubygems.org/downloads/redis-4.0.3.gem
# 安裝
gem install -l redis-4.0.0.gem
gem list -- check redis gem
# 檢查是否安裝好
cd /home/zby/redis-4.0.6/src
./redis-trib.rb
# 如果不報錯,就表示安裝成功
# 在把連接cp到/usr/local/bin/
cp /home/zby/redis-4.0.6/src/redis-trib.rb /usr/local/bin/
redis-trib.rb搭建集羣
# 批量殺死進程
ps -ef | grep redis | awk '{print $2}' | xargs kill
# 三主三從
# 配置redis-8000.conf文件
port 8000
daemonize yes
dir "/home/zby/redis-4.0.6/data"
dbfilename "dump-8000.rdb"
logfile "8000.log"
cluster-enabled yes
cluster-config-file nodes-8000.conf
cluster-require-full-coverage no
# 配置8001,8002,8003,8004,8005
sed 's/8000/8001/g' redis-8000.conf > redis-8001.conf
# 把這6個啓動起來
redis-server redis-8000.conf
redis-server redis-8001.conf
...
# 配置主從關係,前面1表示,一主一從,改爲2就表示1主2從,會自動去分配
redis-trib.rb create --replicas 1 127.0.0.1:8000 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005
# 連接,要帶-c
redis-cli -c -p 8000
擴容集羣
# 準備新節點
新節點
集羣模式
配置和其它節點統一
啓動後是孤兒節點
例如:
redis-server redis-8006.conf
redis-server redis-8007.conf
# 加入集羣
# 把孤兒節點和集羣的節點建立連接
redis-cli -h 127.0.0.1 -p 8000 cluster meet 127.0.0.1 8006
redis-cli -h 127.0.0.1 -p 8000 cluster meet 127.0.0.1 8006
# 建立主從,8007爲從,後面的id是對應的8006爲主
redis-cli -h 127.0.0.1 -p 8007 cluster replicate 68993da5459ee31e38b74193d214af2fc0795201
# 遷移數據
redis-trib.rb reshard 127.0.0.1:8000
# 然後會出來一個問題,問遷移多少?輸入數字
16383/4 = 4096,輸入4096
然後再輸入要那個節點id來接收,輸入接收的節點id
然後再輸入all,在輸入yes,完成
加入集羣的作用:
爲它遷移槽和數據實現擴容
作爲從節點負責故障轉移
加入集羣:
redis-trib.rb add-node new_host:new_port existing_host:existing_port --slave --master-id
redis-trib.rb add-node 127.0.0.1:8006 127.0.0.1:8000
建議使用redis-trib.rb能夠避免新節點已經加入了其他集羣,造成故障
# 遷移槽和數據
比較複雜(原生)
縮容集羣
# 把想要縮容的主從節點的槽,轉移到別的節點上
redis-trib.rb reshard --from 68993da5459ee31e38b74193d214af2fc0795201 --to 07eb77544b77cf50d19320dd54d1be2f14a0326e --slots 337 127.0.0.1:8006
# 在進行刪除,先刪除從節點,在刪除主節點
redis-trib.rb del-node 127.0.0.1:8000 6f562d2bd30ae09d66179a05bfac50abf72de061 # 從
redis-trib.rb del-node 127.0.0.1:8000 68993da5459ee31e38b74193d214af2fc0795201 # 主
批量操作優化
故障發現
通過ping/pong消息實現故障發現:不需要sentinel
主觀下線和客觀下線
# 主觀下線
定義:某個節點認爲另個一節點不可用,“偏見”
# 客觀下線
定義:當半數以上持有槽的主節點都標記某節點主觀下線
# 嘗試客觀下線
通知集羣內所有節點標記故障節點爲客觀下線
通知故障節點的從節點觸發故障轉移流程
故障恢復
# 資格檢查
1.每個從節點檢查與故障主節點的斷線時間
2.超過cluster-node-timeout * cluster-slave-validity-factor取消資格
3.cluster-slave-validity-factor:默認是10
# 準備選舉時間
# 選舉投票
# 替換主節點
1.當前從節點取消複製變爲主節點(slave no one)
2.執行clusterDelSlot撤銷故障主節點負責的槽,並執行clusterAddSlot把這些槽分配給自己
3.向集羣廣播自己的pong消息,表明已經替換了故障從節點
Redis Cluster開發運維常見問題
常見的問題有:集羣完整性,帶寬消耗,Pub/Sub廣播,數據傾斜,讀寫分離,數據遷移,集羣VS單機
集羣完整性
1.cluster-require-full-coverage默認爲yes
集羣中16384個槽全部可用:保證集羣完整性
節點故障或者正在故障轉移:
(error)CLUSTERDOWN The cluster is down
2.大多數業務無法容忍,cluster-require-full-coverage建議設置爲no
帶寬消耗
1.避免“大”集羣:避免多業務使用一個集羣,大業務可以多集羣
2.cluster-node-timeout:帶寬和故障轉移速度的均衡
3.儘量均勻分配到多機器上:保證高可用和帶寬
Pub/Sub廣播
問題:publish在集羣每個節點廣播:加重帶寬
解決:單獨“走”一套Redis Sentinel
讀寫分離
集羣模式下,不建議進行讀寫分離
集羣總結
1.Redis cluster數據分區規則採用虛擬槽方式(16384個槽),每個節點負責一部分槽和相關數據,實現數據和請求的負載均衡
2.搭建集羣劃分四個步驟:準備節點、節點握手、分配槽、複製;
redis-trib.rb工具用於快速搭建集羣
3.集羣伸縮通過在節點之間移動槽和相關數據實現
擴容時根據槽遷移計劃把槽從源節點遷移到新節點
收縮時如果下線的節點有負責的槽需要遷移到其它節點,再通過cluster forget命令讓集羣內所有節點忘記被下線節點
4.使用smart客戶端操作集羣達到通信效率最大化,客戶端內部負責計算維護鍵->槽->節點的映射,用於快速定位到目標節點
5.集羣自動故障轉移過程分爲故障發現和節點恢復,節點下線分爲主觀下線和客觀下線,當超過半數主節點認爲故障節點爲主觀下線時標記它爲客觀下線狀態,從節點負責對客觀下線的主節點觸發故障恢復流程,保證集羣的可用性。
6.開發運維常見問題包括:超大規模集羣帶寬消耗,pub/sub廣播問題,集羣傾斜問題,單機和集羣對比等
緩存
緩存的收益與成本,緩存更新策略,緩存穿透優化,無敵同問題優化,緩存雪崩優化,熱點key重建優化
緩存的收益與成本
# 收益
1.加速讀寫
通過緩存加速度寫速度:CPU L1/L2/L3 Cache,Linux page Cache加速硬盤讀寫、瀏覽器緩存,Ehcache緩存數據庫結果
2.降低後端負載
後端服務器通過前端緩存降低負載:業務端使用Redis降低後端MySQL負載等
# 成本
1.數據不一致:緩存層和數據層有時間窗口不一致,和更新策略有關
2.代碼維護成本:多了一層緩存邏輯
3.運維成本:例如Redis Cluster
# 使用場景
1.降低後端負載:
對高消耗的SQL:join結果集/分組統計結果緩存
2.加速請求響應:
利用Redis/Memcache優化IO響應時間
3.大量寫合併爲批量寫:
如計數器先Redis累加在批量寫DB
緩存更新策略
1.LRU/LFU/FIFO算法剔除:例如maxmemory-policy
2.超時剔除:例如expire
3.主動更新:開發控制聲明週期
# 兩條建議
1.低一致性:最大內存和淘汰策略
2.高一致性:超時剔除和主動更新結合,最大內存和淘汰策略兜底
總結
緩存收益:加速讀寫、降低後端存儲負載
緩存成本:緩存和存儲數據不一致性、代碼維護成本、運維成本
推薦結合剔除、超時、主動更新三種方案共同完成
穿透問題:使用緩存空對象和布隆過濾器來解決,注意它們各自的使用場景和侷限性
無敵同問題:分佈式緩存中,有更多的機器不保證有更高的新能。
有四種批量操作方式:串行命令(for)、串行IO(pipline),並行IO,hash_tah
雪崩問題:緩存層高可用、客戶端降級、提前演練是解決雪崩問題的重要方法
熱點key問題:互斥鎖、“永遠不過期”能夠在一定程度上解決熱點key問題,開發人員在使用時要連接它們各自的使用成本
Redis雲平臺CacheCloud
Redis規模化運維、快速構建、機器部署、應用接入、用戶功能、運維功能
基於Redis的分佈式布隆過濾器
引出布隆過濾器、布隆過濾器原理、布隆過濾器誤差率、本地布隆過濾器、Redis單機布隆過濾器、Redis分佈式布隆過濾器
面試問題
現有50億個電話號碼,現有10萬個電話號碼,要快速準備判斷這些電話號碼是否已經存在?
1.通過數據庫查詢:實現快速有點難;
2.數據預放在集合中:50億 * 8字節 = 40GB(內存浪費或不夠)
3.hyperloglog:準確有點難,有誤差率,官方給出誤差率爲0.81%
布隆過濾器原理
1970年伯頓.布隆剔除,用很小的空間,解決上述類似問題
實現原理:一個很長的二進制向量和若干個哈希函數
Redis的規範
key的設計
# 可讀性和可管理性
以業務名(或數據庫名)爲前綴(防止key衝突),用冒號分割;
比如,業務名:表名:id,如,ugc:video:1
# 簡潔性
保證語義的前提下,控制key的長度,當key較多時,內存佔用也不容忽視,如:
user:{uid}:frieds:messages:{mid}簡化爲u:{uid}:fr:m:{mid}
# 不要包含特殊字符
value設計
拒絕bigkey、選擇合適的數據結構、過期設計
bigkey
# bigkey
string類型控制在10KB以內
hash、list、set、zset元素個數不要超過5000
# bigkey的危害
網絡阻塞,Redis阻塞,集羣節點數據不均衡,頻繁序列化:應用服務器CPU消耗
# bigkey發現
應用異常
redis-cli --bigkeys
scan + debug object
主動報警:網絡流量監控、客戶端監控
內核熱點key問題優化
# bigkey刪除
1.阻塞:注意隱性刪除(過期、rename等)
2.Redis 4.0:lazy delete(unlink命令)
# bigkey預防
優化數據結構:例如二級拆分
物理隔離或萬兆網卡:不是治標方案
命令優化:例如hgetall->hmget、hscan
報警和定期優化
# bigkey總結
牢記Redis單線程特性
選擇合理的數據機構和命令
清除自身OPS
連接bigkey的危害
# 選擇合適的數據結構
# 過期設計
鍵值聲明週期
週期數據需要設置過期時間, object idle time可以找來及key-value
過期時間不宜集中:緩存穿透和雪崩等問題
命令使用技巧
1.【推薦】O(N)以上命令關注N的數量
例如:hgetall、lrange、smembers、zrange、sinter等並非不能使用,但是需要明確N的值,有遍歷的需求可以使用hscan、sscan、zscan代替
2.【推薦】禁用命令
禁止線上使用keys、flushall、flushdb等,通過redis的rename機制禁掉命令,或者使用scan的方式處理
3.【推薦】合理使用select
redis的多數據庫較弱,使用數字進行區分
很多客戶端支持較差
同時多業務用多數據庫實際還是單線程處理,會有干擾
4.【推薦】Redis事務功能較弱,不建議過多使用
Redis的事務功能較弱(不支持回滾)
而且集羣版本(自研和官方)要求一次事務操作的key必須在一個slot上(可以使用hashtag功能解決)
5.【推薦】Redis集羣版本在使用Lua上有特殊要求
6.【推薦】必要情況下使用monitor命令時,要注意不要長時間使用
redis連接池參數設置
maxTotal怎麼設置?maxIdle接近maxTotal即可
舉例:
一次命令時間(borrow|reutrn resource + Jedis執行命令(含網絡)的平均耗時約爲1ms,一個連接的QPS大約是1000
業務期望的QPS是50000
理論的maxTotal = 50000 / 1000 = 50個,可以適當伸縮
安全七大法則
# 1.設置密碼
1.服務端配置:requirepass和masterauth
2.客戶端連接:auth命令和-a參數
3.相關建議:
(1) 密碼要足夠複雜,防止暴力破解
(2) masterauth不要忘記
(3)auth還是通過明文傳輸
# 2.僞裝危險命令
1.服務端配置:rename-command爲空或者隨機字符串,例如,
在配置文件redis-7000.conf裏配置rename-command flushall abc
2.客戶端連接:不可用或者使用指定隨機字符串
3.相關建議:
(1)不支持config set動態設置
(2)RDB和AOF如果包含rename-command之前的命令,將無法使用
(3)config命令本身是在Redis內核會使用到,不建議設置
# 3.bind
1.服務端配置:bind限制的是網卡,並不是客戶端ip
2.相關建議:
(1)bind不支持config set
(2)bind 127.0.0.1 需要謹慎
(3)如果存在外網網卡儘量屏蔽掉
# 4,5,6,7
1.防火牆:殺手鐗
2.定期備份
3.不適用默認端口,防止被弱化攻擊殺掉
4.使用非root用戶啓動