ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

分布式锁的三种实现方式

2021-07-14 03:31:07  阅读:156  来源: 互联网

标签:方式 value order expireTime 三种 key 节点 分布式


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

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

eureka和nacos的区别

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

CAP理论

  • C = 一致性,consistency,任何时候的节点数据都是一样的
  • A = 可用性,avalibility,任何时候的节点请求不管成功失败都必须有回应
  • p = 容错性,partition tolerence,任何时候的数据丢失都不会影响系统正常运行

    任何分布式系统都无法同时满足CAP,只能满足其中两个,大部分IT公司只要求AP保证服务可用,并且在最终实现“C=最终一致性”即可,如何实现C最终一致性:分布式事务、分布式锁

幂等性

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

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设置操作
//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;
    }
}
  • 拿到锁之后,如果进行业务操作时间太长而expireTime设置太短,则会造成原来只有拿到锁才能执行的代码块失效,解决:设置expireTime之后,马上启动一个定时器,在expireTime快要到来之前,用lua原子性脚本删除原锁并重新设置新锁和新的到期时间(删除原key-value,再重新setnx设key-value和expireTime);
  • 在redis集群下,主节点负责写,从节点负责读,由于主从复制是异步的,所以中间有一定的时间差,如果客户A从主节点获取到锁之后,还没来得及同步信息到其他的从节点,主节点就挂了,这时客户B再拿锁就会从 从节点获取到这个把锁,此时两个客户同时获取到了锁,解决:(待讨论)
  • 锁的可重入问题,因为setnx是保证锁唯一的,如果拥有锁的进程想再次获取到该锁,就会失败,解决:当前进程用lua原子脚本把原锁删了,重新设置新锁+新expireTime(删除原key-value,再重新setnx设key-value和expireTime);
基于zookeeper实现分布式锁

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

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

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

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

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

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

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

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有