在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
1. 基本用法1.1 EVAL script numkeys key [key ...] arg [arg ...] numkeys 是key的个数,后边接着写key1 key2... val1 val2....,举例 127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 val1 val2 1) "key1" 2) "key2" 3) "val1" 4) "val2"
1.2 SCRIPT LOAD script 把脚本加载到脚本缓存中,返回SHA1校验和。但不会立马执行,举例 127.0.0.1:6379> SCRIPT LOAD "return 'hello world'" "5332031c6b470dc5a0dd9b4bf2030dea6d65de91"
1.3 EVALSHA sha1 numkeys key [key ...] arg [arg ...] 根据缓存码执行脚本内容。举例 127.0.0.1:6379> SCRIPT LOAD "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" "a42059b356c875f0717db19a51f6aaca9ae659ea" 127.0.0.1:6379> EVALSHA "a42059b356c875f0717db19a51f6aaca9ae659ea" 2 key1 key2 val1 val2 1) "key1" 2) "key2" 3) "val1" 4) "val2"
1.4 SCRIPT EXISTS script [script ...] 通过sha1校验和判断脚本是否在缓存中
1.5 SCRIPT FLUSH 清空缓存 127.0.0.1:6379> SCRIPT LOAD "return 'hello jihite'" "3a43944275256411df941bdb76737e71412946fd" 127.0.0.1:6379> SCRIPT EXISTS "3a43944275256411df941bdb76737e71412946fd" 1) (integer) 1 127.0.0.1:6379> SCRIPT FLUSH OK 127.0.0.1:6379> SCRIPT EXISTS "3a43944275256411df941bdb76737e71412946fd" 1) (integer) 0
1.6 SCRIPT KILL 杀死目前正在执行的脚本 2. 主要优势减少网络开销:多个请求通过脚本一次发送,减少网络延迟 原子操作:将脚本作为一个整体执行,中间不会插入其他命令,无需使用事务 复用:客户端发送的脚本永久存在redis中,其他客户端可以复用脚本 可嵌入性:可嵌入JAVA,C#等多种编程语言,支持不同操作系统跨平台交互 3. 实战直接在redis-cli中直接写lua脚本,这样非常不方便编辑,通常情况下我们都是把lua script放到一个lua文件中,然后执行这个lua脚本, 示例:活跃用户判断:判断一个游戏用户是否属于活跃用户,如果符合标准,则活跃用户人数+1 if redis.call("EXISTS",KEYS[1]) == 1 then return redis.call("INCRBY",KEYS[1],ARGV[1]) else return nil end 存储位置: /Users/jihite/activeuser.lua 执行 $ redis-cli --eval /Users/jihite/activeuser.lua user , 1 (integer) 1 127.0.0.1:6379> get user "1" 127.0.0.1:6379> exit $ redis-cli --eval /Users/jihite/activeuser.lua user , 1 (integer) 2 $ redis-cli 127.0.0.1:6379> get user "2" 127.0.0.1:6379> exit $ redis-cli --eval /Users/jihite/activeuser.lua user , 4 (integer) 6 4. 脚本的安全性如生成随机数这一命令,如果在master上执行完后,再在slave上执行会不一样,这就破坏了主从节点的一致性 为了解决这个问题, Redis 对 Lua 环境所能执行的脚本做了一个严格的限制 —— 所有脚本都必须是无副作用的纯函数(pure function)。所有刚才说的那种情况压根不存在。Redis 对 Lua 环境做了一些列相应的措施:
5. Redis Lua脚本与事务从定义上来说, Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。 使用事务时可能会遇上以下两种错误:
对于发生在 EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回 不过,从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。 在 Redis 2.6.5 以前, Redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。 而新的处理方式则使得在流水线(pipeline)中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯。 至于那些在 EXEC 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。
6. spring-data-redis操作lua上面讲的是如何在redis控制台调用lua脚本,现在我们来讲下怎么在java里面调用 <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.8.1.RELEASE</version> </dependency>
然后我们使用StringRedisTemplate这个类来操作
@Resource private StringRedisTemplate stringRedisTemplate; public <T> T runLua(String fileClasspath, Class<T> returnType, List<String> keys, Object ... values){ DefaultRedisScript<T> redisScript =new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(fileClasspath))); redisScript.setResultType(returnType); return stringRedisTemplate.execute(redisScript,keys,values); }
这个框架把lua脚本封装成RedisScript对象,并且可以将lua脚本执行的结果自动转换为配置的java类型,然后只要直接调用execute方法即可
并且这个execute逻辑中封装了evalsha的优化,源码如下 protected <T> T eval(RedisConnection connection, RedisScript<T> script, ReturnType returnType, int numKeys, byte[][] keysAndArgs, RedisSerializer<T> resultSerializer) { Object result; try { result = connection.evalSha(script.getSha1(), returnType, numKeys, keysAndArgs); } catch (Exception e) { if (!exceptionContainsNoScriptError(e)) { throw e instanceof RuntimeException ? (RuntimeException) e : new RedisSystemException(e.getMessage(), e); } result = connection.eval(scriptBytes(script), returnType, numKeys, keysAndArgs); } if (script.getResultType() == null) { return null; } return deserializeResult(resultSerializer, result); }
因为sha1的算法是通用的,所以在java客户端可以提前算出SHA1校验和,然后用evalsha来执行脚本,如果SHA1对应的脚本,那么还是用eval来执行,eval执行一次后,下次都可以直接调用evalsha了,减少网络开销
7. lua Debug我们写完一个lua脚本,lua和redis的数据类型是不一致的,存在一个转换,并且如果遇到复杂逻辑的lua脚本,如果不能debug,只在自己脑子里面走这个逻辑,是不科学的,如果redis lua也提供了debug功能,要在redis客户端执行 ./redis-cli --ldb --eval /tmp/script.lua mykey somekey , arg1 arg2 然后提供了一些调试命令 lua debugger> help Redis Lua debugger help: [h]elp Show this help. [s]tep Run current line and stop again. [n]ext Alias for step. [c]continue Run till next breakpoint. [l]list List source code around current line. [l]list [line] List source code around [line]. line = 0 means: current position. [l]list [line] [ctx] In this form [ctx] specifies how many lines to show before/after [line]. [w]hole List all source code. Alias for 'list 1 1000000'. [p]rint Show all the local variables. [p]rint <var> Show the value of the specified variable. Can also show global vars KEYS and ARGV. [b]reak Show all breakpoints. [b]reak <line> Add a breakpoint to the specified line. [b]reak -<line> Remove breakpoint from the specified line. [b]reak 0 Remove all breakpoints. [t]race Show a backtrace. [e]eval <code> Execute some Lua code (in a different callframe). [r]edis <cmd> Execute a Redis command. [m]axlen [len] Trim logged Redis replies and Lua var dumps to len. Specifying zero as <len> means unlimited. [a]abort Stop the execution of the script. In sync mode dataset changes will be retained. Debugger functions you can call from Lua scripts: redis.debug() Produce logs in the debugger console. redis.breakpoint() Stop execution as if there was a breakpoint in the next line of code.
用redis.debug() 可以打日志
用redis.breakpoint()在lua脚本里打断点 写个简单的lua脚本来测试下 local value1 = ARGV[1] local value2 = ARGV[2] redis.debug(value1) redis.debug(value2) if(value1>value2) then return "a" else return "b" end
更多细节看官方教程 8.项目实战在我们项目中使用redis生成全局id,代码如下 @Autowired private RedisTemplate<String,Long> redisTemplate; public String nextID(){ String key = Prefix+simpleDateFormatThreadLocal.get().format(new Date()); Long existedID = redisTemplate.opsForValue().get(key); if(existedID!=null){ redisTemplate.opsForValue().set(key,existedID+1); return key+String.format("%04d",existedID+1); }else{ redisTemplate.opsForValue().set(key,1L); return key+"0001"; } }
这段代码是存在问题的,在并发的情况下,get方法可以访问到相同的key,就会出现id重复的问题,测试代码如下
System.out.println("current:"+idGenerator.currentID()); Integer threadSize =5; final CountDownLatch countDownLatch = new CountDownLatch(threadSize); Runnable runnable = new Runnable() { @Override public void run() { for(int i =0 ;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+idGenerator.nextID()); } countDownLatch.countDown(); } }; for(int i =0;i<threadSize;i++){ new Thread(runnable,"Thread"+i).start(); } countDownLatch.await(); System.out.println("current:"+idGenerator.currentID());
当然这边我们也可以使用乐观锁或者分布式锁来实现,但是锁自旋的逻辑还是有潜在危险的 local key = KEYS[1] local id = redis.call('get',key) if(id == false) then redis.call('set',key,1) return key.."0001" else redis.call('set',key,id+1) return key..string.format('%04d',id + 1) end 对应调用java代码如下 public String nextIDLua(){ String key = Prefix+simpleDateFormatThreadLocal.get().format(new Date()); DefaultRedisScript<String> redisScript =new DefaultRedisScript<>(); redisScript.setLocation(new ClassPathResource("lua/genID.lua")); redisScript.setResultType(String.class); //System.out.println(redisScript.getSha1()); return redisTemplate.execute(redisScript,(RedisSerializer<?>) redisTemplate.getKeySerializer(),(RedisSerializer<String>)redisTemplate.getKeySerializer(),Lists.newArrayList(key)); }
|
请发表评论