一、初始Redis
1、Redis特性與優點
- 速度快。redis所有數據都存放於內存;是用C語言實現,更加貼近硬件;使用了單線程架構,避免了多線程競爭問題
- 基於鍵值對的數據結構,支持的數據結構豐富。它主要提供了5種數據結構: 字符串、 哈希、 列表、 集合、 有序集合, 同時在字符串的基礎之上演變出了位圖(Bitmaps) 和HyperLogLog兩種神奇的“數據結構”, 並且隨着LBS(Location Based Service, 基於位置服務) 的不斷發展, Redis3.2版本中加入有關GEO(地理信息定位) 的功能
- 功能豐富。鍵過期功能,用來實現緩存;提供了發布訂閱功能,實現消息系統;支持Lua腳本;提供簡單的事務功能;提供了Pipeline,一次性將一批命令傳給redis服務器,減小了網絡開銷
- 簡單,穩定。源碼少,3.0版本源代碼只有5w行左右
- 客戶端語言多。
- 持久化。兩種持久化方式: RDB和AOF
- 主從復制。復制功能是分布式Redis的基礎
-
高可用和分布式。2.8版本提供了高可用實現Redis Sentinel, 它能夠保證Redis節點的故障發現和故障自動轉移。 Redis從3.0版本正式提供了分布式實現Redis Cluster, 它是Redis真正的分布式實現, 提供了高用、 讀寫和容量的擴展性。
2、應用場景
- 緩存。
- 排行榜系統。redis提供了列表和有序集合支持。
- 計數器應用。
- 社交網絡。
- 消息隊列系統。redis提供了訂閱發布和阻塞隊列功能,雖然和專業的消息隊列軟件比不夠強大,但是可以滿足一般的消息隊列需求
3、redis常用操作
- 服務端啟動。配置文件啟動,redis-server /opt/redis/redis.conf
- 客戶端啟動。redis-cli -h 127.0.0.1 -p 6379
- 停止redis服務。redis-cli shutdown nosave|save 關閉過程:斷開與客戶端的連接;持久化文件生成。不建議kill -9 暴力殺進程,極端下會造成丟失數據的情況
二、API的理解和使用
1、全局命令
- 查看所有鍵。【keys *】會遍歷所有鍵,禁用
- 鍵總數。【dbsize】不會遍歷所有鍵,直接獲取redis內置的鍵總數
- 檢查鍵是否存在。【exists key】存在返回1,不存在返回0
- 刪除鍵。【del key】返回結果為成功刪除的鍵的個數
- 設置鍵過期。【expire key seconds】秒級別;【expireat key timestamp 】秒級時間戳;【pexpire key milliseconds】毫秒級過期 ;【pexpireat key milliseconds-timestamp 】毫秒級時間戳
- 檢查鍵過期。【ttl key】返回鍵的剩余過期時間,秒級;【pttl key】毫秒級;-1,鍵沒設置過期時間;-2 鍵不存在;
- 清除過期時間。【persist】;對於字符串類型鍵,執行set命令也會去掉過期時間;setex命令作為set+expire的組合, 不但是原子執行, 同時減少了一次網絡通訊的時間
- 查看鍵的類型。【type key】鍵不存在,返回none
- 鍵重命名。rename key newkey。如果在rename newkey之前,newkey已經存在,newkey的值會被覆蓋。防止被覆蓋,提供了renamenx命令。重命名期間會刪除原來的key,如果key對應的值過大,存在阻塞redis的可能。
- 隨機返回一個鍵。randomkey。
2、數據結構和內部編碼
每種數據結構都有底層的內部編碼實現,而且是多種實現,redis會在合適的場景下選擇合適的內部編碼。
3、單線程架構
- Redis使用了單線程架構和IO多路復用模型(epoll作為多路復用技術的實現,非阻塞IO)來實現。
- 每次客戶端的請求都會經過發送命令、執行命令、返回結果三個階段。
- 所有客戶端命令都會放入到同一個隊列中,然后逐個被執行。
- 單線程避免了線程切換和競態產生的消耗
4、字符串,最大不能超過512M
(1)命令
- 設置值。set key value [ex seconds] [px milliseconds] [nx|xx]。其中setnx可以作為分布式鎖的實現
- 獲取值。 get key。不存在返回 nil(空)
- 批量設置值,mset key value [key value ...] ;批量獲取值,mget key [key ...] 。一次請求的網絡時間大於命令處理時間。學會使用批量操作,能減少大量的網絡消耗。提高業務處理效率。但是一次批量操作過多,有可能導致Redis阻塞或者網絡擁堵
- 計數。incr、decr、incrby、decrby、incrbyfloat。很多其他的語言和存儲系統通過cas實現計數,會有一定的cpu開銷。redis單線程模型,完全不存在這個問題。
- 追加值。append key value。
- 字符串長度。strlen key。每個中文占用三個字節,也就是三個長度。
- 設置並返回原值。getset key value
- 設置指定位置的字符。setrange key offset value
- 獲取部分字符串。getrange key start end。時間復雜度O(n)
(2)內部編碼,有三種
- int。8個字節的長整型
- embstr。小於等於39個字節的字符串
- raw。大於39個字節的字符串
(3)應用場景
- 緩存
- 計數
- 共享session
- 限速。每個用戶的請求頻率,每個ip的請求頻率
5、哈希
(1)命令
- 設置field值。hset key field value。hsetnx,和setnx作用相同,只不過作用域由鍵變為field
- 獲取field值。hget key field。如果field不存在,返回nil
- 刪除field。hdel key field [field ...]
- 計算field個數。hlen key
- 批量設置和獲取field-value。hmget key field [field ...] ;hmset key field value [field value ...] 。
- 判斷field是否存在。hexists key field。存在返回1,不存在返回0
- 獲取所有field。hkeys key
- 獲取所有value。hvals key
- 獲取所有field-value。hgetall key。如果獲取的元素比較多,可能會阻塞Redis。
- hincrby key field;hincrbyfloat key field
- 計算value的字符串長度。hstrlen key field
(2)內部編碼
- ziplist(壓縮列表)。當哈希類型元素個數小於hash-max-ziplist-entries
配置(默認512個) 、 同時所有值都小於hash-max-ziplist-value配置(默認64
字節) 時, Redis會使用ziplist作為哈希的內部實現, ziplist使用更加緊湊的
結構實現多個元素的連續存儲, 所以在節省內存方面比hashtable更加優秀 - hashtable(哈希表)。當哈希類型無法滿足ziplist的條件時,Redis會使用hashtable作為哈希的內部實現
6、列表。列表中的每個字符串稱為元素(element),一個列表最多存儲2^32-1個元素。可以充當棧和隊列,比較靈活。列表中的元素是有序的且可重復的,可以通過下標獲取某個元素
(1)命令
- 添加操作。從右邊插入數據 rpush key value [value ...];從左邊插入數據 lpush key value [value ...];向某個元素前或后插入數據 linsert key before|after pivot value
- 查找。獲取指定范圍的元素列表 lrange key start end(包含end);獲取指定下標的元素 lindex key index(-1為最后一個元素);獲取列表長度 llen key
- 刪除。從左側彈出元素 lpop key;從右側彈出元素 rpop key;刪除指定元素 lrem key count value(count>0 從左到右,刪除最多count個元素;count<0 從右到左;count=0,刪除所有);按照索引范圍修剪列表 ltrim key start end
- 修改。修改執行索引下標的元素 lset key index newValue;
- 阻塞操作。blpop key [key ...] timeout ;brpop key [key ...] timeout 。需要注意兩點:第一點,如果blpop多個key,一旦有一個鍵能彈出元素,立刻給客戶端返回;第二點,如果多個客戶端對同一個key執行blpop,那么最先執行blpop的客戶端可以獲取到彈出的值。
(2)內部編碼
- ziplist(壓縮列表)。當列表的元素個數小於list-max-ziplist-entries配置(默認512個),同時列表中每個元素的值都小於list-max-ziplist-value配置時(默認64字節),Redis會選用ziplist來作為列表的內部實現來減少內存的使用
- linkedlist(鏈表):當列表類型無法滿足ziplist的條件時, Redis會使用linkedlist作為列表的內部實現。
- Redis3.2版本提供了quicklist內部編碼,簡單地說它是以一個ziplist為節點的linkedlist,它結合了ziplist和linkedlist兩者的優勢,為列表類型提供了一種更為優秀的內部編碼實現
(3)應用場景
- 消息隊列。Redis的lpush+brpop命令組合即可實現阻塞隊列, 生產者客戶端使用lrpush從列表左側插入元素, 多個消費者客戶端使用brpop命令阻塞式的“搶”列表尾部的元素, 多個客戶端保證了消費的負載均衡和高可用性。
- 文章列表。哈希存儲每篇文章的詳細信息。列表存儲每個人的文章列表。支持分頁獲取,如果列表較大,獲取列表中間元素的性能會變差,可以使用redis3.2版本的quicklist內部編碼實現。
- lpush+lpop=Stack(棧) ;lpush+rpop=Queue(隊列) ;lpush+brpop=Message Queue(消息隊列,阻塞隊列); lpush+ltrim=Capped Collection(有限集合)
7、集合(set)。不允許有重復元素,無序,不能通過索引下標獲取元素。一個集合最多可以存儲2^32-1個元素。支持集合內的增刪改查,集合的交集、並集、差集。
(1)命令。集合內操作。
- 添加元素。sadd key element [element ...]
- 刪除元素。srem key element [element ...]
- 計算元素個數。scard key 。時間復雜度O(1),不會遍歷集合。
- 判斷元素是否在集合中。sismember key element 。在集合內返回1,不在返回0
- 隨機從集合中返回指定個數元素。srandmember key [count]
- 從集合隨機彈出指定個數的元素。spop key [count]
- 獲取所有元素。smembers key 。如果元素過多,存在阻塞redis的可能。可以用sscan來完成。
(2)命令,集合間操作。
- 求多個集合的交集。sinter key [key ...]
- 求多個集合的並集。suinon key [key ...]
- 求多個集合的差集。sdiff key [key ...]
- 將集合操作的結果保存。sinterstore/suionstore/sdiffstore destination key [key ...]
(3)內部編碼
- intset(整數集合) : 當集合中的元素都是整數且元素個數小於set-maxintset-entries配置(默認512個)時, Redis會選用intset來作為集合的內部實現,從而減少內存的使用。
- hashtable(哈希表) : 當集合類型無法滿足intset的條件時, Redis會使用hashtable作為集合的內部實現。
(4)使用場景
- 標簽(tag)。使用sinter命令計算用戶的共同興趣。
- 生成隨機數,抽獎
8、有序集合。元素可以排序,每個元素設置一個分數(score)作為排序的依據。集合成員不能重復,但是每個元素的score可以重復
(1)命令。集合內的命令。
- 添加成員。zadd key score member [score member ...]
·Redis3.2為zadd命令添加了nx、 xx、 ch、 incr四個選項:
·nx: member必須不存在, 才可以設置成功, 用於添加。
·xx: member必須存在, 才可以設置成功, 用於更新。
·ch: 返回此次操作后, 有序集合元素和分數發生變化的個數
·incr: 對score做增加, 相當於后面介紹的zincrby。 - 計算成員個數。zcard key
- 計算某個成員的分數。zscore key member
- 計算成員排名。zrank key member (分數由低到高);zrevrank key member (分數由高到低)
- 刪除成員。zrem key member [member ...]
- 增加成員分數。zincrby key increment member
- 返回指定排名范圍的成員。zrange key start end [withscores] (排名由低到高);zrevrange key start end [withscores] (排名由高到低)
- 返回指定分數范圍的成員。zrangebyscore key min max [withscores] [limit offset count] 。其中zrangebyscore按照分數從低到高返回, zrevrangebyscore反之。 例如
下面操作從低到高返回200到221分的成員, withscores選項會同時返回每個
成員的分數。 [limit offset count]選項可以限制輸出的起始位置和個數 。同時min和max還支持開區間(小括號) 和閉區間(中括號) , -inf和
+inf分別代表無限小和無限大。 - 返回指定分數范圍成員個數。zcount key min max
- 刪除指定排名內的升序元素。zremrangebyrank key start end
- 刪除指定分數范圍的成員。zremrangebyscore key min max
(2)命令,集合間的操作
- 交集。zinterstore destination numkeys key [key ...] [weights weight [weight ...]][aggregate sum|min|max]
- destination: 交集計算結果保存到這個鍵。
·numkeys: 需要做交集計算鍵的個數。
·key[key...]: 需要做交集計算的鍵。
weights weight[weight...]: 每個鍵的權重, 在做交集計算時, 每個鍵中
的每個member會將自己分數乘以這個權重, 每個鍵的權重默認是1。
·aggregate sum|min|max: 計算成員交集后, 分值可以按照sum(和) 、
min(最小值) 、 max(最大值) 做匯總, 默認值是sum - 並集。zunionstore destination numkeys key [key ...] [weights weight [weight ...]][aggregate sum|min|max] 。
(3)內部編碼
- ziplist(壓縮列表) : 當有序集合的元素個數小於zset-max-ziplistentries配置(默認128個) , 同時每個元素的值都小於zset-max-ziplist-value配置(默認64字節) 時, Redis會用ziplist來作為有序集合的內部實現, ziplist
可以有效減少內存的使用 - skiplist(跳躍表) : 當ziplist條件不滿足時, 有序集合會使用skiplist作為內部實現, 因為此時ziplist的讀寫效率會下降。
(4)使用場景
- 排行榜
9、鍵管理
(1)鍵遷移
-
dump+restore可以實現在不同的Redis實例之間進行數據遷移的功能, 整個遷移的過程分為兩步:
1) 在源Redis上, dump命令會將鍵值序列化, 格式采用的是RDB格式。
2) 在目標Redis上, restore命令將上面序列化的值進行復原, 其中ttl參數代表過期時間, 如果ttl=0代表沒有過期時間。
第一, 整個遷移過程並非原子性的,而是通過客戶端分步完成的。 第二, 遷移過程是開啟了兩個客戶端連接, 所以dump的結果不是在源Redis和目標Redis之間進行傳輸 -
migrate命令也是用於在Redis實例間進行數據遷移的, 實際上migrate命令就是將dump、 restore、 del三個命令進行組合, 從而簡化了操作流程。
migrate命令具有原子性, 而且從Redis3.0.6版本以后已經支持遷移多個鍵的功能, 有效地提高了遷移效率, migrate在10.4節水平擴容中起到重要作用。
第一, 整個過程是原子執行的, 不需要在多個Redis實例上開啟客戶端的, 只需要在源Redis上執行migrate命令即可。
第二, migrate命令的數據傳輸直接在源Redis和目標Redis上完成的。
第三, 目標Redis完成restore后會發送OK給源Redis, 源Redis接收后會根據migrate對應的選項來決定是否在源Redis上刪除對應的鍵。
migrate host port key|"" destination-db timeout [copy] [replace] keys key1 key2 key3
key|""。要遷移的鍵,多個鍵此處為""。
keys key1 key2 key3。遷移多個鍵
(2)鍵遍歷
- 全量遍歷鍵。keys pattern。支持pattern匹配。如果非要使用,在一個不對外提供服務的Redis從節點上執行, 這樣不會阻塞到客戶端的請求, 但是會影響到主從復制
-
漸進式遍歷。每次執行scan, 可以想象成只掃描一個字典中的一部分鍵, 直到將字典中的所有鍵遍歷完畢。
scan cursor [match pattern] [count number]
·cursor是必需參數, 實際上cursor是一個游標,第一次遍歷從0開始,每次scan遍歷完都會返回當前游標的值,直到游標值為0,表示遍歷結束。
·match pattern是可選參數,它的作用的是做模式的匹配,這點和keys的模式匹配很像。
·count number是可選參數,它的作用是表明每次要遍歷的鍵個數,默認值是10,此參數可以適當增大。
除了scan以外,Redis提供了面向哈希類型、集合類型、有序集合的掃描遍歷命令,解決諸如hgetall、 smembers、 zrange可能產生的阻塞問題,對應的命令分別是hscan、 sscan、 zscan,它們的用法和scan基本類似
漸進式遍歷可以有效的解決keys命令可能產生的阻塞問題,但是scan並非完美無瑕,如果在scan的過程中如果有鍵的變化(增加、 刪除、 修改),scan並不能保證完整的遍歷出來所有的鍵
(3)數據庫管理
- 切換數據庫。select db。各個數據庫之間沒有聯系,可以存在相同的鍵。redis是單線程的,多個數據庫使用同一個cpu,彼此至今還是會受影響。
- 清除數據庫。flushdb/flushall。如果當前數據庫鍵值數量比較多,flushdb/flushall存在阻塞Redis的可能性
三、小功能,大用處
1、慢查詢分析
(1)慢查詢只是統計的命令執行的時間,不包括客戶端發送命令、命令排隊、返回結果的時間。
- slowlog-log-slower-than預設閥值,它的單位是微秒(1秒=1000毫秒=1000000微秒),默認值是10000。slowlog-log-slower-than=0會記錄所有的命令,slowlog-log-slowerthan<0對於任何命令都不會進行記錄。
- 實際上Redis使用了一個列表來存儲慢查詢日志,slowlog-max-len就是列表的最大長度
- 在Redis中有兩種修改配置的方法, 一種是修改配置文件, 另一種是使用config set命令動態修改。如果要Redis將配置持久化到本地配置文件,需要執行config rewrite命令
- 獲取慢查詢日志。slowlog get [n] ,n可以指定條數。每個慢查詢日志有4個屬性組成, 分別是慢查詢日志的標識id、 發生時間戳、 命令耗時、 執行命令和參數
- 獲取慢查詢日志當前的列表長度,slowlog len。
- 慢查詢日志列表清空。slowlog reset。
(2)最佳實踐
- slowlog-max-len配置建議: 線上建議調大慢查詢列表, 記錄慢查詢時Redis會對長命令做截斷操作, 並不會占用大量內存。 增大慢查詢列表可以
減緩慢查詢被剔除的可能, 例如線上可設置為1000以上。 - slowlog-log-slower-than配置建議: 默認值超過10毫秒判定為慢查詢,需要根據Redis並發量調整該值。 由於Redis采用單線程響應命令, 對於高流量的場景, 如果命令執行時間在1毫秒以上, 那么Redis最多可支撐OPS不到1000。 因此對於高OPS場景的Redis建議設置為1毫秒。
- 慢查詢只記錄命令執行時間, 並不包括命令排隊和網絡傳輸時間。 因此客戶端執行命令的時間會大於命令實際執行時間。
- 定期執行slow get命令將慢查詢日志持久化到其他存儲中(例如MySQL) , 然后可以制作可視化界面進行查詢
2、Redis Shell
(1)Redis-cli
- -h 服務端ip
- -p 端口
- -r (repeat)將命令執行多次。redis-cli -r 3 ping
- -i (interval)每個幾秒執行幾次。redis-cli -r 5 -i 1 ping
- -a (auth)密碼
- --slave。將當前客戶端模擬成服務端的從節點。
- --rdb。生成RDB持久化文件,保存到本地。可用來做持久化文件的定期備份
- --eval。執行lua腳本
- --latency。測試客戶端到目標redis服務的網絡延遲;--latency-history,每隔多久輸出一次網絡延遲;--latency-dist,使用統計圖表的形式從控制台輸出延遲統計信息
- --stat。實時獲取redis的重要統計信息。key的數量、內存占用量、客戶端數量、請求數量、連接數量
(2)redis-server
- redis-server --test-memory 1024。檢測當前操作系統能否穩定地分配指定容量的內存給Redis。整個內存檢測的時間比較長。該功能更偏向於調試和測試
(3)redis-benchmark。可以為Redis進行基准性能測試
- -c。代表客戶端的並發數量(默認是50)
- -n(num) 。代表客戶端請求總量(默認是100000) 。redis-benchmark-c100-n20000代表100各個客戶端同時請求Redis, 一共執行20000次
- -r(random) 選項, 可以向Redis插入更多隨機的鍵
- --csv選項會將結果按照csv格式輸出, 便於后續處理, 如導出到Excel等
3、Pipeline。
(1)概述。Redis客戶端執行一次命令,需要經歷發送命令、命令排隊、命令執行、返回結果四個過程。4個過程統稱為一次Round Trip Time ,往返時間。
Pipeline(流水線)機制能將一組Redis命令進行組裝,通過一次RTT傳輸給Redis,再將這組Redis命令的執行結果按順序返回給客戶端
(2)性能測試。執行速度一般比逐條執行要快,客戶端和服務端的網絡延遲越大,Pipeline的效果越明顯
(3)原生批量命令和Pipeline對比。
- 原生批量命令是原子的,Pipeline是非原子的。
- 原生批量命令是一個命令對應多個key,Pipeline支持多個命令。
- 原生批量命令是Redis服務端支持實現的,而Pipeline需要服務端和客戶端的共同實現。
(4)最佳實踐
- 一次組裝Pipeline數據量過大, 一方面會增加客戶端的等待時間, 另一方面會造成一定的網絡阻塞, 可以將一次包含大量命令的Pipeline拆分成多次較小的Pipeline來完成
- Pipeline只能操作一個Redis實例
4、事務與lua
(1)事務
- 簡單事務功能,multi和exec命令之間的命令,能夠原子順序執行。如果事務之中存在命令拼寫錯誤,整個事務不會執行;如果存在運行時錯誤,事務並不會回滾
- watch命令,是事務執行之前,確保事務中的key沒有被其他客戶端修改過,修改過的話就不執行事務。類似於樂觀鎖
- Redis的事務比較簡單,無法保證事務回滾,無法實現事務內命令之間的邏輯運算
(2)lua用法簡述。由c語言實現,許多應用選用它作為腳本語言。尤其在游戲領域
(3)Redis中使用lua
- eval 執行lua腳本。eval 腳本內容 key個數 key列表 參數列表。對應 jedis.eval
- evalsha 執行lua腳本。
script load命令可以將腳本內容加載到Redis內存中,返回SHA1。對應jedis.scriptLoad
evalsha 腳本SHA1值 key個數 key列表 參數列表 - lua 的Redis API。
使用redis.call函數實現對Redis的訪問。例如:redis.call("set", "hello", "world");redis.call("get", "hello")
還可以使用redis.pcall函數實現對Redis的調用,如果redis.call執行失敗,那么腳本執行結束會直接返回錯誤,而redis.pcall會忽略錯誤繼續執行腳本 - 開發提示
Lua可以使用redis.log函數將Lua腳本的日志輸出到Redis的日志文件中,但是一定要控制日志級別。
Redis3.2提供了Lua Script Debugger功能用來調試復雜的Lua腳本 - Redis管理Lua腳本
script load 將Lua腳本加載到Redis內存中
scripts exists sha1 [sha1 …] 判斷sha1是否已經加載到Redis內存中
script flush 清除Redis內存已經加載的所有Lua腳本
script kill 用於殺掉正在執行的Lua腳本。如果此時lua腳本阻塞redis了,可以使用此命令快速將腳本殺掉。如果正在執行的是寫操作,script kill 將不會生效,只能通過shutdown save停掉redis服務。
5、Bitmaps
(1)概述。Bitmaps本身的數據結構就是字符串,支持對字符串的位進行操作。
(2)每個獨立用戶是否訪問過網站存放在Bitmaps中,將訪問的用戶記做1,沒有訪問的用戶記做0,用偏移量作為用戶的id
- 設置值。setbit key offset value 。初始化的時候,如果偏移量非常大,整個初始化過程會非常慢,可能會造成redis阻塞。
- 獲取值。gitbit key offset 。返回0說明沒有訪問過
- 獲取Bitmaps指定范圍值為1的個數 。bitcount [start][end] 。
- Bitmaps間的運算 。bitop op destkey key[key....] 。做多個Bitmaps的and(交集) 、 or(並集) 、 not(非) 、 xor(異或) 操作並將結果保存在destkey中
- 計算Bitmaps中第一個值為targetBit的偏移量。bitpos key targetBit [start] [end]
(3)Bitmaps分析
假設網站有1億用戶, 每天獨立訪問的用戶有5千萬, 如果每天用集合類型和Bitmaps分別存儲活躍用戶。
集合類型內存量:64位*50000000=400M。
Bitmaps內存量:1位*50000000=12.5M。
6、HyperLogLog
- Redis中hyperloglog是用來做基數統計的,其優點是:在輸入元素的數量或者體積非常非常大的時候,計算基數所需的空間總是固定的,並且是很小的。在Redis里面,每個Hyperloglog鍵只需要12Kb的大小就能計算接近2^64個不同元素的基數,但是hyperloglog只會根據輸入元素來計算基數,而不會存儲元素本身,所以不能像集合那樣返回各個元素本身
- pfadd key element [element …]。添加操作。如果添加成功返回1
- pfcount key [key …]。求一個或多個key的基數,計算獨立用戶數
- pfmerge destkey sourcekey [sourcekey ...]。合並。pfmerge可以求出多個HyperLogLog的並集並賦值給destkey
- HyperLogLog內存占用量小得驚人, 但是用如此小空間來估算如此巨大的數據, 必然不是100%的正確, 其中一定存在誤差率。 Redis官方給出的數字是0.81%的失誤率
7、發布訂閱
- 發布消息。publish channel message 。
- 訂閱消息。subscribe channel [channel ...]。多個客戶端訂閱同一個頻道,都會受到消息推送,類似於廣播。
- 取消訂閱。unsubscribe [channel [channel ...]]
- 按照模式訂閱和取消訂閱。psubscribe pattern [pattern...];punsubscribe [pattern [pattern ...]]
- 訂閱查詢。pubsub channels [pattern],查看活躍的頻道;pubsub numsub [channel ...],查看頻道訂閱數;pubsub numpat,查看模式訂閱數
8、GEO
- 增加地理位置信息。geoadd key longitude latitude member [longitude latitude member ...]。longitude、 latitude、 member分別是該地理位置的經度、 緯度、 成員。添加和更新都是用此命令,可以同時添加多個地理位置信息。
- 獲取地理位置信息。geopos key member [member ...]
- 獲取兩個地址位置的距離。geodist key member1 member2 [unit]。unit代表返回結果的單位,m(meters) 代表米、km(kilometers) 代表公里、mi(miles) 代表英里、ft(feet) 代表尺
-
獲取指定位置范圍內的地理信息位置集合。georadius key longitude latitude/georadiusbymember key member radiusm|km|ft|mi [withcoord] [withdist][withhash] [COUNT count] [asc|desc] [store key] [storedist key]
都是以一個地理位置為中心算出指定半徑內的其他地理信息位置 -
獲取geohash。geohash key member [member ...]。Redis使用geohash將二維經緯度轉換為一維字符串。
GEO的數據類型為zset, Redis將所有地理位置信息的geohash存放在zset中
字符串越長, 表示的位置更精確, 例如geohash長度為9時, 精度在2米左右
兩個字符串越相似, 它們之間的距離越近, Redis利用字符串前綴匹配算法實現相關的命令
geohash編碼和經緯度是可以相互轉換的 - 刪除地理位置信息。zrem key member。GEO沒有提供刪除成員的命令, 但是因為GEO的底層實現是zset, 所以可以借用zrem命令實現對地理位置信息的刪除。
四、客戶端
1、客戶端通訊協議
通訊協議是建立在TCP協議之上的。Redis制定了RESP(REdis Serialization Protocol, Redis序列化協議) 實現客戶端與服務端的正常交互
2、Java客戶端Jedis
基本使用、Jedis連接池、Pipeline用法、執行Lua腳本等功能
3、客戶端管理
(1)客戶端API。client .. 命令
-
client list。列出與Redis服務端相連的所有客戶端連接信息。輸出結果的每一行代表一個客戶端的信息, 可以看到每行包含了十幾個屬性
id: 客戶端連接的唯一標識, 這個id是隨着Redis的連接自增的, 重啟Redis后會重置為0
addr: 客戶端連接的ip和端口。
fd: socket的文件描述符, 與lsof命令結果中的fd是同一個, 如果fd=-1代表當前客戶端不是外部客戶端, 而是Redis內部的偽裝客戶端。
name: 客戶端的名字
age和idle分別代表當前客戶端已經連接的時間和最近一次的空閑時間。當age等於idle時,說明連接一直處於空閑狀態
flag是用於標識當前客戶端的類型, 例如flag=S代表當前客戶端是slave客戶端、 flag=N代表當前是普通客戶端, flag=O代表當前客戶端正在執行monitor命令 -
輸入緩沖區: qbuf(總容量)、 qbuf-free(剩余容量)。
redis為每個客戶端分配了輸入緩沖區, 它的作用是將客戶端發送的命令臨時保存, 同時Redis從會輸入緩沖區拉取命令並執行, 輸入緩沖區為客戶端發送命令到Redis執行命令提供了緩沖功能
輸入緩沖區會根據輸入內容大小的不同動態調整, 只是要求每個客戶端緩沖區的大小不能超過1G, 超過后客戶端將被關閉。輸入緩沖區過大主要是因為Redis的處理速度跟不上輸入緩沖區的輸入速度 -
輸入緩沖使用不當會產生兩個問題:
·一旦某個客戶端的輸入緩沖區超過1G, 客戶端將會被關閉。
·輸入緩沖區不受maxmemory控制,一旦超過maxmemory限制, 可能會產生數據丟失、 鍵值淘汰、 OOM等情況 -
監控輸入緩沖區異常的方法有兩種:
通過定期執行client list命令,收集qbuf和qbuf-free找到異常的連接記錄並分析,最終找到可能出問題的客戶端。能精准定位客戶端,但是執行速度較慢,頻繁指定可能阻塞redis
通過info命令的info clients模塊,找到最大的輸入緩沖區,client_biggest_input_buf代表最大的輸入緩沖區,可以設置超過10M(閾值)就進行報警。定位不精准,執行速度快。 -
輸出緩沖區: obl、 oll、 omem。
Redis為每個客戶端分配了輸出緩沖區, 它的作用是保存命令執行的結果返回給客戶端, 為Redis和客戶端交互返回結果提供緩沖。
輸出緩沖區由兩部分組成: 固定緩沖區(16KB) 和動態緩沖區, 其中固定緩沖區返回比較小的執行結果, 而動態緩沖區返回比較大的結果
client list中的obl代表固定緩沖區的長度, oll代表動態緩沖區列表的長度, omem代表使用的字節數
監控輸出緩沖區的方法同輸入緩沖區監控方法 -
輸出緩沖區的容量可以通過參數client-outputbuffer-limit來進行設置,輸出緩沖區也不會受到maxmemory的限制
配置規則:client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
<class>: 客戶端類型, 分為三種。 a) normal: 普通客戶端; b)slave: slave客戶端, 用於復制; c) pubsub: 發布訂閱客戶端。
<hard limit>: 如果客戶端使用的輸出緩沖區大於<hard limit>, 客戶端會被立即關閉。
<soft limit>和<soft seconds>: 如果客戶端使用的輸出緩沖區超過了<softlimit>並且持續了<soft limit>秒, 客戶端會被立即關閉 -
輸出緩沖區出現異常的概率相對會比較大,如何預防;
進行上述監控, 設置閥值, 超過閥值及時處理
限制普通客戶端輸出緩沖區的, 把錯誤扼殺在搖籃中
適當增大slave的輸出緩沖區的,master節點寫入較大, slave客戶端的輸出緩沖區可能會比較大, 一旦slave客戶端連接因為輸出緩沖區溢出被kill, 會造成復制重連
限制容易讓輸出緩沖區增大的命令, 例如, 高並發下的monitor命令就是一個危險的命令。
及時監控內存, 一旦發現內存抖動頻繁, 可能就是輸出緩沖區過大。 - client setName和client getName。client setName用於給客戶端設置名字, 這樣比較容易標識出客戶端的來源。
- client kill ip:port。此命令用於殺掉指定IP地址和端口的客戶端。
-
client pause timeout(毫秒)。client pause命令用於阻塞客戶端timeout毫秒數,在此期間客戶端連接將被阻塞。生產環境中,暫停客戶端成本非常高
該命令可以在如下場景起到作用:
client pause只對普通和發布訂閱客戶端有效,對於主從復制(從節點內部偽裝了一個客戶端)是無效的,也就是此期間主從復制是正常進行的,此命令可以用來讓主從復制保持一致
client pause可以用一種可控的方式將客戶端連接從一個Redis節點切換到另一個Redis節點。 -
monitor命令用於監控Redis正在執行的命令
每個客戶端都有自己的輸出緩沖區, 既然monitor能監聽到所有的命令, 一旦Redis的並發量過大,monitor客戶端的輸出緩沖會暴漲, 可能瞬間會占用大量內存
(2)客戶端相關配置
-
Redis提供了maxclients參數來限制最大客戶端連接數, 一旦連接數超過maxclients, 新的連接將被拒絕。
可以通過info clients來查詢當前Redis的連接數
axclients默認值是10000,可以通過config set maxclients對最大客戶端連接數進行動態設置 - Redis提供了timeout(單位為秒)參數來限制連接的最大空閑時間,一旦客戶端連接的idle時間超過了timeout,連接將會被關閉。默認的timeout是0,動態設置config set timeout 30
- tcp-keepalive:檢測TCP連接活性的周期,默認值為0,也就是不進行檢測,如果需要設置,建議為60,那么Redis會每隔60秒對它創建的TCP連接進行活性檢測,防止大量死連接占用系統資源
- tcp-backlog:TCP三次握手后,會將接受的連接放入隊列中,tcpbacklog就是隊列的大小,它在Redis中的默認值是511。通常來講這個參數不需要調整,但是這個參數會受到操作系統的影響
(3)客戶端統計片段
info clients命令
- connected_clients:代表當前Redis節點的客戶端連接數,需要重點監控,一旦超過maxclients,新的客戶端連接將被拒絕。
- client_longest_output_list: 當前所有輸出緩沖區中隊列對象個數的最大值。
- client_biggest_input_buf: 當前所有輸入緩沖區中占用的最大容量。
- blocked_clients:正在執行阻塞命令(例如blpop、 brpop、brpoplpush)的客戶端個數。
info stats 命令
- total_connections_received: Redis自啟動以來處理的客戶端連接數總數
- rejected_connections: Redis自啟動以來拒絕的客戶端連接數, 需要重點監控
4、客戶端常見異常
(1)無法從連接池獲取到連接
-
JedisPool中的Jedis對象個數是有限的, 默認是8個。如果連接池中沒有空閑Jedis對象,新的請求就需要進行等待(例如設置了maxWaitMillis>0)
在maxWaitMillis時間內仍然無法獲取到Jedis對象就會拋出異常:JedisConnectionException: Could not get a resource from the pool
如果設置了blockWhenExhausted=false, 那么調用者發現池子中沒有資源時, 會立即拋出異常不進行等待 -
造成沒有資源的原因非常多:
客戶端: 高並發下連接池設置過小, 出現供不應求
客戶端: 沒有正確使用連接池, 比如沒有進行釋放
客戶端: 存在慢查詢操作, 這些慢查詢持有的Jedis對象歸還速度會比較慢,造成池子滿了
服務端: 客戶端是正常的, 但是Redis服務端由於一些原因造成了客戶端命令執行過程的阻塞
(2)客戶端讀寫超時,SocketTimeoutException: Read timed out
造成該異常的原因也有以下幾種:
·讀寫超時間設置得過短。
·命令本身就比較慢。
·客戶端與服務端網絡不正常。
·Redis自身發生阻塞。
(3)客戶端連接超時,SocketTimeoutException: connect timed out
造成該異常的原因也有以下幾種:
連接超時設置得過短, 可以通過下面代碼進行設置:jedis.getClient().setConnectionTimeout(time);
Redis發生阻塞, 造成tcp-backlog已滿, 造成新的連接失敗。
客戶端與服務端網絡不正常。
(4)客戶端緩沖區異常
造成這個異常的原因可能有如下幾種:
輸出緩沖區滿。
長時間閑置連接被服務端主動斷開
不正常並發讀寫: Jedis對象同時被多個線程並發操作, 可能會出現上述異常
(5)Lua腳本正在執行
如果Redis當前正在執行Lua腳本, 並且超過了lua-time-limit, 此時Jedis調用Redis時, 會收到下面的異常。
JedisDataException: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
(6)Redis正在加載持久化文件
Jedis調用Redis時, 如果Redis正在加載持久化文件, 那么會收到下面的異常:
JedisDataException: LOADING Redis is loading the dataset in memory
(7)Redis使用的內存超過maxmemory配置
Jedis執行寫操作時, 如果Redis的使用內存大於maxmemory的設置, 會收到下面的異常, 此時應該調整maxmemory並找到造成內存增長的原因
JedisDataException: OOM command not allowed when used memory > 'maxmemory'.
(8)客戶端連接數過大
- 如果客戶端連接數超過了maxclients, 新申請的連接就會出現如下異常:
JedisDataException: ERR max number of clients reached - 一般來說可以從兩個方面進行着手解決:
如果應用方是分布式結構的話,可以通過下線部分應用節點(例如占用連接較多的節點),使得Redis的連接數先降下來。從而讓絕大部分節點可以正常運行,此時再通過查找程序bug或者調整maxclients進行問題的修復。
果此時客戶端無法處理, 而當前Redis為高可用模式(例如Redis Sentinel和Redis Cluster) , 可以考慮將當前Redis做故障轉移。
但是無論從哪個方面進行處理, 故障的快速恢復極為重要, 當然更為重要的是找到問題的所在, 否則一段時間后客戶端連接數依然會超過maxclients。
5、客戶端案例分析
(1)Redis主節點內存陡增
- 服務端現象: Redis主節點內存陡增, 幾乎用滿maxmemory, 而從節點內存並沒有變化
- 客戶端現象: 客戶端產生了OOM異常, 也就是Redis主節點使用的內存已經超過了maxmemory的設置, 無法寫入新的數據
-
分析原因:
確實有大量寫入, 但是主從復制出現問題: 查詢了Redis復制的相關信息, 復制是正常的, 主從數據基本一致。主從的鍵個數基本一致,使用dbsize命令
其他原因造成主節點內存使用過大: 排查是否由客戶端緩沖區造成主節點內存陡增, 使用info clients命令發現客戶端輸出緩沖區不正常,client_longest_output_list:225698
通過client list命令找到omem不正常的連接, 一般來說大部分客戶端的omem為0,redis-cli client list | grep -v "omem=0"
最后發現是因為有客戶端在執行monitor命令造成的
(2)客戶端周期性超時
- 客戶端現象: 客戶端出現大量超時, 經過分析發現超時是周期性出現的
- 服務端現象: 服務端並沒有明顯的異常, 只是有一些慢查詢操作
-
原因分析:
網絡原因: 服務端和客戶端之間的網絡出現周期性問題, 經過觀察網絡是正常的
客戶端: 由於是周期性出現問題, 就和慢查詢日志的歷史記錄對應了一下時間, 發現只要慢查詢出現, 客戶端就會產生大量連接超時, 兩個時間點基本一致
最終找到問題是慢查詢操作造成的, 通過執行hlen發現有200萬個元素, 這種操作必然會造成Redis阻塞, 有定時任務代碼每5分鍾執行一次hgetall操作
五、持久化
1、RDB。RDB持久化是把當前進程數據生成快照保存到硬盤的過程, 分為手動觸發和自動觸發
(1)觸發機制
- 手動觸發:save命令。阻塞當前Redis服務器,直到RDB過程完成。線上環境不建議使用
- 手動觸發:bgsave命令。Redis進程執行fork操作創建子進程,RDB持久化由子進程負責。阻塞只發生在fork階段,時間很短。
- 自動觸發:使用save相關配置:“save m n”,表示m秒內發生了n次數據修改,自動觸發bgsave。
- 自動觸發:從節點執行全量復制操作,主節點自動執行bgsave生成RDB文件,發給從節點
- 自動觸發:執行debuge reload命令重新加載Redis時,會自動觸發save操作
- 自動觸發:執行shutdown命令時,如果沒有開啟AOF功能,會自動觸發bgsave
(2)流程說明
- 監測是否存在正在執行的子進程,如RDB/AOF進程,如果存在直接返回
- 父進程創建子進程,fork過程中父進程會阻塞,通過info stats 命令查看latest_fork_usec選項查看最近一次fork的耗時,單位為微秒
- 父進程fork完成后,返回“Backgroung saving started”,不在阻塞父進程,開始響應其他命令
- 子進程創建RDB文件,根據父進程內存生成快照,完成后對原有文件進行原子替換
- 子進程發送信號給父進程表示完成,父進程更新統計信息
(3)RDB文件的處理
- RDB文件保存在dir配置的路徑下,名字為dbfilename配置。可以通過命令動態修改 config set dir;config set dbfilename
- Redis采用默認的LZF算法對生成的RDB文件進行壓縮,默認開啟,雖然壓縮過程會消耗cpu,但是可以大幅降低壓縮后的文件體積,方便保存到磁盤和通過網絡傳輸到從節點,線上建議開啟
- 如果Redis加載損壞的RDB文件時拒絕啟動,可以使用Redis提供的redis-check-dump工具檢測RDB文件生成對應的錯誤報告
(4)RDB的優缺點
- 優點:RDB是一個緊湊壓縮的二進制文件,代表Redis在某個時間點上的快照。適用於備份、全量復制等場景。
- 優點:Redis加載RDB恢復數據的速度遠遠快於AOF的方式
- 缺點:RDB沒有辦法做到實時持久化
- 缺點:REB文件使用特定的二進制格式保存,存在老版本的服務器無法兼容新版RDB格式的問題。
2、AOF。以獨立日志的形式記錄每次寫命令。重啟時再重新執行AOF文件中的命令,達到恢復數據的目的
(1)配置
- 開啟AOF功能需要設置配置: appendonly yes, 默認不開啟。 AOF文件名通過appendfilename配置設置, 默認文件名是appendonly.aof。 保存路徑同RDB持久化方式一致, 通過dir配置指定。
- 工作流程操作: 命令寫入(append) 、 文件同步(sync) 、 文件重寫(rewrite) 、 重啟加載(load)
(2)命令寫入緩存
- 寫入的命令內容直接是文本協議格式,會把內容追加到緩沖區。文本協議具有很好的兼容性
- 先寫入緩沖區,避免了每次的命令直接追加到硬盤,提高性能。
(3)文件同步到磁盤,由參數appendfsync控制。設置不同值含義不同
- always。命令寫入緩沖區之后,調用系統fsync命令同步到磁盤,fsync完成后線程返回
- everysec。命令寫入緩沖區之后,調用系統的write操作,write操作完成后線程返回。fsync同步操作由專門的線程每秒調用一次
- no。命令寫入緩沖區之后,調用系統的write操作,不進行fsync操作。同步到硬盤由操作系統負責,通常同步周期最長30秒
- write操作會觸發延遲寫(delayed write)機制,Linux內核提供頁緩沖區提高硬盤IO性能。write寫入系統緩沖區之后直接返回。同步硬盤操作依賴於操作系統調度機制
- fsync操作,針對單個文件操作,強制磁盤同步。將阻塞直到寫入硬盤完成后返回。
- always配置太耗費性能,只能支持大約幾百tps並發寫入;no配置,每次系統不同周期不可控;everysec,建議采用的同步策略,也是默認配置
(4)AOF文件重寫。把Redis進程內的數據轉化成寫命令同步更新到新的AOF文件,AOF重新降低了占用空間,更小的AOF文件可以更快的被Redis加載
-
重寫之后的AOF文件變小的原因 :
進程內已經超時的數據不在寫入文件
舊的AOF文件中含有無效命令,新的AOF文件只保留最終數據的寫入命令
多條寫命令可以合並成一個