Jedis與Lua腳本結合

使用Lua腳本的好處
   一、減小網絡開銷:能夠將多個請求經過腳本的形式一次發送,減小網絡時延和請求次數。

   二、原子性的操做: Redis會將整個腳本做爲一個總體執行,中間不會被其餘命令插入。所以在編寫腳本的過程當中無需擔憂會出現競態條件,無需使用事務。

   三、代碼複用:客戶端發送的腳步會永久存在redis中,這樣,其餘客戶端能夠複用這一腳原本完成相同的邏輯。

   四、速度快:見 與其它語言的性能比較, 還有一個 JIT編譯器能夠顯著地提升多數任務的性能; 對於那些仍然對性能不滿意的人, 能夠把關鍵部分使用C實現, 而後與其集成, 這樣還能夠享受其它方面的好處。

    五、能夠移植:只要是有ANSI C 編譯器的平臺均可以編譯,你能夠看到它能夠在幾乎全部的平臺上運行:從 Windows 到Linux,一樣Mac平臺也沒問題, 再到移動平臺、遊戲主機,甚至瀏覽器也能夠完美使用 (翻譯成JavaScript).

    六、源碼小巧:20000行C代碼,能夠編譯進182K的可執行文件,加載快,運行快。
 

eval 命令
(1)首先須要瞭解Redis eval 命令:
eval 命令也會將腳本添加到腳本緩存中,可是它會當即對輸入的腳本進行求值。
 
eg
EVAL script numkeys key [key ...] arg [arg ...]  
script參數是一段Lua腳本程序,它會被運行在Redis服務器上下文中,這段腳本沒必要(也不該該)定義爲一個Lua函數。
numkeys參數用於指定鍵名參數的個數。
鍵名參數 key [key ...] 從EVAL的第三個參數開始算起,表示在腳本中所用到的那些Redis鍵(key),這些鍵名參數能夠在 Lua中經過全局變量KEYS數組,用1爲基址的形式訪問( KEYS[1] , KEYS[2] ,以此類推)。
在命令的最後,那些不是鍵名參數的附加參數 arg [arg ...] ,能夠在Lua中經過全局變量ARGV數組訪問,訪問的形式和KEYS變量相似( ARGV[1] 、 ARGV[2] ,諸如此類)
 
如:
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 username age jack 20

通常java運行lua腳本,採用的也是相似上述表達式,後面描述java

(2)對於一段長的lua腳本,能夠將腳本放在一個文件中,經過以下命令執行lua腳本redis

$ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...apache

              --eval,告訴redis-cli讀取並運行後面的lua腳本
               path/to/redis.lua,是lua腳本的位置
               KEYS[1] KEYS[2],是要操做的鍵,能夠指定多個,在lua腳本中經過KEYS[1], KEYS[2]獲取
               ARGV[1] ARGV[2],參數,在lua腳本中經過ARGV[1], ARGV[2]獲取。數組

注意: KEYS和ARGV中間的 ',' 兩邊的空格,不能省略。瀏覽器

 

看下面例子:緩存

在以下文件夾中以一個lua腳本    jedisCallLuaTest.lua服務器

local key=KEYS[1local args=ARGV 
//說明:設置一個key = userName,value=Jack,20s過時時間
return redis.call("setex",key,unpack(args))

去客戶端獲取:網絡

 

過時了。。。。ide


EVALSHA命令
將腳本 script 添加到腳本緩存中,但並不當即執行這個腳本。
語法以下:
redis 127.0.0.1:6379> EVALSHA sha1 numkeys key [key ...] arg [arg ...]

參數說明:函數

  • sha1 : 經過 SCRIPT LOAD 生成的 sha1 校驗碼。
  • numkeys: 用於指定鍵名參數的個數。
  • key [key ...]: 從 EVAL 的第三個參數開始算起,表示在腳本中所用到的那些 Redis 鍵(key),這些鍵名參數能夠在 Lua 中經過全局變量 KEYS 數組,用 1 爲基址的形式訪問( KEYS[1] , KEYS[2] ,以此類推)。
  • arg [arg ...]: 附加參數,在 Lua 中經過全局變量 ARGV 數組訪問,訪問的形式和 KEYS 變量相似( ARGV[1] 、 ARGV[2] ,諸如此類)。

eg:


整合Jedis + lua

(1)Jedis的使用
建立Jedis對象,主要是用於單個redis
 
public class RedisClient {
    private static JedisPool    jedisPool    = null;
    private static String        addr        = "127.0.0.1";
    private static int            port        = 6379;
    static {
        try {
            JedisPoolConfig config new JedisPoolConfig();
            // 鏈接耗盡時是否阻塞, false報異常,ture阻塞直到超時, 默認true
            config.setBlockWhenExhausted(true);
            // 設置的逐出策略類名, 默認DefaultEvictionPolicy(當鏈接超過最大空閒時間,或鏈接數超過最 大空閒鏈接數)
            config.setEvictionPolicyClassName("org.apache.commons.pool2.impl.DefaultEvictionPolicy");
            // 是否啓用pool的jmx管理功能, 默認true
            config.setJmxEnabled(true);
            // MBean ObjectName = new
            // ObjectName("org.apache.commons.pool2:type=GenericObjectPool,name="
            // + "pool" + i); 默認爲"pool", JMX不熟,具體不知道是幹啥的...默認就好.
            config.setJmxNamePrefix("pool");
            // 是否啓用後進先出, 默認true
            config.setLifo(true);
            // 最大空閒鏈接數, 默認8個 
            config.setMaxIdle(8);
            // 最大鏈接數, 默認8個
            config.setMaxTotal(8);
            // 獲取鏈接時的最大等待毫秒數(若是設置爲阻塞時BlockWhenExhausted),若是超時就拋異常, 小於零:阻塞不肯定的時間,
            // 默認-1
            config.setMaxWaitMillis(-1);
            // 逐出鏈接的最小空閒時間 默認1800000毫秒(30分鐘)
            config.setMinEvictableIdleTimeMillis(1800000);
            // 最小空閒鏈接數, 默認0
            config.setMinIdle(0);
            // 每次逐出檢查時 逐出的最大數目 若是爲負數就是 : 1/abs(n), 默認3
            config.setNumTestsPerEvictionRun(3);
            // 對象空閒多久後逐出, 當空閒時間>該值 且 空閒鏈接>最大空閒數
            // 時直接逐出,再也不根據MinEvictableIdleTimeMillis判斷 (默認逐出策略)
            config.setSoftMinEvictableIdleTimeMillis(1800000);
            // 在獲取鏈接的時候檢查有效性, 默認false
            config.setTestOnBorrow(false);
            // 在空閒時檢查有效性, 默認false
            config.setTestWhileIdle(false);
            // 逐出掃描的時間間隔(毫秒) 若是爲負數,則不運行逐出線程, 默認-1
            config.setTimeBetweenEvictionRunsMillis(-1);
            jedisPool new JedisPool(config, addr, port, 3000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public synchronized static Jedis getJedis() {
        try {
            if (jedisPool != null) {
                Jedis resource = jedisPool.getResource();
                return resource;
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public static void close(final Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
    
    /*---------------------測試---------------------------*/
    public static void main(java.lang.String[] args) {
        Jedis jedis = RedisClient.getJedis();
        // do something
        RedisClient.testCallLua(jedis);
        RedisClient.close(jedis);
    }
    
    public static void testCallLua(Jedis jedis){
        String luaStr = "return {KEYS[1],KEYS[1],ARGV[1],ARGV[2]}";
        Object result = jedis.eval(luaStr, Lists.newArrayList("userName","age"), Lists.newArrayList("Jack","20"));
        System.out.println(result);
    }
}

運行結果:

[userName, userName, Jack, 20]

同理將工程中的lua文件加載成String,做爲參數運行也是可行的。

java: 該段代碼圖個方便,引用了guava.jar包

/**
     * 調用lua腳本
     * @param jedis
     */
    public static void testCallLuaFile(Jedis jedis){
        String luaStr null;
        
        //帶反斜槓,路徑爲classPath,不帶反斜槓,路徑爲類的同一目錄
        Reader r = new InputStreamReader(RedisClient.class.getResourceAsStream("/jedisCallLuaTest.lua"));
        try {
            luaStr = CharStreams.toString(r);
            Object result = jedis.eval(luaStr, Lists.newArrayList("userName"), Lists.newArrayList("20","Tom"));
            System.out.println(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

注意:getResourceAsStream()這但是好幫手,用到將文件內容加載成String,必定要想到他。CharStreams是guava.jar中的對象。

Reader轉String還有以下兩種伎倆:(發散。。。。)

@Test
public void apcheIo() throws IOException{
    String luaStr=null;
    //帶反斜槓,路徑爲classPath,不帶反斜槓,路徑爲類的同一目錄
    Reader r= new InputStreamReader(Reader2StrDemo.class.getResourceAsStream("/jedisCallLuaTest.lua"));

    luaStr = org.apache.commons.io.IOUtils.toString(r);
    System.out.println(luaStr);
}

@Test
public void java8() throws IOException{
    String luaStr=null;
    String path = "F:\\xxxx\\ideaProjects\\java8-pro\\resource\\jedisCallLuaTest.lua";

    luaStr = Files.lines(Paths.get(path),Charset.defaultCharset()).collect(Collectors.joining());
    System.out.println(luaStr);
}

運行結果:

相關文章
相關標籤/搜索