目錄
- 學習計劃
- 學習筆記
- 百問
- redis在什么情況下會變慢?
- 單執行緒的redis,如何知道要運行定時任務?
學習計劃
- Redis的介紹、優缺點、使用場景
- Linux中的安裝
- 常用命令
- Redis各個資料型別及其使用場景
- Redis字串(String)
- Redis哈希(Hash)
- Redis串列(List)
- Redis集合(Set)
- Redis有序集合(sorted set)
- Redis - 瑞士軍刀
- 慢查詢
- pipeline流水線
- 發布訂閱
- bitmap
- HyperLogLog演算法
- GEO
- Redis持久化,資料備份與恢復
- RDB
- AOF
- SpringBoot + Jedis + 1主2從3哨兵 實作Redis的高可用
- SpringBoot + Jedis + Redis Cluster代碼案例
- 高可用
- 主從復制
- Redis Sentinel
- Redis Cluster
- Redis安全如何保證
- Redis性能測驗
- 整理自己的RedisUtil https://www.runoob.com/redis/redis-java.html
- Redis面試題匯總
- https://www.w3cschool.cn/redis/redis-ydwp2ozz.html
- 系列文章 https://www.cnblogs.com/jack1995/category/1076657.html
- Redis常用客戶端總結
- Redis實戰使用場景
學習筆記
Redis的介紹、優缺點、使用場景
- Redis是什么: 開源的,基于鍵值的存盤服務系統,支持多種資料型別,性能高,功能豐富
- 特性(主要有8個特性):
- 速度快:官方給出的結果是10W OPS,每秒10W的讀寫(為什么是10W,因為記憶體的相應時間是100納秒-10萬分之一秒),資料存盤在記憶體中;使用C語言開發;Redis使用單執行緒,減少背景關系切換,本質原因是計算機存盤介質的速度,記憶體比硬碟優幾個數量級),MemoryCache可以使用多核,性能上優于Redis,
- 持久化:Redis所有的資料保持在記憶體中,對資料的更新將異步地保存到磁盤上,斷掉,宕機? RDB快照/AOF日志模式來確保,MemoryCache不提供持久化
- 多種資料結構:Redis提供字串,HashTable, 鏈表,集合,有序集合;另外新版本的redis提供BitMaps位圖,HyperLogLog超小記憶體唯一值計數,GEORedis3.2提供的地理位置定位,相比memocache只提供字串的key-value結構
- 支持多種編程語言:Java,PHP,Ruby,Lua,Node
- 功能豐富: 發布訂閱,支持Lua腳本,支持簡單事務,支持pipline來提高客戶端的并發效率
- 簡單:單機核心代碼23000行,讓開發者容易吃透和定制化;不依賴外部庫;單執行緒模型
- 主從復制:主服務器的資料可以同步到從服務器上,為高可用提供可能
- 高可用、分布式:2.8版本后提供Redis-Sentinel支持高可用;3.0版本支持分布式
- 典型應用場景:
- 快取系統:快取一些資料減少對資料庫的訪問,提高回應速度
- 計數器:類似微博的轉發數,評論數,incr/decr的操作是原子性的不會出錯
- 訊息佇列系統:發布訂閱的模型,訊息佇列不是很強
- 排行版: 提供的有序集合能提供排行版的功能,例如粉絲數,關注數
- 實時系統:利用位圖實作布隆過濾器,秒殺等
安裝
- Linux中安裝
wget http://download.redis.io/releases/redis-5.0.7.tar.gz tar -zxvf redis-5.0.7.tar.gz mv redis-5.0.7 /usr/local/redis 不需要先創建/usr/local.redis檔案夾 cd /usr/local/redis make make install vi redis.conf * bind 0.0.0.0 開發訪問 * daemonize yes 設定后臺運行 redis-server ./redis.conf 啟動 redis-cli 進入命令列,進行簡單的命令操作 vi redis.conf > requirepass password 修改密碼 redis-cli 再次進入cmd > shutdown save 關閉redis,同時持久化當前資料 redis-server ./redis.conf 再次啟動redis redis-cli 進入命令列 > auth password 將redis配置成系統服務,redis/utils中自帶命令,我們只需修改引數 /usr/local/redis/utils/./install_server.sh [root~ utils]# ./install_server.sh Welcome to the redis service installer Please select the redis port for this instance: [6379] 默認埠不管 Selecting default: 6379 Please select the redis config file name [/etc/redis/6379.conf] /usr/local/redis/redis.conf 修改組態檔路徑 Please select the redis log file name [/var/log/redis_6379.log] /usr/local/redis/redis.log 修改日志檔案路徑 Please select the data directory for this instance [/var/lib/redis/6379] /usr/local/redis/data 修改資料存盤路徑 Please select the redis executable path [/usr/local/bin/redis-server] Selected config: Port : 6379 Config file : /usr/local/redis/redis.conf Log file : /usr/local/redis/redis.log Data dir : /usr/local/redis/data Executable : /usr/local/bin/redis-server Cli Executable : /usr/local/bin/redis-cli chkconfig --list | grep redis 查看redis服務配置項 redis_6379 0:off 1:off 2:on 3:on 4:on 5:on 6:off 服務名是redis_6379 - 可執行檔案說明
- redis-server: Redis服務器,啟動Redis的
- redis-cli: Redis命令列客戶端連接
- redis-benchmark: 對Redis做性能測驗
- redis-check-aof: AOF檔案修復工具
- redis-check-dump: RDB檔案檢查工具
- redis-sentinel: Sentinel服務器(2.8以后)
- 啟動方式
- redis-server: 最簡單的默認啟動,使用redis的默認引數
- 動態引數啟動:redis-server --port yourorderpoint
- 組態檔的方式: redis-server configpath
- 比較:
- 生產環境選擇配置啟動;單機多實體組態檔可以選擇組態檔分開
- Redis客戶端回傳值
- 狀態回復:ping->pong
- 錯誤恢復:執行錯誤的回復
- 整數回復:例如incr會回傳一個整數
- 字串回復: get
- 多行字串回復:mget
- 常用配置
- daemonize: 是否是守護行程(y/n)
- port埠:默認是6379
- logfile:Redis系統日志
- dir:Redis作業目錄
- 常用命令:在線練習http://try.redis.io/
redis-cli -h x.x.x.x -p x 連接 auth "password" 驗證密碼 redis-cli --raw可以避免中文亂碼 exit 退出 select index 切換到指定的資料庫 keys * 顯示所有key,如果鍵值對多不建議使用,keys會遍歷所有key,可以在從節點使用;時間復雜度O(N) dbsize 算出所有的key的數量,只是數量;時間復雜度O(1) exists key key是否存在,存在回傳1,不存在回傳0;時間復雜度O(1) incr key 將key的值加一,是原子操作 decr key 將key的值加一,會出現復數,是原子操作 del key 洗掉key,洗掉成功回傳1,失敗回傳0;時間復雜度O(1) expire key seconds 設定過期時間,過期之后就不存在了;時間復雜度O(1) ttl key 查看key剩余的過期時間,key不存在回傳-2;key存在沒設定過期時間回傳-1;(TTL Time To Live) persist key 去掉key的過期時間,再查看ttl key,回傳值是-1,表示key存在并且沒有設定過期時間 type key 查看型別;時間復雜度O(1) config get * 獲取配置資訊 set key value插入值 sadd myset 1 2 3 4 插入set get key獲取值 del key洗掉key cat redis.conf | grep -v "#" | grep -v "^$" 查看組態檔,去除所有的#,去除所有的空格 setnx key value #key不存在,才設定 set key value xx #可以存在,才設定 set key value [exporation EX seconds | PX milliseconds] [NX|EX] mget key1 key2 key3 批量獲取 1次mget=1次網路時間+n次命令時間;時間復雜度O(n) mset key1 value1 key2 value2 批量插入;時間復雜度O(n) n次get = n次網路時間 + n次命令時間,mget一次就能完成,省去大量的網路時間 getset key newvalue # set key newvalue并回傳舊的value append key value #將value追加到舊的value strlen key #獲取value的長度,中文占2個位元組 incrbyfloat key 3.5 #增加key對應的值 set/get/del, incr(自增1)/decr(自減1)/incrby(incrby key n自增n)/decrby getrange key start end #獲取value從start到end的值 setrange key index value #設定指定下標為一個新的值 hset key field value #給key的field設定值 hget key field #獲取key的field的值 hdel key field #洗掉key的field的值 hgetall key #獲取key的所有值 hexists key field # 判斷key的field是否存在 hlen key #獲取key field的數量 hmset key field1 value1 field2 value2 hmget key field1 field2 hsetnx/hincrby/hdecry/hincrbyfloat lpush key value1 value2...valueN #從左邊插入 rpush key value1 value2...valueN #從右邊插入 linsert key before|after value newValue rinsert key before|after value newValue lpop key #從左邊彈出一個item rpop key #從右邊彈出一個item lrem key count value #若count等于0或者不填,表示洗掉所有的value值相等的item;若count>0,表示從左到右洗掉最多count個value相等的item;若count<0,表示從右到左,洗掉最多Math.abs(count)個value相等的項 ltrim key start end #按照索引范圍修剪串列,可以用來慢洗掉,因為全洗掉可能會阻塞redis lrang key start end #獲取key中從start到end的值 lindex key index #取第index的值 llen key #算出串列的長度 lset key index newValue #修改index的值為newValue blpop key timeout #lpop阻塞版本,timeout是阻塞時間,timeout=0表示死等,lpop會立馬回傳,有時候資料更新不那么及時,或者訊息佇列中訊息未及時處理,我們可以使用這個 brpop key timeout lpush + LPOP = STACK lpush + RPOP = QUEUE lpush + ltrim = 有序的集合 lpush + rpop = 訊息佇列 sadd key value #不支持插入重復元素,失敗回傳0 srem key element #洗掉集合中的element元素 smembers key #查看集合元素 sinter key1 key2 #取出相同:交集 sdiff key1 key2 #取出key1中key2沒有的元素:差集 sunion key1 key2 #取出二者所有的元素:并集 sdiff|sinter|sunion store key #將結果存到key中,有時候計算一次耗時 scard key #計算集合大小 sismember key element #判斷element是否在集合中 srandmember #回傳所有元素,結果是無序的,小心使用,可能結果很大 smembers key #獲取集合中的所有元素 spop key #從集合中隨機彈出一個元素 scan SADD = Tagging SPOP/SRANDMEMBER = Random item SADD + SINTER = Social Graph zadd key score element #添加score和element O(logN): 使用xx和跳表的資料結構 zrem key element #洗掉元素 zscore key element #回傳元素的分數 zincrby key increScore element #增加或減少元素分數 zcard key #回傳元素的總個數 zrank key element #獲取element的排名 zrange key start end [withscores] #回傳指定索引范圍內的升序元素 zrangebyscore key minScore maxScore [withscore] #回傳分數在minScore和maxScore之間的元素 zcount key minScore maxScore #回傳有序集合內在指定分數范圍內的個數 zremrangebyrank key start end #洗掉指定排名內的元素 zremrangebyscore key minScore maxScore #洗掉指定分數內的元素 zrevrang/zrevrange/集合間的操作zsetunion info replication 查看分片,能夠獲取到主從的數量和狀態 config get databases 獲取所有資料庫
資料結構和內部編碼
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-86qRnZ3y-1613619964700)(png/redis-data-type-structure.PNG)]
- Reids支持5中存盤的資料格式: String, Hash, List, Set, Sorted Set
-
string- redis 的 string 可以包含任何資料,比如jpg圖片或者序列化的物件,最大能存盤 512MB,
- 使用場景:快取/計數器/分布式鎖/Web集群session共享/分布式系統全域序號(不用每次都拿,一次拿1000個放到記憶體中)…
- 常用命令:
- 實戰:實作分布式的id生成器,可以使用incr的思路,但是實際中會比這復雜
-
hash- 是一個鍵值(key=>value)對集合,Redis hash 是一個 string 型別的 field 和 value 的映射表,hash 特別適合用于存盤物件,
- 實戰:統計用戶主頁的訪問量, hincrby user:1:info pageview count
- Redis集群架構下不太適合
-
list- Redis 列表是簡單的字串串列,按照插入順序排序,串列最多可存盤 232 - 1 元素 (4294967295, 每個串列可存盤40多億),
- 實戰:微博按時間順序展示訊息
-
set- 是 string 型別的無序集合,不允許插入重復元素,插入重復元素失敗回傳0,集合是通過哈希表實作的,所以添加,洗掉,查找的復雜度都是 O(1),
- 實戰:抽獎系統(量不是很大的時候);like,star可以放到集合中;標簽tag
-
zset- 有序集合:有序且無重復元素,和 set 一樣也是string型別元素的集合,且不允許重復的成員,不同的是每個元素都會關聯一個double型別的分數,redis正是通過分數來為集合中的成員進行從小到大的排序,zset的成員是唯一的,但分數(score)卻可以重復,
- 實戰:排行榜
-
Redis客戶端: Java的Jedis(Socket通信),Python的redis-py
瑞士軍刀
慢查詢
- 生命周期
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lYKcmDsI-1613619964703)(png/redis-cmd-lifecycle.png)]
兩點說明:
- 慢查詢發生在第3階段,比如keys *等這些需要掃描全表的操作
- 客戶端超時不一定慢查詢,但慢查詢是客戶端超時的一個可能因素
- 兩個配置
- slowlog-log-slower-than=n(微秒):命令執行時間超過x微秒,會被丟到一個固定長度的慢查詢queue中;n<0表示不配置
- slowlog-max-len: 先進先出的佇列,固定長度,保存在記憶體中(重啟redis會消失)
- 配置方法
- 默認值
- config get slowlog-max-len=128
- config get slowlog-log-slower-than=10000
- 修改組態檔重啟
- 動態配置
- config set slowlog-max-len 1000
- config set slowlog-log-slower-than 1000
- 默認值
- 常用命令
- slowlog get [n]:獲取慢查詢佇列
- slowlog len: 獲取慢查詢佇列的長度
- slowlog reset: 清空慢查詢佇列
- 運維經驗
- slowlog-max-len不要設定過大,默認10ms,通常設定1ms,根據QPS來設定
- slowlog-log-slower-than不要設定過小,通常設定1000左右
- 定期持久化慢查詢
pipeline流水線(批量操作)
當遇到批量網路命令的時候,n次時間=n次網路時間+n次命令時間,舉個例子,北京到上海的距離是1300公里,光速是3萬公里/秒,假設光纖傳輸速度是光速的2/3,也就是萬公里/秒,那么一次命令的傳輸時間是 1300/20000*2(來回)=13毫秒,
什么是pipeline流水線,1次pipeline(n條命令)=1次網路時間+n次命令時間;pipeline命令在redis服務端會被拆分,因此pipeline命令不是一個原子的命令,注意每次pipeline攜帶資料量;pipeline每次只能作用在一個Redis節點上;M操作和pipeline的區別,M(mset)操作是redis的原生命令,是原子操作,pipeline不是原子操作,
for(int i = 0; i < 10000; i++>) {
jedis.hset(key, field, value); //1萬次hset差不多要50秒
for(0->100) {
Pipeline pipeline = jedis.pipelined();
for(0->100) {
pipeline.hset(key,field,value);
}
pipeline.syncAndReturnAll(); //拆分100次,每次100個命令,大概需要0.7秒
}
發布訂閱:類似生產者消費者模型
- 角色:發布者(publisher),頻道(channel),訂閱者(subscriber); 發布者將訊息發布到頻道中,訂閱者訂閱相關的頻道;
- API: publish/subscribe/unsubscribe
- publish channel message : publish sohu:tv “hello world”
- subscribe sohu:tv
- unsubscribe [channel]
- psubscribe [pattern] #訂閱模式 sohu*
bitmap:位圖:資料量很大的時候節省存盤記憶體,資料量小了,不節省
hyperloglog(演算法,資料結構):
- 極小空間完成獨立數量統計,本質是個string
- api: pfadd key element[s]:向hyperloglog添加元素 pfcount key[s]:計算hyperloglog的獨立總數 pfmerge key1 key2合并
GEO: 3.2提供的用于計算地理位置資訊;資料型別是zset,可以使用zset的洗掉命令
-
使用場景:微信搖一搖看附近好友
-
api:
- geo key longitude latitude member #增加地理位置資訊
- geopos key member[n] #獲取地理位置資訊
- geodist key member1 membe2 [unit] m米 km千米 mi英里 ft尺 獲取兩地位置的距離
- georadius #算出指定范圍內的地址位置資訊的集合,語法復雜了點
-
總結下Redis資料結構和型別的常見用法
| 型別 | 簡介 | 特性 | 使用場景 |
|---|---|---|---|
| String | 二進制安全 | 可以包含任何資料,比如JPG圖片或者序列化的物件,一個鍵最大能存盤512M | |
| Hash | 鍵值對集合,即編程中的Map | 適合存盤物件,并且可以向資料庫中那樣update一個屬性(Memcache中需要將字串反序列化成物件之后再修改屬性,然后序列化回去) | 存取/讀取/修改 用戶資訊 |
| List | 雙向鏈表 | 增刪快,提供了操作某一元段元素的API | 1. 最新訊息,按照時間線顯示 2. 訊息佇列 |
| Set | 哈希表實作,元素不重復 | 添加/洗掉/修改的復雜度都是O(1),為集合提供求交集/并集/差集的操作 | 1. 打label/tag,如文章 2. 查找共同好友 3. 抽獎系統 |
| Zset | 將Set中的元素增加一個double型別的權重score,按照score排序 | 資料插入集合就好序了 | 排行榜 |
| Hyperloglog | 本質是string | 極小空間完成獨立資料量統計 | 統計基數,不完全正確 |
| GEO | 資料型別是zset | 存盤地理位置資訊,并提供計算距離等操作 | 微信搖一搖查看附近好友 |
| Bitmap | 位圖 | 資料量很大的時候節省存盤記憶體,資料量小了不節省 | 1. 設定用戶的狀態 2. BitMap解決海量資料尋找重復、判斷個別元素是否在 |
- String 簡單動態字串 Simple Dynamic String, SDS
Redis沒有直接使用C語言的傳統字串表示,而是自己構建了一種名為簡單動態字串(Simple Dynamic String, SDS)的抽象型別,并將SDS用作Redis的默認字串表示,
每個sds.h/sdshdr結構表示一個SDS值:
struct sdshdr {
int len; // 記錄buf陣列中已經使用的位元組數量
int free; // 記錄buf陣列中未使用位元組的數量
char buf[]; // 位元組陣列,用于保存字串,SDS遵循C字串以空字符結尾的慣例
}
Redis持久化
-
持久化的作用:redis所有資料保存在記憶體中,對資料的更新將異步地保存到磁盤上,
-
主流資料庫持久化實作方式:快照(MySQL Dump/Redis RDB),寫日志(MySQL Binlog/Redis AOF)
-
RDB:
- 創建RDB檔案(二進制檔案)到硬碟中,啟動后載入RDB檔案到記憶體
- 三種觸發機制
-
save(同步) - 會產生阻塞
- 檔案策略:如存在老的RDB檔案,新的替換老的,新的會先生成到一個臨時檔案
-
bgsave(異步) - 不會阻塞
- 客戶端執行bgsave之后,redis會使用linux的一個fork()命令生成主行程的一個子行程(fork的操作會執行一個記憶體頁的拷貝,使用copy-on-write策略),子行程會創建RDB檔案,創建完畢后將成功的訊息回傳給redis,fork()出來的子行程執行快的話不會阻塞主行程,否則也會阻塞redis,阻塞的實際點就是生成出來這個子行程,由于是異步,在創建的程序中還有其他命令在執行,如何保證RDB檔案是最新的呢?在資料量大的時候bgsave才能突出優點,
命令 save bgsave IO型別 同步 異步 阻塞 是 是(阻塞發生在fork子行程 復雜度 O(n) O(n) 優點 不會消耗額外記憶體 不阻塞客戶端命令 缺點 阻塞客戶端命令 需要fork,消耗記憶體 -
自動觸發:多少秒內有多少changes會異步(bgsave)生成一個RDB檔案,如60秒內有1W條changes,默認的規則,可以改;不是很好吧,無法控制頻率;另外兩條是900秒內有1條changes, 300秒內有10條changes;
-
配置
- dbfilename dump.rdb
- dir ./
- stop-writes-on-bgsave-error yes 當bgsave發生錯誤是停止寫RDB檔案
- rdbcompression yes 采用壓縮格式
- rdbchecksum yes 采用校驗和
-
其他不能忽視的點:
- 全量復制;debug reload;shutdown save會執行rdb檔案的生成
-
-
AOF:
- RDB現存問題:耗時,耗性能(fork,IO),不可控(突然宕機)
- AOF:redis中的cmd會先重繪到緩沖區,然后更具配置AOF的策略,異步存追加到AOF檔案中,發生宕機后,可以通過AOF恢復,基本上資料是完整的
- AOF的三種策略(配置的三種屬性)
- always:來一條命令寫一條;不丟失資料,IO開銷較大
- everysec:每秒把緩沖區fsync到AOF檔案;丟1秒資料
- no:作業系統決定什么時候把緩沖區同步到AOF就什么時候追加;不用配置,但是不可控,取決于作業系統
- AOF重寫
- 如果AOF檔案很大的話,恢復會很慢,AOF的重寫是優化一些命名,使其變成1條,對于過期資料沒必要Log,本質是把過期的沒有用的,重復的過濾掉,以此減少磁盤占用量,加速恢復,極端的例子,1億次incr,實際只需要set counter n就夠了
- 重寫的兩種方式
- bgrewriteaof:異步執行,redis fork出一個子行程,然后進行AOF重寫
- AOF重寫配置
- auto-aof-rewrite-min-size: AOF檔案到達多大的時候才開始重寫
- auto-aof-rewrite-percentage: AOF檔案的增長率到達了多大才開始重寫
- 統計
- aof_current_size AOF當前尺寸 位元組
- aof_base_size AOF上次重啟和重寫的尺寸 位元組,方便自動重寫判斷
- 重寫觸發機制(同時滿足如下兩條)
- aof_current_size > auto-aof-rewrite-min-size
- (aof_current_size - aof_base_size) / aof_base_size > auto-aof-rewrite-percentage
- 其他配置
- appendonly yes
- appendfilename “”
- appendfsync everysec
- dir /xx
- no-appendfsync-on-rewrite yes AOF在重啟之后恢復,要權衡是否開啟AOF日志追加的功能,這個時候IO很大,如果設定為yes,也就意味著在恢復之前的日志資料會丟失
-
RDB & AOF最佳策略:RDB優先于AOF先啟用
- RDB:建議關掉,集中管理,在從節點開RDB
- AOF:建議開啟,每秒刷盤
- 最佳策略:小分片(log檔案分片)
-
常見問題
- fork操作:是一個同步操作,做一個記憶體頁的拷貝;與記憶體量息息相關,記憶體越大,耗時越長;執行info命令,有個latest_fork_usec的值,看下上次fork執行耗時
- 行程外開銷:
- CPU:RDB AOF檔案生成,屬于CPU密集型操作(不要和CPU密集型應用部署在一起,減少RDB AOF頻率);記憶體:fork記憶體開銷;硬碟:IO開銷大,選用SSD磁盤
- AOF追加阻塞:主執行緒將命令刷到AOF緩沖區,同步執行緒同步命令到硬碟,同時主執行緒會對比上次fsync的時間,如果大于2秒就阻塞主執行緒,否則不阻塞,主執行緒這么做是為了達到每秒刷盤的目的,讓子執行緒完成AOF,以此來達到資料同步,AOF發生阻塞怎么定位:redis日志/info persistence(aof_delayed_fsync累計阻塞次數,是累計,不好分清什么時候發生阻塞)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xpmcQL4p-1613619964705)(png/redis-aof-append-block.png)]
- 單機多實體部署
高可用
Redis主從復制
-
主從復制:單機故障/容量瓶頸/QPS瓶頸;一個master可以有多個slave,一個slave只能有一個master,資料必須是單流向,從master流向slave
-
復制的配置:
- 使用slaeof命令,在從redis中執行slave masterip:port使其成為master的從服務器,就能從master拉取資料了;執行slaveof no one清除掉不成為從節點,但是資料不清楚;
- 修改配置, slaveof ip port / slave-read-only yes(從節點只做都操作);配置要更改的話,要重啟,所以選擇的時候謹慎
-
全量復制
- run_id(使用info server可以看到run_id),重啟之后run_id就沒有了,當從服務器去復制主服務器,主服務器run_id會在從服務器上做一個標識,當從服務器發現主服務器的run_id發生了變化,說明主服務器發生了變化(重啟或者什么的),那么從服務器就要把主服務器的資料都同步過來
- 偏移量:部分復制中的一個依據,后面說
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Beksma8n-1613619964707)(png/redis-full-reclipate.png)]
- 決議下上面的全量復制的程序,slave向master發送psync的命令要去master全量復制資料(PSYNC <MASTER_RUN_ID> ,其中?表示我不知道master的runId啊,第一次連嘛,-1表示我都要,這時候slava咱啥也不知道),master大人收到了小弟的請求之后,大方的把自己的runId/offset發了過去,小弟收到后先存下來;在master大人把自個的資訊發給小弟之后,立馬投入了創建快照RDB的作業,一個bgsave命令立馬開工,RDB生產了就發給slave;咦,細心的我們發現你這不對啊,你master創建快照到創建完成這之間新增的資料咋辦,master吭吭了兩聲,我在開始快照的那一刻,后期的所有寫命令都額外往buffer中存了一份,來保證我給你的是完整的,當我發送完RDB之后,立馬給你發buffer;slave小弟內心對master大人產生了膜拜之情,收到了RDB/buffer之后,先把自己的老資料flush掉,然后load RDB,把最新的buffer刷一遍,分分鐘讓自己向master看齊,
- 開銷:bgsave時間, RDB檔案網路傳輸時間,從節點清空資料時間,從節點加載RDB的時間,可能的AOF重寫時間
-
部分復制:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-pJOZhYyd-1613619964708)(png/redis-part-replication.png)]
- 解釋下上面的部分復制的程序,當遇到網路抖動,那這段時間內資料在slave上就會發生丟失,那么這些資料slave是不知道的,在2.8之前redis會重新做一次全量復制,但是很顯然這樣做開銷很大,2.8之后提出部分復制的功能;當matster發現slave連接不上的時候,master在進行寫操作的時候,也會往緩沖區寫,等到下一次slave連上之后,slave會發送一條pysnc {offset}{runId}的命令,其中offset是slave自己的,相當于告訴master我的偏移量是多少,master判斷slave的offset在緩沖區內(緩沖區有start/end offset)就向slave發送continue命令,然后把這部分資料發送給slave;當master發現slave這個offset偏移量很大的時候,也就意味著slave丟失了很多資料,那么就進行一次全量復制
-
故障處理:
- master/slave宕機的情況,主從模式沒有實作故障的完全自動轉移
- 常見問題:
- 讀寫分離:讀流量分攤到從節點,可能遇到復制資料延遲,也可能讀到過期的資料,從節點故障怎么辦
- 主從配置不一致:主從maxmemory不一致,可能會丟失資料;主從記憶體不一致
- 規避全量復制:第一次不可避免;小主節點,低峰處理(夜間);主節點重啟后runId發生了變化
- 規避復制風暴
- 單機主節點復制風暴,如果是1主N從,當master重啟之后,所有的slave都會發生全量復制,可想而知這樣非常容易造成redis服務的不可用
Redis-Sentinel
- 主從復制高可用?
- 手動故障轉移,例如選出新的slave做master;寫能力和存盤能力受限;
- 架構說明
- Redis Sentinel是一個監控redis主從以及實施故障轉移的工具,sentinel不是一個是多個的(會選舉出一個master sentinel),這樣可以保證sentinel的高可用和公平(不是一個sentinel判斷不可用就不可用),可以把Redis Sentinel看成一個redis的額外行程,用來監控reids服務的可用與不可用;客戶端不再記住redis的地址,而是記錄sentinel的地址,sentinel知道誰是真的master;當多個sentinel發現并確認master有問題,sentinel內部會先選出來一個領導,讓這個領導來完成故障的轉移(因為執行slave no noe/new master這些命令只需要一個sentinel就夠了),sentinel從slave中選舉一個作為master,然后通知其他的slave去新的master獲取資料,sentinel可以監控多套master-slave,
- 安裝配置
- 配置開啟主從節點
- 配置開啟sentinel監控主節點(sentinel是特殊的redis):sentinel默認埠是23679
sentinel monitor mymaster 127.0.0.1 7000 2 監控的主節點名字是mymaster,2表示2個sentinel覺得當前master有問題提才發生故障轉移 sentinel down-after-milliseconds mymaster 30000 表示30秒不通之后就停掉master sentinel parallel-syncs mymaster 1 表示每次并發的復制是1個在復制,這樣可以減少master的壓力 sentinel failover-timeout mymaster 180000 故障轉移時間
- 實作原理: redis sentinel做失敗判定和故障轉移
- redis sentinel內部有三個定時任務
-
- 每10秒每個sentinel對master和slave執行info:可以從replication中發現slave節點,確認主從關系
-
- 每2秒每個sentinel通過master節點的channel交換資訊(pub/sub): 什么意思呢,master節點上有個發布訂閱的頻道用于sentinel節點進行資訊交換,利用的原理就是每個sentinel發布一個資訊其他sentinel都可以收到這樣一個原理,這個資訊都包含當前sentinel節點的資訊,以及它當前對master/slave做出的判斷,這個頻道是啥呢,sentinel:hello,這個名字內部規定的
-
- 每1秒每個sentinel對其他sentinel和redis執行ping操作-心跳檢測,是失敗判斷的依據
-
- redis sentinel內部有三個定時任務
- 主觀下線和客觀下線:
- sentinel monitor quorum是法定人數,有quorum個sentinel認為master不可用了那么master就會被客觀下線
- sentinel down-after-milliseconds 一個sentinel如果在timeout毫秒內沒收到master的回復就做主動下線的操作
- 主觀下線:每個sentinel節點對redis節點失敗都有自己的判斷,這里的節點可以是master,也可以是slave
- 客觀下線:所有的sentinel節點對某個redis節點認為失敗的個數達到quorum個才下線
- 領導者選舉
- 為啥要選領導者,因為只需要一個sentinel節點就能完成故障轉移,怎么選舉呢?
- 每個做完主觀下線的sentinel節點(就是發現某個節點不可用了,并發出了自己的判斷的節點)都會向其他sentinel節點發送sentinel is-master-down-by-addr命令,要求將自己設定為領導者,那么收到這個命令的sentinel如果在之前沒有同意過其他sentinel的話,就會同意這個請求,否則拒絕,換句話說每個sentinel只有一個同意票,這個同意票給第一個問自己的節點,好了,票發完了,如果這個sentinel節點發現自己擁有的票數超過sentinel集合半數并且操作quorum,那么它將成為領導者;如果此程序有多個sentinel節點成為領導者,那么過段時間將重新進行一次選舉,領導者選舉使用的是一個Raft演算法,以上是抽象程序,所以sentinel的個數(>=3最好為奇數)和quorum的個數需要合理配置,
- 為啥要選領導者,因為只需要一個sentinel節點就能完成故障轉移,怎么選舉呢?
- 故障轉移(sentinel領導者節點完成)
-
- 從slave節點中選舉一個“合適的”節點作為新的master節點
-
- 對上面的slave節點執行slaveof no one命令讓其成為master節點
-
- 向剩余的slave節點發送命令,讓它們成為新的master節點的slave節點,復制規則和parallel-syncs(允許并行復制的個數)引數有關,復制的程序master是做了優化的,只需要一個RDB的生成,然后同時向slave節點發送RDB和buffer,有一定的開銷,特別是網路
-
- 更新對原來master節點配置為slave,并保持對其“關注”,當其恢復后命令它去復制新的master節點
如何選擇合適的slave的節點-
- 選擇slave-priority(slave節點優先級)最高的slave節點,如果存在就回傳,不存在則繼續,一般不配置這個引數,什么情況下配置呢,當有一臺slave節點的機器配置很高,我們希望當master掛了之后它能成為新的master時,做這個設定,
-
- 選擇復制偏移量最大的slave節點(復制的最完整),如果存在則回傳,不存在則繼續,
-
- 選擇runId最小的slave節點,runId最小就是最早的slave節點,假設它復制的最多,
-
-
運維和開發
- 節點運維:上下和下線
- 機器下線:機器因為不能用了或者過保等什么原因要換機器
- 機器性能不足:例如CPU、記憶體、硬碟、網路等
- 節點自生保障:例如服務不穩定等
- sentinel failover 主節點主動故障轉移,已經選舉了sentinel領導者,所以上述程序可以省略
- 從節點下線:臨時下線還是永久下線,永久下線可能要清理掉一些組態檔,從節點下線的時候也要考慮讀寫分離的情況,因為這時候有可能正在讀,
- 節點上線: 主節點執行sentinel failover進行替換,從節點slaveof即可,sentinel節點可以感知
JedisSentinelPool的實作
看源代碼
-
- switch-master: 切換主節點
-
- convert-to-slave: 切換從節點
-
- sdown:主觀下線
Redis-Cluster
- 為什么需要集群
- 并發量/
- 資料量: 業務需要500G怎么辦,機器記憶體是16~256G
- 網路流量
Redis 3.0版本提供了分布式技術
- 資料分布
- 資料磁區
兩種方式
- 順序磁區
- 哈希磁區
- 節點取余磁區
- 新增節點之后基本所有資料要產生飄逸,一般產生多倍擴容節點,飄逸量少
- 一致性哈希磁區
- 解決了哈希磁區中新增節點造成書飄逸的問題
- 虛擬槽磁區
- 好復雜沒咋懂
- 節點取余磁區
- 搭建集群
- 原生安裝(模擬3主3從)
-
- 配置開啟節點 enable-sync yes
port ${port} daemonize yes dir "/path" dbfilename "dump-${port}.rdb" logfile "${port}.log" cluster-enabled yes cluster-config-file nodes-${port}.conf #redis啟動后這個nodes-port.conf會自動生成開啟命令 redis-server redis-7000.conf / redis-server redis-7001.conf /…,
此時開啟的節點都是相互孤立的沒有任何通信,
查看當前狀態127.0.0.1:7000> cluster info cluster_state:fail cluster_slots_assigned:0 cluster_slots_ok:0 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:1 cluster_size:0 cluster_current_epoch:0 cluster_my_epoch:0 cluster_stats_messages_sent:0 cluster_stats_messages_received:0顯示當前是cluster狀態
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-r9ybM5NT-1613619964710)(png/redis-cluster.png)]
-
- meet(節點的握手)
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7001 [root@xx cluster]# redis-cli -p 7000 cluster meet 47.x.x.16 7001 OK [root@xx cluster]# redis-cli -p 7000 cluster nodes 862e370d2342cf6bf883421003846e171770234e :7000@17000 myself,master - 0 0 0 connected 886b6e6c29f985f7f85acb1bf548d0937918eca3 4.x.x.6:7001@17001 handshake - 0 0 0 connected 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這地方注意啊,手動配置的時候有個巨坑(坑了我1天):在redis cluster架構中,每個redis要開發兩個埠,比如一個是6379,那么另一個就是加10000之后的埠號,比如16379,16379埠是用來進行節點間通信的,也就是cluster bus集群總線,cluster bus的通信用來進行故障檢測、配置更新、故障轉移授權等操作,
-
- 指派槽
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} -
- 主從關系的分配
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} -
常用命令
cluster notes 查看集群的node幾點情況 cluster slots 查看slots的分布 cluster info 查看當前集群的狀態,這個命令可以看到集群是否屬于可用狀態 redis-cli -c -p x 注意加-c,表示集群[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uQGdazdu-1613619964711)(png/redis-cluster-nodes.png)]
開始的一串字符是nodeid, 整行表示的意識是我的nodeId是多少,哪個ip的那個埠,我是主還是從,是從的話從的誰(誰的nodeId),是主的話還能看到slots的分布情況
-
- 官方工具安裝
- redis-trib.rb實作對redis集群的自動化安裝
- 原生安裝(模擬3主3從)
- 集群伸縮
- 客戶端路由
- 集群原理
- 集群是怎么通信的:16379 埠號是用來進行節點間通信的,也就是 cluster bus 的東西,cluster bus 的通信,用來進行故障檢測、配置更新、故障轉移授權,cluster bus 用了另外一種二進制的協議,gossip 協議,用于節點間進行高效的資料交換,占用更少的網路帶寬和處理時間,
- 開發運維常見問題
Redis事務
-
Redis 事務可以一次執行多個命令, 并且帶有以下三個重要的保證:
- 批量操作在發送 EXEC 命令前被放入佇列快取,
- 收到 EXEC 命令后進入事務執行,事務中任意命令執行失敗,其余的命令依然被執行,
- 在事務執行程序,其他客戶端提交的命令請求不會插入到事務執行命令序列中,
-
Redis事務從開始到執行會經歷以下三個階段:開始事務 -> 命令入隊 -> 執行事務,單個 Redis 命令的執行是原子性的,但 Redis 沒有在事務上增加任何維持原子性的機制,所以 Redis 事務的執行并不是原子性的,事務可以理解為一個打包的批量執行腳本,但批量指令并非原子化的操作,中間某條指令的失敗不會導致前面已做指令的回滾,也不會造成后續的指令不做,這是官網上的說明 From redis docs on transactions: It’s important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands.
-
Redis 通過監聽一個 TCP 埠或者 Unix socket 的方式來接收來自客戶端的連接,當一個連接建立后,Redis 內部會進行以下一些操作:
- 首先,客戶端 socket 會被設定為非阻塞模式,因為 Redis 在網路事件處理上采用的是非阻塞多路復用模型,
- 然后為這個 socket 設定 TCP_NODELAY 屬性,禁用 Nagle 演算法
- 然后創建一個可讀的檔案事件用于監聽這個客戶端 socket 的資料發送
-
Redis 管道技術可以在服務端未回應時,客戶端可以繼續向服務端發送請求,并最終一次性讀取所有服務端的回應,管道技術最顯著的優勢是提高了 redis 服務的性能,
-
Redis 磁區
- 磁區是分割資料到多個Redis實體的處理程序,因此每個實體只保存key的一個子集,
- 磁區的優勢:
- 通過利用多臺計算機記憶體的和值,允許我們構造更大的資料庫,
- 通過多核和多臺計算機,允許我們擴展計算能力;通過多臺計算機和網路配接器,允許我們擴展網路帶寬,
- 磁區的不足:
- 涉及多個key的操作通常是不被支持的,舉例來說,當兩個set映射到不同的redis實體上時,你就不能對這兩個set執行交集操作,
- 涉及多個key的redis事務不能使用,
- 當使用磁區時,資料處理較為復雜,比如你需要處理多個rdb/aof檔案,并且從多個實體和主機備份持久化檔案,
- 增加或洗掉容量也比較復雜,redis集群大多數支持在運行時增加、洗掉節點的透明資料平衡的能力,但是類似于客戶端磁區、代理等其他系統則不支持這項特性,然而,一種叫做presharding的技術對此是有幫助的,
- 磁區型別:Redis 有兩種型別磁區, 假設有4個Redis實體 R0,R1,R2,R3,和類似user:1,user:2這樣的表示用戶的多個key,對既定的key有多種不同方式來選擇這個key存放在哪個實體中,也就是說,有不同的系統來映射某個key到某個Redis服務,
- 范圍磁區
- 最簡單的磁區方式是按范圍磁區,就是映射一定范圍的物件到特定的Redis實體,比如,ID從0到10000的用戶會保存到實體R0,ID從10001到 20000的用戶會保存到R1,以此類推,這種方式是可行的,并且在實際中使用,不足就是要有一個區間范圍到實體的映射表,這個表要被管理,同時還需要各 種物件的映射表,通常對Redis來說并非是好的方法,
- 哈希磁區
- 另外一種磁區方法是hash磁區,這對任何key都適用,也無需是object_name:這種形式,像下面描述的一樣簡單:用一個hash函式將key轉換為一個數字,比如使用crc32 hash函式,對key foobar執行crc32(foobar)會輸出類似93024922的整數,對這個整數取模,將其轉化為0-3之間的數字,就可以將這個整數映射到4個Redis實體中的一個了,93024922 % 4 = 2,就是說key foobar應該被存到R2實體中,注意:取模操作是取除的余數,通常在多種編程語言中用%運算子實作,【當磁區較多或發生變化的時候需要處理一些額外的情況】
- 范圍磁區
其他
- Redis設定port為6379的原因
I/O多路復用技術(multiplexing)
關于I/O多路復用(又被稱為“事件驅動”),首先要理解的是,作業系統為你提供了一個功能,當你的某個socket可讀或者可寫的時候,它可以給你一個通知,這樣當配合非阻塞的socket使用時,只有當系統通知我哪個描述符可讀了,我才去執行read操作,可以保證每次read都能讀到有效資料而不做純回傳-1和EAGAIN的無用功,寫操作類似,作業系統的這個功能通過select/poll/epoll/kqueue之類的系統呼叫函式來使用,這些函式都可以同時監視多個描述符的讀寫就緒狀況,這樣,多個描述符的I/O操作都能在一個執行緒內并發交替地順序完成,這就叫I/O多路復用,這里的“復用”指的是復用同一個執行緒,
下面舉一個例子,模擬一個tcp服務器處理30個客戶socket,假設你是一個老師,讓30個學生解答一道題目,然后檢查學生做的是否正確,你有下面幾個選擇:1. 第一種選擇:按順序逐個檢查,先檢查A,然后是B,之后是C、D,,,這中間如果有一個學生卡住,全班都會被耽誤,這種模式就好比,你用回圈挨個處理socket,根本不具有并發能力,2. 第二種選擇:你創建30個分身,每個分身檢查一個學生的答案是否正確, 這種類似于為每一個用戶創建一個行程或者執行緒處理連接,3. 第三種選擇,你站在講臺上等,誰解答完誰舉手,這時C、D舉手,表示他們解答問題完畢,你下去依次檢查C、D的答案,然后繼續回到講臺上等,此時E、A又舉手,然后去處理E和A,,, 這種就是IO復用模型,Linux下的select、poll和epoll就是干這個的,將用戶socket對應的fd注冊進epoll,然后epoll幫你監聽哪些socket上有訊息到達,這樣就避免了大量的無用操作,此時的socket應該采用非阻塞模式,這樣,整個程序只在呼叫select、poll、epoll這些呼叫的時候才會阻塞,收發客戶訊息是不會阻塞的,整個行程或者執行緒就被充分利用起來,這就是事件驅動,所謂的reactor模式,
什么時候適合用快取
- 資料訪問頻率
- 訪問頻率高,適合用快取,效果好
- 訪問頻率低,不建議使用,效果不佳
- 資料讀寫比例
- 讀多寫少,適合快取,效果好
- 讀少寫多,不建議使用,效果不佳
- 資料一致性
- 一致性要求低,適合快取,效果好
- 一致性要求高,不建議快取,效果不佳
Redis中的快取穿透、快取雪崩、快取擊穿
- 快取穿透
快取穿透,是指查詢一個資料庫一定不存在的資料,正常的使用快取流程大致是,資料查詢先進行快取查詢,如果key不存在或者key已經過期,再對資料庫進行查詢,并把查詢到的物件,放進快取,如果資料庫查詢物件為空,則不放進快取,
- 引數傳入物件主鍵ID
- 根據key從快取中獲取物件
- 如果物件不為空,直接回傳
- 如果物件為空,進行資料庫查詢
- 如果從資料庫查詢出的物件不為空,則放入快取(設定過期時間)
想象一下這個情況,如果傳入的引數為-1,會是怎么樣?這個-1,就是一定不存在的物件,就會每次都去查詢資料庫,而每次查詢都是空,每次又都不會進行快取,假如有惡意攻擊,就可以利用這個漏洞,對資料庫造成壓力,甚至壓垮資料庫,即便是采用UUID,也是很容易找到一個不存在的KEY,進行攻擊,
小編在作業中,會采用快取空值的方式,也就是【代碼流程】中第5步,如果從資料庫查詢的物件為空,也放入快取,只是設定的快取過期時間較短,比如設定為60秒,
redisTemplate.opsForValue().set(String.valueOf(goodsId), null, 60, TimeUnit.SECONDS); // 設定過期時間
解決方案
-
介面層增加校驗,如用戶鑒權校驗,id做基礎校驗,id<=0的直接攔截;
-
從快取取不到的資料,在資料庫中也沒有取到,這時也可以將key-value對寫為key-null,快取有效時間可以設定短點,如30秒(設定太長會導致正常情況也沒法使用),這樣可以防止攻擊用戶反復用同一個id暴力攻擊
-
快取雪崩
快取雪崩,是指在某一個時間段,快取集中過期失效,
產生雪崩的原因之一,比如在寫本文的時候,馬上就要到雙十二零點,很快就會迎來一波搶購,這波商品時間比較集中的放入了快取,假設快取一個小時,那么到了凌晨一點鐘的時候,這批商品的快取就都過期了,而對這批商品的訪問查詢,都落到了資料庫上,對于資料庫而言,就會產生周期性的壓力波峰,
小編在做電商專案的時候,一般是采取不同分類商品,快取不同周期,在同一分類中的商品,加上一個隨機因子,這樣能盡可能分散快取過期時間,而且,熱門類目的商品快取時間長一些,冷門類目的商品快取時間短一些,也能節省快取服務的資源,
其實集中過期,倒不是非常致命,比較致命的快取雪崩,是快取服務器某個節點宕機或斷網,因為自然形成的快取雪崩,一定是在某個時間段集中創建快取,那么那個時候資料庫能頂住壓力,這個時候,資料庫也是可以頂住壓力的,無非就是對資料庫產生周期性的壓力而已,而快取服務節點的宕機,對資料庫服務器造成的壓力是不可預知的,很有可能瞬間就把資料庫壓垮,
解決方案:
-
快取資料的過期時間設定隨機,防止同一時間大量資料過期現象發生,
-
如果快取資料庫是分布式部署,將熱點資料均勻分布在不同搞得快取資料庫中,
-
設定熱點資料永遠不過期,
-
redis 持久化,一旦重啟,自動從磁盤上加載資料,快速恢復快取資料,
-
快取擊穿
快取擊穿,是指存在hot key,在不停的扛著大并發,大并發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大并發就穿破快取,直接請求資料庫,就像在一個屏障上鑿開了一個洞,
小編在做電商專案的時候,把這貨就成為“爆款”,
其實,大多數情況下這種爆款很難對資料庫服務器造成壓垮性的壓力,達到這個級別的公司沒有幾家的,所以,務實主義的小編,對主打商品都是早早的做好了準備,讓快取永不過期,即便某些商品自己發酵成了爆款,也是直接設為永不過期就好了,
解決方案
- 設定熱點資料永遠不過期,
- 加互斥鎖,互斥鎖參考代碼如下:
說明:
1)快取中有資料,直接走上述代碼13行后就回傳結果了
2)快取中沒有資料,第1個進入的執行緒,獲取鎖并從資料庫去取資料,沒釋放鎖之前,其他并行進入的執行緒會等待100ms,再重新去快取取資料,這樣就防止都去資料庫重復取資料,重復往快取中更新資料情況出現,另外看到一種方案,當查詢快取沒有資料時,進而去資料庫查找,即使資料庫沒有回傳結果也要創建快取,這樣做是避免快取擊穿,但是要注意這個key設定的過期時間要短,
3)當然這是簡化處理,理論上如果能根據key值加鎖就更好了,就是執行緒A從資料庫取key1的資料并不妨礙執行緒B取key2的資料,上面代碼明顯做不到這點,
百問
Redis番外篇
Redis 最開始的設計可能就是想做一個快取來用,但是分布式環境復雜,暴露的問題可能比較多,所以 Redis 就要做集群,做集群后,可能和 Memcahed 效果類似了,我們要超越它,所以可能就有了多資料型別的存盤結構,光做快取,如何已宕機資料就丟失了,我們的口號是超越 Memcahed,所以我們要支持資料持久化,于是可能就有了 AOF 和 RDB,就可以當資料庫來用來,這樣 Redis 的高效可靠的設計,所以它又可以用來做訊息中間件,這就是 Redis 的三大特點,可以用來做:快取、資料庫和訊息中間件,
再來說說,Redis 如何設計成但行程單執行緒的?
根據官方的測驗結果《How fast is Redis?》來看,在操作記憶體的情況下,CPU 并不能起到決定性的作用,反而可能帶來一些其他問題,比如鎖,CPU 切換帶來的性能開銷等,這一點我們可以根據官方的測驗報告,提供的資料來證明,而且官方提供的資料是可以達到100000+的QPS(每秒內查詢次數),這個資料并不比采用單行程多執行緒 Memcached 差!所以在基于記憶體的操作,CPU不是 Redis 瓶頸的情況下,官方采用來單行程單執行緒的設計,
Redis單執行緒為什么這么快?
快的原因有主要三點:
- 純記憶體操作:Redis是基于記憶體的,所有的命令都在記憶體中完成,記憶體的回應速度相比硬碟是非常快的,記憶體的回應時間大約是100納秒,Redis官方給出的OPS是10W
- 編程語言:Redis采用C語言撰寫,不依賴第三方類別庫,執行速度快
- 執行緒模型:Redis使用單執行緒操作,避免了執行緒的切換和競態消耗
- 采用了非阻塞IO多路復用機制:多路I/O復用模型是利用 select、poll、epoll 可以同時監察多個流的 I/O 事件的能力,在空閑的時候,會把當前執行緒阻塞掉,當有一個或多個流有 I/O 事件時,就從阻塞態中喚醒,于是程式就會輪詢一遍所有的流(epoll 是只輪詢那些真正發出了事件的流),并且只依次順序的處理就緒的流,這種做法就避免了大量的無用操作,這里“多路”指的是多個網路連接,“復用”指的是復用同一個執行緒,加上Redis自身的事件處理模型將epoll中的連接,讀寫,關閉都轉換為了事件,不在I/O上浪費過多的時間
- 由于是單執行緒,所以就存在一個順序讀寫的問題,順序讀寫比隨機讀寫的速度快,
- Redis的資料結構是經過專門的研究和設計的,所以操作起來簡單且快,
最后,再說一點,Redis 是單行程和單執行緒的設計,并不是說它不能多行程多執行緒,比如備份時會 fork 一個新行程來操作;再比如基于 COW 原理的 RDB 操作就是多執行緒的,
Redis如何處理過期資料?Slave不能處理資料,那資料過期了怎么辦?
1主2從的模式中,當master掛掉之后怎么辦?
這種典型的模式就不上圖了,master讀寫,slave1/2只讀,當master掛掉之后,redis服務不可用,需要立馬手動處理,兩種處理方式,第一種是把master重新啟動起來,不用改變現有的主從結構,缺點是什么呢,master重新啟動并完成RDB/AOF的恢復是個耗時的程序,另外會造成slave1/2發生全量復制;第二種就是重新選舉新的master,具體怎么做呢?選折其中一個slave,執行命令 slave no one來解除自己是從服務器的身份,使其稱為一個master,注意的點是這個slave要改成讀寫模式;連到另一個slave,執行slave new master,讓它去找master,整個程序是一個手動的程序,Redis Sentinel就是這樣一個功能,自動完成切換,帥的一比,
Redis分布式鎖你真的會用嗎?
https://www.xttblog.com/?p=4598
Redis使用注意的點
- 由于是單執行緒模型,因此一次只運行一條命令
- 拒絕長(慢)命令:keys, flushall,flushdb, slow lua script, mutil/exec, operate big value(collection)
Redis Sentinel和Redis Cluster的區別
- sentinel
實作高可用,但是沒有磁區
監控,能持續監控Redis的主從實體是否正常作業;
通知,當被監控的Redis實體出問題時,能通過API通知系統管理員或其他程式;
自動故障恢復,如果主實體無法正常作業,Sentinel將啟動故障恢復機制把一個從實體提升為主實體,其他的從實體將會被重新配置到新的主實體,且應用程式會得到一個更換新地址的通知,
Redis Sentinel是一個分布式系統,可以部署多個Sentinel實體來監控同一組Redis實體,它們通過Gossip協議來確定一個主實體宕機,通過Agreement協議來執行故障恢復和配置變更, - Redis Cluster特點如下:
- 所有的節點相互連接;
- 集群訊息通信通過集群總線通信,,集群總線埠大小為客戶端服務埠+10000,這個10000是固定值;
- 節點與節點之間通過二進制協議進行通信;
- 客戶端和集群節點之間通信和通常一樣,通過文本協議進行;
- 集群節點不會代理查詢;
redis3.0以后推出了cluster,具有Sentinel的監控和自動Failover能力,同時提供一種官方的磁區解決方案
Redis分布式鎖/Redis的setnx命令如何設定key的失效時間(同時操作setnx和expire
Redis的setnx命令是當key不存在時設定key,但setnx不能同時完成expire設定失效時長,不能保證setnx和expire的原子性,我們可以使用set命令完成setnx和expire的操作,并且這種操作是原子操作,
下面是set命令的可選項:
set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:設定失效時長,單位秒
PX milliseconds:設定失效時長,單位毫秒
NX:key不存在時設定value,成功回傳OK,失敗回傳(nil)
XX:key存在時設定value,成功回傳OK,失敗回傳(nil)
案例:設定name=p7+,失效時長100s,不存在時設定
1.1.1.1:6379> set name p7+ ex 100 nx
假如Redis里面有1億個key,其中有10w個key是以某個固定的已知的前綴開頭的,如何將它們全部找出來?
可以使用keys [pattern]來列舉出來,由于Redis是單執行緒的,在使用keys命令的時候會導致執行緒阻塞一段時間,我們也可以使用scan指令以無阻塞的方式取出來,但會有一定重復的概率,客戶端去重就可以了,如果是主從模式的話,可以在從服務器執行keys命令,盡量不影響現有業務,
使用過Redis做異步佇列么,你是怎么用的?
Redis如何實作延時佇列?
RDB的原理是什么?
你給出兩個詞匯就可以了,fork和cow,fork是指redis通過創建子行程來進行RDB操作,cow指的是copy on write,子行程創建后,父子行程共享資料段,父行程繼續提供讀寫服務,寫臟的頁面資料會逐漸和子行程分離開來,
Pipeline有什么好處,為什么要用pipeline?
可以將多次IO往返的時間縮減為一次,前提是pipeline執行的指令之間沒有因果相關性,使用redis-benchmark進行壓測的時候可以發現影響redis的QPS峰值的一個重要因素是pipeline批次指令的數目,
是否使用過Redis集群,集群的高可用怎么保證,集群的原理是什么?
- Redis Sentinal 著眼于高可用,在master宕機時會自動將slave提升為master,繼續提供服務,
- Redis Cluster 著眼于擴展性,在單個redis記憶體不足時,使用Cluster進行分片存盤,
Redis集群是如何通信的?
Redis集群采用gossip(流言)協議來通信,Gossip協議的主要職責就是資訊交換,資訊交換的載體就是節點彼此發送的Gossip訊息,常用的Gossip訊息可分為:ping訊息、pong訊息、meet訊息、fail訊息,集群中的每個節點都會單獨開辟一個TCP通道,用于節點之間的彼此通信,通信埠是在基礎埠的基礎上加上1萬,每個節點在固定周期內通過特定規則選擇幾個節點發送ping訊息,接收到ping訊息的節點用pong訊息作為回應,ping訊息發送封裝了自身節點和部分其他節點的狀態資料,pong訊息:當接收到ping、meet訊息時,作為回應訊息回復給發送方確認訊息正常通信,pong訊息內部封裝了自身狀態資料,節點也可以向集群內廣播自身的pong訊息來通知整個集群對自身狀態進行更新,一段時間后整個集群達到了狀態的一致性,
PS:定時任務默認每秒執行10次,每秒會隨機選取5個節點找出最久沒有通信的節點發送ping訊息,用于保證Gossip資訊交換的隨機性
Reids Key是如何尋址的?
分布式尋址演算法
hash 演算法(大量快取重建)
一致性 hash 演算法(自動快取遷移)+ 虛擬節點(自動負載均衡)
redis cluster 的 hash slot 演算法
hash 演算法
來了一個 key,首先計算 hash 值,然后對節點數取模,然后打在不同的 master 節點上,一旦某一個 master 節點宕機,所有請求過來,都會基于最新的剩余 master 節點數去取模,嘗試去取資料,這會導致大部分的請求過來,全部無法拿到有效的快取,導致大量的流量涌入資料庫,
一致性 hash 演算法
一致性 hash 演算法將整個 hash 值空間組織成一個虛擬的圓環,整個空間按順時針方向組織,下一步將各個 master 節點(使用服務器的 ip 或主機名)進行 hash,這樣就能確定每個節點在其哈希環上的位置,
來了一個 key,首先計算 hash 值,并確定此資料在環上的位置,從此位置沿環順時針“行走”,遇到的第一個 master 節點就是 key 所在位置,
在一致性哈希演算法中,如果一個節點掛了,受影響的資料僅僅是此節點到環空間前一個節點(沿著逆時針方向行走遇到的第一個節點)之間的資料,其它不受影響,增加一個節點也同理,
燃鵝,一致性哈希演算法在節點太少時,容易因為節點分布不均勻而造成快取熱點的問題,為了解決這種熱點問題,一致性 hash 演算法引入了虛擬節點機制,即對每一個節點計算多個 hash,每個計算結果位置都放置一個虛擬節點,這樣就實作了資料的均勻分布,負載均衡,
redis cluster 的 hash slot 演算法
redis cluster 有固定的 16384 個 hash slot,對每個 key 計算 CRC16 值,然后對 16384 取模,可以獲取 key 對應的 hash slot,
redis cluster 中每個 master 都會持有部分 slot,比如有 3 個 master,那么可能每個 master 持有 5000 多個 hash slot,hash slot 讓 node 的增加和移除很簡單,增加一個 master,就將其他 master 的 hash slot 移動部分過去,減少一個 master,就將它的 hash slot 移動到其他 master 上去,移動 hash slot 的成本是非常低的,客戶端的 api,可以對指定的資料,讓他們走同一個 hash slot,通過 hash tag 來實作,
如何解決DB和快取一致性問題?
經典的使用場景是對于熱點的資料讀操作是從Redis中讀取的,那么當資料發生寫的操作該怎么辦?因為寫的操作要同時在資料庫和快取中進行,涉及到雙寫,那么就必然存在雙寫資料不一致的問題,如果業務的場景是要求強一致性,那就不要用快取,那么我接下來就結合我的理解談談一些經典的使用場景下,如何盡可能的保證雙寫的一致性,
-
讀的時候先從Redis讀取,如果Redis中沒有,那么再去資料庫中讀,讀完之后放回Redis,然后回傳回應,寫的時候先洗掉快取,然后再去寫入資料庫,然后等待100毫秒再次洗掉快取,這里有幾點點需要說明:
為什么是先洗掉快取而不是先更新資料?
假設我們先更新資料,再洗掉快取,那么存在資料更新成功,但是洗掉快取失敗的情況,先洗掉快取,如果資料庫寫操作成功,下次讀快取時會從資料庫獲取新的資料并放入快取;如果資料庫寫操作失敗,那么再次讀到的資料也是舊的資料,也保證了快取的一致性,另外,有的快取資料是經過計算的資料,不是單純的某個欄位的值,如果去更新的話會是一個復雜的程序,倒不如下次獲取的時候去計算并放入快取,這也是一個“懶”的思想,
為什么寫入資料庫后還要再次洗掉快取?采用延時雙刪策略
我們在資料庫寫操作的時候是不能保證沒有讀的操作,特別是在高并發場景下,往往資料庫寫操作還沒完成,就已經有讀的操作完成并將修改前的資料放入快取,這也就造成了快取和資料庫中的資料不一致的問題,那么解決這個問題有一下方法- 資料庫寫操作完成后再次洗掉快取,這樣出現不一致的時間就只會在資料庫寫操作和再次洗掉快取這段時間內,如果業務能夠容忍短時間的不一致,可以采用這個方法,
- 如果業務對一致性要求較高,那么可以在第一次洗掉快取后對后續的操作做串行處理,后續的所有操作都需要等待資料庫寫操作的完成,具體的代碼思路可以是這樣子,將請求放入JVM的一個Queue中,將請求積壓在佇列中,同步等待寫操作的完成,這個思路的實作有些復雜,還涉及到如果Queue中積累的請求過多該怎么處理,請求過多的話也就說明這是個熱點key,
第二種方案:異步更新快取(基于訂閱binlog的同步機制)
技術整體思路:
MySQL binlog增量訂閱消費+訊息佇列+增量資料更新到redis
讀Redis:熱資料基本都在Redis
寫MySQL:增刪改都是操作MySQL
更新Redis資料:MySQ的資料操作binlog,來更新到RedisRedis更新
1)資料操作主要分為兩大塊:
一個是全量(將全部資料一次寫入到redis)
一個是增量(實時更新)
這里說的是增量,指的是mysql的update、insert、delate變更資料,2)讀取binlog后分析 ,利用訊息佇列,推送更新各臺的redis快取資料,
這樣一旦MySQL中產生了新的寫入、更新、洗掉等操作,就可以把binlog相關的訊息推送至Redis,Redis再根據binlog中的記錄,對Redis進行更新,
其實這種機制,很類似MySQL的主從備份機制,因為MySQL的主備也是通過binlog來實作的資料一致性,這里可以結合使用canal(阿里的一款開源框架),通過該框架可以對MySQL的binlog進行訂閱,而canal正是模仿了mysql的slave資料庫的備份請求,使得Redis的資料更新達到了相同的效果,
當然,這里的訊息推送工具你也可以采用別的第三方:kafka、rabbitMQ等來實作推送更新Redis,
以上就是Redis和MySQL資料一致性詳解,
redis 的過期策略都有哪些?記憶體淘汰機制都有哪些?手寫一下 LRU 代碼實作?
參考Redis過期策略
redis在什么情況下會變慢?
單執行緒的redis,如何知道要運行定時任務?
redis是單執行緒的,執行緒不但要處理定時任務,還要處理客戶端請求,執行緒不能阻塞在定時任務或處理客戶端請求上,那么,redis是如何知道何時該運行定時任務的呢?
Redis 的定時任務會記錄在一個稱為最小堆的資料結構中,這個堆中,最快要執行的任務排在堆的最上方,在每個回圈周期,Redis 都會將最小堆里面已經到點的任務立即進行處理,處理完畢后,將最快要執行的任務還需要的時間記錄下來,這個時間就是接下來處理客戶端請求的最大時長,若達到了該時長,則暫時不處理客戶端請求而去運行定時任務,
Redis的五種資料型別分別是什么資料結構實作的?
Redis的字串資料型別既可以存盤字串,又可以存盤整數和浮點數,Redis在內部是怎么存盤這些值的?
Redis的一部分命令只能對特定的資料型別執行操作,比如append只能對字串執行,hset只能對hash執行,而另一部分命令卻可以對所有資料型別執行,比如del, type, expire,不同的命令在執行時如何進行型別檢查的?Redis在內部是否實作了一個型別系統?
Redis沒有直接使用C語言的傳統字串表示,而是自己構建了一個名為簡單動態字串的抽象型別,并將SDS用作Redis的默認字串表示,
struct sdshdr {
// buf中已經占用空間的長度
int len;
// buf中剩余可用空間的長度
int free;
// 資料空間,值存在這里,
char bu[];
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/261348.html
標籤:其他
