Java分布式锁三种典型解决方案

分布式锁解决方案

Demo1
Demo2
说明:暂未实际使用做个大概记录,后面会陆续补充详细的内容和遇到的问题

一、基于数据库实现分布式锁(建一个表存方法锁,方法名做唯一性约束)

缺点:
  • 这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
  • 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
  • 这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
  • 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
解决方案:
  1. 数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
  2. 没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
  3. 非阻塞的?搞一个while循环,直到insert成功再返回成功。
  4. 非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。

二、基于缓存(Redis等)实现分布式锁

获取锁使用指令:SET resource_name my_random_value NX PX 30000
方案如下:

try{
lock = redisTemplate.opsForValue().setIfAbsent(lockKey, LOCK);
logger.info("cancelCouponCode是否获取到锁:"+lock);
if (lock) {
	// TODO
	redisTemplate.expire(lockKey,1, TimeUnit.MINUTES); //成功设置过期时间
	return res;
}else {
	logger.info("cancelCouponCode没有获取到锁,不执行任务!");
}finally{
	if(lock){	
		redisTemplate.delete(lockKey);
		logger.info("cancelCouponCode任务结束,释放锁!");		
	}else{
		logger.info("cancelCouponCode没有获取到锁,无需释放锁!");
	}
}
缺点:
  • 在这种场景(主从结构)中存在明显的竞态:客户端A从master获取到锁,在master将锁同步到slave之前,master宕掉了。 slave节点被晋级为master节点,客户端B取得了同一个资源被客户端A已经获取到的另外一个锁。安全失效!

三、基于Zookeeper实现分布式锁

节点类型:
  • 持久节点 (PERSISTENT):默认的节点类型。创建节点的客户端与zookeeper断开连接后,该节点依旧存在
  • 持久节点顺序节点(PERSISTENT_SEQUENTIAL):在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号
  • 临时节点(EPHEMERAL):当创建节点的客户端与zookeeper断开连接后,临时节点会被删除
  • 临时顺序节点(EPHEMERAL_SEQUENTIAL):在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与zookeeper断开连接后,临时节点会被删除
过程:
  1. 首先,在Zookeeper当中创建一个持久节点ParentLock。当第一个客户端想要获得锁时,需要在ParentLock这个节点下面创建一个临时顺序节点 Lock1

  2. 之后,Client1查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock1是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁

  3. 这时候,如果再有一个客户端 Client2 前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock2;于是,Client2向排序仅比它靠前的节点Lock1注册Watcher,用于监听Lock1节点是否存在。这意味着Client2抢锁失败,进入了等待状态

释放锁:
  • 当任务完成时,Client1会显示调用删除节点Lock1的指令
  • 获得锁的Client1在任务执行过程中,如果Duang的一声崩溃,则会断开与Zookeeper服务端的链接。根据临时节点的特性,相关联的节点Lock1会随之自动删除
缺点:
  • 性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上
  • 其实,使用Zookeeper也有可能带来并发问题,只是并不常见而已。考虑这样的情况,由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。就可能产生并发问题。这个问题不常见是因为zk有重试机制,一旦zk集群检测不到客户端的心跳,就会重试,Curator客户端支持多种重试策略。多次重试之后还不行的话才会删除临时节点

四、整体比较:

  • zk:有封装好的框架容易实现,有等待锁的队列大大提升抢锁的效率;但添加和删除节点效率低
  • redis:set和del性能较高;但实现复杂,要考虑超时、原子性、误删等操作,而且没有等待队列,客户端自旋等锁效率低
三种方案的比较
  • 从理解的难易程度角度(从低到高)
    数据库 > 缓存 > Zookeeper
  • 从实现的复杂性角度(从低到高)
    Zookeeper >= 缓存 > 数据库
  • 从性能角度(从高到低)
    缓存 > Zookeeper >= 数据库
  • 从可靠性角度(从高到低)
    Zookeeper > 缓存 > 数据库

可参考:链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值