Java客戶端Jedis
Java有很多優(yōu)秀的Redis客戶端(詳見:http://redis.io/clients#java ),
這里介紹使用較為廣泛的客戶端Jedis,本節(jié)將按照以下幾個(gè)方面對(duì)Jedis進(jìn)行介
紹:
- 獲取Jedis
- Jedis的基本使用
- Jedis連接池使用
- Jedis中Pipleline使用
- Jedis的Lua腳本使用
-
獲取Jedis
Jedis屬于Java的第三方開發(fā)包,在Java中獲取第三方開發(fā)包通常有兩種方式:
- 直接下載目標(biāo)版本的Jedis-${version}.jar包加入到項(xiàng)目中。
- 使用集成構(gòu)建工具,例如maven、gradle等將Jedis目標(biāo)版本的配置加入到項(xiàng)
目中。
通常在實(shí)際項(xiàng)目中使用第二種方式,但如果只是想測(cè)試一下Jedis,第一種方法
也是可以的。在寫本書時(shí),Jedis最新發(fā)布的穩(wěn)定版本2.8.2,以Maven為例
子,在項(xiàng)目中加入下面的依賴即可:<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.2</version> </dependency>對(duì)于第三方開發(fā)包,版本的選擇也是至關(guān)重要的,因?yàn)镽edis更新速度比較快,
如果客戶端跟不上服務(wù)端的速度,有些特性和bug不能及時(shí)更新,不利于日常開
發(fā)。通常來講選取第三方開發(fā)包有如下兩個(gè)策略:選擇比較穩(wěn)定的版本,也就是盡可能選擇穩(wěn)定的里程碑版本,這些版本已經(jīng)經(jīng)
過多次alpha,beta的修復(fù),基本算是穩(wěn)定了。選擇更新活躍的第三方開發(fā)包,例如Redis3.0有了Redis Cluster新特性,
但是如果使用的客戶端一直不支持,并且維護(hù)的人也比較少,這種就謹(jǐn)慎選擇。
本節(jié)介紹的Jedis基本滿足上述兩個(gè)特點(diǎn),下面將對(duì)Jedis的基本使用方法進(jìn)行
介紹。 -
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í)行如果:
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)行來進(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.smemebers{b, a}; //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("myset", 0, -1);參數(shù)除了可以是字符串,Jedis還提供了字節(jié)數(shù)組的參數(shù),例如:
public String set(final String key, String value) public String set(final String 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ù)庫的客戶端不同,Jedis本身沒有提供序列化
的工具,也就是說開發(fā)者需要自己引入序列化的工具。序列化的工具有很多,例
如XML、Json、谷歌的Protobuf、Facebook的Thrift等等,對(duì)于序列化工具的
選擇開發(fā)者可以根據(jù)自己需求決定。 -
Jedis連接吃的使用方法
上節(jié)介紹的是Jedis的直連方式,所謂直連是指Jedis每次都會(huì)新建TCP連接,使
用后再斷開連接,對(duì)于頻繁訪問Redis的場(chǎng)景顯然不是高效的使用方式。因此生
產(chǎn)環(huán)境中一般使用連接池的方式對(duì)Jedis連接進(jìn)行管理,所有Jedis對(duì)象預(yù)先放
在池子中(JedisPool),每次要連接Redis,只需要在池子中劫,用完了再歸
還給池子。客戶端連接Redis使用的是TCP協(xié)議,直連的方式每次需要建立TCP連接,而連接
池的方式可以預(yù)先初始化號(hào)Jedis連接,所以每次只需要從Jedis連接池借用即
可,而借用和歸還操作是在本地進(jìn)行的,只有少量的并發(fā)同步開銷,遠(yuǎn)遠(yuǎn)小于新
建TCP連接的開銷。另外智聯(lián)的方式無法限制Jedis對(duì)象的個(gè)數(shù),在極端情況下
可能會(huì)造成連接泄露,而連接池的形式可以有效地保護(hù)和控制資源的使用。但是
直連的方式也并不是一無是處,下表給出兩種方式各自的優(yōu)劣勢(shì)。方式 優(yōu)點(diǎn) 缺點(diǎn) 直連 簡(jiǎn)單方便,適用于少量長(zhǎng)期連接的場(chǎng)景 1.存在每次/關(guān)閉TCP連接開銷 2.資源無法控制,極端情況會(huì)出現(xiàn)連接泄露 3.Jedis對(duì)象線程不安全 連接池 1.無需每次連接都生成Jedis對(duì)象,降低開銷 2.使用連接池的形式保護(hù)和控制資源的使用 相對(duì)于直連,使用相對(duì)麻煩,尤其在資源的管理上需要很多參數(shù)來保證,一旦規(guī)劃不合理也會(huì)出現(xiàn)問題 Jedis提供了JedisPool這個(gè)類作為對(duì)Jedis的連接池,同時(shí)使用了Apache的通
用對(duì)象池工具common-pool作為資源的管理工具,下面是使用JedisPool操作
Redis的代碼實(shí)例:1) Jedis連接池(通常JedisPool是單例的):
//common-pool連接池配置,這里使用默認(rèn)配置,后面小節(jié)會(huì)介紹具體配置說明 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); //初始化Jedis連接池 JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);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){ //如果適應(yīng)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ù)、最小空閑李娜結(jié)束、連接活性檢測(cè),
等等,下表給出GenericObjectPoolConfig其他屬性及其含義解釋。參數(shù)名 含義 默認(rèn)值 maxActive 連接池中最大連接數(shù) 8 maxIdle 連接池中最大空閑的連接數(shù) 8 minIdle 連接池中最少空間的李娜結(jié)束 0 maxWaitMillis 當(dāng)連接池資源用盡后,調(diào)用者的最大等待時(shí)間(單位為毫秒),一般不建議使用默認(rèn)值 -1,表示永遠(yuǎn)不超時(shí),一直等。 jmxEnabled 是否開啟jmx監(jiān)控,如果應(yīng)用開啟了jmx端口并且jmxEnabled設(shè)置為true,就可以通過jconsole或者jvisualvm看到關(guān)于連接池的相關(guān)統(tǒng)計(jì),有助于了解連接池的使用情況,并且可以針對(duì)其做監(jiān)控統(tǒng)計(jì)。 true minEvictableIdleTimeMillis 連接的最小空間時(shí)間,達(dá)到此值后空閑連接將被移除 1000L * 60L * 30毫秒 = 30分鐘 numTestsPerEvictionRun 做空間連接檢測(cè)時(shí),每次的采樣數(shù) 3 testOnBorrow 想連接池借用連接是否做連接有效性檢測(cè)(ping),無效連接會(huì)被移除,每次借用多執(zhí)行一次ping命令 false testOnReturn 向連接池歸還連接時(shí)是否做連接有效性檢測(cè)(ping),無效連接會(huì)被移除,每次歸還多執(zhí)行一次ping命令 false timeBetweenEvictionRunMillis 空閑連接的檢測(cè)周期(單位為毫秒) -1:表示不做檢測(cè) blockWhenExhausted 當(dāng)連接池用盡后,調(diào)用者是否要等待,這個(gè)參數(shù)是和maxWaitMillis對(duì)應(yīng)的,只有當(dāng)此參數(shù)為true時(shí),maxWaitMillis才會(huì)生效 true -
Redis中Pipeline的使用方法
下面代碼是借助Pipeline來模擬批量刪除:
public void mdel(List<String> keys){ Jedis jedis = new Jedis("127.0.0.1", 6379); //1)生成pipeline對(duì)象 Pipeline pipeline = jedis.pipelined(); //2)pipeline執(zhí)行命令,注意此事命令并未真正執(zhí)行 for(String key : keys){ pipeline.del(key); } //3)執(zhí)行命令 pipeline.sync(); }除了pipeline。sync(),還可以使用pipeline.syncAndReturnAll()將
pipeline的命令進(jìn)行返回,例如下面代碼將set和incr做了一次pipeline操
作,并順序打印了兩個(gè)命令的結(jié)果:Jedis jedis = new Jedis("127.0.0.1", 6379); Pipeline pipeline = jedis.pipelined(); pipeline.set("hello", "world"); pipeline.incr("counter"); List<Object> resultList = pipeline.syncAndReturnAll(); for(Object object : resultList){ System.out.println(object); }輸出結(jié)果為:
OK 1 -
Jedis的Lua腳本
Jedis中執(zhí)行Lua腳本和redis-cli十分類似,Jedis提供了三個(gè)重要的函數(shù)實(shí)現(xiàn)
Lua腳本的執(zhí)行:Object eval(String script, int key count, String ... params) Object evalSha(Stirng sha1, int keyCount, String ... params) String scriptLoad(String script)eval函數(shù)有三個(gè)參數(shù),分別是:
- script:Lua腳本內(nèi)容。
- keyCount:鍵的個(gè)數(shù)。
- params:相關(guān)參數(shù)KEYS和ARGV。
scriptLoad和evalSha函數(shù)要一起使用,首先使用scriptLoad將腳本加載到
Redis中,evalSha函數(shù)用來執(zhí)行腳本的SHA1校驗(yàn)和,它需要三個(gè)參數(shù):- scriptSha:腳本的SHA1。
- keyCount:鍵的個(gè)數(shù)。
- params:相關(guān)參數(shù)KEYS和ARGV
-
重點(diǎn)注意以下幾點(diǎn):
1) Jedis操作放在 try catch finally里更加合理。
2) 區(qū)分直連和連接池兩種實(shí)現(xiàn)方式的優(yōu)缺點(diǎn)。
3) jedis.close()方法的兩種實(shí)現(xiàn)方式。
4) Jedis依賴了common-pool
5) 如果key和value涉及了字節(jié)數(shù)組,需要自己選擇適合的序列化方法。