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


           近期使用redis碰到了多个并发处理同一个缓存的情况。在这样的情况下须要进行加锁机制。

    本来想使用java自带的ReadWriteLock进行设置读写锁,这也是上家公司使用的方法。

    后来经过商讨,给予排除。原因无他,就是java自带的并不能跨jvm进行加锁,意思就是说Aserver上的write锁无法限制Bserver上的同一个方法,也就是说不适用于分布式部署的环境。

            后来经过多方面查看资料。终于决定使用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单独起一个实例就可以,初期这样部署会有资源的浪费。中后期能够这样操作。

  • 相关阅读:
    【crontab】误删crontab及其恢复
    New Concept English there (7)
    New Concept English there (6)
    New Concept English there (5)
    New Concept English there (4)
    New Concept English there (3)
    New Concept English there (2)Typing speed exercise
    New Concept English there (1)Typing speed exercise
    New Concept English Two 34 game over
    New Concept English Two 33 94
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/5224281.html
Copyright © 2020-2023  润新知