Redis Lock分布式锁

  • 使用Redisson第三方库
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
</dependency>
//使用这个interface中的API
private RedissonClient client;
  • 自定义实现Redis做分布式锁
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Redis通过事件循环来处理网络I/O事件,能够同时处理多个客户端的连接请求,但其核心数据处理(读、写、删除)都是在一个单线程的主线程中处理,且每次只能执行一个命令,从而保证了数据的一致性。而分布式锁正是利用这一特性通过它的SETNX命令(键不存在则设置成功,表示获取锁)来实现简单的分布式锁功能。

获取锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent(key, value);
try{
...具体业务
} finally {
释放锁
redisTemplate.delete(key);
}

缺陷:当业务程序未执行结束,出现宕机等情况导致未执行释放锁的操作,则会出现锁死的情况。

为防止锁死可以在获取锁时设置锁的过期时间
Boolean lock = redisTemplate.opsForValue().setIfAbsent(key, value, expire);

缺陷:setIfAbsent(key, value, expire)该方法执行的命令SETNX和EXPIRE命令是两个独立的操作,即先执行SETNX再执行EXPIRE,所以还是可能存在SETNX执行结束后出现服务宕机而导致锁死的情况。

针对上述情况只实现SETNX和EXPIRE命令一起作为一个原子性的操作执行即可。

  • Lua实现SETNX和EXPIRE

1.使用Lua可以避免客户端与redis服务之间的多次通信减少网络开销,如当前SETNX和EXPIRE两个命令的执行就是两次网络连接请求。
2.Lua脚本与redis事务一起使用,要么全部成功要么全部失败,保证了Lua脚本中一系列命令的原子性操作

public <T> Boolean setIfAbsent(String key, T value, Duration duration) {
        DefaultRedisScript<Boolean> luaScript = new DefaultRedisScript<>();
        //获取lua脚本的资源文件
        ClassPathResource resource = new ClassPathResource("setnx_expire.lua");
        //setLocation和setScriptSource都可以,后者需要自己构建数据类型new ResourceScriptSource(resource)
        luaScript.setLocation(resource);
        luaScript.setResultType(Boolean.class);
        //过期时间设置为秒
        int expire = (int) duration.toSeconds();
        return redisTemplate.execute(luaScript, Lists.newArrayList(key), value, expire);
    }

Lua脚本:setnx_expire.lua

---获取第keys一个参数即键
local key = KEYS[1]
---获取args第一个参数的值即键对应的值
local value = ARGV[1]
---获取args第二个参数的值即键对应的过期时间,单位秒
local timeout = tonumber(ARGV[2])

---执行SETNX命令
local result = redis.call('SETNX', key, value )

if result == 1 then
    --- 键不存在设置成功,并设置过期时间 EXPIRE
    redis.call('EXPIRE', key, timeout )
    return true
else
    --- 键已存在设置失败
    return false
end

上述Lua脚本中涉及到的通过KEYS和ARGV数组获取变量这个是在Redis中通过EVAL和EVALSHA执行lua脚本时的传参接收方式
redis.call()方法是redis服务在运行lua脚本时提供的一种接口用于访问redis数据

进入redis服务命令行,EVAL或EVALSHA执行lua脚本命令
redis-cli
> EVAL "local key = KEYS[1]; print(key)"
语法:
redis.call(command, key1, key2,..., arg1, arg2, ...)
命令描述redis-clilua script
获取键值GET keylocal value = redis.call(‘GET’, ‘key’)
设置键值SET key valueredis.call(‘SET’, ‘key’, ‘value’)
键不存在则写如键值SET key value NXredis.call(‘SETNX’, key ,value)
删除键值DEL key [key…]redis.call(‘DEL’, ‘key’)
检查键值是否存在EXISTS keyredis.call(‘EXISTS’, ‘key’)
获取键类型TYPE keylocal type = redis.call(‘TYPE’, ‘key’)
键值加1INCR keyredis.call(‘INCR’,‘key’)
键值加指定增量INCRBY key incrementredis.call(‘INCRBY’,‘key’, number)
键值减1DECR keyredis.call(‘DECR’,‘key’)
键值减指定增量DECRBY key decrementredis.call(‘DECRBY’,‘key’, number)
获取一组键值MGET key [key…]local values = redis.call(‘MGET’, ‘key1’, ‘key2’)
哈希设置键值HSET hash hashkey valueredis.call(‘HSET’, ‘hash’, ‘hashkey’, ‘value’)
集合添加一个或多个元素SADD key member [member …]redis.call(‘SADD’, ‘set’, ‘member1’, ‘member2’)
从集合移除元素SREM key member [member …]redis.call(‘SREM’, ‘set’, ‘member1’, ‘member2’)
设置键过期(秒)EXPIRE key secondsredis.call(‘EXPIRE’, ‘key’, seconds)
设置键过期(毫秒)PEXPIRE key millisecondsredis.call(‘PEXPIRE’, ‘key’, milliseconds )
描述其它命令
哈希命令HGET / HGETALL / HDEL
列表命令LPUSH / RPUSH / LPOP / RPOP
集合命令SCARD / SISMEMBER / ZADD / ZREM / ZRANGE
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值