分布式锁的多种实现

目前几乎所有的大型web应用全都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式应用中的CAP理论告诉我们:

任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)、分区容错性(Partation tolerance)。最多只能同时满足其中两项。

所以在设计之初,就需要对三者做出取舍。一般在互联网场景中,都会选择牺牲强一致性,来换取高可用性,系统只需要保证最终一致性

那么问题就是,如何保证最终一致性?通常的技术方案有分布式事务,分布式锁。我们需要保证,在同一时刻,只有一个线程执行某一个特定方法。我们知道,在单机环境中,或者是单JVM中,java的并发api可以保证并发安全。但是在集群情况下,java的并发api就没办法了。我们需要分布式锁。

通常分布式锁的实现方案有三种:

1.基于数据库实现
2.基于缓存(redis、memcached、tair)
3.基于zookeeper

在思考方案前,首先要考虑目标,我们需要什么样的分布式锁?

1、安全,要保证加锁的方案同一时刻只能被一台机器上的一个线程访问。
2、可重入锁,避免死锁
3、最好是阻塞锁
4、高可用的获取锁和释放锁功能,尤其要关注释放锁的可靠性。

基于数据库的实现

基于数据库表

这应该是最直接能想到的方法。
创建一张表,基于表的数据操作锁。
当我们要加锁是,创建一条记录。释放锁时,删除这条记录。

  1. 创建表
CREATE TABLE `myLock`(
`id` int(11) not null auto_increment comment '主键',
`method_name` varchar(64) not null default '' comment '锁定的方法名',
`desc` varchar(1024) not null default '备注信息',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '更新时间',
primary key (`id`),
unique key `uidx_method_name` (`method_name`) using btree
 )engine=InnoDB default charset=utf8 comment='锁定中的方法'

当我们需要锁住某个方法时,执行插入。

insert into myLock(method_name,desc) values ('theMethod','the desc');

当需要释放锁时,执行delete。

delete from myLock where method_name =  theMethod

这种实现有什么问题?

  1. 该方案强依赖于数据库的可用性。数据库是单点,一旦挂掉,将导致整个业务不可用。
  2. 该锁不是阻塞的。依赖insert操作,一旦失败直接返回。没有获得锁的线程不会进入队列,要想获取锁,只能再次发起insert。
  3. 该锁没有失效时间。一旦delete失败,锁就永远存在了。
  4. 该锁不是可重入锁。

有没有解决方法?

  1. 数据库主备,自动切换。增加了方案复杂性,增加了money投入。
  2. 搞个while循环
  3. 定时任务
  4. 加字段。记录主机信息和线程现象,下一次获取的时候比对一下。
基于数据库排它锁

原理:利用数据库自带的排他锁机制。select for update。
还是先建了上面那张表。
在mysql的InnoDB引擎下,可以用下面的代码:


public boolean lock(){
    connection.setAutoCommit(false)
    while(true){
        try{
            result = select * from myLock where method_name=xxx for update;
            if(result==null){
                return true;
            }
        }catch(Exception e){

        }
        sleep(1000);
    }
    return false;
}

这样可以给某条记录加上排他锁,之后其他的线程就不可以再加排它锁了。要注意的是,InnoDB在加锁的时候只有通过索引检索的时候才会使用行级锁,否则表级锁。这个索引一定要建成唯一索引,否则会出现多个重载方法无法访问的情况,重载方法就再加个参数类型字段。

释放锁的代码:

public void unlock(){
    connection.commit();
}

有没有什么问题?

  1. 单点问题依然存在
  2. 天生是阻塞的。for update失败会一直处于阻塞状态。
  3. 服务宕机之后,数据库自己会把锁释放掉。
  4. 依然是不可重入锁。
  5. 另外要注意数据库执行时的优化。你建了索引,数据库执行时不一定用的。

总结:

数据库这种方式看起来简单,实际不会采用的。因为要埋这些坑,要加入更多的代码。使方案变得越来越复杂。

基于缓存实现

基于缓存实现,在性能上回比较好,而且缓存一般都是集群部署的,解决了单点问题。
例如,redis使用setnx,expire。
例子很多,但是在生产环境中,没有用过。setnx不是原子的。exire超时也不是很靠谱。

基于Zookeeper实现

基于Zookeeper临时有序节点实现分布式锁。
大体原理:当每个客户端对某个方法加锁时,在Zookeeper的对应目录下,会生成一个唯一的瞬态的有序的znode。
判断是否获得锁,只要判断自己是不是目录下最小的znode。
如果不是,注册对比自己小的znode的监听。当比自己小的znode删除时,收到消息,判断自己是不是最小的。

当某个客户端获取锁后,突然挂掉。没关系,对应的znode会被自动删除,不会出现锁释放不了的问题。
是阻塞锁
可重入
集群部署

使用zookeeper还是比较靠谱的,而且可以直接使用第三方包Curator,这个包直接封装了一个可重入的锁服务。

public boolean tryLock(long timeout, TimeUnit unit) throws Exception {
        return interProcessMutex.acquire(timeout, unit);
}
public void unlock() throws Exception {
        interProcessMutex.release();
}

Curator的interProcessMutex.acquire获取锁,release释放锁.
超级简单,好用。

只是性能上要比基于缓存差一点。
但是生产环境上还是推荐zookeeper。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Java 中分布式锁实现可以通过以下几种方法: 1. 基于数据库:将锁存储在数据库中,当线程需要获取锁时,尝试在数据库中创建一条记录,创建成功则获得锁。 2. 基于 ZooKeeper:ZooKeeper 是一个开源的分布式协调服务,可以用于实现分布式锁。 3. 基于 Redis:Redis 是一个高性能的内存数据存储系统,也可以用作分布式锁实现。 4. 基于 Java 锁:Java 提供了 Lock 和 ReadWriteLock 接口用于实现锁,可以通过结合远程方法调用或共享内存来实现分布式锁。 这些方法都有其优缺点,选择哪种方法要根据具体场景决定。 ### 回答2: 在Java中,分布式锁可以通过以下几种方式来实现: 1. 基于数据库:可以使用数据库的事务隔离级别来实现分布式锁。通过在数据库中创建一张表,将锁标识作为一条记录插入到表中,其他线程在获取锁之前需要先查询该表,如果锁标识已存在,则表示锁被占用,否则可以获取锁。 2. 基于Redis:Redis是一种内存数据库,支持分布式操作。可以通过Redis的setnx命令来实现分布式锁。使用setnx命令可以将一个标识作为Key插入到Redis中,如果该Key不存在,则表示获取到了锁,否则表示锁已经被其他线程持有。另外,可以结合Redis的expire命令给锁设置一个超时时间,防止死锁。 3. 基于ZooKeeper:ZooKeeper是一个高性能的分布式协调服务,可以用来实现分布式锁。通过创建顺序临时节点,每个线程在获取锁之前都在指定目录下创建一个节点,获取锁的线程是当前序号最小的节点,其他线程需要监听前一个节点的删除事件,从而确定自己是否获取到锁。 4. 基于第三方框架:除了使用数据库、Redis和ZooKeeper外,还可以使用一些第三方框架来实现分布式锁,比如Curator、Apache Shiro等。这些框架提供了简单易用的API和高层次的封装,可以方便地在分布式环境中实现锁的功能。 无论哪种方式,都需要在获取锁时加上超时机制,以避免死锁的情况发生。同时,还需要注意锁的释放机制,确保在锁不再使用时及时释放,以免造成资源浪费。 ### 回答3: 在Java中,实现分布式锁多种方式,下面以几个常用的方式作简要介绍。 1. 基于数据库: 使用数据库中的表或行作为锁的持有状态。可以通过创建一个包含列如锁名称和锁状态的表,利用数据库的事务特性来实现对锁的获取和释放操作。通过在表中插入一行并设定锁状态为占用来获得锁,释放锁时删除该行。 2. 基于缓存: 使用分布式缓存来实现分布式锁。比如使用Redis或ZooKeeper等分布式缓存工具。通过在缓存中设定一个key及其值来实现锁的获取和释放。获取锁时尝试在缓存中将key设定为某个特定值,成功则获得锁,否则等待锁释放。释放锁时删除缓存中的key。 3. 基于ZooKeeper: ZooKeeper是一个分布式协调服务,可以用于实现分布式锁。可以利用ZooKeeper的节点特性和watch机制来实现锁。每个客户端在ZooKeeper中创建一个临时顺序节点,按顺序获取节点的锁,进行业务操作,完成后删除节点释放锁。 4. 基于分布式锁框架: 可以使用第三方的分布式锁框架,如Curator、RedLock等。这些框架一般会封装底层细节,提供简洁的API供开发者使用,更易于实现分布式锁。 无论采用哪种方式实现分布式锁,都需要注意以下几个问题:避免死锁、锁的争用激烈时的性能问题、锁的超时处理以及锁的可重入性等。同时,合理的设计和选择分布式锁的方式也很重要,根据实际业务场景和需求做出选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值