A Distributed Mutex based on Redis

Set key to hold string value if key does not exist. In that case, it is equal to SET. When keyalready holds a value, no operation is performed. SETNX is short for "SET if N ot e X ists".

Return value

Integer reply, specifically:

  • 1 if the key was set
  • 0 if the key was not set

Examples

redis>  SETNX mykey "Hello"
(integer) 1
redis>  SETNX mykey "World"
(integer) 0
redis>  GET mykey
"Hello"
redis> 

Design pattern: Locking with SETNX

NOTE: Starting with Redis 2.6.12 it is possible to create a much simpler locking primitive using the SET command to acquire the lock, and a simple Lua script to release the lock. The pattern is documented in the SET command page.

The old SETNX based pattern is documented below for historical reasons.

SETNX can be used as a locking primitive. For example, to acquire the lock of the key foo, the client could try the following:

SETNX lock.foo <current Unix time + lock timeout + 1>

# 通过SETNX的返回值来确定获取锁(设置值)是否成功

If SETNX returns 1 the client acquired the lock, setting the lock.foo key to the Unix time at which the lock should no longer be considered valid. The client will later use DEL lock.foo in order to release the lock.

If SETNX returns 0 the key is already locked by some other client. We can either return to the caller if it's a non blocking lock, or enter a loop retrying to hold the lock until we succeed or some kind of timeout expires.

Handling deadlocks

# 死锁:
# 发生死锁时,如果一个意欲重置死锁的客户端抢先了另一个也准备重置的客户端?
# 就会出现锁被重置两次的情况!

In the above locking algorithm there is a problem: what happens if a client fails, crashes, or is otherwise not able to release the lock? It's possible to detect this condition because the lock key contains a UNIX timestamp. If such a timestamp is equal to the current Unix time the lock is no longer valid.

When this happens we can't just call DEL against the key to remove the lock and then try to issue a SETNX, as there is a race condition here, when multiple clients detected an expired lock and are trying to release it.

  • C1 and C2 read lock.foo to check the timestamp, because they both received 0 after executing SETNX, as the lock is still held by C3 that crashed after holding the lock.
  • C1 sends DEL lock.foo
  • C1 sends SETNX lock.foo and it succeeds
  • C2 sends DEL lock.foo
  • C2 sends SETNX lock.foo and it succeeds
  • ERROR: both C1 and C2 acquired the lock because of the race condition.

# 解决办法:使用GETSET来重置锁,而不是使用SETNX来重置,这样在SET前就能够先GET锁值以判断是不是已经被重置过了。

Fortunately, it's possible to avoid this issue using the following algorithm. Let's see how C4, our sane client, uses the good algorithm:

  • C4 sends SETNX lock.foo in order to acquire the lock

  • The crashed client C3 still holds it, so Redis will reply with 0 to C4.

  • C4 sends GET lock.foo to check if the lock expired. If it is not, it will sleep for some time and retry from the start.

  • Instead, if the lock is expired because the Unix time at lock.foo is older than the current Unix time, C4 tries to perform:

    GETSET lock.foo <current Unix timestamp + lock timeout + 1>
    
  • Because of the GETSET semantic, C4 can check if the old value stored at key is still an expired timestamp. If it is, the lock was acquired.

  • If another client, for instance C5, was faster than C4 and acquired the lock with the GETSET operation, the C4GETSET operation will return a non expired timestamp. C4 will simply restart from the first step. Note that even if C4 set the key a bit a few seconds in the future this is not a problem.

Important note: In order to make this locking algorithm more robust, a client holding a lock should always check the timeout didn't expire before unlocking the key with DEL because client failures can be complex, not just crashing but also blocking a lot of time against some operations and trying to issue DEL after a lot of time (when the LOCK is already held by another client).

# 注意,上述基于时间戳的超时判断要求NTP。故可以在客户端使用Redis.time命令来统一获取服务端时间作为当前时间。


# 还有一种方法是使用阻塞队列来充当同步锁:

Another way is using BLPOP command to make use of blocking queue of Redis.

See: http://www.davidverhasselt.com/2011/08/06/a-distributed-mutex-and-semaphore-using-redis/

The project is here: https://github.com/dv/redis-semaphore 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redisson是一个基于Redis的Java驻内存数据网格(In-Memory Data Grid)和分布式锁(Distributed Lock)框架。它提供了分布式集合、对象、队列、映射、锁等常用数据结构的封装,同时提供了一些分布式服务的实现,如分布式限流、分布式调度等。 在项目中使用Redisson,首先需要引入Redisson的依赖。例如Maven项目可以在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.15.5</version> </dependency> ``` 接着可以通过RedissonClient来获取Redisson的实例,例如: ``` Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); ``` 这里使用了单节点模式,连接本地的Redis服务。其他模式和配置可以参考Redisson的官方文档。 获取了Redisson的实例后,就可以使用Redisson提供的各种分布式数据结构和服务了。例如: ``` // 获取分布式Map RMap<String, String> map = redisson.getMap("myMap"); // 获取分布式List RList<String> list = redisson.getList("myList"); // 获取分布式锁 RLock lock = redisson.getLock("myLock"); // 获取分布式计数器 RAtomicLong counter = redisson.getAtomicLong("myCounter"); // 获取分布式限流器 RSemaphore semaphore = redisson.getSemaphore("mySemaphore"); ``` 以上代码只是一些简单的示例,更多的使用方法和细节可以参考Redisson的官方文档和示例代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值