API的理解和使用
通用命令
keys
dbsize #計算key的總數
exists key #檢查key是否存在
del key [key] #刪除指定key-value
type key #返回key的型別
expire key seconds #key在seconds過期
ttl key #檢視key剩餘的過期時間
persist key #去掉key的過期時間
複製程式碼
-2表示過期
-1代表key存在,並且沒有過期時間
kyes基本不在生產環境使用
keys * #遍歷所有key
key [pattern]
複製程式碼
命令 | 時間複雜度 |
---|---|
keys | O(n) |
dbsize | O(1) |
del | O(1) |
exists | O(1) |
expire | O(1) |
type | O(1) |
資料結構和內部編碼
redis為什麼這麼快?- 純記憶體
- 非阻塞IO
- 避免執行緒切換和競態消耗
單執行緒需要注意什麼?
- 一次只執行一條命令
- 拒絕長(慢)命令
keys, flushall, flushdb, slow lua script, mutil/exec, operate big value(collention)
- 其實不是單執行緒
fysnc file descriptor close file descriptor 複製程式碼
字串
get key #獲取key對應的value O(1)
set key value #設定key-value O(1)
del key #刪除key-value O(1)
mset key value key value #批量設定key-value O(n)
mget key key #批量獲取key-value O(n)
incr key #key自增1,如果key不存在,自增後get(key)=1 O(1)
decr key #key自減1,如果key不存在,自減後get(key)=-1 O(1)
incrby key k #key自增k,如果key不存在,自增後get(key)=k O(1)
decrby key k #key自減k,如果key不存在,自減後get(key)=-k O(1)
set key value #不管key是否存在,都設定 O(1)
setnx key value #key不存在,才設定 O(1)
set key value xx #key存在,才設定 O(1)
getset key newvalue #set key newvalue並返回舊的value
append key value #將value追加到舊的value
strlen key #返回字串的長度(注意中文)
incrbyfloat key 3.5 #增加key對應的值3.5
getrange key start end #獲取字串指定下標的所有的值
setrange key index value #設定下標所有對應的值
複製程式碼
hash
雜湊鍵值結構
hmset key field value field value #批量設定 O(n)
hmget key field field #批量獲取 O(n)
hget key field #獲取hash key對應的field的value O(1)
hset key field value #設定hash key對應field的value O(1)
hdel key field #刪除hash key對應的field的value O(1)
hexists key field #判斷hash key 是否有field O(1)
hlen key #獲取hash key field的數量 O(1)
hgetall key #h返回hash key對應所有的field和value O(n)
hvals key #返回hash key對應所有field的value O(n)
hkeys key #返回hash key對應所有field O(n)
hsetnx key field value #設定hash key對應field的value(如field存在,則失敗) O(1)
hincrby key field intCounter #hash key 對應的field的value自增intCounter O(1)
hincrbyfloat key field floatCounter #hincrby浮點數版 O(1)
複製程式碼
記錄網站每個使用者個人主頁的訪問量
#redis實現
incr userid:pagevies(單執行緒,無競爭)
hincrby user:1:info pageview count
#java模擬程式碼
public VideoInfo get(long id){
String redisKey = redisPrefix + id;
Map<String,String> hashMap = redis.hgetAll(redisKey);
VideoInfo videoInfo = transferMapToVideo(hashMap);
if(videoInfo == null){
videoInfo = mysql.get(id);
if(videoInfo != null){
redis.hmset(redisKey, transferMapToVideo(videoInfo))
}
}
return videoInfo;
}
複製程式碼
小心使用hgetall(redis單執行緒) 例子:如儲存一個使用者的資訊的實現,下面說3種情形,當然還有更多種其他方式
- String v1
- String v2
- hash 比較
命令 | 優點 | 缺點 |
---|---|---|
string v1 | 程式設計簡單,可能節約記憶體 | 1. 序列號開銷 2. 設定屬性要操作整個資料 |
string v2 | 直觀,可以部分更新 | 1. 記憶體佔用較大 2. key較為分散 |
hash | 直觀、節省空間、可以部分更新 | 1. 程式設計稍微複雜 2. ttl不好控制 |
list
特點:有序、可以重複、左右兩邊插入彈出
rpush key value value ...valueN #從列表右端插入值(1-N個)
lpush key value value ...valueN #從列表左端插入值(1-N個)
linsert key before|after value newValue #在list指定的前|後插入newValue
lpop key #從列表左側彈出一個item
rpop key #從列表右側彈出一個item
#根據count值,從列表中刪除所有value相等的項
#count > 0,從左到右,刪除最多count個value相等的項
#count < 0,從右到左,刪除最多Math.abs(count)個value相等的項
#count = 0,刪除所有value相等的項
lrem key count value
ltrim key start end #按照索引範圍修剪列表 O(n)
lrange key start end #獲取列表指定索引範圍所有item O(n)
llen key #獲取列表長度 O(1)
lset key index newValue #設定列表指定索引值為newValue O(n)
blpop key timeout #lpop阻塞版本,timeout是阻塞超時時間,timeout=0為永遠不阻塞 O(1)
brpop key timeout #rpop阻塞版本,timeout是阻塞超時時間,timeout=0為永遠不阻塞 O(1)
##小建議-資料結構類比
lpush + lpop = stack
lpush + rpop = queue
lpush + ltrim = capped collection
lpush + brpop = message quere
複製程式碼
慢查詢
- slowlog-max-len
- 先進先出佇列
- 固定長度
- 儲存在記憶體中
慢查詢命令
slowlog get [n] #獲取慢查詢佇列
slowlog len #獲取慢查詢佇列長度
slowlog reset #清空慢查詢佇列
複製程式碼
- 慢查詢閥值(單位:微妙)
- slowlog-log-slower-than=0 記錄所有命令
- slowlog-log-slower=than<0 不記錄任何命令 配置方式
1. 預設值
config get slowlog-max-len = 128
config get slowlog-log-slower-than = 1000
2. 修改配置檔案重啟
3. 動態配置
config set slowlog-max-len 1000
config set slowlog-log-slower-than 1000
複製程式碼
運維經驗
- slowlog-max-len不要設定過大,預設10ms,通常設定1ms
- slowlog-log-slower-than不要設定過小,通常設定1000左右
- 理解命令生命週期
- 定期持久化慢查詢
pipeline
批量網路命令通訊模型
什麼是流水線 流水線作用命令 | N個命令操作 | 1次pipeline(n個命令) |
---|---|---|
時間 | n次網路 + n次命令 | 1次網路 + n次命令 |
資料量 | 1條命令 | n條命令 |
- redis的命令時間是微秒級別
- pipeline每次條數要控制(網路原因)
pipeline-Jedis實現
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<type>jar</type>
</dependency>
#沒用pipeline, 1W次hset需要50s
Jedis jedis = new Jedis("127.0.0.1", 6379);
for(int i=0;i<10000;i++){
jedis.hset("hashkey:"+i,"field"+i, "value"+i);
}
#使用pipeline
Jedis jedis = new Jedis("127.0.0.1", 6379);
for(int i=0;i<100;i++){
Pipeline pipeline = jedis.pipelined();
for(int j=i*100; j<(i+1)*100;j++){
pipeline.hset("hashkey:"+j,"field"+j, "value"+j);
}
pipeline.syncAndReturnAll();
}
複製程式碼
使用建議
- 注意每次pipeline攜帶的資料量
- pipeline每次只能作用在一個Redis節點上
- M操作和pipeline的區別
釋出訂閱
角色 釋出者(publisher) 訂閱者(subscriber) 頻道(channel) 模型
publish channel message #釋出訊息
subscribe [channel] #一個或多個
unsubscribe [channel] #一個或多個
psubscribe [pattern...] #訂閱模式
punsubscribe [pattern...] #退訂指定的模式
pubsub channels #列出至少有一個訂閱者的頻道
pubsub numsub [channel...] #列出給定頻道的訂閱者數量
pubsub numpat #列出被訂閱模式的數量
複製程式碼
點陣圖
setbit key offset value #給點陣圖指定索引設定值
getbit key offset #獲取點陣圖指定索引的值
bitcount key [start end] #獲取點陣圖指定範圍(start到end,單位為位元組,如果不指定就是獲取全部)位值為1的個數
bitop key targetBit [start] [end] #計算點陣圖指定範圍第一個偏移量對應的值等於targetBit的位置
複製程式碼
獨立使用者統計
- 使用set和Bitmap兩種方式
- 1億使用者,5千萬獨立
資料型別 | 每個userid佔用空間 | 需要儲存的使用者量 | 全部儲存量 |
---|---|---|---|
set | 32位(假設userid用的是整型,實際場景很多用長整型) | 50000000 | 32位*50000000=190.7348633MB |
Bitmap | 1位 | 100000000 | 1位*100000000=11.920929MB |
一天 | 一個月 | 一年 | ||
---|---|---|---|---|
set | 200M | 6G | 72G | 大約值 |
Bitmap | 12.5M | 375M | 4.5G | 大約值 |
只有十萬獨立使用者呢?
資料型別 | 每個userid佔用空間 | 需要儲存的使用者量 | 全部儲存量 |
---|---|---|---|
set | 32位(假設userid用的是整型,實際場景很多用長整型) | 100000 | 32位*1000000=0.3814697MB |
Bitmap | 1位 | 100000 | 1位*100000000=0.0119209MB |
使用建議
- type = string,最大512MB
- 注意setbit的偏移量,可能有較大耗時
- 點陣圖不是絕對好
HyperLogLog
- 極小空間完成獨立數量統計
- 本質還是字串
- pfcount 統計有一定錯誤率0.81%
- 無法取出單條資料
pfadd key element [element...] #向hyperloglog新增元素
pfcount key [key] #計算hyperloglog的獨立總數
pfmerge destkey sourcekey [sourcekey] #合併多個hyperloglog
複製程式碼
geo地理資訊定位
- 3.2版本以後才有geo
- geoKey的型別是zset,
type geoKey = zset
- 沒有刪除的API,可以使用
zrem key member
geo key longitude latitude member [longitude latitude member...] #增加地理位置資訊
geopos key member [member...] #獲取地理位置資訊
geodist key member1 member2 [unit] #獲取兩個地理位置的距離,unit:m、km、mi、ft
複製程式碼
Redis持久化的取捨和選擇
redis持久化RDB
觸發機制save 阻塞的 檔案策略:如果存在老的RDB檔案,替換 時間複雜度O(n)
bgsave
save與bgsave
命令 | save | bgsave |
---|---|---|
IO型別 | 同步 | 非同步 |
阻塞 | 是 | 是(阻塞發生再fork) |
複雜度 | O(n) | O(n) |
優點 | 不會消耗額外記憶體 | 不阻塞客戶端命令 |
缺點 | 阻塞客戶端命令 | 需要fork,消耗記憶體 |
# 配置redis.conf
save 900 1
save 300 10
save 60 10000
dbfilename dump.rdb
dir ./
stop-write-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
#最佳配置
dbfilename dump-${port}.rdb #指定對應哪個redis的備份
dir /bigdiskpath #指定具體檔案目錄
stop-write-on-bgsave-error yes
rdbcompression yes
複製程式碼
觸發機制
- 全量複製
- debug reload
- shutdown
RDB總結
- RDB是redis記憶體到硬碟的快照,用於持久化
- save通常會阻塞redis
- bgsave不會阻塞redis,但是會fork新程式
- save自動配置滿足任一就會被執行
- 有些觸發機制不容忽視
AOF
RDB有什麼問題 耗時、耗效能 不可控、丟失資料
AOF執行原理-建立
AOF執行原理-恢復AOF的三種策略 always
everysec no命令 | always | everysec | no |
---|---|---|---|
優點 | 不丟失資料 | 每秒一次fsync丟一秒資料 | 不用管 |
缺點 | IO開銷較大,一般的sata盤只有幾百TPS | 丟一秒資料 | 不可控 |
AOF重寫
- 減少硬碟佔用量
- 加速回復速度
AOF重寫2種方式
-
bgrewriteaof命令
-
自動
配置名 含義 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
#配置redis.conf appendonly yes appendfilename "appendonly-${port}.aof" appendfsync everysec dir /bigdiskpath no-appendfsync-on-rewrite yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb 複製程式碼
AOF重寫流程
AOF阻塞問題
大於2秒會造成主執行緒阻塞,無法進行後續客戶端發來的命令 每秒刷盤的策略不止是隻丟失1秒的資料,也有可能是幾秒如何定位
- Redis日誌
- reids命令
info Persistence
(無法看到具體時間點) - linux top 命令觀察IO使用率
RDB和AOF選擇
命令 | RDB | AOF |
---|---|---|
啟動優先順序 | 低 | 高 |
體積 | 小 | 大 |
恢復速度 | 快 | 慢 |
資料安全性 | 丟資料 | 根據策略決定 |
輕重 | 重 | 輕 |
最佳策略
- 小分片
- 快取或儲存
- 監控(硬碟、記憶體、負載、網路)
- 足夠的記憶體
fork操作
- 同步操作(阻塞)
- 與記憶體量息息相關:記憶體越大,耗時越長(與機器型別無關)
- info:latest_fork_usec
改善fork
- 優先使用物理機或者高效支援fork操作的虛擬化技術
- 控制redis例項最大可用記憶體:maxmemory
- 合理配置Linux記憶體分配策略:vm.overommit_memory=1
- 降低fork頻率:例如放寬AOF重寫自動觸發機制,不必要的全量複製
子程式開銷與優化
- CPU:
- 開銷:RDB和AOF檔案生成,屬於CPU密集型
- 優化:不做主reids CPU繫結,不和密集型CPU部署在一起
- 記憶體
- 開銷:fork記憶體開銷,Linux:copy-on-write
- 優化:Linux:echo never > /sys/kernel/mm/transparent_hugepage/enabled(關閉增加fork速度)
- 硬碟
- 開銷:RDB和AOF檔案寫入,可以結合iostat,iotop分析
- 優化:
- 不和高硬碟負載服務部署再一起:儲存服務,訊息佇列等。
- no-appendfsync-on-rewrite = yes
- 根據寫入量決定磁碟型別:例如SSD
- 單機多例項持久化檔案目錄可以考慮分盤儲存
redis複製的原理與優化
單機有什麼問題? 機器故障 容量瓶頸 QPS瓶頸
簡單總結- 一個master可以有多個slave
- 一個slave只能有一個master
- 資料流向是單向的,master到slave
slaveof ip port
slave-read-only yes
slaveof on one
複製程式碼
全量複製
開銷:- bgsave時間
- RDB檔案網路傳輸時間
- 從節點清空資料時間
- 從節點載入RDB的時間
- 可能的AOF重寫時間
部分複製
Redis主從複製和叢集配置
主從複製的常見問題 讀寫分離- 讀流量分攤到從節點,提高訪問速度
- 可能遇到問題:複製資料延遲、讀到過期資料、從節點故障
配置不一致
- 例如maxmemory不一致,丟失資料
- 例如資料結構優化引數(例如hash-max-ziplist-entries):記憶體不一致
規避全量複製
- 第一次全量複製
- 第一次不可避免,從節點必須全量
- 解決:小主節點(maxmemory)分資料量,訪問低峰時刻
- 節點執行ID不匹配
- 主節點重啟(執行ID改變)
- 解決:故障轉移,例如哨兵或叢集
- 複製積壓緩衝區不足
- 網路中斷,部分複製無法滿足
- 解決:增大複製緩衝區配置rel_backlog_size,網路增強
規避複製風暴
- 單主節點複製風暴:
- 問題:主節點重啟,多從節點複製
- 解決:更換複製拓撲 slave-1從master複製資料之後,接下來的slave都從slave-1複製資料,減輕master壓力
- 單機器複製風暴
- 如圖:機器當機後,大量全量複製
- 主節點分散多機器
Redis Sentinel
主從複製-master宕掉故障處理
Redis Cluster
快取設計與優化
Redis雲平臺CacheCloud
記憶體管理
Redis 資料結構與記憶體管理策略(上)
Redis 資料結構與記憶體管理策略(下)
原理、方法雙管齊下,大神帶你細解Redis記憶體管理和優化
開發運維常見坑
redis調整核心引數
Redis安全
redis 熱點Key的發現與解決之道
redis4.0之基於LFU的熱點key發現機制