其他分享
首页 > 其他分享> > 分布式锁的三种实现方式

分布式锁的三种实现方式

作者:互联网

点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人。

文章不定期同步公众号,还有各种一线大厂面试原题、我的学习系列笔记。

eureka和nacos的区别

nacos eureka
应用 nacos是阿里巴巴的开源中间件,可以直接启动jar即可用 eureka需要连着springboot项目一起启动才可用
负载均衡 nacos默认提供权重设置功能,调整承载流量压力
心跳机制 nacos支持由客户端或服务端发起的健康检查 Eureka是由客户端发起心跳
负载均衡策略 用Ribion 用Ribion
dubbo和k8s的集成 支持 不支持
选型建议 希望引入alibaba生态圈;希望在线对服务上下线&在线流量管理 希望引入spring clound生态圈
一致性协议 支持AP+CP任一种实现 AP
动态配置 支持(方便管理所有环境的服务配置) 不支持

CAP理论

幂等性

幂等性:多次重复请求/多次重复操作某一资源,产生的结果是一样的;对于数据库而言,幂等性就是多次重复地对数据库进行某一操作,得到的结果的一样的;对于接口而言,在设计的时候需要考虑幂等性,就是多次重复请求某一个接口,从接口处得到的结果是一样的

sql请求的幂等性
操作 是否幂等 示例
查询 select * from user where name='afei'
新增 insert into user(userid,name) values(1,'afei');若userid是主键,那这个sql就是幂等性的,因为只有第一次数据可以被插入,对数据库产生的结果是一样的;若userid不是主键,那这个sql就不是幂等性的,因为可以重复插入,对数据库产生的结果是不一样的

分布式锁

由上可知分布式锁主要用于解决CAP中的‘C’数据一致性问题:分布式环境中,可能存在多个进程竞争同一个资源,这就需要实现多进程间的“互斥锁”,在java中自带有实现线程间的互斥锁(Synchronized,Reentranlock),但是分布式环境下进程间的互斥需要自己实现,需要把这个“互斥锁”存在公共的地方被多个进程访问到,这样同一时刻只有一个进程能拿到“互斥锁”,进而保证了数据的一致性=‘C’,一般可用redis/zookeeper/数据库来实现分布式锁

基于数据库实现分布式锁

又分为两种方式:表锁、版本号机制

CREATE TABLE `order`  (  //实现分布式锁的表
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_no` int(11) DEFAULT NULL comment `锁住的订单号资源`,
  PRIMARY KEY (`id`),
  unique key `unique_order_no`(`order_no`)
)ENGINE = INNODB

可知order_no为唯一性约束,当想锁住某个orderNo时->先把它插入表中->当有多个相同的order_no提交到数据库,只有一个能成功->想释放锁时,删除该条记录。可以先检查某个order_no是否在表中,不存在则插入,“检查+插入”应该放到同一个事务中:

  @Transactional  //“检查+插入”应该放到同一个事务中
  public boolean addOrder(int orderNo) {
    if(orderMapper.selectOrder(orderNo)==null){ //检查
               //order表不存在该条记录则插入,表示orderNo订单号被锁定
               int result = orderMapper.addOrder(orderNo);  
               if(result>0){
                      return true;
               }
         }
    return false;
  }
  
  public void fun(int orderNo){
      if(addOrder(orderNo)) {//拿到分布式锁
           //业务处理
          ...
          //处理完删除order表的orderNo记录,表示释放分布式锁
          orderMapper.delete(orderNo);
      }
  }
CREATE TABLE `order`  (  //实现分布式锁的表
  `version` int(11) NOT NULL,  //版本号、
  `order_no` int(11) DEFAULT NULL comment `锁住的订单号资源`,
)ENGINE = INNODB

假如已存在version=1,orderNo='N1'的记录,则:

//先获取锁:

select version from order where order_no='N1';

//再占用锁:

udpate order ser version='2' where order_no='N1' and version='1';//更新成功则拿到锁

基于redis实现分布式锁

redis可以用【setnx(key,value)+设置expire】实现分布式锁,这是目前比较优的一种解决方案

public boolean fun(Jedis jedis,String key,String value,int expireTime){
    Long r=jedis.setnx(key,value);//若key不存在则保存(key,value)并返回1,代表拿到分布式锁;若key已存在则设置失败并返回0,代表分布式锁已被占用
    if(r==1){ //拿到分布式锁
    
        //设置锁的过期时间:从当前时点开始经过expireTime(s)之后该key失效,key-value会被删除,代表分布式锁被释放;
        jedis.expire(key,expireTime);
        return true;//拿到true可以向下执行业务操作
    }
    return false;
}

产生的问题:

//redis2.6.12之后:setnx提供了expireTime参数,可以一步设置过期时间
public boolean fun(Jedis jedis,String key,String value,int expireTime){
    String r=jedis.setnx(key,value,"NX","PX",expireTime);//分布式锁已被占用
    if("OK".equals(e)){ //拿到分布式锁
        //设置过期时间释放分布式锁
        jedis.expire(key,expireTime);
        return true;
    }
}
基于zookeeper实现分布式锁

先创建一个持久型父节点,每当客户端们想竞争访问共享资源时,都会在该父节点下新建一个临时有序的子节点->

不断地会有新的临时有序子节点被创建->

后面来的客户端在访问资源时,会检查自己创建的节点是否序号最小,若最小则获取锁,否则阻塞等待->

被阻塞等待的节点均会获取到上一个节点,并为上一节点注册watch事件监听节点是否还存在->

等到上一节点使用完共享资源,则会删除自身,进而触发watch事件被下一节点监听到,下一节点重复上面步骤:检查自己序号是否最小......

OK,如果文章哪里有错误或不足,欢迎各位留言。

创作不易,各位的「三连」是二少创作的最大动力!我们下期见!
分布式锁的三种实现方式

标签:方式,value,order,expireTime,三种,key,节点,分布式
来源: https://www.cnblogs.com/mofes/p/15009158.html