配置redis
gem 'uuid'
gem 'redis' # https://github.com/redis/redis-rb
redis_client = Redis.new({host:'192.168.0.84', port:6379, db:0})
实现一个简单的reids锁
# 加锁
redis_client.set('lock_name', 'abc',ex: 20,nx: true)
# 释放锁
redis_client.del('lock_name')
避免锁被其他人释放
如果超过20s线程a没有执行完,但是key已经过期,这时候key已经消失,此时线程b获取锁设置时间,这个时候线程a执行完毕,就会执行释放锁,把b刚加的锁给释放了。以需要增加判断是不是自己加的锁。
lock_uuid = UUID.new.generate
redis_client.set('lock_name', lock_uuid,ex: 20,nx: true)
# 释放锁,判断这个lock_uuid 是不是自己加的,如果是才释放
if redis_client.get('lock_name') == lock_uuid
redis_client.del('lock_name')
end
这个时候又有极端问题发生了,如果线程a判断是自己的锁过后,在删除前,这个锁过期了,这个时候线程b又获取到锁了,结果线程a又给线程b把锁释放了。这时候使用lua脚本保持操作的原子性,不会被其他客户端打断。
# 释放锁
lua_script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"
redis_client.eval(lua_script,:keys => ["lock_name"],:argv => [lock_uuid])
# 一个完整的事例
def redis_lock(key,uuid,ex = 20)
# 一起设置确保原子性
redis_client.set(key, uuid,ex: ex,nx: true)
end
def redis_lock_del(key,uuid)
# lua 脚本确保原子性
lua_script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"
redis_client.eval(lua_script,:keys => [key],:argv => [uuid])
end
def redis_lock_with_black(key,ex = 20,&block)
lock_uuid = UUID.new.generate
begin
if redis_lock(key,lock_uuid,ex)
yield block
end
rescue => e
puts e
ensure
redis_lock_del(key,lock_uuid)
end
end
超过预期的设置的时间,使用watchdog策略
这样看起来比较完美了,但是这样就结束了吗?如果在过期时间内代码没有执行完,锁就被其他线程获取了 在当前线程下,启动一个线程,用来做延期操作,只要这个锁没有被释放,就是需要延期的,java中设置为守护线程,主线程结束时,这个子线程也会结束。在ruby中可能需要使用信号或者自己启动线程来做监控。
在主从等reids服务上同步锁会有延迟,需要传输时间,无法确保一致性。
上面的操作都是在单个redis实例上操作的。如果redis是主从,就会从在延迟同步到问题。可以使用下面的工具。 RedLock 是一种分布式锁算法,旨在解决在分布式环境下实现强一致性分布式锁的问题。RedLock 使用多个 Redis 实例(各个实例无关联)进行加锁,并要求在大多数实例上成功获取锁才认为获取成功,以提高分布式锁的可靠性和容错性。
红锁 redlock
- Redlock-rb (Ruby implementation). There is also a fork of Redlock-rb that adds a gem for easy distribution.