Redis命令行工具有趣的罕見用法
我們天天都在使用 Redis 內(nèi)置的命令行工具 redis-cli,久而久之以為它就是一個(gè)簡(jiǎn)單的交互式 Redis 數(shù)據(jù)結(jié)構(gòu)手工操作程序,但是它背后強(qiáng)大的功能絕大多數(shù)同學(xué)可能聞所未聞。本節(jié)我們一起來(lái)挖掘這些鮮為人知的有趣用法。
執(zhí)行單條命令
平時(shí)在訪問(wèn) Redis 服務(wù)器,一般都會(huì)使用 redis-cli 進(jìn)入交互模式,然后一問(wèn)一答來(lái)讀寫服務(wù)器,這種情況下我們使用的是它的「交互模式」。還有另外一種「直接模式」,通過(guò)將命令參數(shù)直接傳遞給 redis-cli 來(lái)執(zhí)行指令并獲取輸出結(jié)果。
- $ redis-cli incrby foo 5
- (integer) 5
- $ redis-cli incrby foo 5
- (integer) 10
如果輸出的內(nèi)容較大,還可以將輸出重定向到外部文件
- $ redis-cli info > info.txt
- $ wc -l info.txt
- 120 info.txt
上面的命令指向的服務(wù)器是默認(rèn)服務(wù)器地址,如果想指向特定的服務(wù)器可以這樣
- // -n 2 表示使用第2個(gè)庫(kù),相當(dāng)于 select 2
- $ redis-cli -h localhost -p 6379 -n 2 ping
- PONG
批量執(zhí)行命令
在平時(shí)線上的開發(fā)過(guò)程中,有時(shí)候我們免不了要手工造數(shù)據(jù),然后導(dǎo)入 Redis。通常我們會(huì)編寫腳本程序來(lái)做這件事。不過(guò)還有另外一種比較便捷的方式,那就是直接使用 redis-cli 來(lái)批量執(zhí)行一系列指令。
- $ cat cmds.txt
- set foo1 bar1
- set foo2 bar2
- set foo3 bar3
- ......
- $ cat cmds.txt | redis-cli
- OK
- OK
- OK
- ...
上面的指令使用了 Unix 管道將 cat 指令的標(biāo)準(zhǔn)輸出連接到 redis-cli 的標(biāo)準(zhǔn)輸入。其實(shí)還可以直接使用輸入重定向來(lái)批量執(zhí)行指令。
- $ redis-cli < cmds.txt
- OK
- OK
- OK
- ...
set 多行字符串
如果一個(gè)字符串有多行,你希望將它傳入 set 指令,redis-cli 要如何做?可以使用 -x 選項(xiàng),該選項(xiàng)會(huì)使用標(biāo)準(zhǔn)輸入的內(nèi)容作為最后一個(gè)參數(shù)。
- $ cat str.txt
- Ernest Hemingway once wrote,
- "The world is a fine place and worth fighting for."
- I agree with the second part.
- $ redis-cli -x set foo < str.txt
- OK
- $ redis-cli get foo
- "Ernest Hemingway once wrote,\n\"The world is a fine place and worth fighting for.\"\nI agree with the second part.\n"
重復(fù)執(zhí)行指令
redis-cli 還支持重復(fù)執(zhí)行指令多次,每條指令執(zhí)行之間設(shè)置一個(gè)間隔時(shí)間,如此便可以觀察某條指令的輸出內(nèi)容隨時(shí)間變化。
- // 間隔1s,執(zhí)行5次,觀察qps的變化
- $ redis-cli -r 5 -i 1 info | grep ops
- instantaneous_ops_per_sec:43469
- instantaneous_ops_per_sec:47460
- instantaneous_ops_per_sec:47699
- instantaneous_ops_per_sec:46434
- instantaneous_ops_per_sec:47216
如果將次數(shù)設(shè)置為 -1 那就是重復(fù)無(wú)數(shù)次永遠(yuǎn)執(zhí)行下去。如果不提供 -i 參數(shù),那就沒有間隔,連續(xù)重復(fù)執(zhí)行。在交互模式下也可以重復(fù)執(zhí)行指令,形式上比較怪異,在指令前面增加次數(shù)
- 127.0.0.1:6379> 5 ping
- PONG
- PONG
- PONG
- PONG
- PONG
- # 下面的指令很可怕,你的屏幕要憤怒了
- 127.0.0.1:6379> 10000 info
- .......
導(dǎo)出 csv
redis-cli 不能一次導(dǎo)出整個(gè)庫(kù)的內(nèi)容為 csv,但是可以導(dǎo)出單條指令的輸出為 csv 格式。
- $ redis-cli rpush lfoo a b c d e f g
- (integer) 7
- $ redis-cli --csv lrange lfoo 0 -1
- "a","b","c","d","e","f","g"
- $ redis-cli hmset hfoo a 1 b 2 c 3 d 4
- OK
- $ redis-cli --csv hgetall hfoo
- "a","1","b","2","c","3","d","4"
當(dāng)然這種導(dǎo)出功能比較弱,僅僅是一堆字符串用逗號(hào)分割開來(lái)。不過(guò)你可以結(jié)合命令的批量執(zhí)行來(lái)看看多個(gè)指令的導(dǎo)出效果。
- $ redis-cli --csv -r 5 hgetall hfoo
- "a","1","b","2","c","3","d","4"
- "a","1","b","2","c","3","d","4"
- "a","1","b","2","c","3","d","4"
- "a","1","b","2","c","3","d","4"
- "a","1","b","2","c","3","d","4"
看到這里讀者應(yīng)該明白 --csv 參數(shù)的效果就是對(duì)輸出做了一次轉(zhuǎn)換,用逗號(hào)分割,僅此而已。
執(zhí)行 lua 腳本
在 lua 腳本小節(jié),我們使用 eval 指令來(lái)執(zhí)行腳本字符串,每次都是將腳本內(nèi)容壓縮成單行字符串再調(diào)用 eval 指令,這非常繁瑣,而且可讀性很差。redis-cli 考慮到了這點(diǎn),它可以直接執(zhí)行腳本文件。
- 127.0.0.1:6379> eval "return redis.pcall('mset', KEYS[1], ARGV[1], KEYS[2], ARGV[2])" 2 foo1 foo2 bar1 bar2
- OK
- 127.0.0.1:6379> eval "return redis.pcall('mget', KEYS[1], KEYS[2])" 2 foo1 foo2
- 1) "bar1"
- 2) "bar2"
下面我們以腳本的形式來(lái)執(zhí)行上面的指令,參數(shù)形式有所不同,KEY 和 ARGV 之間需要使用逗號(hào)分割,并且不需要提供 KEY 的數(shù)量參數(shù)
- $ cat mset.txt
- return redis.pcall('mset', KEYS[1], ARGV[1], KEYS[2], ARGV[2])
- $ cat mget.txt
- return redis.pcall('mget', KEYS[1], KEYS[2])
- $ redis-cli --eval mset.txt foo1 foo2 , bar1 bar2
- OK
- $ redis-cli --eval mget.txt foo1 foo2
- 1) "bar1"
- 2) "bar2"
如果你的 lua 腳本太長(zhǎng),--eval 將大有用處。
監(jiān)控服務(wù)器狀態(tài)
我們可以使用 --stat 參數(shù)來(lái)實(shí)時(shí)監(jiān)控服務(wù)器的狀態(tài),間隔 1s 實(shí)時(shí)輸出一次。
- $ redis-cli --stat
- ------- data ------ --------------------- load -------------------- - child -
- keys mem clients blocked requests connections
- 2 6.66M 100 0 11591628 (+0) 335
- 2 6.66M 100 0 11653169 (+61541) 335
- 2 6.66M 100 0 11706550 (+53381) 335
- 2 6.54M 100 0 11758831 (+52281) 335
- 2 6.66M 100 0 11803132 (+44301) 335
- 2 6.66M 100 0 11854183 (+51051) 335
如果你覺得間隔太長(zhǎng)或是太短,可以使用 -i 參數(shù)調(diào)整輸出間隔。
掃描大 KEY
這個(gè)功能太實(shí)用了,我已經(jīng)在線上試過(guò)無(wú)數(shù)次了。每次遇到 Redis 偶然卡頓問(wèn)題,第一個(gè)想到的就是實(shí)例中是否存在大 KEY,大 KEY的內(nèi)存擴(kuò)容以及釋放都會(huì)導(dǎo)致主線程卡頓。如果知道里面有沒有大 KEY,可以自己寫程序掃描,不過(guò)這太繁瑣了。redis-cli 提供了 --bigkeys 參數(shù)可以很快掃出內(nèi)存里的大 KEY,使用 -i 參數(shù)控制掃描間隔,避免掃描指令導(dǎo)致服務(wù)器的 ops 陡增報(bào)警。
- $ ./redis-cli --bigkeys -i 0.01
- # Scanning the entire keyspace to find biggest keys as well as
- # average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
- # per 100 SCAN commands (not usually needed).
- [00.00%] Biggest zset found so far 'hist:aht:main:async_finish:20180425:17' with 1440 members
- [00.00%] Biggest zset found so far 'hist:qps:async:authorize:20170311:27' with 2465 members
- [00.00%] Biggest hash found so far 'job:counters:6ya9ypu6ckcl' with 3 fields
- [00.01%] Biggest string found so far 'rt:aht:main:device_online:68:{-4}' with 4 bytes
- [00.01%] Biggest zset found so far 'machine:load:20180709' with 2879 members
- [00.02%] Biggest string found so far '6y6fze8kj7cy:{-7}' with 90 bytes
redis-cli 對(duì)于每一種對(duì)象類型都會(huì)記錄長(zhǎng)度最大的 KEY,對(duì)于每一種對(duì)象類型,刷新一次最高記錄就會(huì)立即輸出一次。它能保證輸出長(zhǎng)度為 Top1 的 KEY,但是 Top2、Top3等 KEY 是無(wú)法保證可以掃描出來(lái)的。一般的處理方法是多掃描幾次,或者是消滅了 Top1 的 KEY 之后再掃描確認(rèn)還有沒有次大的 KEY。
采樣服務(wù)器指令
現(xiàn)在線上有一臺(tái) Redis 服務(wù)器的 OPS 太高,有很多業(yè)務(wù)模塊都在使用這個(gè) Redis,如何才能判斷出來(lái)是哪個(gè)業(yè)務(wù)導(dǎo)致了 OPS 異常的高。這時(shí)可以對(duì)線上服務(wù)器的指令進(jìn)行采樣,觀察采樣的指令大致就可以分析出 OPS 占比高的業(yè)務(wù)點(diǎn)。這時(shí)就要使用 monitor 指令,它會(huì)將服務(wù)器瞬間執(zhí)行的指令全部顯示出來(lái)。不過(guò)使用的時(shí)候要注意即使使用 ctrl+c 中斷,否則你的顯示器會(huì)噼里啪啦太多的指令瞬間讓你眼花繚亂。
- $ redis-cli --host 192.168.x.x --port 6379 monitor
- 1539853410.458483 [0 10.100.90.62:34365] "GET" "6yax3eb6etq8:{-7}"
- 1539853410.459212 [0 10.100.90.61:56659] "PFADD" "growth:dau:20181018" "2klxkimass8w"
- 1539853410.462938 [0 10.100.90.62:20681] "GET" "6yax3eb6etq8:{-7}"
- 1539853410.467231 [0 10.100.90.61:40277] "PFADD" "growth:dau:20181018" "2kei0to86ps1"
- 1539853410.470319 [0 10.100.90.62:34365] "GET" "6yax3eb6etq8:{-7}"
- 1539853410.473927 [0 10.100.90.61:58128] "GET" "6yax3eb6etq8:{-7}"
- 1539853410.475712 [0 10.100.90.61:40277] "PFADD" "growth:dau:20181018" "2km8sqhlefpc"
- 1539853410.477053 [0 10.100.90.62:61292] "GET" "6yax3eb6etq8:{-7}"
診斷服務(wù)器時(shí)延
平時(shí)我們?cè)\斷兩臺(tái)機(jī)器的時(shí)延一般是使用 Unix 的 ping 指令。Redis 也提供了時(shí)延診斷指令,不過(guò)它的原理不太一樣,它是診斷當(dāng)前機(jī)器和 Redis 服務(wù)器之間的指令(PING指令)時(shí)延,它不僅僅是物理網(wǎng)絡(luò)的時(shí)延,還和當(dāng)前的 Redis 主線程是否忙碌有關(guān)。如果你發(fā)現(xiàn) Unix 的 ping 指令時(shí)延很小,而 Redis 的時(shí)延很大,那說(shuō)明 Redis 服務(wù)器在執(zhí)行指令時(shí)有微弱卡頓。
- $ redis-cli --host 192.168.x.x --port 6379 --latency
- min: 0, max: 5, avg: 0.08 (305 samples)
時(shí)延單位是 ms。redis-cli 還能顯示時(shí)延的分布情況,而且是圖形化輸出。
- $ redis-cli --latency-dist

圖片
這個(gè)圖形的含義作者沒有描述,讀者們可以嘗試破解一下。
遠(yuǎn)程 rdb 備份
執(zhí)行下面的命令就可以將遠(yuǎn)程的 Redis 實(shí)例備份到本地機(jī)器,遠(yuǎn)程服務(wù)器會(huì)執(zhí)行一次bgsave操作,然后將 rdb 文件傳輸?shù)娇蛻舳?。遠(yuǎn)程 rdb 備份讓我們有一種“秀才不出門,全知天下事”的感覺。
- $ ./redis-cli --host 192.168.x.x --port 6379 --rdb ./user.rdb
- SYNC sent to master, writing 2501265095 bytes to './user.rdb'
- Transfer finished with success.
模擬從庫(kù)如果你想觀察主從服務(wù)器之間都同步了那些數(shù)據(jù),可以使用 redis-cli 模擬從庫(kù)。
- $ ./redis-cli --host 192.168.x.x --port 6379 --slave
- SYNC with master, discarding 51778306 bytes of bulk transfer...
- SYNC done. Logging commands from master.
- ...
從庫(kù)連上主庫(kù)的第一件事是全量同步,所以看到上面的指令卡頓這很正常,待首次全量同步完成后,就會(huì)輸出增量的 aof 日志。