4.2、Java客戶端Jedis

Java客戶端Jedis

Java有很多優(yōu)秀的Redis客戶端(詳見:http://redis.io/clients#java ),
這里介紹使用較為廣泛的客戶端Jedis,本節(jié)將按照以下幾個方面對Jedis進行介
紹:

- 獲取Jedis
- Jedis的基本使用
- Jedis連接池使用
- Jedis中Pipleline使用
- Jedis的Lua腳本使用
  1. 獲取Jedis

    Jedis屬于Java的第三方開發(fā)包,在Java中獲取第三方開發(fā)包通常有兩種方式:

    • 直接下載目標版本的Jedis-${version}.jar包加入到項目中。
    • 使用集成構(gòu)建工具,例如maven、gradle等將Jedis目標版本的配置加入到項
      目中。

    通常在實際項目中使用第二種方式,但如果只是想測試一下Jedis,第一種方法
    也是可以的。在寫本書時,Jedis最新發(fā)布的穩(wěn)定版本2.8.2,以Maven為例
    子,在項目中加入下面的依賴即可:

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.8.2</version>
    </dependency>
    

    對于第三方開發(fā)包,版本的選擇也是至關重要的,因為Redis更新速度比較快,
    如果客戶端跟不上服務端的速度,有些特性和bug不能及時更新,不利于日常開
    發(fā)。通常來講選取第三方開發(fā)包有如下兩個策略:

    • 選擇比較穩(wěn)定的版本,也就是盡可能選擇穩(wěn)定的里程碑版本,這些版本已經(jīng)經(jīng)
      過多次alpha,beta的修復,基本算是穩(wěn)定了。

    • 選擇更新活躍的第三方開發(fā)包,例如Redis3.0有了Redis Cluster新特性,
      但是如果使用的客戶端一直不支持,并且維護的人也比較少,這種就謹慎選擇。

    本節(jié)介紹的Jedis基本滿足上述兩個特點,下面將對Jedis的基本使用方法進行
    介紹。

  2. Jedis的基本使用方法

    Jedis的使用方法非常簡單,只要下面三行代碼就可以實現(xiàn)get功能:

    # 1. 生成一個Jedis對象,這個對象負責和指定Redis實例進行通信
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    # 2. jedis執(zhí)行set操作
    jedis.set("hello", "world");
    # 3. jedis執(zhí)行get操作,value="world"
    String value = jedis.get("hello");
    

    可以看到初始化Jedis需要兩個參數(shù):Redis實例的IP和端口,除了這兩個參數(shù)
    外,還有一個包含了四個參數(shù)的構(gòu)造函數(shù)是比較常用的:

    Jedis(final String host, final int port, final int connectionTimeout, final int soTImeout)
    

    參數(shù)說明:

    • host:Redis實例的所在機器的IP。
    • port:Redis實例的端口。
    • connectionTimeout:客戶端連接超時。
    • soTimeout:客戶端讀寫超時。

    如果想看一下執(zhí)行如果:

    String setResult = jedis.set("hello", "world");
    String getResult = jedis.get("hello");
    System.out.println(setResult);
    System.out.println(getResult);
    

    輸入結(jié)果為:

    OK
    world
    

    可以看到jedis.set的返回結(jié)果是OK,和redis-cli的執(zhí)行效果是一樣的,只不
    過結(jié)果類型變?yōu)榱薐ava的數(shù)據(jù)類型。上面的這種寫法只是為了演示使用,在實
    際項目中比較推薦使用try catch finally的形式來進行來進行代碼的書寫:
    一方面可以在Jedis出現(xiàn)異常的時候(本身是網(wǎng)絡操作),將異常進行捕獲或者
    拋出;另一個方面無論執(zhí)行成功或者失敗,將Jedis連接關閉掉,在開發(fā)中關閉
    不用的連接資源是一種好的習慣,代碼類似如下:

    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();
        }
    }
    

    下面用一個例子說明Jedis對于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對象序列化為二進制,當應用需要獲取Java
    對象時, 使用get(final byte[] key)函數(shù)將字節(jié)數(shù)組取出,然后反序列化為
    Java對象即可。和很多NoSQL數(shù)據(jù)庫的客戶端不同,Jedis本身沒有提供序列化
    的工具,也就是說開發(fā)者需要自己引入序列化的工具。序列化的工具有很多,例
    如XML、Json、谷歌的Protobuf、Facebook的Thrift等等,對于序列化工具的
    選擇開發(fā)者可以根據(jù)自己需求決定。

  3. Jedis連接吃的使用方法

    上節(jié)介紹的是Jedis的直連方式,所謂直連是指Jedis每次都會新建TCP連接,使
    用后再斷開連接,對于頻繁訪問Redis的場景顯然不是高效的使用方式。因此生
    產(chǎn)環(huán)境中一般使用連接池的方式對Jedis連接進行管理,所有Jedis對象預先放
    在池子中(JedisPool),每次要連接Redis,只需要在池子中劫,用完了再歸
    還給池子。

    客戶端連接Redis使用的是TCP協(xié)議,直連的方式每次需要建立TCP連接,而連接
    池的方式可以預先初始化號Jedis連接,所以每次只需要從Jedis連接池借用即
    可,而借用和歸還操作是在本地進行的,只有少量的并發(fā)同步開銷,遠遠小于新
    建TCP連接的開銷。另外智聯(lián)的方式無法限制Jedis對象的個數(shù),在極端情況下
    可能會造成連接泄露,而連接池的形式可以有效地保護和控制資源的使用。但是
    直連的方式也并不是一無是處,下表給出兩種方式各自的優(yōu)劣勢。

    方式 優(yōu)點 缺點
    直連 簡單方便,適用于少量長期連接的場景 1.存在每次/關閉TCP連接開銷 2.資源無法控制,極端情況會出現(xiàn)連接泄露 3.Jedis對象線程不安全
    連接池 1.無需每次連接都生成Jedis對象,降低開銷 2.使用連接池的形式保護和控制資源的使用 相對于直連,使用相對麻煩,尤其在資源的管理上需要很多參數(shù)來保證,一旦規(guī)劃不合理也會出現(xiàn)問題

    Jedis提供了JedisPool這個類作為對Jedis的連接池,同時使用了Apache的通
    用對象池工具common-pool作為資源的管理工具,下面是使用JedisPool操作
    Redis的代碼實例:

    1) Jedis連接池(通常JedisPool是單例的):

    //common-pool連接池配置,這里使用默認配置,后面小節(jié)會介紹具體配置說明
    GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
    //初始化Jedis連接池
    JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
    

    2)獲取Jedis對象不再是直接生成一個Jedis對象進行直連,而是從連接池直接
    獲取,代碼如下:

    Jedis jedis = null;
    try{
        //1. 從連接池獲取jedis對象
        jedis = jedisPool.getResource();
        //2. 執(zhí)行操作
        jedis.get("hello");
    }catch (Exception e){
        logger.error(e.getMessage(), e);
    }finally{
        if(jedis != null){
            //如果適應JedisPool,close操作不是關閉連接,代表歸還連接池
            jedis.close();
        }
    }
    

    這里可以看到在finally中依然是jedis.close()操作,為什么會把連接關閉
    呢,這不和連接池的原則違背了嗎?但實際上Jedis的close()實現(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會判斷當前連接是否已經(jīng)斷開。

    • dataSource == null代表直連,jedis.close()代表關閉連接。

    前面GenericObjectPoolConfig使用的是默認配置,實際它提供有很多參數(shù),
    例如池子中最大連接數(shù)、最大空閑連接數(shù)、最小空閑李娜結(jié)束、連接活性檢測,
    等等,下表給出GenericObjectPoolConfig其他屬性及其含義解釋。

    參數(shù)名 含義 默認值
    maxActive 連接池中最大連接數(shù) 8
    maxIdle 連接池中最大空閑的連接數(shù) 8
    minIdle 連接池中最少空間的李娜結(jié)束 0
    maxWaitMillis 當連接池資源用盡后,調(diào)用者的最大等待時間(單位為毫秒),一般不建議使用默認值 -1,表示永遠不超時,一直等。
    jmxEnabled 是否開啟jmx監(jiān)控,如果應用開啟了jmx端口并且jmxEnabled設置為true,就可以通過jconsole或者jvisualvm看到關于連接池的相關統(tǒng)計,有助于了解連接池的使用情況,并且可以針對其做監(jiān)控統(tǒng)計。 true
    minEvictableIdleTimeMillis 連接的最小空間時間,達到此值后空閑連接將被移除 1000L * 60L * 30毫秒 = 30分鐘
    numTestsPerEvictionRun 做空間連接檢測時,每次的采樣數(shù) 3
    testOnBorrow 想連接池借用連接是否做連接有效性檢測(ping),無效連接會被移除,每次借用多執(zhí)行一次ping命令 false
    testOnReturn 向連接池歸還連接時是否做連接有效性檢測(ping),無效連接會被移除,每次歸還多執(zhí)行一次ping命令 false
    timeBetweenEvictionRunMillis 空閑連接的檢測周期(單位為毫秒) -1:表示不做檢測
    blockWhenExhausted 當連接池用盡后,調(diào)用者是否要等待,這個參數(shù)是和maxWaitMillis對應的,只有當此參數(shù)為true時,maxWaitMillis才會生效 true
  4. Redis中Pipeline的使用方法

    下面代碼是借助Pipeline來模擬批量刪除:

    public void mdel(List<String> keys){
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //1)生成pipeline對象
        Pipeline pipeline = jedis.pipelined();
        //2)pipeline執(zhí)行命令,注意此事命令并未真正執(zhí)行
        for(String key : keys){
            pipeline.del(key);
        }
        //3)執(zhí)行命令
        pipeline.sync();
    }
    

    除了pipeline。sync(),還可以使用pipeline.syncAndReturnAll()將
    pipeline的命令進行返回,例如下面代碼將set和incr做了一次pipeline操
    作,并順序打印了兩個命令的結(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
    
  5. Jedis的Lua腳本

    Jedis中執(zhí)行Lua腳本和redis-cli十分類似,Jedis提供了三個重要的函數(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ù)有三個參數(shù),分別是:

    • script:Lua腳本內(nèi)容。
    • keyCount:鍵的個數(shù)。
    • params:相關參數(shù)KEYS和ARGV。

    scriptLoad和evalSha函數(shù)要一起使用,首先使用scriptLoad將腳本加載到
    Redis中,evalSha函數(shù)用來執(zhí)行腳本的SHA1校驗和,它需要三個參數(shù):

    • scriptSha:腳本的SHA1。
    • keyCount:鍵的個數(shù)。
    • params:相關參數(shù)KEYS和ARGV
  6. 重點注意以下幾點:

    1) Jedis操作放在 try catch finally里更加合理。
    2) 區(qū)分直連和連接池兩種實現(xiàn)方式的優(yōu)缺點。
    3) jedis.close()方法的兩種實現(xiàn)方式。
    4) Jedis依賴了common-pool
    5) 如果key和value涉及了字節(jié)數(shù)組,需要自己選擇適合的序列化方法。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容