Redis Lua 總結
版本:version 2.6.0及以上
參考連接:http://redis.io/commands/eval
使用腳本的好處:
- 減少網絡開銷。可以將多個請求通過腳本的形式一次發送,減少網絡時延
- 原子操作。redis會將整個腳本作為一個整體執行,中間不會被其他命令插入。因此在編寫腳本的過程中無需擔心會出現競態條件,無需使用事務。
- 復用。客戶端發送的腳步會永久存在redis中,這樣,其他客戶端可以復用這一腳本而不需要使用代碼完成相同的邏輯
使用腳本的限制:
1、不支持集群
All Redis commands must be analyzed before execution to determine which keys the command will operate on. In order for this to be true forEVAL, keys must be passed explicitly. This is useful in many ways, but especially to make sure Redis Cluster can forward your request to the appropriate cluster node.
Note this rule is not enforced in order to provide the user with opportunities to abuse the Redis single instance configuration, at the cost of writing scripts not compatible with Redis Cluster.
2、原子操作。運行腳本的時候redis只會單獨運行此腳本,不會運行其他腳本和執行其他redis命令。如果腳本執行很慢就會影響redis的高響應。
調用Lua腳本的語法:
$ redis-cli --eval path/to/redis.lua numberkeys KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...
- --eval,告訴redis-cli讀取並運行后面的lua腳本
- path/to/redis.lua,是lua腳本的位置,也可以直接為腳本字符串。是一個Lua 5.1 script。
- numberkeys ,指定后續參數有幾個key。
- KEYS[1] KEYS[2],是要操作的鍵,可以指定多個,在lua腳本中通過KEYS[1], KEYS[2]獲取
- ARGV[1] ARGV[2],參數,在lua腳本中通過ARGV[1], ARGV[2]獲取。
Lua腳本的簡單實例:
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
Lua腳本的調用redis命令實例:
redis.call()
redis.pcall()
> eval "return redis.call('set','foo','bar')" 0
OK
> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
call與pcall基本上一樣。腳本報錯時,call會直接報錯,pcall不會報錯,會把錯誤信息放到lua table 的err字段中。
Lua腳本與redis數據類型轉換:
In other words there is a one-to-one conversion between Lua and Redis types. The following table shows you all the conversions rules:
Redis to Lua conversion table.
- Redis integer reply -> Lua number
- Redis bulk reply -> Lua string
- Redis multi bulk reply -> Lua table (may have other Redis data types nested)
- Redis status reply -> Lua table with a single
ok
field containing the status - Redis error reply -> Lua table with a single
err
field containing the error - Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type
Lua to Redis conversion table.
- Lua number -> Redis integer reply (the number is converted into an integer)
- Lua string -> Redis bulk reply
- Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)
- Lua table with a single
ok
field -> Redis status reply - Lua table with a single
err
field -> Redis error reply - Lua boolean false -> Redis Nil bulk reply.
- Lua boolean true -> Redis integer reply with value of 1.
- lua中number數據類型,在integer和float之間沒有區別,都會返回integer【會去掉小數】。如果想要float的話,請返回string,redis自己也是這樣做的【如zscore命令】
存在nil和float的情況
> eval "return {1,2,3.3333,'foo',nil,'bar'}" 0
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) "foo"
Lua腳本redis命令:eval和evalSHA,其實客戶端實現都是evalSHA,即使客戶端顯示的執行eval。
evla每次執行時需要不斷的發送腳本到server端,而evalSHA是server記住一個SHA1【緩存】,就直接執行。如果沒有記住則返回一個特殊錯誤,使客服端使用eval代替。
redis中的lua腳本名利:
1、SCRIPT FLUSH 唯一強制redis清除腳本緩存的方法。
2、SCRIPT EXISTS sha1 sha2 ... shaN 返回SHA1 是否存在redis腳本緩存中
3、SCRIPT LOAD script 加載腳本,在不執行腳本的情況下確保evalSHA不會失敗
4、SCRIPT KILL 打斷執行時間長的腳本【因為是原子操作,所以不會對其他的動作造成影響】
Lua腳本redis JAVA調用:
public class RedisLuaTest {
/**
* 數據: user_001 age 1 user_002 age 2
* 腳本功能:獲取arg[1]字段大於arg[2]參數的key。
* 測試使用前先將類上面的注釋放開【@RunWith和@ContextConfiguration】
* author liuzd
*/
// public static final String SCRIPT="local resultKeys={};for k,v in ipairs(KEYS) do local tmp = redis.call('hget', v, ARGV[1]);if tmp > ARGV[2] then table.insert(resultKeys,v);end;end;return resultKeys;";
public static final String SCRIPT="local resultKeys={}; local tmp = redis.call('hget', 'user_001', ARGV[1]); table.insert(resultKeys,'user_001');return resultKeys;";
@Autowired
private ICache cache;
@SuppressWarnings("unchecked")
@Test
public void test(){
Jedis jedis = cache.getResource().getJedis();
// jedis.auth(auth); //后續redis配置為需要密碼訪問時。做設置。
String[] allUserKeys={};
// String[] allUserKeys={"user_001","user_002"};
List<String> keys = Arrays.asList(allUserKeys);
List<String> params = new ArrayList<String>();
// params.add("age");
// params.add("1");
String shaFuncKey = jedis.scriptLoad(SCRIPT);//加載腳本,
List<String> resultKeys=new ArrayList<String>();
try {
resultKeys = (List<String>)jedis.evalsha(shaFuncKey, keys, params);
} catch (Exception e) {
e.printStackTrace();
System.out.println("lua 腳本執行報錯:"+e);
}
for (String str : resultKeys) {
System.out.println(str);
}
}
}
暫時redis腳本先研究到這。