V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
dackh
V2EX  ›  程序员

cap 是啥?分布式锁怎么实现?分布式事务怎么搞?

  •  
  •   dackh ·
    dackh · 2020-06-04 17:41:31 +08:00 · 1397 次点击
    这是一个创建于 1638 天前的主题,其中的信息可能已经有所发展或是发生改变。

    hhhh 喜欢的可以点 star

    CAP 跟 BASE

    CAP

    分布式不能同时满足一致性(Consisency),可用性(Available),分区容忍性(Partition Tolerance),最多只能同时满足其中两项。

    clipboard.png

    一致性

    一致性指的是多个数据副本是否能够保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。

    对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。

    可用性

    可用性指分布式系统在面对各种异常时可以提供正常的服务能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99%的时间是可用的。

    在可用性的条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。

    分区容忍性

    网络分区指分布式系统中的节点被划分了多个区域,每个区域内部都可以通信,但是区域之间无法通信。

    在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性的可用服务,除非整个网络都出现了故障。

    权衡

    在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的,因此,CAP 理论实际上是要在一致性和可用性之间做权衡。

    可用性跟一致性往往的冲突的,很难使它们同时满足,在多个节点之间进行数据同步时

    • 为了保证一致性(CP):就需要让所有节点下线成为不可用状态,等待同步完成。
    • 为了保证可用性(AP):在同步过程中允许读取所有节点的数据,但是数据可能不一致。

    BASE

    BASE 是基本可用(Basically Available)、软状态(Soft Satue)和最终一致性(Eventually Consistent)三个短语的缩写。

    BASE 理论是对 CAP 中的一致性和可用性权衡的结果,它的核心意思:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。

    基本可用

    指分布式系统在出现故障的时候,保证核心可用,允许损失部分可用性。

    例如:电商在做促销的时候,为了保证购物系统的稳定性,部分消费者可用会被引导到一个降级的页面。

    软状态

    指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即允许系统不同节点的数据副本进行同步存在时延。

    最终一致性

    最终一致性强调是系统中所有的数据副本,在经过一段时间内的同步后,最终能达到一致的状态。

    ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 要求最终一致,通过牺牲一致性来达到可用性,通常运用在大型分布式系统中。

    在实际的分布式场景,不同业务单元和组件对一致性的要求是不同的,因此 ACID 和 BASE 往往会结合在一起。

    一致性协议

    2PC

    两阶段提交(two-phase Commit),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定参与者是否真正执行事务。

    运行过程

    准备阶段

    协调者询问参与者事务是否执行承购,参与者回复事务执行结果。

    在该过程,参与者执行了事务,但是还没提交。

    clipboard.png

    提交阶段

    如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。

    clipboard.png

    存在的问题

    • 同步阻塞:所有的参与者都等待其他参与者响应之前都处于阻塞状态,无法进行其他操作。
    • 单点问题:协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响,特别是在阶段二发生故障,所有参与者会一直处于等待状态,无法完成其他操作。
    • 数据不一致:在阶段二,如果协调者只发送部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统的数据不一致。
    • 太过保守: 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。

    3PC

    Paxos 跟 Raft

    分布式锁

    数据库的唯一索引

    获得锁的时候想表中插入一条数据,释放锁删除这条记录,通过唯一索引保证记录只插入一次,通过这条记录来判断是否处于锁定状态。

    存在问题:

    • 没有失效时间,解锁失败其他进程无法再获得锁。
    • 只能是非阻塞锁,插入失败直接报错,无法重试。
    • 不可重入,已经获得锁的进程也必须重新获得锁。

    Redis

    使用 SETNX(set if not exit) 指令插入一个键值对,如果 key 不存在,则插入,返回 true,已经存在返回 false 。

    redis> EXISTS job                # job 不存在
    (integer) 0
    
    redis> SETNX job "programmer"    # job 设置成功
    (integer) 1
    
    redis> SETNX job "code-farmer"   # 尝试覆盖 job,失败
    (integer) 0
    
    redis> GET job                   # 没有被覆盖
    "programmer"
    

    原理跟数据库唯一索引类似,保证只存在一个 Key 的键值对。

    同时可以通过 EXPIRE 指定过期时间,从而避免锁释放失败问题。

    存在问题:

    • Client A 获得在 master 节点获得了锁
    • 在 master 将 key 备份到 slave 节点之前,master 宕机
    • slave 被提升为 master
    • Client B 在新的 master 节点处获得了锁,Client A 也持有这个锁。

    RedLock 算法

    使用多个 redis 实例实现分布式锁,避免发生单点故障问题。

    • 尝试从 N 个互相独立 Redis 实例获取锁。
    • 获取锁的时间小于锁的过期时间,并且(N/2 + 1)实例以上获取了锁,才认为获取锁成功。
    • 如果获取失败,就到每个实例释放锁。

    Zookeeper

    获取锁:

    • 创建一个锁目录 /lock
    • 当客户端需要获取锁时,在 /lock 目录下新建临时顺序节点,
    • 客户端获取 /lock 下的子节点列表,判断自己创建的子节点是否是列表中序号最小的子节点,如果是则认为获取锁,否则则监听自己的前一个节点,获取子节点的变更通知后重复此步骤直至获取锁。

    释放锁:

    • 执行业务代码,完成后删除对应子节点。
    • 客户端机器宕机,Zookeeper 上临时节点自动移出。

    分布式事务

    XA / 二段式提交

    XA 方案,即:两阶段提交,有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。

    TCC 方案

    全称为:Try 、Confirm 、Cancel

    • Try:对各个服务的资源做检测以及对资源进行锁定和预留。
    • Confirm:在各个服务中执行实际的操作。
    • Cancel:任何一个业务方法执行出错,进行补偿,对执行成功的业务逻辑进行回滚操作。

    这种方案使用的人很少,因为事务回滚依赖于自己写的代码进行回滚跟补偿,会造成补偿代码巨大。

    本地消息表

    • A 系统在自己本地事务里操作的同时,插入一条数据到消息表。
    • 接着 A 系统将这个消息发送到 MQ 中去。
    • B 系统接收到这个消息之后,在一个事务里,往自己本地消息中插入一条数据,同时执行其他的业务操作,如果这个消息已经呗处理了,那么此时这个事务会回滚,这样保证不会重复处理消息。
    • B 系统执行成功之后,就会更新自己本地消息表的状态以及 A 系统消息表的状态。
    • 如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理。
    • 这个方案保证了最终一致性,那么 B 事务失败了,但是 A 会不断的重发消息,直到 B 那边成功为止。

    本地消息表

    可靠消息最终一致性

    阿里的 RocketMQ 就支持消息事务:

    • A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;
    • 如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息;
    • 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;
    • mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。
    • 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。
    • 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ ? RabbitMQ ?自己封装一套类似的逻辑出来,总之思路就是这样子的。

    rocketMQ

    参考

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1014 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 21:01 · PVG 05:01 · LAX 13:01 · JFK 16:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.