分布式锁的特性:
分布式锁的实现方式有:数据库锁、redis式的分布式锁、zookeeper分布式锁。
数据库锁:
其实现方式一:
建一张锁表,其表字段大概有id与方法名称(必须对方法名称做唯一性约束)。通过对锁表中的数据进行操作来实现分布式锁。
如果要操作该资源,就在锁表中新增一条记录;操作完成后就将该记录删除掉。
这方法有些弊端:
当然,这些弊端也是有解决办法的:
其实现方式二:
也可以用数据库的排它锁来实现分布式锁,用for update来实现:在查询记录后面用“for update”来加排他锁,加锁后其他线程不可用,用connection.commit()来释放。
这种用排他锁的方式可以解决阻塞和释放锁(时间失效性)的问题,但单点和非重入却没办法解决。
redis分布式锁:
用缓存来实现分布式锁,可以解决数据库单点的问题,因为缓存大多数都是集群的。只不过为了保证分布式锁可用,得同时满足下面四个条件:
redis的分布式锁,只需要一行代码就可以进行加锁:
jedis.set(String key, String value, String nxxx, String expx, int time)
其中,key是用来表示锁的,因为key具有唯一性;
value一般用requestId,用uuid().toString()即可。这一般用于解锁时,对同一客户端做校验用;
nxxx用“NX”,即set if not exist(如不存在则set,存在则不作任何操作);
expx用“PX”,表示该锁的过期时间,由time来确定;
这条代码可以看出,redis分布式锁可以满足上面的条件。且比数据库分布式锁简单得多,且性能也比较好。
切记:jedis.setnx()
和jedis.expire()这种方式虽然能加锁,但不具有原子性。因为不能做到同一客户端进行加、解锁,因此使用时要慎重。
zookeeper分布式锁:
用zookeeper的临时有序节点可以实现分布式锁,其实现思路为每个客户端对某个方法加锁,在zookeeper上与该方法对应的节点目录上生成一个临时有序节点即可。
判断一个方法是否加锁,只需要判断这个有序节点的序号是否是最小的一个。释放锁只需要将临时节点删除即可。
这实现方式可以解决死锁、非阻塞、非重入的问题,因为zookeeper是集群部署的,同时也解决了单点问题。
(避免死锁:客户端在zookeeper上创建临时节点,在执行业务期间客户端挂了后,该临时节点回自动删除,不会影响其他客户获得锁;
阻塞式锁:客户端在zookeeper上创建临时节点时,可以给该节点绑定监听。该节点有任何变化zookeeper都会通知客户端,客户端可以判断该节点的序号是否是最小的,若是则可以执行业务逻辑处理;
重入式锁:客户端在zookeeper创建节点时,可以把客户端相关信息写入节点中,再次想获取锁时,只需要验证是否是最小序号即可
)
当然,zookeeper的分布式锁性能不是最好的,因为zookeeper加锁和解锁,都是动态创建节点和删除节点的,都是leader这个角色完成的,然后同步到follower。所以性能不是最好的