先讲一下为什么使用分布式锁:
在传统的单体应用中,我们可以使用Java并发处理相关的API(如ReentrantLock或synchronized)来实现对共享资源的互斥控制,确保在高并发情况下同一时间只有一个线程能够执行特定方法。然而,随着业务的发展,单体应用逐渐演化为分布式系统,多线程、多进程分布在不同机器上,这导致了原有的单机部署下的并发控制策略失效。为了解决这一问题,我们需要引入一种跨JVM的互斥机制来管理共享资源的访问,这就是分布式锁所要解决的核心问题。
Lua介绍
Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
为什么要用Lua呢
Redis采用单线程架构,可以保证单个命令的原子性,但是无法保证一组命令在高并发场景下的原子性。
在以下场景中:
- 当 事务1执行删除操作时,查询到的锁值确实相等。
- 在 事务1执行删除操作之前,锁的过期时间刚好到达,导致 Redis 自动释放了该锁。
- 事务2获取了这个已被释放的锁。
- 当 事务1执行删除操作时,会意外地删除掉 事务2持有的锁。
上面的删除情况也无法保证原子性,只能通过lua脚本实现
如果redis客户端通过lua脚本把3个命令一次性发送给redis服务器,那么这三个指令就不会被其他客户端指令打断。Redis 也保证脚本会以原子性(atomic)的方式执行: 当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。
Lua脚本命令
在Redis中需要通过eval命令执行lua脚本
highlighter-
EVAL script numkeys key [key ...] arg [arg ...] script:lua脚本字符串,这段Lua脚本不需要(也不应该)定义函数。 numkeys:lua脚本中KEYS数组的大小 key [key ...]:KEYS数组中的元素 arg [arg ...]:ARGV数组中的元素
案列1:动态传参
lua
EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 5 8 10 30 40 50 60 70 # 输出:8 10 60 70 EVAL "if KEYS[1] > ARGV[1] then return 1 else return 0 end" 1 10 20 # 输出:0 EVAL "if KEYS[1] > ARGV[1] then return 1 else return 0 end" 1 20 10 # 输出:1
案列2:执行redis类库方法
lua
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 bbb 20