为什么要在Redis中使用Lua脚本
我们都知道Redis是一种高性能的key-value内存数据库,企业环境里面的很多应用场景都使用了它,因为在部分场景下它可以作为关系型数据库的补充,比如分布式缓存等。
在使用Redis的过程中我们接触了很多Redis的操作命令,它的官方网站上就提供了200多个命令,可以说”想怎么用就怎么用“,几乎涵盖了所有的kev-value操作。
不过,自从Redis2.6版本以后,在支持原有自身的操作命令的基础上,还增加了对Lua脚本命令的支持,原先的200多种命令还不够用吗?
在这么多的原生命令中,有不少原子性命令,比如INCR、DECR等,结合Redis单线程的”基因“,我们能够很容易的规避Read-Modify-Write带来的事务安全性问题。
但是呢,有些操作需要将若干个原子性命令合成一个的时候,Redis就无能为力了。
比如SET NX这个命令,我们常常用它来作为分布式锁,因为这个命令是在key不存在的情况下才能被SET成功,所以当多个请求去并发设置同一个key的时候,发现如果有进程已经SET成功了,就会导致SET失败,也就是获得锁失败,也就保证了只有一个请求进程能够获取到锁。
仅仅SET还不行,还必须有DEL,不然锁就会被一直拿着,当然你是可以为其设置超时时间的,但并不保险。
比如,有线程1、线程2和线程3这三个线程。如果线程1执行的时间较长没有来得及释放,锁就过期了,此时线程2是可以获取到锁的。当线程1执行完成之后,释放锁,实际上就把线程2的锁释放掉了。这个时候,线程3又是可以获取到锁的,而此时如果线程2执行完释放锁实际上就是释放的线程3设置的锁。
TIP:在DEL的时候可以通过对比VALUE值判断是否是同一个客户端来规避上述删除别人锁的现象。不过这里是说明客观上存在这样一个不安全的情况。
这种情况下,也就是说我们要把SET和DEL合并在一起使用,那就不是原子性的操作了。因为在SET和DEL之间会被其它线程插入的。
Redis意识到了上面的问题后,便推出了Lua脚本的功能。
这样,当我们遇到上述类似场景的时候,就可以把命令包装成一条Lua脚本命令,发送给Redis服务器。基于Redis服务器单线程来执行Lua脚本命令,就能够像执行原生的Redis命令那样,再也不会被其它请求打断了。
String luaScript = "local in = ARGV[1] local curr=redis.call('get', KEYS[1]) if in==curr then redis.call('del', KEYS[1]) end return 'OK'"";
----END----
这里记录,我每周碰到的,或想到的,引起触动,或感动的,事物的思考及笔记。不见得都对,但开始思考记录总是好的。