java mysql 分布式锁_分布式锁的多种实现详解

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

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

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

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

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

1.基于数据库实现

2.基于缓存(redis、memcached、tair)

3.基于zookeeper

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

1、安全,要保证加锁的方案同一时刻只能被一台机器上的一个线程访问。

2、可重入锁,避免死锁

3、最好是阻塞锁

4、高可用的获取锁和释放锁功能,尤其要关注释放锁的可靠性。

基于数据库的实现

基于数据库表

这应该是最直接能想到的方法。

创建一张表,基于表的数据操作锁。

当我们要加锁是,创建一条记录。释放锁时,删除这条记录。

创建表

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='锁定中的方法'

1

2

3

4

5

6

7

8

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

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

1

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

delete from myLock where method_name = theMethod

1

这种实现有什么问题?

该方案强依赖于数据库的可用性。数据库是单点,一旦挂掉,将导致整个业务不可用。

该锁不是阻塞的。依赖insert操作,一旦失败直接返回。没有获得锁的线程不会进入队列,要想获取锁,只能再次发起insert。

该锁没有失效时间。一旦delete失败,锁就永远存在了。

该锁不是可重入锁。

有没有解决方法?

数据库主备,自动切换。增加了方案复杂性,增加了money投入。

搞个while循环

定时任务

加字段。记录主机信息和线程现象,下一次获取的时候比对一下。

基于数据库排它锁

原理:利用数据库自带的排他锁机制。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;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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

释放锁的代码:

public void unlock(){

connection.commit();

}

1

2

3

4

有没有什么问题?

单点问题依然存在

天生是阻塞的。for update失败会一直处于阻塞状态。

服务宕机之后,数据库自己会把锁释放掉。

依然是不可重入锁。

另外要注意数据库执行时的优化。你建了索引,数据库执行时不一定用的。

总结:

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

基于缓存实现

基于缓存实现,在性能上回比较好,而且缓存一般都是集群部署的,解决了单点问题。

例如,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();

}

1

2

3

4

5

6

Curator的interProcessMutex.acquire获取锁,release释放锁.

超级简单,好用。

只是性能上要比基于缓存差一点。

但是生产环境上还是推荐zookeeper。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值