面试官问实现分布式锁有哪些?

面试经常被问到分布式锁,今天我就带大家深入剖析下分布式锁的各种方案。

什么是分布式锁

概述 为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。

怎样实现分布式锁

1.基于数据库实现分布式锁

2.基于缓存实现分布式锁

3.基于Zookeeper实现分布式锁

一、基于数据库实现分布式锁
首先新建一个数据库表,可以通过数据库的排他锁来实现分布式锁。基于MySql的InnoDB引擎,可以使用以下方法来实现加锁操作:
在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁(这里再多提一句,InnoDB引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁。这里我们希望使用行级锁,就要给method_name添加索引,值得注意的是,这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上。)。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。我们可以认为获得排它锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,再通过以下方法解锁:
通过connection.commit()操作来释放锁。
这种方法可以有效的解决上面提到的无法释放锁和阻塞锁的问题。
但是还是无法直接解决数据库单点和可重入问题。

二、基于缓存实现分布式锁(以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没有获取到锁,无需释放锁!");
  }

Jedis提供了和redis命令高度一致的方法,学习成本低,它支持redis的基本数据类型和特性;日常使用相对来说更容易上手,但它提供的分布式锁的封装程度不高,很多逻辑需要我们自己去实现。
Redisson对redis的命令进行了高度封装,提供了许多强大了分布式服务api;不支持字符串操作,不支持redis的一些基本特性。它的方法和我们日常的redis命令有较大差别,上手难度较大,但是它所提供的分布式锁功能较为完善,不需要我们去实现一些比较复杂的逻辑。
以及redis实现分布式锁会引起一系列的问题
比如:
缓存和数据库双写一致性问题

		缓存雪崩问题
		
		缓存击穿问题
		
		缓存的并发竞争Key的问题

这个在下篇我们会讲到

在程序中没有完美的方案,只有合理的取舍。

三、基于Zookeeper实现分布式锁
zookeeper实现分布式锁的原理就是多个节点同时在一个指定的节点下面创建临时会话顺序节点,谁创建的节点序号最小,谁就获得了锁,并且其他节点就会监听序号比自己小的节点,一旦序号比自己小的节点被删除了,其他节点就会得到相应的事件,然后查看自己是否为序号最小的节点,如果是,则获取锁
先上代码吧:

    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技术资源!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT Talk

谢谢您对我的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值