面试官问实现分布式锁有哪些?
面试经常被问到分布式锁,今天我就带大家深入剖析下分布式锁的各种方案。
什么是分布式锁
概述 为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。
怎样实现分布式锁
1.基于数据库实现分布式锁
2.基于缓存实现分布式锁
3.基于Zookeeper实现分布式锁
一、基于数据库实现分布式锁
首先新建一个数据库表,可以通过数据库的排他锁来实现分布式锁。基于MySql的InnoDB引擎,可以使用以下方法来实现加锁操作:
在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁(这里再多提一句,InnoDB引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁。这里我们希望使用行级锁,就要给method_name添加索引,值得注意的是,这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上。)。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。我们可以认为获得排它锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,再通过以下方法解锁:
通过connection.commit()操作来释放锁。
这种方法可以有效的解决上面提到的无法释放锁和阻塞锁的问题。
但是还是无法直接解决数据库单点和可重入问题。
二、基于缓存实现分布式锁(以Redis为例)
获取锁使用命令:
1 | SET resource_name my_random_value NX PX 30000 |
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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没有获取到锁,无需释放锁!" ); } |
Jedis提供了和redis命令高度一致的方法,学习成本低,它支持redis的基本数据类型和特性;日常使用相对来说更容易上手,但它提供的分布式锁的封装程度不高,很多逻辑需要我们自己去实现。
Redisson对redis的命令进行了高度封装,提供了许多强大了分布式服务api;不支持字符串操作,不支持redis的一些基本特性。它的方法和我们日常的redis命令有较大差别,上手难度较大,但是它所提供的分布式锁功能较为完善,不需要我们去实现一些比较复杂的逻辑。
以及redis实现分布式锁会引起一系列的问题
比如:
缓存和数据库双写一致性问题
缓存雪崩问题
缓存击穿问题
缓存的并发竞争Key的问题
这个在下篇我们会讲到
在程序中没有完美的方案,只有合理的取舍。
三、基于Zookeeper实现分布式锁
zookeeper实现分布式锁的原理就是多个节点同时在一个指定的节点下面创建临时会话顺序节点,谁创建的节点序号最小,谁就获得了锁,并且其他节点就会监听序号比自己小的节点,一旦序号比自己小的节点被删除了,其他节点就会得到相应的事件,然后查看自己是否为序号最小的节点,如果是,则获取锁
先上代码吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public interface DistributedLock { /**获取锁,如果没有得到就等待*/ public void acquire() throws Exception; /** * 获取锁,直到超时 * @param time超时时间 * @param unit time参数的单位 * @return是否获取到锁 * @throws Exception */ public boolean acquire ( long time, TimeUnit unit) throws Exception; /** * 释放锁 * @throws Exception */ public void release() throws Exception; } |
然后我们来分析下使用zookeeper实现分布式锁的算法流程,假设锁空间的根节点为/lock:
1.客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。
2.客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听/lock的子节点变更消息,获得子节点变更通知后重复此步骤直至获得锁;
3.执行业务代码;
4.完成业务流程后,删除对应的子节点释放锁。
总结下,zookeeper的优点和缺点:
(1)优点:ZooKeeper分布式锁(如InterProcessMutex),能有效的解决分布式问题,不可重入问题,使用起来也较为简单。
(2)缺点:ZooKeeper实现的分布式锁,性能并不太高。
因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。大家知道,ZK中创建和删除节点只能通过Leader服务器来执行,然后Leader服务器还需要将数据同步到所有的Follower机器上,这样频繁的网络通信,性能的短板是非常突出的。
总之,在高性能,高并发的场景下,不建议使用ZooKeeper的分布式锁。而由于ZooKeeper的高可用特性,所以在并发量不是太高的场景,推荐使用ZooKeeper的分布式锁。
目前市场上使用的最多的就是redis和zookeeper的方式,这两种方案比较下来,如果在并发量很大的时候,我们还是使用redis实现分布式锁,如果并发量不高,那么zookeeper就适用正常业务需要。
微信搜索 IT说说 公众号,获取更多java技术资源!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· spring官宣接入deepseek,真的太香了~