Redis大全手冊(上)

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-valueO(1) del key#刪除key-valueO(1) mset key value key value#批量設定key-valueO(n) mget key key#批量獲取key-valueO(n) incr key#key自增1,如果key不存在,自增後get(key)=1O(1) decr key#key自減1,如果key不存在,自減後get(key)=-1O(1) incrby key k#key自增k,如果key不存在,自增後get(key)=kO(1) decrby key k#key自減k,如果key不存在,自減後get(key)=-kO(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#設定下標所有對應的值 複製程式碼
記錄網站每個使用者個人主頁的訪問量
#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; } 複製程式碼


hash
雜湊鍵值結構

hmset key field value field value#批量設定O(n) hmget key field field#批量獲取O(n) hget key field#獲取hash key對應的field的valueO(1) hset key field value#設定hash key對應field的valueO(1) hdel key field#刪除hash key對應的field的valueO(1) hexists key field#判斷hash key 是否有fieldO(1) hlen key#獲取hash key field的數量O(1) hgetall key#h返回hash key對應所有的field和valueO(n) hvals key#返回hash key對應所有field的valueO(n) hkeys key#返回hash key對應所有fieldO(n) hsetnx key field value#設定hash key對應field的value(如field存在,則失敗)O(1) hincrby key field intCounter#hash key 對應的field的value自增intCounterO(1) hincrbyfloat key field floatCounter#hincrby浮點數版O(1) 複製程式碼
小心使用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#獲取列表指定索引範圍所有itemO(n) llen key#獲取列表長度O(1) lset key index newValue#設定列表指定索引值為newValueO(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每次條數要控制(網路原因)

從上圖舉例,redis命令的執行時間是很快的,但是由於資料需要通過網路傳輸,由於2個地區相隔很遠,資料以光速度傳播也需要時間,然而這個時間有可能比redis執行時間要長。
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(); } 複製程式碼
使用建議
釋出訂閱
角色釋出者(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地理資訊定位
type geoKey = zset 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秒的資料,也有可能是幾秒
如何定位
info Persistence
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速度)
- 硬碟
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,網路增強
規避複製風暴
- 單主節點複製風暴:
- 問題:主節點重啟,多從節點複製
- 解決:更換複製拓撲
- 單機器複製風暴
- 如圖:機器宕機後,大量全量複製
- 主節點分散多機器