开课吧孤尽T31训练营学习笔记-DAY27-Redis分布式锁

77 阅读2分钟

Redis分布式锁

在同一个JVM内部,大家往往采用synchronized或者Lock的方式来解决多线程间的并发安全问题,但是在分布式架构下,在JVM之间就需要一种更加高级的锁机制,来处理这种跨JVM进程之间的安全问题,解决方案之一就是:使用分布式锁。今天跟着老师学习了使用redis实现分布式锁的方案,接下来主要介绍使用redis如何实现分布式锁。

一、redis分布式锁原理

1.1 加锁基本原理

redis分布式锁机制,主要借助setnx和expire两个命令完成。

当mylock不存在时,将key设置为1,存在不做任务操做,返回0

setnx mylock "1"

设置10秒过期时间

expire mylock 10

Redis分布式锁原理:

image.png

1.2 存在的问题

问题

由于加锁和设置过期时间在两个语句里,存在加锁问题:setnx成功,设置过期时间expire失败,如果没有手工释放,锁永远被占用,其他线程永远抢不到锁。

解决方案

方案1:使用set命令时同时设置过期时间

set key value [EX senconds][PX milliseconds][NX|XX]

示例: set mylock "1" EX 100 NX

方案2:使用lua脚本,将锁的命令放在lua脚本中,做原子性的执行

1.3 redis中的Lua

eval script numkeys key [key ...] arg [arg ...]

eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]} 2 key1 key2 arg1 arg2"

script是一段Lua5.1脚本程序,会运行在redis服务器context中。

numkeys: 指定键名参数的个数。

真正有实战意义的脚本:

eval "return redis.call('set', KEYS[1], ARGV[1])" 1 foo bar

相当于: set foo "bar"

二、解锁 还需系铃人

加锁是会使用一个uuid来加锁,作为自己锁的标志。解锁的时候,只能解自己的锁,不能把别人加的锁给解了。

2.1 错误的解锁案例

案例一:

jedis.del(lockKey)

案例二, 已经考虑了这个问题了,但是不能完全避免,操作不原子:

requestId.equals(jedis.get(lockKey)) {
    jedis.del(lockKey);
}

2.2 正确做法

将判定和解锁操作封装在lua脚本中,做原子性的操作:

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

三、锁过期问题

3.1 问题来源

锁过期自动释放。业务实际执行时间大于加锁时间时,就会让线程在无所状态下运行,造成数据紊乱。

3.2 解决方案

方案一:乐观锁

方案二:watch dog自动延期机制

下篇分享,将继续分享redisson完美解决分布式锁的方案。