赞
踩
java有synchronize和Lock,mysql 修改类的sql也带有锁。锁定数据状态,让数据状态在并发场景,按我们预想逻辑进行状态转移,然而在分布式,集群的情况下,怎么去锁定数据状态呢
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
setnx + expire
Long result = jedis.setnx(lockKey, requestId);
if (result == 1) {
// 这里程序突然崩溃,则无法设置过期时间,将发生死锁
jedis.expire(lockKey, expireTime);
}
// setnx + expire 放在lua脚本执行
String script = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
" redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if(result.equals(1)){
.... //加锁成功的操作
}
set {key} {value} nx ex {second}
(正确方式)String lockKey = "锁的KEY值";//固定的
String requestId = "当次加锁操作的唯一标识";
int expireTime = 1000;//失效时间
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
//-------- 错误方式 ------------
// 判断加锁与解锁是不是同一个客户端
if (requestId.equals(jedis.get(lockKey))) {
// 若在此时,这把锁突然不是这个客户端的,则会误解锁
jedis.del(lockKey);
}
//-------- 正确的方式 使用 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));
1: 获取当前时间,以毫秒为单位
2: 按顺序向5个master节点请求加锁。客户端设置网络连接和响应超时时间,并且超时时间要小于锁的失效时间。(假设锁自动失效时间为10秒,则超时时间一般在5-50毫秒之间,我们就假设超时时间是50ms吧)。如果超时,跳过该master节点,尽快去尝试下一个master节点。
3: 客户端使用当前时间减去开始获取锁时间(即步骤1记录的时间),得到获取锁使用的时间。当且仅当超过一半(N/2+1,这里是5/2+1=3个节点)的Redis master节点都获得锁,并且使用的时间小于锁失效时间时,锁才算获取成功。(如上图,10s> 30ms+40ms+50ms+4m0s+50ms)
4: 如果取到了锁,key的真正有效时间就变啦,需要减去获取锁所使用的时间。
5: 如果获取锁失败(没有在至少N/2+1个master实例取到锁,有或者获取锁时间已经超过了有效时间),客户端要在所有的master节点上解锁(即便有些master节点根本就没有加锁成功,也需要解锁,以防止有些漏网之鱼)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.4.3</version>
</dependency>
Config config = new Config().useSingleServer().setAddress("127.0.0.1:6380").setDatabase(0); RedissonClient rLock1 = Redisson.create(config); config = new Config().useSingleServer().setAddress("127.0.0.1:6381").setDatabase(0); RedissonClient rLock2 = Redisson.create(config); config = new Config().useSingleServer().setAddress("127.0.0.1:6382").setDatabase(0); RedissonClient rLock3 = Redisson.create(config); //初始化 String lockKey = "XXX"; RLock rLock1 = redissonRed1.getLock(lockKey); RLock rLock2 = redissonRed2.getLock(lockKey); RLock rLock3 = redissonRed2.getLock(lockKey); RedissonRedLock rLock = new RedissonRedLock(rLock1,rLock2,rLock3); //加锁 rLock.lock(); //释放 rLock.unlock();
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.4.1</version>
</dependency>
创建临时有序节点,每个线程均能创建节点成功,但是其序号不同,只有序号最小的可以拥有锁,其它线程只需要监听比自己序号小的节点状态即可
1: 在指定的节点下创建一个锁目录lock
2: 线程X进来获取锁在lock目录下,并创建临时有序节点
3: 线程X获取lock目录下所有子节点,并获取比自己小的兄弟节点,如果不存在比自己小的节点,说明当前线程序号最小,顺利获取锁
4: 此时线程Y进来创建临时节点并获取兄弟节点,判断自己是否为最小序号节点,发现不是,于是设置监听(watch)比自己小的节点(这里是为了发生上面说的羊群效应)
5: 线程X执行完逻辑,删除自己的节点,线程Y监听到节点有变化,进一步判断自己是已经是最小节点,顺利获取锁
//初始化
CuratorFramework curatorFramework= CuratorFrameworkFactory.newClient("zookeeper1.tq.master.cn:2181",new ExponentialBackoffRetry(1000,3));
curatorFramework.start();
//创建临时节点锁
String lockPath = "/distributed/lock/";//根节点
//可重入排它锁
String lockName = "xxxx";
InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework, lockPath + lockName);
//加锁
interProcessMutex.acquire(2, TimeUnit.SECONDS)
//释放锁
if(interProcessMutex.isAcquiredInThisProcess()){
interProcessMutex.release();
curatorFramework.delete().inBackground().forPath(lockPath + lockName);
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。