《Redis开发与运维》- 核心知识整理二(Lua脚本、发布订阅、客户端等)
目錄
- 1 事物
- 2 Lua腳本
- 2.1 Lua腳本的好處
- 2.2 Lua腳本的使用
- 2.3 script kill
- 3 Bitmaps
- 3.1 數(shù)據(jù)結(jié)構(gòu)模型
- 3.2 Bitmaps的指令
- 3.3 Bitmaps分析
- 4 發(fā)布訂閱
- 4.1 基本概念
- 4.2 命令
- 4.3 使用場(chǎng)景
- 5 客戶端通信協(xié)議
- 6 Java客戶端Jedis
- 6.1 Jedis的基本使用方法
- 6.2 Jedis連接池的使用方法
- 7 客戶端API
- 7.1 client list
- 7.2 monitor
- 7.3 客戶端相關(guān)配置
1 事物
Redis提供了簡(jiǎn)單的事務(wù)功能,將一組需要一起執(zhí)行的命令放到multi和exec兩個(gè)命令之間。multi命令代表事務(wù)開始,exec命令代表事務(wù)結(jié)束,它們之間的命令是原子順序執(zhí)行的,例如下面操作實(shí)現(xiàn)了上述用戶關(guān)注問題。
127.0.0.1:6379> multi OK 127.0.0.1:6379> sadd user:a:follow user:b QUEUED 127.0.0.1:6379> sadd user:b:fans user:a QUEUED可以看到sadd命令此時(shí)的返回結(jié)果是QUEUED,代表命令并沒有真正執(zhí)行,而是暫時(shí)保存在Redis中。如果此時(shí)另一個(gè)客戶端執(zhí)行sismember user:a:follow user:b返回結(jié)果應(yīng)該為0。
127.0.0.1:6379> sismember user:a:follow user:b (integer) 0只有當(dāng)exec執(zhí)行后,用戶A關(guān)注用戶B的行為才算完成,如下所示返回的兩個(gè)結(jié)果對(duì)應(yīng)sadd命令。
127.0.0.1:6379> exec 1) (integer) 1 2) (integer) 1 127.0.0.1:6379> sismember user:a:follow user:b (integer) 1如果要停止事務(wù)的執(zhí)行,可以使用discard命令代替exec命令即可。
127.0.0.1:6379> discard OK 127.0.0.1:6379> sismember user:a:follow user:b (integer) 0如果事務(wù)中的命令出現(xiàn)錯(cuò)誤,Redis的處理機(jī)制也不盡相同。
1.命令錯(cuò)誤
例如下面操作錯(cuò)將set寫成了sett,屬于語法錯(cuò)誤,會(huì)造成整個(gè)事務(wù)無法執(zhí)行,key和counter的值未發(fā)生變化:
127.0.0.1:6388> mget key counter 1) "hello" 2) "100" 127.0.0.1:6388> multi OK 127.0.0.1:6388> sett key world (error) ERR unknown command 'sett' 127.0.0.1:6388> incr counter QUEUED 127.0.0.1:6388> exec (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6388> mget key counter 1) "hello" 2) "100"2.運(yùn)行時(shí)錯(cuò)誤
例如用戶B在添加粉絲列表時(shí),誤把sadd命令寫成了zadd命令,這種就是運(yùn)行時(shí)命令,因?yàn)檎Z法是正確的:
127.0.0.1:6379> multi OK 127.0.0.1:6379> sadd user:a:follow user:b QUEUED 127.0.0.1:6379> zadd user:b:fans 1 user:a QUEUED 127.0.0.1:6379> exec 1) (integer) 1 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 127.0.0.1:6379> sismember user:a:follow user:b (integer) 1可以看到Redis并不支持回滾功能,sadd user:a:follow user:b命令已經(jīng)執(zhí)行成功,開發(fā)人員需要自己修復(fù)這類問題。 有些應(yīng)用場(chǎng)景需要在事務(wù)之前,確保事務(wù)中的key沒有被其他客戶端修改過,才執(zhí)行事務(wù),否則不執(zhí)行(類似樂觀鎖)。Redis提供了watch命令來解決這類問題,下表展示了兩個(gè)客戶端執(zhí)行命令的時(shí)序。
事務(wù)中watch命令演示時(shí)序
可以看到“客戶端-1”在執(zhí)行multi之前執(zhí)行了watch命令,“客戶端-2”在“客戶端-1”執(zhí)行exec之前修改了key值,造成事務(wù)沒有執(zhí)行(exec結(jié)果為nil),整個(gè)代碼如下所示:
#T1:客戶端1 127.0.0.1:6379> set key "java" OK #T2:客戶端1 127.0.0.1:6379> watch key OK #T3:客戶端1 127.0.0.1:6379> multi OK #T4:客戶端2 127.0.0.1:6379> append key python (integer) 11 #T5:客戶端1 127.0.0.1:6379> append key jedis QUEUED #T6:客戶端1 127.0.0.1:6379> exec (nil) #T7:客戶端1 127.0.0.1:6379> get key "javapython"Redis提供了簡(jiǎn)單的事務(wù),之所以說它簡(jiǎn)單,主要是因?yàn)樗恢С质聞?wù)中的回滾特性,同時(shí)無法實(shí)現(xiàn)命令之間的邏輯關(guān)系計(jì)算,當(dāng)然也體現(xiàn)了Redis的“keep it simple”的特性,Lua腳本同樣可以實(shí)現(xiàn)事務(wù)的相關(guān)功能,但是功能要強(qiáng)大很多。
2 Lua腳本
2.1 Lua腳本的好處
Lua腳本功能為Redis開發(fā)和運(yùn)維人員帶來如下三個(gè)好處:
·Lua腳本在Redis中是原子執(zhí)行的,執(zhí)行過程中間不會(huì)插入其他命令。
·Lua腳本可以幫助開發(fā)和運(yùn)維人員創(chuàng)造出自己定制的命令,并可以將這些命令常駐在Redis內(nèi)存中,實(shí)現(xiàn)復(fù)用的效果。
·Lua腳本可以將多條命令一次性打包,有效地減少網(wǎng)絡(luò)開銷。
2.2 Lua腳本的使用
下面以一個(gè)例子說明Lua腳本的使用,當(dāng)前列表記錄著熱門用戶的id,假設(shè)這個(gè)列表有5個(gè)元素,如下所示:
127.0.0.1:6379> lrange hot:user:list 0 -1 1) "user:1:ratio" 2) "user:8:ratio" 3) "user:3:ratio" 4) "user:99:ratio" 5) "user:72:ratio"user:{id}:ratio代表用戶的熱度,它本身又是一個(gè)字符串類型的鍵:
127.0.0.1:6379> mget user:1:ratio user:8:ratio user:3:ratio user:99:ratio user:72:ratio 1) "986" 2) "762" 3) "556" 4) "400" 5) "101"現(xiàn)要求將列表內(nèi)所有的鍵對(duì)應(yīng)熱度做加1操作,并且保證是原子執(zhí)行,此功能可以利用Lua腳本來實(shí)現(xiàn)。
1)將列表中所有元素取出,賦值給mylist:
local mylist = redis.call("lrange", KEYS[1], 0, -1)2)定義局部變量count=0,這個(gè)count就是最后incr的總次數(shù):
local count = 03)遍歷mylist中所有元素,每次做完count自增,最后返回count:
for index,key in ipairs(mylist) do redis.call("incr",key) count = count + 1 end return count將上述腳本寫入lrange_and_mincr.lua文件中,并執(zhí)行如下操作,返回結(jié)果為5。
redis-cli --eval lrange_and_mincr.lua hot:user:list (integer) 5執(zhí)行后所有用戶的熱度自增1:
127.0.0.1:6379> mget user:1:ratio user:8:ratio user:3:ratio user:99:ratio user:72:ratio 1) "987" 2) "763" 3) "557" 4) "401" 5) "102"本節(jié)給出的只是一個(gè)簡(jiǎn)單的例子,在實(shí)際開發(fā)中,開發(fā)人員可以發(fā)揮自己的想象力創(chuàng)造出更多新的命令。
2.3 script kill
此命令用于殺掉正在執(zhí)行的Lua腳本。如果Lua腳本比較耗時(shí),甚至Lua腳本存在問題,那么此時(shí)Lua腳本的執(zhí)行會(huì)阻塞Redis,直到腳本執(zhí)行完畢或者外部進(jìn)行干預(yù)將其結(jié)束。下面我們模擬一個(gè)Lua腳本阻塞的情況進(jìn)行說明。下面的代碼會(huì)使Lua進(jìn)入死循環(huán):
while 1 == 1 do end執(zhí)行Lua腳本,當(dāng)前客戶端會(huì)阻塞:
127.0.0.1:6379> eval 'while 1==1 do end' 0Redis提供了一個(gè)lua-time-limit參數(shù),默認(rèn)是5秒,它是Lua腳本的“超時(shí)時(shí)間”,但這個(gè)超時(shí)時(shí)間僅僅是當(dāng)Lua腳本時(shí)間超過lua-time-limit后,向其他命令調(diào)用發(fā)送BUSY的信號(hào),但是并不會(huì)停止掉服務(wù)端和客戶端的腳本執(zhí)行,所以當(dāng)達(dá)到lua-time-limit值之后,其他客戶端在執(zhí)行正常的命令時(shí),將會(huì)收到“Busy Redis is busy running a script”錯(cuò)誤,并且提示使用script kill或shutdown nosave命令來殺掉這個(gè)busy的腳本:
127.0.0.1:6379> get hello (error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.此時(shí)Redis已經(jīng)阻塞,無法處理正常的調(diào)用,這時(shí)可以選擇繼續(xù)等待,但更多時(shí)候需要快速將腳本殺掉。使用shutdown save顯然不太合適,所以選擇script kill,當(dāng)script kill執(zhí)行之后,客戶端調(diào)用會(huì)恢復(fù):
127.0.0.1:6379> script kill OK 127.0.0.1:6379> get hello "world"但是有一點(diǎn)需要注意,如果當(dāng)前Lua腳本正在執(zhí)行寫操作,那么script kill將不會(huì)生效。例如,我們模擬一個(gè)不停的寫操作:
while 1==1 do redis.call("set","k","v") end此時(shí)如果執(zhí)行script kill,會(huì)收到如下異常信息:
(error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.上面提示Lua腳本正在向Redis執(zhí)行寫命令,要么等待腳本執(zhí)行結(jié)束要么使用shutdown save停掉Redis服務(wù)。可見Lua腳本雖然好用,但是使用不當(dāng)破壞性也是難以想象的。
3 Bitmaps
3.1 數(shù)據(jù)結(jié)構(gòu)模型
許多開發(fā)語言都提供了操作位的功能,合理地使用位能夠有效地提高內(nèi)存使用率和開發(fā)效率。Redis提供了Bitmaps這個(gè)“數(shù)據(jù)結(jié)構(gòu)”可以實(shí)現(xiàn)對(duì)位的操作。把數(shù)據(jù)結(jié)構(gòu)加上引號(hào)主要因?yàn)?#xff1a;
·Bitmaps本身不是一種數(shù)據(jù)結(jié)構(gòu),實(shí)際上它就是字符串(如下圖所示),但是它可以對(duì)字符串的位進(jìn)行操作。
·Bitmaps單獨(dú)提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一個(gè)以位為單位的數(shù)組,數(shù)組的每個(gè)單元只能存儲(chǔ)0和1,數(shù)組的下標(biāo)在Bitmaps中叫做偏移量。
字符串"big"用二進(jìn)制表示
3.2 Bitmaps的指令
本節(jié)將每個(gè)獨(dú)立用戶是否訪問過網(wǎng)站存放在Bitmaps中,將訪問的用戶記做1,沒有訪問的用戶記做0,用偏移量作為用戶的id。
1.設(shè)置值
setbit key offset value設(shè)置鍵的第offset個(gè)位的值(從0算起),假設(shè)現(xiàn)在有20個(gè)用戶, userid=0,5,11,15,19的用戶對(duì)網(wǎng)站進(jìn)行了訪問,那么當(dāng)前Bitmaps初始化結(jié)果如圖所示。
setbit使用
具體操作過程如下,unique:users:2016-04-05代表2016-04-05這天的獨(dú)立訪問用戶的Bitmaps:
127.0.0.1:6379> setbit unique:users:2016-04-05 0 1 (integer) 0 127.0.0.1:6379> setbit unique:users:2016-04-05 5 1 (integer) 0 127.0.0.1:6379> setbit unique:users:2016-04-05 11 1 (integer) 0 127.0.0.1:6379> setbit unique:users:2016-04-05 15 1 (integer) 0 127.0.0.1:6379> setbit unique:users:2016-04-05 19 1 (integer) 0如果此時(shí)有一個(gè)userid=50的用戶訪問了網(wǎng)站,那么Bitmaps的結(jié)構(gòu)變成了下圖所示,第20位~49位都是0。
userid=50用戶訪問
很多應(yīng)用的用戶id以一個(gè)指定數(shù)字(例如10000)開頭,直接將用戶id和Bitmaps的偏移量對(duì)應(yīng)勢(shì)必會(huì)造成一定的浪費(fèi),通常的做法是每次做setbit操作時(shí)將用戶id減去這個(gè)指定數(shù)字。在第一次初始化Bitmaps時(shí),假如偏移量非常大,那么整個(gè)初始化過程執(zhí)行會(huì)比較慢,可能會(huì)造成Redis的阻塞。
2.獲取值
getbit key offset獲取鍵的第offset位的值(從0開始算),下面操作獲取id=8的用戶是否在2016-04-05這天訪問過,返回0說明沒有訪問過:
127.0.0.1:6379> getbit unique:users:2016-04-05 8 (integer) 0由于offset=1000000根本就不存在,所以返回結(jié)果也是0:
127.0.0.1:6379> getbit unique:users:2016-04-05 1000000 (integer) 03.獲取Bitmaps指定范圍值為1的個(gè)數(shù)
bitcount [start][end]下面操作計(jì)算2016-04-05這天的獨(dú)立訪問用戶數(shù)量:
127.0.0.1:6379> bitcount unique:users:2016-04-05 (integer) 5[start]和[end]代表起始和結(jié)束字節(jié)數(shù),下面操作計(jì)算用戶id在第1個(gè)字節(jié)到第3個(gè)字節(jié)之間的獨(dú)立訪問用戶數(shù),對(duì)應(yīng)的用戶id是11,15,19。
127.0.0.1:6379> bitcount unique:users:2016-04-05 1 3 (integer) 34.Bitmaps間的運(yùn)算
bitop op destkey key[key....]bitop是一個(gè)復(fù)合操作,它可以做多個(gè)Bitmaps的and(交集)、or(并集)、not(非)、xor(異或)操作并將結(jié)果保存在destkey中。假設(shè)2016-04-04訪問網(wǎng)站的userid=1,2,5,9,如圖所示。
2016-04-04訪問網(wǎng)站的用戶Bitmaps
下面操作計(jì)算出2016-04-04和2016-04-03兩天都訪問過網(wǎng)站的用戶數(shù)量,如圖所示。
127.0.0.1:6379> bitop and unique:users:and:2016-04-04_03 unique: users:2016-04-03 unique:users:2016-04-03 (integer) 2 127.0.0.1:6379> bitcount unique:users:and:2016-04-04_03 (integer) 2如果想算出2016-04-04和2016-04-03任意一天都訪問過網(wǎng)站的用戶數(shù)量(例如月活躍就是類似這種),可以使用or求并集,具體命令如下:
127.0.0.1:6379> bitop or unique:users:or:2016-04-04_03 unique: users:2016-04-03 unique:users:2016-04-03 (integer) 2 127.0.0.1:6379> bitcount unique:users:or:2016-04-04_03 (integer) 6
利用bitop and命令計(jì)算兩天都訪問網(wǎng)站的用戶
3.3 Bitmaps分析
假設(shè)網(wǎng)站有1億用戶,每天獨(dú)立訪問的用戶有5千萬,如果每天用集合類型和Bitmaps分別存儲(chǔ)活躍用戶可以得到下表:
set和Bitmaps存儲(chǔ)一天活躍用戶的對(duì)比
很明顯,這種情況下使用Bitmaps能節(jié)省很多的內(nèi)存空間。但Bitmaps并不是萬金油,假如該網(wǎng)站每天的獨(dú)立訪問用戶很少,例如只有10萬(大量的僵尸用戶),那么兩者的對(duì)比如下表所示,很顯然,這時(shí)候使用Bitmaps就不太合適了,因?yàn)榛旧洗蟛糠治欢际?。
set和Bitmaps存儲(chǔ)一天活躍用戶的對(duì)比(獨(dú)立用戶比較少)
4 發(fā)布訂閱
4.1 基本概念
Redis提供了基于“發(fā)布/訂閱”模式的消息機(jī)制,此種模式下,消息發(fā)布者和訂閱者不進(jìn)行直接通信,發(fā)布者客戶端向指定的頻道(channel)發(fā)布消息,訂閱該頻道的每個(gè)客戶端都可以收到該消息,如圖所示。Redis提供了若干命令支持該功能,在實(shí)際應(yīng)用開發(fā)時(shí),能夠?yàn)榇祟悊栴}提供實(shí)現(xiàn)方法。
Redis發(fā)布訂閱模型
4.2 命令
Redis主要提供了發(fā)布消息、訂閱頻道、取消訂閱以及按照模式訂閱和取消訂閱等命令。
1.發(fā)布消息
publish channel message下面操作會(huì)向channel:sports頻道發(fā)布一條消息“Tim won the championship”,返回結(jié)果為訂閱者個(gè)數(shù),因?yàn)榇藭r(shí)沒有訂閱,所以返回結(jié)果為0:
127.0.0.1:6379> publish channel:sports "Tim won the championship" (integer) 02.訂閱消息
subscribe channel [channel ...]訂閱者可以訂閱一個(gè)或多個(gè)頻道,下面操作為當(dāng)前客戶端訂閱了 channel:sports頻道:
127.0.0.1:6379> subscribe channel:sports Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channel:sports" 3) (integer) 1此時(shí)另一個(gè)客戶端發(fā)布一條消息:
127.0.0.1:6379> publish channel:sports "James lost the championship" (integer) 1當(dāng)前訂閱者客戶端會(huì)收到如下消息:
127.0.0.1:6379> subscribe channel:sports Reading messages... (press Ctrl-C to quit) ... 1) "message" 2) "channel:sports" 3) "James lost the championship"如果有多個(gè)客戶端同時(shí)訂閱了channel:sports,整個(gè)過程如圖3-17所示。有關(guān)訂閱命令有兩點(diǎn)需要注意:
·客戶端在執(zhí)行訂閱命令之后進(jìn)入了訂閱狀態(tài),只能接收subscribe、psubscribe、unsubscribe、punsubscribe的四個(gè)命令。
·新開啟的訂閱客戶端,無法收到該頻道之前的消息,因?yàn)镽edis不會(huì)對(duì)發(fā)布的消息進(jìn)行持久化。
多個(gè)客戶端同時(shí)訂閱頻道channel:sports
開發(fā)提示
和很多專業(yè)的消息隊(duì)列系統(tǒng)(例如Kafka、RocketMQ)相比,Redis的發(fā)布訂閱略顯粗糙,例如無法實(shí)現(xiàn)消息堆積和回溯。但勝在足夠簡(jiǎn)單,如果當(dāng)前場(chǎng)景可以容忍的這些缺點(diǎn),也不失為一個(gè)不錯(cuò)的選擇。
3.取消訂閱
unsubscribe [channel [channel ...]]客戶端可以通過unsubscribe命令取消對(duì)指定頻道的訂閱,取消成功后,不會(huì)再收到該頻道的發(fā)布消息:
127.0.0.1:6379> unsubscribe channel:sports 1) "unsubscribe" 2) "channel:sports" 3) (integer) 04.3 使用場(chǎng)景
聊天室、公告牌、服務(wù)之間利用消息解耦都可以使用發(fā)布訂閱模式,下面以簡(jiǎn)單的服務(wù)解耦進(jìn)行說明。如圖所示,圖中有兩套業(yè)務(wù),上面為視頻管理系統(tǒng),負(fù)責(zé)管理視頻信息;下面為視頻服務(wù)面向客戶,用戶可以通過各種客戶端(手機(jī)、瀏覽器、接口)獲取到視頻信息。
發(fā)布訂閱用于視頻信息變化通知
假如視頻管理員在視頻管理系統(tǒng)中對(duì)視頻信息進(jìn)行了變更,希望及時(shí)通知給視頻服務(wù)端,就可以采用發(fā)布訂閱的模式,發(fā)布視頻信息變化的消息到指定頻道,視頻服務(wù)訂閱這個(gè)頻道及時(shí)更新視頻信息,通過這種方式可以有效解決兩個(gè)業(yè)務(wù)的耦合性。
·視頻服務(wù)訂閱video:changes頻道如下:
·視頻管理系統(tǒng)發(fā)布消息到video:changes頻道如下:
publish video:changes "video1,video3,video5"·當(dāng)視頻服務(wù)收到消息,對(duì)視頻信息進(jìn)行更新,如下所示:
for video in video1,video3,video5 update {video}5 客戶端通信協(xié)議
幾乎所有的主流編程語言都有Redis的客戶端, 不考慮Redis非常流行的原因,如果站在技術(shù)的角度看原因還有兩個(gè):
第一,客戶端與服務(wù)端之間的通信協(xié)議是在TCP協(xié)議之上構(gòu)建的。
第二,Redis制定了RESP(REdis Serialization Protocol,Redis序列化協(xié)議)實(shí)現(xiàn)客戶端與服務(wù)端的正常交互,這種協(xié)議簡(jiǎn)單高效,既能夠被機(jī)器解析,又容易被人類識(shí)別。例如客戶端發(fā)送一條set hello world命令給服務(wù)端,按照RESP的標(biāo)準(zhǔn),客戶端需要將其封裝為如下格式(每行用\r\n分隔):
這樣Redis服務(wù)端能夠按照RESP將其解析為set hello world命令,執(zhí)行后回復(fù)的格式如下:
+OK可以看到除了命令(set hello world)和返回結(jié)果(OK)本身還包含了一些特殊字符以及數(shù)字,下面將對(duì)這些格式進(jìn)行說明。
1.發(fā)送命令格式
RESP的規(guī)定一條命令的格式如下,CRLF代表"\r\n"。
依然以set hell world這條命令進(jìn)行說明。 參數(shù)數(shù)量為3個(gè),因此第一行為:
*3參數(shù)字節(jié)數(shù)分別是355,因此后面幾行為:
$3 SET $5 hello $5 world有一點(diǎn)要注意的是,上面只是格式化顯示的結(jié)果,實(shí)際傳輸格式為如下代碼,整個(gè)過程如圖所示:
*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n2.返回結(jié)果格式
Redis的返回結(jié)果類型分為以下五種,如下圖所示:
·狀態(tài)回復(fù):在RESP中第一個(gè)字節(jié)為"+“。
·錯(cuò)誤回復(fù):在RESP中第一個(gè)字節(jié)為”-“。
·整數(shù)回復(fù):在RESP中第一個(gè)字節(jié)為”:“。
·字符串回復(fù):在RESP中第一個(gè)字節(jié)為”$“。
·多條字符串回復(fù):在RESP中第一個(gè)字節(jié)為”*"。
客戶端和服務(wù)端使用RESP標(biāo)準(zhǔn)進(jìn)行數(shù)據(jù)交互
Redis五種回復(fù)類型在RESP下的編碼
6 Java客戶端Jedis
Java有很多優(yōu)秀的Redis客戶端(詳見:http://redis.io/clients#java),這里介紹使用較為廣泛的客戶端Jedis。
6.1 Jedis的基本使用方法
Jedis的使用方法非常簡(jiǎn)單,只要下面三行代碼就可以實(shí)現(xiàn)get功能:
# 1. 生成一個(gè)Jedis對(duì)象,這個(gè)對(duì)象負(fù)責(zé)和指定Redis實(shí)例進(jìn)行通信 Jedis jedis = new Jedis("127.0.0.1", 6379); # 2. jedis執(zhí)行set操作 jedis.set("hello", "world"); # 3. jedis執(zhí)行g(shù)et操作, value="world" String value = jedis.get("hello");可以看到初始化Jedis需要兩個(gè)參數(shù):Redis實(shí)例的IP和端口,除了這兩個(gè)參數(shù)外,還有一個(gè)包含了四個(gè)參數(shù)的構(gòu)造函數(shù)是比較常用的:
Jedis(final String host, final int port, final int connectionTimeout, final int soTimeout)參數(shù)說明:
·host:Redis實(shí)例的所在機(jī)器的IP。
·port:Redis實(shí)例的端口。
·connectionTimeout:客戶端連接超時(shí)。
·soTimeout:客戶端讀寫超時(shí)。
如果想看一下執(zhí)行結(jié)果:
String setResult = jedis.set("hello", "world"); String getResult = jedis.get("hello"); System.out.println(setResult); System.out.println(getResult);輸出結(jié)果為:
OK world可以看到j(luò)edis.set的返回結(jié)果是OK,和redis-cli的執(zhí)行效果是一樣的,只不過結(jié)果類型變?yōu)榱薐ava的數(shù)據(jù)類型。上面的這種寫法只是為了演示使用,在實(shí)際項(xiàng)目中比較推薦使用try catch finally的形式來進(jìn)行代碼的書寫:一方面可以在Jedis出現(xiàn)異常的時(shí)候(本身是網(wǎng)絡(luò)操作),將異常進(jìn)行捕獲或者拋出;另一個(gè)方面無論執(zhí)行成功或者失敗,將Jedis連接關(guān)閉掉,在開發(fā)中關(guān)閉不用的連接資源是一種好的習(xí)慣,代碼類似如下:
Jedis jedis = null; try {jedis = new Jedis("127.0.0.1", 6379); jedis.get("hello"); } catch (Exception e) { logger.error(e.getMessage(),e); } finally { if (jedis != null) { jedis.close(); } }下面用一個(gè)例子說明Jedis對(duì)于Redis五種數(shù)據(jù)結(jié)構(gòu)的操作,為了節(jié)省篇幅,所有返回結(jié)果放在注釋中。
// 1.string // 輸出結(jié)果:OK jedis.set("hello", "world"); // 輸出結(jié)果:world jedis.get("hello"); // 輸出結(jié)果:1 jedis.incr("counter"); // 2.hash jedis.hset("myhash", "f1", "v1"); jedis.hset("myhash", "f2", "v2"); // 輸出結(jié)果:{f1=v1, f2=v2} jedis.hgetAll("myhash"); // 3.list jedis.rpush("mylist", "1"); jedis.rpush("mylist", "2"); jedis.rpush("mylist", "3"); // 輸出結(jié)果:[1, 2, 3] jedis.lrange("mylist", 0, -1); // 4.set jedis.sadd("myset", "a"); jedis.sadd("myset", "b"); jedis.sadd("myset", "a"); // 輸出結(jié)果:[b, a] jedis.smembers("myset"); // 5.zset jedis.zadd("myzset", 99, "tom"); jedis.zadd("myzset", 66, "peter"); jedis.zadd("myzset", 33, "james"); // 輸出結(jié)果:[[["james"],33.0], [["peter"],66.0], [["tom"],99.0]] jedis.zrangeWithScores("myzset", 0, -1);參數(shù)除了可以是字符串,Jedis還提供了字節(jié)數(shù)組的參數(shù),例如:
public String set(final String key, String value) public String set(final byte[] key, final byte[] value) public byte[] get(final byte[] key) public String get(final String key)有了這些API的支持,就可以將Java對(duì)象序列化為二進(jìn)制,當(dāng)應(yīng)用需要獲取Java對(duì)象時(shí),使用get(final byte[]key)函數(shù)將字節(jié)數(shù)組取出,然后反序列化為Java對(duì)象即可。和很多NoSQL數(shù)據(jù)庫(例如Memcache、Ehcache)的客戶端不同,Jedis本身沒有提供序列化的工具,也就是說開發(fā)者需要自己引入序列化的工具。序列化的工具有很多,例如XML、Json、谷歌的Protobuf、Facebook的Thrift等等,對(duì)于序列化工具的選擇開發(fā)者可以根據(jù)自身需求決定。
6.2 Jedis連接池的使用方法
之前介紹的是Jedis的直連方式,所謂直連是指Jedis每次都會(huì)新建TCP連接,使用后再斷開連接,對(duì)于頻繁訪問Redis的場(chǎng)景顯然不是高效的使用方式,如圖所示。
Jedis直連Redis
因此生產(chǎn)環(huán)境中一般使用連接池的方式對(duì)Jedis連接進(jìn)行管理,如圖所示,所有Jedis對(duì)象預(yù)先放在池子中(JedisPool),每次要連接Redis,只需要在池子中借,用完了在歸還給池子。
Jedis連接池使用方式
客戶端連接Redis使用的是TCP協(xié)議,直連的方式每次需要建立TCP連接,而連接池的方式是可以預(yù)先初始化好Jedis連接,所以每次只需要從Jedis連接池借用即可,而借用和歸還操作是在本地進(jìn)行的,只有少量的并發(fā)同步開銷,遠(yuǎn)遠(yuǎn)小于新建TCP連接的開銷。另外直連的方式無法限制Jedis對(duì)象的個(gè)數(shù),在極端情況下可能會(huì)造成連接泄露,而連接池的形式可以有效的保護(hù)和控制資源的使用。但是直連的方式也并不是一無是處,下表給出兩種方式各自的優(yōu)劣勢(shì)。
Jedis直連方式和連接池方式對(duì)比
Jedis提供了JedisPool這個(gè)類作為對(duì)Jedis的連接池,同時(shí)使用了Apache的通用對(duì)象池工具common-pool作為資源的管理工具,下面是使用JedisPool操作Redis的代碼示例:
1)Jedis連接池(通常JedisPool是單例的):
2)獲取Jedis對(duì)象不再是直接生成一個(gè)Jedis對(duì)象進(jìn)行直連,而是從連接池直接獲取,代碼如下:
Jedis jedis = null; try {// 1. 從連接池獲取jedis對(duì)象 jedis = jedisPool.getResource(); // 2. 執(zhí)行操作 jedis.get("hello"); } catch (Exception e) { logger.error(e.getMessage(),e); } finally { if (jedis != null) { // 如果使用JedisPool,close操作不是關(guān)閉連接,代表歸還連接池 jedis.close(); } }這里可以看到在finally中依然是jedis.close()操作,為什么會(huì)把連接關(guān)閉呢,這不和連接池的原則違背了嗎?但實(shí)際上Jedis的close()實(shí)現(xiàn)方式如下:
public void close() { // 使用Jedis連接池 if (dataSource != null) { if (client.isBroken()) { this.dataSource.returnBrokenResource(this); } else { this.dataSource.returnResource(this); } // 直連 } else { client.close(); } }參數(shù)說明:
·dataSource!=null代表使用的是連接池,所以jedis.close()代表歸還連接給連接池,而且Jedis會(huì)判斷當(dāng)前連接是否已經(jīng)斷開。
·dataSource=null代表直連,jedis.close()代表關(guān)閉連接。
前面GenericObjectPoolConfig使用的是默認(rèn)配置,實(shí)際它提供有很多參數(shù),例如池子中最大連接數(shù)、最大空閑連接數(shù)、最小空閑連接數(shù)、連接活性檢測(cè),等等,例如下面代碼:
上面幾個(gè)是GenericObjectPoolConfig幾個(gè)比較常用的屬性,下表給出了Generic-ObjectPoolConfig其他屬性及其含義解釋。
GenericObjectPoolConfig的重要屬性
7 客戶端API
7.1 client list
client list命令能列出與Redis服務(wù)端相連的所有客戶端連接信息,例如下面代碼是在一個(gè)Redis實(shí)例上執(zhí)行client list的結(jié)果:
127.0.0.1:6379> client list id=254487 addr=10.2.xx.234:60240 fd=1311 name= age=8888581 idle=8888581 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get id=300210 addr=10.2.xx.215:61972 fd=3342 name= age=8054103 idle=8054103 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get id=5448879 addr=10.16.xx.105:51157 fd=233 name= age=411281 idle=331077 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ttl id=2232080 addr=10.16.xx.55:32886 fd=946 name= age=603382 idle=331060 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get id=7125108 addr=10.10.xx.103:33403 fd=139 name= age=241 idle=1 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=del id=7125109 addr=10.10.xx.101:58658 fd=140 name= age=241 idle=1 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=del ...輸出結(jié)果的每一行代表一個(gè)客戶端的信息,可以看到每行包含了十幾個(gè)屬性,它們是每個(gè)客戶端的一些執(zhí)行狀態(tài),理解這些屬性對(duì)于Redis的開發(fā)和運(yùn)維人員非常有幫助。下面將選擇幾個(gè)重要的屬性進(jìn)行說明,其余通過表格的形式進(jìn)行展示。
(1)標(biāo)識(shí):id、addr、fd、name
這四個(gè)屬性屬于客戶端的標(biāo)識(shí):
·id:客戶端連接的唯一標(biāo)識(shí),這個(gè)id是隨著Redis的連接自增的,重啟Redis后會(huì)重置為0。
·addr:客戶端連接的ip和端口。
·fd:socket的文件描述符,與lsof命令結(jié)果中的fd是同一個(gè),如果fd=-1 代表當(dāng)前客戶端不是外部客戶端,而是Redis內(nèi)部的偽裝客戶端。
·name:客戶端的名字,后面的client setName和client getName兩個(gè)命令會(huì)對(duì)其進(jìn)行說明。
(2)輸入緩沖區(qū):qbuf、qbuf-free
Redis為每個(gè)客戶端分配了輸入緩沖區(qū),它的作用是將客戶端發(fā)送的命令臨時(shí)保存,同時(shí)Redis從會(huì)輸入緩沖區(qū)拉取命令并執(zhí)行,輸入緩沖區(qū)為客戶端發(fā)送命令到Redis執(zhí)行命令提供了緩沖功能,如圖所示。
client list中qbuf和qbuf-free分別代表這個(gè)緩沖區(qū)的總?cè)萘亢褪S嗳萘?#xff0c;Redis沒有提供相應(yīng)的配置來規(guī)定每個(gè)緩沖區(qū)的大小,輸入緩沖區(qū)會(huì)根據(jù)輸入內(nèi)容大小的不同動(dòng)態(tài)調(diào)整,只是要求每個(gè)客戶端緩沖區(qū)的大小不能超過1G,超過后客戶端將被關(guān)閉。下面是Redis源碼中對(duì)于輸入緩沖區(qū)的硬編碼:
輸入緩沖區(qū)基本模型
輸入緩沖使用不當(dāng)會(huì)產(chǎn)生兩個(gè)問題:
·一旦某個(gè)客戶端的輸入緩沖區(qū)超過1G,客戶端將會(huì)被關(guān)閉。
·輸入緩沖區(qū)不受maxmemory控制,假設(shè)一個(gè)Redis實(shí)例設(shè)置了 maxmemory為4G,已經(jīng)存儲(chǔ)了2G數(shù)據(jù),但是如果此時(shí)輸入緩沖區(qū)使用了3G,已經(jīng)超過maxmemory限制,可能會(huì)產(chǎn)生數(shù)據(jù)丟失、鍵值淘汰、OOM等情況(如圖所示)。
輸入緩沖區(qū)超過了maxmemory
執(zhí)行效果如下:
127.0.0.1:6390> info memory # Memory used_memory_human:5.00G ... maxmemory_human:4.00G ....上面已經(jīng)看到,輸入緩沖區(qū)使用不當(dāng)造成的危害非常大,那么造成輸入緩沖區(qū)過大的原因有哪些?輸入緩沖區(qū)過大主要是因?yàn)镽edis的處理速度跟不上輸入緩沖區(qū)的輸入速度,并且每次進(jìn)入輸入緩沖區(qū)的命令包含了大量bigkey,從而造成了輸入緩沖區(qū)過大的情況。還有一種情況就是Redis發(fā)生了阻塞,短期內(nèi)不能處理命令,造成客戶端輸入的命令積壓在了輸入緩沖區(qū), 造成了輸入緩沖區(qū)過大。那么如何快速發(fā)現(xiàn)和監(jiān)控呢?監(jiān)控輸入緩沖區(qū)異常的方法有兩種:
·通過定期執(zhí)行client list命令,收集qbuf和qbuf-free找到異常的連接記錄并分析,最終找到可能出問題的客戶端。
·通過info命令的info clients模塊,找到最大的輸入緩沖區(qū),例如下面命令中的其中client_biggest_input_buf代表最大的輸入緩沖區(qū),例如可以設(shè)置超過10M就進(jìn)行報(bào)警:
這兩種方法各有自己的優(yōu)劣勢(shì),下表對(duì)兩種方法進(jìn)行了對(duì)比。
對(duì)比client list和info clients監(jiān)控輸入緩沖區(qū)的優(yōu)劣勢(shì)
運(yùn)維提示
輸入緩沖區(qū)問題出現(xiàn)概率比較低,但是也要做好防范,在開發(fā)中要減少bigkey、減少Redis阻塞、合理的監(jiān)控報(bào)警。
(3)輸出緩沖區(qū):obl、oll、omem
Redis為每個(gè)客戶端分配了輸出緩沖區(qū),它的作用是保存命令執(zhí)行的結(jié)果返回給客戶端,為Redis和客戶端交互返回結(jié)果提供緩沖,如圖所示。與輸入緩沖區(qū)不同的是,輸出緩沖區(qū)的容量可以通過參數(shù)client-output-buffer-limit來進(jìn)行設(shè)置,并且輸出緩沖區(qū)做得更加細(xì)致,按照客戶端的不同分為三種:普通客戶端、發(fā)布訂閱客戶端、slave客戶端,如圖所示。
客戶端輸出緩沖區(qū)模型
三種不同類型客戶端的輸出緩沖區(qū)
對(duì)應(yīng)的配置規(guī)則是:
client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>·class:客戶端類型,分為三種。a)normal:普通客戶端;b) slave:slave客戶端,用于復(fù)制;c)pubsub:發(fā)布訂閱客戶端。
·hard limit:如果客戶端使用的輸出緩沖區(qū)大于,客戶端會(huì)被立即關(guān)閉。
·soft limit和soft seconds:如果客戶端使用的輸出緩沖區(qū)超過了并且持續(xù)了秒,客戶端會(huì)被立即關(guān)閉。
Redis的默認(rèn)配置是:
client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60和輸入緩沖區(qū)相同的是,輸出緩沖區(qū)也不會(huì)受到maxmemory的限制,如果使用不當(dāng)同樣會(huì)造成maxmemory用滿產(chǎn)生的數(shù)據(jù)丟失、鍵值淘汰、OOM等情況。
監(jiān)控輸出緩沖區(qū)的方法依然有兩種:
·通過定期執(zhí)行client list命令,收集obl、oll、omem找到異常的連接記錄并分析,最終找到可能出問題的客戶端。
·通過info命令的info clients模塊,找到輸出緩沖區(qū)列表最大對(duì)象數(shù),例如:
其中,client_longest_output_list代表輸出緩沖區(qū)列表最大對(duì)象數(shù),這兩種統(tǒng)計(jì)方法的優(yōu)劣勢(shì)和輸入緩沖區(qū)是一樣的,這里就不再贅述了。相比于輸入緩沖區(qū),輸出緩沖區(qū)出現(xiàn)異常的概率相對(duì)會(huì)比較大,那么如何預(yù)防呢?方法如下:
·進(jìn)行上述監(jiān)控,設(shè)置閥值,超過閥值及時(shí)處理。
·限制普通客戶端輸出緩沖區(qū)的,把錯(cuò)誤扼殺在搖籃中,例如可以進(jìn)行如下設(shè)置:
·適當(dāng)增大slave的輸出緩沖區(qū)的,如果master節(jié)點(diǎn)寫入較大,slave客戶端的輸出緩沖區(qū)可能會(huì)比較大,一旦slave客戶端連接因?yàn)檩敵鼍彌_區(qū)溢出被kill,會(huì)造成復(fù)制重連。
·限制容易讓輸出緩沖區(qū)增大的命令,例如,高并發(fā)下的monitor命令就是一個(gè)危險(xiǎn)的命令。
·及時(shí)監(jiān)控內(nèi)存,一旦發(fā)現(xiàn)內(nèi)存抖動(dòng)頻繁,可能就是輸出緩沖區(qū)過大。
(4)客戶端的存活狀態(tài)
client list中的age和idle分別代表當(dāng)前客戶端已經(jīng)連接的時(shí)間和最近一次的空閑時(shí)間:
例如上面這條記錄代表當(dāng)期客戶端連接Redis的時(shí)間為603382秒,其中空閑了331060秒:
id=254487 addr=10.2.xx.234:60240 fd=1311 name= age=8888581 idle=8888581 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get例如上面這條記錄代表當(dāng)期客戶端連接Redis的時(shí)間為8888581秒,其中空閑了8888581秒,實(shí)際上這種就屬于不太正常的情況,當(dāng)age等于idle時(shí),說明連接一直處于空閑狀態(tài)。 為了更加直觀地描述age和idle,下面用一個(gè)例子進(jìn)行說明:
String key = "hello"; // 1) 生成jedis,并執(zhí)行g(shù)et操作 Jedis jedis = new Jedis("127.0.0.1", 6379); System.out.println(jedis.get(key)); // 2) 休息10秒 TimeUnit.SECONDS.sleep(10); // 3) 執(zhí)行新的操作ping System.out.println(jedis.ping()); // 4) 休息5秒 TimeUnit.SECONDS.sleep(5); // 5) 關(guān)閉jedis連接 jedis.close();下面對(duì)代碼中的每一步進(jìn)行分析,用client list命令來觀察age和idle參數(shù)的相應(yīng)變化。
注意
為了與redis-cli的客戶端區(qū)分,本次測(cè)試客戶端IP地址:10.7.40.98。
1)在執(zhí)行代碼之前,client list只有一個(gè)客戶端,也就是當(dāng)前的redis-cli,下面為了節(jié)省篇幅忽略掉這個(gè)客戶端。
127.0.0.1:6379> client list id=45 addr=127.0.0.1:55171 fd=6 name= age=2 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client2)使用Jedis生成了一個(gè)新的連接,并執(zhí)行g(shù)et操作,可以看到IP地址為10.7.40.98的客戶端,最后執(zhí)行的命令是get,age和idle分別是1秒和0秒:
127.0.0.1:6379> client list id=46 addr=10.7.40.98:62908 fd=7 name= age=1 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get3)休息10秒,此時(shí)Jedis客戶端并沒有關(guān)閉,所以age和idle一直在遞增:
127.0.0.1:6379> client list id=46 addr=10.7.40.98:62908 fd=7 name= age=9 idle=9 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get4)執(zhí)行新的操作ping,發(fā)現(xiàn)執(zhí)行后age依然在增加,而idle從0計(jì)算,也就是不再閑置:
127.0.0.1:6379> client list id=46 addr=10.7.40.98:62908 fd=7 name= age=11 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ping5)休息5秒,觀察age和idle增加:
127.0.0.1:6379> client list id=46 addr=10.7.40.98:62908 fd=7 name= age=15 idle=5 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ping6)關(guān)閉Jedis,Jedis連接已經(jīng)消失:
redis-cli client list | grep "10.7.40.98”為空(5)客戶端的限制maxclients和timeout
Redis提供了maxclients參數(shù)來限制最大客戶端連接數(shù),一旦連接數(shù)超過maxclients,新的連接將被拒絕。maxclients默認(rèn)值是10000,可以通過info clients來查詢當(dāng)前Redis的連接數(shù):
可以通過config set maxclients對(duì)最大客戶端連接數(shù)進(jìn)行動(dòng)態(tài)設(shè)置:
127.0.0.1:6379> config get maxclients 1) "maxclients" 2) "10000" 127.0.0.1:6379> config set maxclients 50 OK 127.0.0.1:6379> config get maxclients 1) "maxclients" 2) "50"一般來說maxclients=10000在大部分場(chǎng)景下已經(jīng)絕對(duì)夠用,但是某些情況由于業(yè)務(wù)方使用不當(dāng)(例如沒有主動(dòng)關(guān)閉連接)可能存在大量idle連接, 無論是從網(wǎng)絡(luò)連接的成本還是超過maxclients的后果來說都不是什么好事,因此Redis提供了timeout(單位為秒)參數(shù)來限制連接的最大空閑時(shí)間,一旦客戶端連接的idle時(shí)間超過了timeout,連接將會(huì)被關(guān)閉,例如設(shè)置timeout為30秒:
#Redis默認(rèn)的timeout是0,也就是不會(huì)檢測(cè)客戶端的空閑 127.0.0.1:6379> config set timeout 30 OK下面繼續(xù)使用Jedis進(jìn)行模擬,整個(gè)代碼和上面是一樣的,只不過第2)步驟休息了31秒:
String key = "hello"; // 1) 生成jedis,并執(zhí)行g(shù)et操作 Jedis jedis = new Jedis("127.0.0.1", 6379); System.out.println(jedis.get(key)); // 2) 休息31秒 TimeUnit.SECONDS.sleep(31); // 3) 執(zhí)行g(shù)et操作 System.out.println(jedis.get(key)); // 4) 休息5秒 TimeUnit.SECONDS.sleep(5); // 5) 關(guān)閉jedis連接 jedis.close();執(zhí)行上述代碼可以發(fā)現(xiàn)在執(zhí)行完第2)步之后,client list中已經(jīng)沒有了Jedis的連接,也就是說timeout已經(jīng)生效,將超過30秒空閑的連接關(guān)閉掉:
127.0.0.1:6379> client list id=16 addr=10.7.40.98:63892 fd=6 name= age=19 idle=19 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get # 超過timeout后,Jedis連接被關(guān)閉 redis-cli client list | grep “10.7.40.98”為空同時(shí)可以看到,在Jedis代碼中的第3)步拋出了異常,因?yàn)榇藭r(shí)客戶端已經(jīng)被關(guān)閉,所以拋出的異常是JedisConnectionException,并且提示Unexpected end of stream:
stream: world Exception in thread "main" redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.如果將Redis的loglevel設(shè)置成debug級(jí)別,可以看到如下日志,也就是客戶端被Redis關(guān)閉的日志:
12885:M 26 Aug 08:46:40.085 - Closing idle clientRedis的默認(rèn)配置給出的timeout=0,在這種情況下客戶端基本不會(huì)出現(xiàn)上面的異常,這是基于對(duì)客戶端開發(fā)的一種保護(hù)。例如很多開發(fā)人員在使用JedisPool時(shí)不會(huì)對(duì)連接池對(duì)象做空閑檢測(cè)和驗(yàn)證,如果設(shè)置了timeout>0,可能就會(huì)出現(xiàn)上面的異常,對(duì)應(yīng)用業(yè)務(wù)造成一定影響,但是如果Redis的客戶端使用不當(dāng)或者客戶端本身的一些問題,造成沒有及時(shí)釋放客戶端連接,可能會(huì)造成大量的idle連接占據(jù)著很多連接資源,一旦超過maxclients;后果也是不堪設(shè)想。所在在實(shí)際開發(fā)和運(yùn)維中,需要將timeout設(shè)置成大于0,例如可以設(shè)置為300秒,同時(shí)在客戶端使用上添加空閑檢測(cè)和驗(yàn)證等等措施,例如JedisPool使用common-pool提供的三個(gè)屬性:minEvictableIdleTimeMillis、
testWhileIdle、timeBetweenEvictionRunsMillis。
(6)客戶端類型
client list中的flag是用于標(biāo)識(shí)當(dāng)前客戶端的類型,例如flag=S代表當(dāng)前客戶端是slave客戶端、flag=N代表當(dāng)前是普通客戶端,flag=O代表當(dāng)前客戶端正在執(zhí)行monitor命令,下表列出了11種客戶端類型。
(7)其他
上面已經(jīng)將client list中重要的屬性進(jìn)行了說明,下表列出之前介紹過以及一些比較簡(jiǎn)單或者不太重要的屬性。
client list命令結(jié)果的全部屬性
7.2 monitor
monitor命令用于監(jiān)控Redis正在執(zhí)行的命令,如圖4-11所示,我們打開了兩個(gè)redis-cli,一個(gè)執(zhí)行set get ping命令,另一個(gè)執(zhí)行monitor命令。可以看到monitor命令能夠監(jiān)聽其他客戶端正在執(zhí)行的命令,并記錄了詳細(xì)的時(shí)間戳。
monitor命令演示
monitor的作用很明顯,如果開發(fā)和運(yùn)維人員想監(jiān)聽Redis正在執(zhí)行的命令,就可以用monitor命令,但事實(shí)并非如此美好,每個(gè)客戶端都有自己的輸出緩沖區(qū),既然monitor能監(jiān)聽到所有的命令,一旦Redis的并發(fā)量過大,monitor客戶端的輸出緩沖會(huì)暴漲,可能瞬間會(huì)占用大量?jī)?nèi)存,下圖展示了monitor命令造成大量?jī)?nèi)存使用。
高并發(fā)下monitor命令使用大量輸出緩沖區(qū)
7.3 客戶端相關(guān)配置
·timeout:檢測(cè)客戶端空閑連接的超時(shí)時(shí)間,一旦idle時(shí)間達(dá)到了timeout,客戶端將會(huì)被關(guān)閉,如果設(shè)置為0就不進(jìn)行檢測(cè)。
·maxclients:客戶端最大連接數(shù),前面已進(jìn)行分析,這里不再贅述,但是這個(gè)參數(shù)會(huì)受到操作系統(tǒng)設(shè)置的限制。
·tcp-keepalive:檢測(cè)TCP連接活性的周期,默認(rèn)值為0,也就是不進(jìn)行檢測(cè),如果需要設(shè)置,建議為60,那么Redis會(huì)每隔60秒對(duì)它創(chuàng)建的TCP連接進(jìn)行活性檢測(cè),防止大量死連接占用系統(tǒng)資源。
·tcp-backlog:TCP三次握手后,會(huì)將接受的連接放入隊(duì)列中,tcp-
backlog就是隊(duì)列的大小,它在Redis中的默認(rèn)值是511。通常來講這個(gè)參數(shù)不需要調(diào)整,但是這個(gè)參數(shù)會(huì)受到操作系統(tǒng)的影響,例如在Linux操作系統(tǒng)中,如果/proc/sys/net/core/somaxconn小于tcp-backlog,那么在Redis啟動(dòng)時(shí)會(huì)看到如下日志,并建議將/proc/sys/net/core/somaxconn設(shè)置更大。
修改方法也非常簡(jiǎn)單,只需要執(zhí)行如下命令:
echo 511 > /proc/sys/net/core/somaxconn總結(jié)
以上是生活随笔為你收集整理的《Redis开发与运维》- 核心知识整理二(Lua脚本、发布订阅、客户端等)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DevExpress.Utils.Too
- 下一篇: mysql 常用命令(一)