分布式锁是什么?为什么要使用分布式锁?分布式锁具体有哪些实现?

        

 

        在这个互联网鸿飞腾达的时刻,我们的生活也在改变,从曾经的单体架构到现在的微服务、分布式等等这些庞然大物,技术也在不断的更新和提高,头发也掉的越来越多。

一、分布式锁是什么:

        众所周知,大家对锁的概念,我相信都了如指掌,但分布式锁是什么?很显然,从字面的意思来看,就是在分布式系统架构里所用到的锁,主要就是为了解决分布式系统中共享资源的访问的问题,与单体架构中的锁,最大的区别就是,细粒度的划分:从线程间的资源共享到进程间的资源共享。

二、为什么要使用分布式锁:

        现在大多数的项目都是由分布式架构所搭建起来的,常常会采用集群以及部署在多台服务器上等方式运行,想必现在nginx的反向代理、负载均衡已经无人不知无人不晓了吧。由于通过用户不同的电脑已经不同的浏览器进行访问项目,就会由nginx转发给集群中的项目实例,此时常用的锁由于跨服务器的原因导致失效。但当请求过来时,例如有三台服务器收到了请求,都去查询数据库中是否还有一个商品,当发现还有10件商品的时候,三个台服务器同时进行,都买了4件,到最后的时候发现库存成负数了,这就导致了超卖问题。现在就必须要用到分布式锁,来解决这个问题了。

三、分布式锁的实现方式:

 分布式锁的实现常见的一共有三种:

                    1.使用关系型mysql数据库实现分布式锁。

                    2.使用redis非关系型数据库实现分布式锁。

                    3.使用zookeeper注册中心来实现分布式锁。

(1)使用关系型数据库来实现分布式锁:

        首先,要新建一个表,代码如下:

create table test(
    id int(4) primary key auto_increment comment '主键',
    method_name varchar(255) not null comment '方法名',
    datetime timestamp not null comment '过期时间',
    unique key unique_method_name ( method_name )
)ENGINE=InnoDB default charset=utf8;

    定义一个自增的主键、一个拥有唯一索引的方法名,以及一个过期时间(这个后面详细说明)。

    接下来,当有一个请求进行访问时,往数据库中插入一个数据,代码如下:

insert into test values(null,'当前执行的方法名',current_timestamp + 想要加的秒数);

     由于数据库中的第二列method_name添加了唯一约束,同一时间只会有一个请求访问当前方法,知道方法执行完成之后再删除掉对应的数据,代码如下:

delete from test where method_name = '当前执行的方法名';

这样就可以保证分布式锁的作用。

        注:现在来解释上面为什么要加一个过期时间,当一个请求过来的时候,向数据库中添加了数据之后,服务器突然宕机,此时数据库中的数据还在,当服务器恢复之后,由于这个方法的数据一直在,所以导致无法有请求来执行这个方法。所以总体来说,过期时间再加上一个周期性检查数据是否过期的任务,来防止服务器突然宕机等情况,导致数据库中的数据没有被删除。

数据库作分布式锁的缺点:

1、不具有可重入的特性:当一个请求进行访问之后,往数据库插入数据,此时再想获取锁,因为数据库中有数据,所以无法再次获取锁。

2、不会等待锁的释放:当数据库中有数据后,一个请求再过来时,发现数据库中有数据,则会直接失效。

3、数据库需要高可用性:虽然大量请求过来后,只会执行一个请求,但这些请求都会去访问数据库,看看数据库中是否这个数据,这就导致大量请求去访问数据库可能会导致数据库宕机,所以数据库集群、主从复制等等这些操作还是很有必要的。

(2)使用redis非关系型数据库实现分布式锁:

Redis的特点:

        1.Redis是单线程的,速度非常快。

        2.Redis所能承受的压力,比mysql数据库高出很多。

        近几年来,redis的发展可谓是翻天覆地,redis用来作缓存这个思想也是家喻户晓了,好了,废话不多说我们直接上代码:

/**
 * 获得锁
 */
 public boolean getLock(String lockName, long milliseconds) {
     boolean success = redisTemplate.opsForValue().setIfAbsent(lockName, "lock",milliseconds, TimeUnit.MILLISECONDS);
     return success != null && success;
 }

        setIfAbsent就是当键不存在时,设置数据,并且可以设置数据的过期时间,但是如果键存在,就什么都不做。这和原生redis中的setnx这个命令相似。

        由于redis执行命令是单线程的,所以当一个请求执行完之后,其他命令执行时,都会发现存在键,所以就操作失败

当方法执行完之后,就将当前的key进行删除,代码如下:

/**
 * 释放锁
 */ 
public void releaseLock(String lockName) {
     redisTemplate.delete(lockName);
 }

这样,redis设置了过期时间,也设置了手动删除key,就不怕服务器突然宕机,导致数据一直存在。

(3)使用Zookeeper注册中心来实现分布式锁:

        ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,他的内部是一个文件系统目录的树结构,和Linux系统比较相似。

实现方式如下,首先我们先创建一个父目录,代码如下:

//首先先创建永久父节点
public void create(){
    zkClient.createPersistent("/lock","持久节点");
}

//在父节点下创建临时节点
public void lock(){
    zkClient.createEphemeral("/lock/方法名","临时节点");
}


//删除临时节点
public void unlock(){
    zkClient.delete("/locak/方法名");
}

我们可以采用创建临时节点的方法来进行加锁,当所有请求同时创建临时节点的时候,只会有一个请求能够成功创建临时节点,此时其他的请求都会直接报错,返回,我们使用服务降级来解决这一问题。

当服务器宕机之后,zookeeper也会随之下线,服务器重连之后,由于zookeeper也会进行重新连接,所以临时节点也会随之消亡,不必担心没有删除临时节点突然宕机的情况。

缺点:过于频繁创建和删除节点,在性能这一方面远远不如redis,并且只要有一处不可用,整个zookeeper服务就不可用,因为zookeeper满足CAP定理中的CP。

四、总结:

        上面三种方式都是可以的,具体主要是根据你的业务需求来进行使用,当然,我只说了比较常用的方式,还有其他方式也可以进行使用。

个人认为在某一般的场景下:

        Redis  >  mysql  >  zookeeper

毕竟Redis是现在的王道,哈哈。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值