java dubbo 分布式锁_dubbo 常用的基于redis的分布式锁实现

小弟本着先会用在学习原理的原则 先用了dubbo 现在在实际业务中 因为分布式项目做了集群,需要用的分布式锁,就用到了基于redis的分布式锁,废话不多说,先来代码:

package com.tiancaibao.utils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

/**

* Redis distributed lock implementation.

*

* @author qingzhipeng

*/

public class RedisLock {

private static Logger logger = LoggerFactory.getLogger(RedisLock.class);

private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;

/**

* Lock key path.

*/

private String lockKey;

/**

* 锁超时时间,防止线程在入锁以后,无限的执行等待

*/

private int expireMsecs = 60 * 1000;

/**

* 锁等待时间,防止线程饥饿

*/

private int timeoutMsecs = 10 * 1000;

private volatile boolean locked = false;

/**

* Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.

*

* @param lockKey lock key (ex. account:1, ...)

*/

public RedisLock(String lockKey) {

this.lockKey = lockKey + "_lock";

}

/**

* Detailed constructor with default lock expiration of 60000 msecs.

*

*/

public RedisLock(String lockKey, int timeoutMsecs) {

this(lockKey);

this.timeoutMsecs = timeoutMsecs;

}

/**

* Detailed constructor.

*

*/

public RedisLock(String lockKey, int timeoutMsecs, int expireMsecs) {

this(lockKey, timeoutMsecs);

this.expireMsecs = expireMsecs;

}

/**

* @return lock key

*/

public String getLockKey() {

return lockKey;

}

private String get(final String key) {

Object obj = null;

try {

obj=JedisClusterUtil.clusterGetKey(key);

} catch (Exception e) {

logger.error("get redis error, key : {}", key);

}

return obj != null ? obj.toString() : null;

}

private boolean setNX(final String key, final String value) {

Long result = 0l;

try {

result=JedisClusterUtil.clusterSetNxKey(key, value);

} catch (Exception e) {

logger.error("setNX redis error, key : {}", key);

}

return result== 1l ? true : false;

}

private String getSet(final String key, final String value) {

String result = null;

try {

result=JedisClusterUtil.clusterGetSetKey(key, value);

} catch (Exception e) {

logger.error("setNX redis error, key : {}", key);

}

return result;

}

/**

* 获得 lock.

* 实现思路: 主要是使用了redis 的setnx命令,缓存了锁.

* reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间)

* 执行过程:

* 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁

* 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值

*

* @return true if lock is acquired, false acquire timeouted

* @throws InterruptedException in case of thread interruption

*/

public synchronized boolean lock() throws InterruptedException {

int timeout = timeoutMsecs;

while (timeout >= 0) {

long expires = System.currentTimeMillis() + expireMsecs + 1;

String expiresStr = String.valueOf(expires); //锁到期时间

if (this.setNX(lockKey, expiresStr)) {

// lock acquired

locked = true;

return true;

}

String currentValueStr = this.get(lockKey); //redis里的时间

if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {

//判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的

// lock is expired

String oldValueStr = this.getSet(lockKey, expiresStr);

//获取上一个锁到期时间,并设置现在的锁到期时间,

//只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的

if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {

//防止误删(覆盖,因为key是相同的)了他人的锁——这里达不到效果,这里值会被覆盖,但是因为什么相差了很少的时间,所以可以接受

//[分布式的情况下]:如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁

// lock acquired

locked = true;

return true;

}

}

timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;

/*

延迟100 毫秒, 这里使用随机时间可能会好一点,可以防止饥饿进程的出现,即,当同时到达多个进程,

只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足.

使用随机的等待时间可以一定程度上保证公平性

*/

Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);

}

return false;

}

/**

* Acqurired lock release.

*/

public synchronized void unlock() {

if (locked) {

JedisClusterUtil.clusterDelKey(lockKey);

locked = false;

}

}

}

小弟请教了群里的大牛,发现这种做法很常见。但一些细节性的问题还是要好好琢磨一下,他是怎么实现分布式锁的呢?

简单说 就是讲锁的类型 与 超时时间组合成key-value模式 存放setnx  到redis中。

当多台服务器 的同一个接口产生并发时,业务正常的情况下:

c0设置了锁

c1

if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis())

线程无法通过这个判断(c0 的锁没过期是大于当前时间的,因为c0的锁是当前时间加上失效时间的+1的和)。再来个c2也无法进入。所以正常业务  就是ok的。

当c0线程因为服务宕机或者业务流程过长导致超时呢? 没有释放锁的时候呢。

上面的if判断就不能阻挡了,但

if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {

可以阻挡,当c1和c2并发进入时:

C1使用getSet方法 获取到c0的失效时间value

C2也执行了getSet方法(失败的,获取到的oldvalue就是空串),这就保证了c1的oldValueStr 与currentValueStr是相等 且不为空,继而c1获取到锁。拥有执行权限,而c2  oldValueStr 与currentValueStr不等(获取到的是c1的失效时间)。只能继续循环获取或者退出

注意:这里可能导致超时时间不是其原本的超时时间,C1的超时时间可能被C2覆盖了,但是他们相差的毫秒及其小,这里忽略了。

但是:释放锁 还需要一些注意的地方。那就是判断一下是否超时

//为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,

//操作完的时候锁因为超时已经被别人获得,这时就不必解锁了。

RedisLock redisLock = new RedisLock("userInitialFix");

try {

if (redisLock.lock()) {// 获取锁,如果成功进行查询数据库匹配债权

selectMaxMoneyByAnyThing = debtOriginalAssetBillsService.selectMaxMoneyByAnyThing(days[i],

"OLD_PRODUCT", amount);

DebtOriginalAssetBillsWithBLOBs new_DebtOriginalAsset = new DebtOriginalAssetBillsWithBLOBs();

if (selectMaxMoneyByAnyThing != null) {

new_DebtOriginalAsset.setId(selectMaxMoneyByAnyThing.getId());

new_DebtOriginalAsset.setRemainAmount(selectMaxMoneyByAnyThing.getRemainAmount() - amount);

new_DebtOriginalAsset.setArrivalAmount(selectMaxMoneyByAnyThing.getArrivalAmount() + amount);

debtOriginalAssetBillsService.updateSelectiveById(new_DebtOriginalAsset);// 更新债权表

break;

}

} else {

// 等一秒继续进行匹配防止无谓循环

Thread.sleep(1000);

// 继续去调用

return matchDebtOriginalAsset(day, amount);

}

} catch (Exception e) {

System.out.println("用户初始化定期金额出错!!");

e.printStackTrace();

} finally {

redisLock.unlock();// 释放锁

}

上述代码 就差了一个超时的处理。

参考https://www.cnblogs.com/0201zcr/p/5942748.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值