分布式锁简介
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。其存在就可以让不同系统或者同一系统的不同服务器访问同一个或一组资源的时候,加锁来达到互斥的目的,使得数据可以达到一致性,不至于出现脏数据。一般的分布式锁可以通过数据库的乐观锁,redis的分布式锁,Zookeeper的分布式锁来实现。然而Java本身就有自己的锁,为什么还要用分布式锁呢?先来了解一下Java中的锁
Java中的锁
Java中主要提供了用关键字修饰的synchronized锁以及Lock接口来实现锁的功能。需要明确二者具体的差异:
- Lock是接口,而synchronized是Java中的关键字
- synchronized的锁是由JVM控制的,所以不会发生死锁,而Lock可能产生死锁
- Lock可以让等待响应的线程中断,而synchronized不能。
- 通过Lock可以知道线程有没有获得锁,而synchronized不能。
- Lock并不是完全排他的,在没获得锁对象的时候还是可以进行读操作的。
Java锁遇到的问题
我们知道现在很多企业的项目都是在分布式环境下进行的,因而会遇到有很多服务器的情况,具体如图:
这个时候各个服务器的Java锁只能锁住他自己分配到的线程,而对其他服务器的线程是无能为力的,这时候用Java锁就已经有点鞭长莫及了,就无法实现数据的一致性了,就要考虑要在所有服务器间来一把统一的锁来锁住变量,可以完成不同服务器的线程之间的通信,这就是我们要引入的分布式锁。
分布式锁
分布式锁应该具有以下的特点:
- 互斥性与可重入性:一个持有锁其他人无法占有(但是可以进行读操作),用完之后其他人可以再次持有该锁
- 锁超时高效与高可用:效率一定要高,否则面对高并发的环境很容易影响客户体验
- 支持阻塞和非阻塞
我们要实现的效果图应该如此:
用redis分布式锁之前先要熟悉几个常用的redis命令,再之后会有大用处:
- setnx key value:set if no exists。当key已经存在的时候,不操作;当key存在的时候赋值。
- expire key seconds:设置key的过期时间。如果key已过期,将会自动删除。
- del key:删除key;
基本锁原理
原理:利用setnx设置值,当不存在该key的时候则表示加锁成功,否则表示已经有线程获得该锁了,阻塞该线程。
缺点:如果一旦获得锁且未释放的线程挂了就会造成死锁。
改进锁
原理:为了防止上述基本锁发生死锁的情况产生,可以在setnx之后再用expire设置过期时间,当超时的时候自动解锁。
缺点:因为setnx和expire在一起并不是原子操作,理论上还是存在在执行完setnx之后线程挂掉,而expire还没有执行的情况,此时就会再次产生死锁的情况。
再改进
原理:为了解决上述情况发生,就可以用lua脚本使redis的两个操作成为一个院子操作,这样就可以实现不发生死锁的分布式锁了。