使用Lua腳本的好處
1、減少網絡開銷:可以將多個請求通過腳本的形式一次發送,減少網絡時延和請求次數。
2、原子性的操作: Redis會將整個腳本作為一個整體執行,中間不會被其他命令插入。因此在編寫腳本的過程中無需擔心會出現競態條件,無需使用事務。
3、代碼復用:客戶端發送的腳步會永久存在redis中,這樣,其他客戶端可以復用這一腳本來完成相同的邏輯。
4、速度快:見 與其它語言的性能比較, 還有一個 JIT編譯器可以顯著地提高多數任務的性能; 對於那些仍然對性能不滿意的人, 可以把關鍵部分使用C實現, 然后與其集成, 這樣還可以享受其它方面的好處。
5、可以移植:只要是有ANSI C 編譯器的平台都可以編譯,你可以看到它可以在幾乎所有的平台上運行:從 Windows 到Linux,同樣Mac平台也沒問題, 再到移動平台、游戲主機,甚至瀏覽器也可以完美使用 (翻譯成JavaScript).
6、源碼小巧:20000行C代碼,可以編譯進182K的可執行文件,加載快,運行快。
2、原子性的操作: Redis會將整個腳本作為一個整體執行,中間不會被其他命令插入。因此在編寫腳本的過程中無需擔心會出現競態條件,無需使用事務。
3、代碼復用:客戶端發送的腳步會永久存在redis中,這樣,其他客戶端可以復用這一腳本來完成相同的邏輯。
4、速度快:見 與其它語言的性能比較, 還有一個 JIT編譯器可以顯著地提高多數任務的性能; 對於那些仍然對性能不滿意的人, 可以把關鍵部分使用C實現, 然后與其集成, 這樣還可以享受其它方面的好處。
5、可以移植:只要是有ANSI C 編譯器的平台都可以編譯,你可以看到它可以在幾乎所有的平台上運行:從 Windows 到Linux,同樣Mac平台也沒問題, 再到移動平台、游戲主機,甚至瀏覽器也可以完美使用 (翻譯成JavaScript).
6、源碼小巧: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腳本,采用的也是類似上述表達式,后面描述
(2)對於一段長的lua腳本,可以將腳本放在一個文件中,通過如下命令執行lua腳本
$ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...
--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[1] local args=ARGV
//說明:設置一個key = userName,value=Jack,20s過期時間 return redis.call("setex",key,unpack(args))
去客戶端獲取:

過期了。。。。
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); }
運行結果: