redis缓存工具Jedis进行跨jvm加锁(分布式应用)--不幸暂弃用--可以做第三方锁使用

       最近使用redis碰到了多个并发处理同一个缓存的情况。在这种情况下需要进行加锁机制。本来想使用java自带的ReadWriteLock进行设置读写锁,这也是上家公司使用的方法。后来经过商讨,给予排除。原因无他,就是java自带的并不能跨jvm进行加锁,意思就是说A服务器上的write锁无法限制B服务器上的同一个方法,也就是说不适用于分布式部署的环境。

        后来经过多方面查看资料。最终决定使用redis自身的方法setnx来进行加锁机制。网上有很多关于setnx来进行加锁的方法。不过大部分都会有一个相同的缺陷,就是直接使用setnx加锁、使用del释放锁。这种情况下呢。如果加锁后发生异常导致没有释放锁,则会成为死锁。参考:http://blog.csdn.net/java2000_wl/article/details/8740911

        再后来有一种新的思路,将setnx和expire结合使用,使得锁有一个有效期,这样当发生异常,有效期一过可以自动释放锁。这个算是有一些改进,不过由于setnx和expire是两步操作,不具有原子性,如果setnx操作之后发生异常,还是会造成死锁。

        然后就考虑有什么办法可以使得setnx和expire操作同步实现并具有原子性。那么转换一下思路,将setnx的value值设置成当前时间过后的某一刻时间(比如1分钟之后),这个是不是就可以间接代替expire操作了。是的,这种方式可以实现。那么现在就是这种方式加上del基本可以实现加锁和解锁(并且可以解因异常为释放的锁)。只不过仍有一些缺陷,因为这种情况会造成竞争关系,参照:https://github.com/huangz1990/redis/commit/18dbaee4f40f435970a09da427b8f45bd26b4072#diff-b643df753e12d0d07a872f91487c957dR34

         根据上面链接给出的解决办法(即新算法思路)使用有效期来判断之后不是删除key,而是直接给key赋予新值,使用getset,然后判断得到的值和原来的值是否相等,相等即获得锁。最后提供了手动解锁的方法(即删除key就可以)。下面给出代码:


public boolean lock(String key, long timeout) {
		boolean lockSuccess = false;
		ShardedJedis shardedJedis = pool.getResource();
		try{
			long start = System.currentTimeMillis();
			String lockKey = "lock_"+key;
			do{
				long result = shardedJedis.setnx(lockKey, String.valueOf(System.currentTimeMillis()+LOCKKEY_EXPIRE_TIME*1000+1));
				if(result == 1){
					lockSuccess = true;
					break;
				}else{
					String lockTimeStr = shardedJedis.get(lockKey);
					if(StringUtils.isNumeric(lockTimeStr)){//如果key存在,锁存在
						long lockTime = Long.valueOf(lockTimeStr);
						if(lockTime < System.currentTimeMillis()){//锁已过期
							String originStr = shardedJedis.getSet(lockKey, String.valueOf(System.currentTimeMillis()+LOCKKEY_EXPIRE_TIME*1000+1));
							if(StringUtils.isNoneBlank(originStr)&&originStr.equals(lockTimeStr)){//表明锁由该线程获得
								lockSuccess = true;
								break;
							}
						}
					}
				}
				//如果不等待,则直接返回
				if(timeout == 0){
					break;
				}
				//等待300ms继续加锁
				Thread.sleep(300);
			}while((System.currentTimeMillis()-start) < timeout);

		}catch(Exception e){
			e.printStackTrace();
		}finally{
			...
		}
		return lockSuccess;
	}

	public void unLock(String key) {
		ShardedJedis shardedJedis = pool.getResource();
		try{
			String lockKey = "lock_"+key;
			shardedJedis.del(lockKey);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			...
		}
	}


       大概就这些吧~~~

共勉!

------补充:

     后来在实际应用当中,用代码测试了一下:使用5000并发线程进行加锁操作,会报很多的connect timeout异常,由于setnx也是在操作redis和redis进行连接,会占用Jedis连接池中的连接,并且没有获得锁的线程一直在等待。所以我们暂时弃用。而且redis是单线程的,读写效率都很高,除非一些特殊业务需要严格的数据同步,否则redis也不需要加锁。所以等以后碰到这种特殊业务再详细学习并谨慎使用吧。

     特意写出来,防止误导别人~~

-------再补充:

     其实redis本身不需要加锁,需要加锁的是进行redis操作之前的数值计算等,这就需要进行锁控制了。而对于这种是可以考虑使用redis加锁的,只需要给加锁的redis单独起一个实例即可,初期这样部署会有资源的浪费,中后期可以这样操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值