Redis之Lua脚本—
Redission不仅提供了一套丰富的Redis客户端功能,还增加了很多高级功能,其中就包括
分布式锁、发布和订阅、支持Lua脚本。Redission在底层利用Redis的SETNX命令实现分布式锁,并且处理了锁的续期问题,使用起来非常方便。
然而,为什么在实际项目中,我们还需要使用Lua脚本呢?
这主要是因为Redisson的分布式锁机制在某些特定场景下可能无法满足需求。
1,
复杂的原子操作:Redisson的分布式锁主要用于实现简单的原子操作,例如“获取锁,执行操作,释放锁”。但是,如果你需要执行更复杂的原子操作,例如“获取锁,执行操作1,执行操作2,释放锁”,这时就需要使用Lua脚本。
.
2,自定义的锁机制:在某些场景下,你可能需要自定义锁的机制,例如锁的公平性、锁的>时时间等。虽然Redisson提供了一些配置选项,但是如果你需要更细粒度的控制,那么使用Lua脚本可能是更好的选择。
.
3,跨多个Redis实例的操作:如果你需要跨多个Redis实例进行操作,例如在一个Redis实例上获取锁,在另一个Redis实例上执行操作,那么使用Lua脚本可能更方便,能够避免跨多个Redis实例操作时可能出现的竞态条件或数据不一致问题。
.
4,避免网络开销:使用Lua脚本可以将多个操作合并为一个操作,从而减少网络开销。这在某些对性能要求较高的场景下非常有用。
Lua脚本介绍
Redis
从2.6版本开始,通过内嵌一个Lua解释器,支持在服务器端执行Lua脚本。这个特性为Redis提供了非常大的灵活性。通过Lua脚本,我们可以实现复杂的逻辑,包括判断、循环等这在纯Redis命令中是很难实现的
.Lua是一种轻量级的解释型脚本语言,直接在运行时执行Lua脚本,而无需预先编译。 Lua脚本的主要优点包括:
简单:Lua的语法简洁直观,易于学习。
高效:尽管Lua是解释型语言,但它的执行速度非常快,因为编译成字节码。
安全:Lua提供了沙盒环境,允许在隔离的环境中运行代码,从而提高安全性。
灵活:Lua可以很容易地与其他语言(如C、C++、Java等)集成,使得你可以在其他应用程序中嵌入Lua脚本,扩展应用程序的功能。
Lua脚本为啥是高性能的?
轻量级:Lua是一种轻量级脚本语言,解释器体积小、启动速度快,消耗的内存资源较少,使得Lua脚本在运行时具有较低的开销
.
动态类型:Lua是一种动态类型语言,它允许变量在运行时改变类型。这种灵活性避免了静态类型语言在类型转换和类型检查方面的开销,使得Lua脚本在执行时更加高效。
.
即时编译:Lua使用了一种混合的编译和解释策略,它将脚本代码编译成字节码,然后在运行时解释执行。这种即时编译的方式可以在一定程度上提高执行效率,因为编译后的字节码更加优化和紧凑。
.
垃圾回收:Lua内置了一个高效的垃圾回收机制,可以自动管理内存。这减少了开发人员手动管理内存的负担,避免了内存泄漏和内存碎片问题,使得Lua脚本在长时间运行时仍能保持稳定的性能。
.
协程支持:Lua内置了对协程(coroutine)的支持,这是一种轻量级的线程,可以在单个线程中并发执行多个任务。协程的使用可以减少线程切换和同步的开销,提高脚本的并发性能。
接下来,自定义自旋锁、通过Lua脚本实现分布式锁操作
public class RedisLockHelper {
private Logger logger = LoggerFactory.getLogger(RedisLockHelper.class);
/**
* 加锁超时时间:500毫秒
*/
private static final long TIME_OUT = 500L;
//定义获取锁的lua脚本
private final static DefaultRedisScript<Long> LOCK_LUA_SCRIPT = new DefaultRedisScript<>(
"if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) else return 0 end"
, Long.class
);
//定义释放锁的lua脚本
private final static DefaultRedisScript<Long> RELEASE_LOCK_LUA_SCRIPT = new DefaultRedisScript<>(
"if redis.call('GET',KEYS[1]) == ARGV[1] then return redis.call('DEL',KEYS[1]) else return -1 end"
, Long.class
);
@Lazy
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 自旋锁
* @param lockKey
* @param lockValues
* @param expire
* @return
*/
private Boolean spinLock(String lockKey, String lockValues, int expire){
long startTime = System.currentTimeMillis();
while (true){
boolean lockRes = tryLock(lockKey, lockValues, expire);
if(lockRes){
return Boolean.TRUE;
}
//加锁消耗时长
long consumeTime = System.currentTimeMillis() - startTime;
if(consumeTime >= TIME_OUT){
logger.warn("获取锁超时:锁key值:{}", lockKey);
return Boolean.FALSE;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public boolean tryLock(String lockKey, String value, int expire) {
// 参数一:redisScript,参数二:key列表
Long result = redisTemplate.execute(LOCK_LUA_SCRIPT, Collections.singletonList(lockKey), value, String.valueOf(expire));
if(result != null && result == 1L){
if(logger.isDebugEnabled()){
logger.info("[加锁成功]result:{}, key:[{}], value:[{}] ", result, lockKey, value);
}
return true;
}else {
logger.warn("[加锁失败]result:{}, key:[{}], value:[{}] ", result, lockKey, value);
return false;
}
}
public void releaseLock(String lockKey, String value) {
Long result = redisTemplate.execute(RELEASE_LOCK_LUA_SCRIPT, Collections.singletonList(lockKey), value);
if(result != null && result == 1L){
if(logger.isDebugEnabled()){
logger.info("[锁释放环节]:锁释放成功!result:{}, key:[{}], value:[{}] ", result, lockKey, value);
}
}else {
logger.warn("[锁释放环节]:查询不到锁!result:{}, key:[{}], value:[{}] ", result, lockKey, value);
}
}
public static String getLockValues() {
return UUID.randomUUID().toString().replace("-", "");
}
}