《Redis開發與運維》讀書筆記


一、初始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反之。 例如
    下面操作從低到高返回200221分的成員, withscores選項會同時返回每個
    成員的分數。 [limit offset count]選項可以限制輸出的起始位置和個數 。同時minmax還支持開區間(小括號) 和閉區間(中括號) , -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) 
  • -nnum) 。代表客戶端請求總量(默認是100000) 。redis-benchmark-c100-n20000代表100各個客戶端同時請求Redis, 一共執行20000次 
  • -rrandom) 選項, 可以向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文件只保留最終數據的寫入命令
    多條寫命令可以合並成一個

 

、集群

1、數據分布


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM