分布式个人整理

目录

1.ACP理论

2.分布式事务实现的四种方式

3.分布式锁

4.分布式锁的三种实现方式



1.ACP理论

任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能满足两种,要么是cp,要么是ap,所以在绝大部分场景中都要牺牲强一致性而最终取得最终一致性的结果

2.分布式事务实现的四种方式

两阶段提交(2pc):引入协调者(Coordinator)来协调参与者的行为

        准备阶段:协调者询问参与者是否执行成功,参与者返回执行结果,在此阶段参与者执行了事务但未提交

        提交阶段:如果参与者都已经执行成功,协调者则发消息让所有参与者提交事务,否则的话则让所有事务回滚

        缺点:所有事物参与者都需要等待协调者响应,在这期间不能有其他的操作,并且如果在提交阶段协调者出现问题导致不能给参与者发起响应,所有参与者会一直等待,假如在提交阶段协调者向参与者返回消息的时候出现网络问题会导致一部分参与者收到消息一部分没收到消息,就会导致数据不同一,综合来说2pc方法容错率比较低,都比较依赖协调者

补偿事务(TCC):TCC就是采取补偿机制,核心逻辑就是每一个事物的操作都配置一个对应的确认和撤销的操作,它一共分三个阶段

        Try阶段:是对业务资源进行校验和预留

        Confirm阶段:对业务系统做确认提交,Try阶段成功执行Confirm阶段

        Cancel阶段:在第二阶段如果失败则执行此阶段,释放Try阶段资源的预留,取消业务执行

        缺点:跟2pc比起来流程会简单一些,但是数据的一致性不如2pc,而且第2,3步骤都有可能失败,TCC属于在业务层做补偿机制,所以需要写很多的代码来实现这个功能,很麻烦

本地消息表:本地消息表事务方式就是根据创建本地消息表和消息队列来解决这个问题

        第一阶段:A业务处理业务之后将要操作B业务,这个时间将要操作的B业务数据存入本地消息表中,本地事务可以确保这个存入本地消息表的这个过程一定会成功

        第二阶段:本地消息表将消息发送到消息队列中,如果发送失败则重试,发送成功删除本地消息表

        第三阶段:B业务接收到消息按照消息数据进行业务B

        缺点:消息表逻辑会耦合到业务中,如果封装的不好会导致开发业务变麻烦

MQ事务消息:有一些第三方mq支持事务,例如RocketMQ

        第一阶段:发送Prepared消息,获取消息地址

        第二阶段:处理本地业务

        第三阶段:根据第一阶段的地址去访问消息并修改状态,如果消息接收失败RocketMQ会定期扫描Prepared消息,然后像发送方确认该条消息是否执行成功,如果没成功是否要重新发送,这些可以根据发送方策略来决定如何实施下一步行为

        缺点:实施难度大,大部分主流mq都不支持事务,RocketMQ事务消息部分代码也未开源

3.分布式锁

分布式锁是什么?:在开发过程中,多线程访问一个共享变量需要用到同步访问,但这只是单机的情况下可以完美的解决这个问题,但是随着业务的发展出现集群,为了解决集群之前的同步问题,就会用到分布式锁;

分布式锁应该具备什么条件?:

1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行; 
2、高可用的获取锁与释放锁; 
3、高性能的获取锁与释放锁; 
4、具备可重入特性; 
5、具备锁失效机制,防止死锁; 
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

4.分布式锁的三种实现方式

基于数据库实现分布式锁:

        方案1:可以利用主键的唯一性或者唯一索引来判断,如果多个请求访问数据库数据库会保证只有一个操作可以成功,那就可以认识这个操作的线程获取了这个锁,如果要释放锁删除该条数据

        方案2:可以根据版本号字段去判断,修改数据前读取版本号,每次修改版本号加1,然后可以写一个sql去判断每次增加的版本号是否正确

        方案3:可以利用悲观锁,在查询语句后面加上for update ,在查询过程中给数据增加悲观锁,当一个线程用了悲观锁之后其他线程无法再加悲观锁,最后通过connection.commit()操作来释放锁(注意: InnoDB 引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁。这里我们希望使用行级锁,就要给要执行的方法字段名添加索引,值得注意的是,这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上。)

        缺点:因为是基于数据库的实现方式,所以数据库一旦出现问题就会导致业务不可用,而且一般这个锁都没有时间,如果什么意外步骤导致解锁失败就会一直卡在这,并且对于方案2来说对数据库的压力也会很大

基于redis锁实现:

        基于setnx(),expire()分布式锁

        第一步:首先往redis中放一个值,用setnx()方法存放两个参数(key,value),该方法是原子的,所以如果key不存在,占位成功,返回1,否则返回0失败,可以根据这点配合业务逻辑实施占位;

        第二步:同时也可以给值通过expire()设置一个过期时间,是为了出现死锁现象

        第三步:最后执行代码成功后用delete方法删掉这个值

        如果在第一步的时候执行成功,而第二步的时候发生宕机,也会导致死锁,在这种情况下可以增加get()和getset()还有setnx()进行实现

        基于setnx(),get(),getset()实现分布式锁

        注:getset(key,value)使用如果当前key有值,会对key设置新的value值并且返回上一个的旧value值,如果key没有值则设置新值返回null

        第一步:setnx(lockkey, oldExpireTime (注:当前时间+过期超时时间)),如果返回 1,则获取锁成功;如果返回 0 则没有获取到锁,转向 2。

        第二步:get(lockkey) 获取值 oldExpireTime ,并将这个 value 值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向 3。

        第三步:计算 newExpireTime = 当前时间+过期超时时间,然后 getset(lockkey, newExpireTime) 会返回当前 lockkey 的值currentExpireTime。

        第四步:判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前 getset 设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。

        第五步:在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行 delete 释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。

        基于 REDLOCK 做分布式锁

        Redlock 是 Redis 给出的集群模式的 Redis 分布式锁,它基于 N 个完全独立的 Redis 节点(通常情况下 N 可以设置成 5)。

        第一步:客户端获取当前时间,以毫秒为单位。

        第二步:客户端尝试获取 N 个节点的锁,(每个节点获取锁的方式和前面说的缓存锁一样),N 个节点以相同的 key 和 value 获取锁。客户端需要设置接口访问超时,接口超时时间需要远远小于锁超时时间,比如锁自动释放的时间是 10s,那么接口超时大概设置 5-50ms。这样可以在有 redis 节点宕机后,访问该节点时能尽快超时,而减小锁的正常使用。

        第三步:客户端计算在获得锁的时候花费了多少时间,方法是用当前时间减去在步骤一获取的时间,只有客户端获得了超过 3 个节点的锁,而且获取锁的时间小于锁的超时时间,客户端才获得了分布式锁。

        第四步:客户端获取的锁的时间为设置的锁超时时间减去步骤三计算出的获取锁花费时间。

        第五步:如果客户端获取锁失败了,客户端会依次删除所有的锁。

基于zookeeper实现:

第一步:在 /lock 节点下创建一个有序临时节点 (EPHEMERAL_SEQUENTIAL)。

第二步:判断创建的节点序号是否最小,如果是最小则获取锁成功。不是则取锁失败,然后 watch 序号比本身小的前一个节点。

第三步:当取锁失败,设置 watch 后则等待 watch 事件到来后,再次判断是否序号最小。

第四步:取锁成功则执行代码,最后释放锁(删除该节点)。

参考资料:https://www.cnblogs.com/seesun2012/p/9214653.html

        

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值