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单独起一个实例就可以,初期这样部署会有资源的浪费。中后期能够这样操作。
相关文章
- 简单易用:使用Redis连接工具(redis连接工具)
- Redis工具类——高效管理数据的必备利器(redis的工具类)
- 连接Redis的简单工具:一步到位(连接redis 的工具)
- 使用Redis监控工具轻松解决日常管理问题(常用redis 监控工具)
- 使用Redis增强性能缓存工具进行优化(缓存工具redis)
- 工具拥抱Redis最佳可视化体验(最好的redis可视化)
- 固定IP地址链接Redis服务器(指定ip链接redis)
- Redis必要的强大数据库工具(为啥需要redis)
- 玩转Redis实现高效缓存(如何用redis做缓存)
- 使用C构建基于Redis的分布式系统(基于redis c)
- 激发后台运行效能使用Redis服务器工具(后台服务工具redis)
- 令人惊叹Redis集群的迁移利器(redis集群迁移工具)
- 重启后重新展翅Redis集群服务器重启之旅(redis集群服务器重启)
- 稳固安全Redis集群分布式锁(redis集群分布式锁6)
- 简单示范Redis配置文件指南(redis配置文件示例)
- 连接Redis,客户端工具尽在指尖(redis连接客户端工具)
- Redis集群 革命性的可视化管理工具(redis集群可视化工具)
- 改善工作效率Redis集群可视化工具(redis集群可视化工具)
- 简单明了Redis Auth设置方法(redis 设置auth)
- Redis实现菜单项快速缓存化(redis菜单缓存)