关于程序员:分布式事务的21种武器-7

69次阅读

共计 6874 个字符,预计需要花费 18 分钟才能阅读完成。

在分布式系统中,事务的解决散布在不同组件、服务中,因而分布式事务的 ACID 保障面临着一些非凡难点。本系列文章介绍了 21 种分布式事务设计模式,并剖析其实现原理和优缺点,在面对具体分布式事务问题时,能够抉择适合的模式进行解决。原文: Exploring Solutions for Distributed Transactions (7)

在不同业务场景下,能够有不同的解决方案,常见办法有:

  1. 阻塞重试(Blocking Retry)
  2. 二阶段和三阶段提交(Two-Phase Commit (2PC) and Three-Phase Commit (3PC))
  3. 基于后盾队列的异步解决(Using Queues to Process Asynchronously in the Background)
  4. TCC 弥补(TCC Compensation Matters)
  5. 本地音讯表(异步保障)/ 发件箱模式(Local Message Table (Asynchronously Ensured)/Outbox Pattern)
  6. MQ 事务(MQ Transaction)
  7. Saga 模式(Saga Pattern)
  8. 事件驱动(Event Sourcing)
  9. 命令查问职责拆散(Command Query Responsibility Segregation, CQRS)
  10. 原子提交(Atomic Commitment)
  11. 并行提交(Parallel Commits)
  12. 事务复制(Transactional Replication)
  13. 一致性算法(Consensus Algorithms)
  14. 工夫戳排序(Timestamp Ordering)
  15. 乐观并发管制(Optimistic Concurrency Control)
  16. 拜占庭容错(Byzantine Fault Tolerance, BFT)
  17. 分布式锁(Distributed Locking)
  18. 分片(Sharding)
  19. 多版本并发管制(Multi-Version Concurrency Control, MVCC)
  20. 分布式快照(Distributed Snapshots)
  21. 主从复制(Leader-Follower Replication)

本文将介绍 MVCC、分布式快照以及主从复制三种模式。

19. 多版本并发管制(Multi-Version Concurrency Control, MVCC)

  • 容许多个事务并发拜访雷同的数据,而不会互相烦扰。
  • 创立同一数据对象的多个版本,并容许事务同时拜访不同的版本。通过这种形式,事务能够在非阻塞的状况下读取数据,并且能够在不产生抵触或不统一的状况下执行写操作。
  • 波及如下步骤:

    1. 当事务想要读写数据时,首先查看零碎事务表,以确定是否能够进行。如果事务能够持续,则为其调配惟一的事务 ID。
    2. 当事务写入数据对象时,创立该对象的新版本,并且事务 ID 与该版本相关联,新版本被增加到零碎版本表中。
    3. 当事务读取数据对象时,会在版本表中搜寻该对象在事务开始之前创立的最新版本,并从对象的那个版本读取数据。
    4. 数据对象的每个版本都与事务 ID、开始工夫和完结工夫相关联。开始工夫为版本创立的工夫,完结工夫为版本被更新版本取代的工夫。
    5. 当事务提交时,完结工夫被记录在事务表中。与该事务关联的所有版本的数据对象都被标记为已提交,并且完结工夫设置为事务的完结工夫。
    6. 事务终止时,其完结工夫记录在事务表中。与该事务关联的所有版本的数据对象都被标记为终止,并且完结工夫设置为事务的完结工夫。
    7. 当新的事务开始时,只能拜访在它开始工夫之前曾经提交的数据对象,确保事务只读取统一的数据。
import sqlite3
import time

# Connect to the database
conn = sqlite3.connect('example.db')

# Create a table for testing MVCC
conn.execute('''CREATE TABLE test (
                 id INTEGER PRIMARY KEY,
                 name TEXT,
                 value INTEGER,
                 version INTEGER
                 )''')

# Insert some initial data
conn.execute("INSERT INTO test (name, value, version) VALUES ('foo', 42, 1)")

# Start a transaction
tx = conn.begin()

# Read the current value of 'foo'
cursor = tx.execute("SELECT value, version FROM test WHERE name ='foo'")
value, version = cursor.fetchone()

# Increment the value of 'foo'
new_value = value + 1

# Insert a new version of the data
new_version = version + 1
tx.execute("INSERT INTO test (name, value, version) VALUES ('foo', ?, ?)", (new_value, new_version))

# Update the version of the original data
tx.execute("UPDATE test SET version = ? WHERE name ='foo'AND version = ?", (new_version, version))

# Commit the transaction
tx.commit()

# Print the final value of 'foo'
cursor = conn.execute("SELECT value FROM test WHERE name ='foo'")
value = cursor.fetchone()[0]
print(f"Final value of'foo': {value}")

# Close the database connection
conn.close()

示例代码

  • 事务能够依据本人的工夫戳读取适当版本的数据,而不会影响其余事务。
  • 波及如下步骤:

    1. 连贯数据库 SQLite3
    2. 创立测试 MVCC 的表
    3. 插入初始数据
    4. 启动事务
    5. 读取 ’foo’ 的以后值
    6. 将 ’foo’ 的值 +1
    7. 插入版本号更高的数据新版本
    8. 更新原始数据版本号,使其与新版本相匹配
    9. 提交事务
    10. 输入 ’foo’ 的最终值
    11. 敞开数据库连贯

长处

  • 容许多个事务同时读写数据
  • 防止应用锁
  • 保护数据的多个版本
  • 在事务之间提供高度隔离

毛病

  • 减少了数据库设计的复杂性
  • 因为须要存储多版本数据,减少了存储开销
  • 因为须要扫描多版本数据,减少了查问执行工夫

实用场景

  • 具备高读写比率,并且大多数是只读事务的利用
  • 须要同时执行许多事务
  • 须要高并发性和一致性的联机事务处理 (OLTP) 零碎
  • 事务须要高度隔离的利用,例如金融应用程序

20. 分布式快照(Distributed Snapshots)

  • 记录分布式系统在特定工夫点的状态
  • 可用于多种利用,如分布式数据库、分布式文件系统和分布式音讯代理。
  • 波及如下步骤:

    1. 抉择启动快照的过程。该过程向零碎中其余过程发送标记音讯,当过程接管到标记音讯时,获取其以后状态的快照,并将音讯发送给相邻过程。
    2. 当过程接管到标记音讯时,记录其本地状态,包含过程状态及其通信通道。
    3. 过程记录本地状态后,将标记音讯发送给相邻过程,相邻过程启动快照过程。
    4. 过程期待来自相邻过程的所有标记音讯实现快照。
    5. 过程接管到所有标记音讯后,记录所有用于与其余过程通信的通道状态。
    6. 一旦过程记录了所有通道的状态,就向发动快照的过程发送确认音讯。
    7. 发动快照的过程收到所有过程的确认音讯后,联合本地状态和通道状态信息,构建分布式系统的快照。
from multiprocessing import Process, Queue

class ProcessNode(Process):
    def __init__(self, pid, processes, markers):
        super().__init__()
        self.pid = pid
        self.processes = processes
        self.markers = markers
        self.state = 0

    def send_message(self, dest):
        self.processes[dest].queue.put(self.pid)

    def run(self):
        while True:
            if not self.queue.empty():
                message = self.queue.get()
                if message == 'marker':
                    self.markers[self.pid] = True
                    for i in range(len(self.processes)):
                        if i != self.pid:
                            self.send_message(i)
                else:
                    self.state += message
                    for i in range(len(self.markers)):
                        if self.markers[i]:
                            self.send_message(i)
                            self.markers[i] = False
            else:
                # do some work
                self.state += 1

示例代码

  • Chandy-Lamport 算法是一种罕用的分布式快照算法。
  • ProcessNode类 —— 扩大 multiprocessing.Process

    • 每个 ProcessNode 实例代表分布式系统中的一个过程。
    • send_message办法 —— 将音讯发送到另一个过程
    • run办法 —— 定义过程的主逻辑
    • 如果接管到 marker 音讯,则过程将标记设置为 True,并向所有其余过程发送marker 音讯,开始记录其状态。
    • 如果接管到非 marker 音讯,则该过程将该音讯增加到其状态,并向所有设置了标记的其余过程发送 marker 音讯。
    • 如果队列中没有音讯,则过程执行本人的工作并扭转其状态。
    • 每个过程获取其本地状态的快照,并向其余过程发送音讯以获取统一的全局状态。

长处

  • 实现跨多个节点的数据一致性
  • 实现容错机制,并能从故障中复原

毛病

  • 实现简单
  • 引入额定的网络流量和开销
  • 难以调试和诊断问题
  • 要求批改现有零碎

实用场景

  • 要求跨多个节点实现数据一致性的金融零碎
  • 须要跨多个节点实现数据一致性的库存治理和订单跟踪零碎
  • 须要容错性和一致性的牢靠消息传递

21. 主从复制(Leader-Follower Replication)
  • 在分布式系统中复制数据
  • 一个节点充当领导者,其余节点充当追随者
  • 领导者节点 —— 更新数据
  • 跟随者节点 —— 复制领导者节点所做的更改
  • 波及如下步骤:

    1. 领导者节点收到客户端的写申请
    2. 领导者节点更新其本地数据正本,并将更新发送给所有追随者节点
    3. 跟随者节点将更新利用到本人的本地数据正本
    4. 领导者节点向客户端发送写操作胜利的确认信息
    5. 如果跟随者节点呈现故障,领导者节点将更新信息发送给代替节点,以确保代替节点领有最新的数据正本
    6. 客户端想要读取数据时,能够向领导者节点或任何跟随者节点申请数据。如果从跟随者节点申请数据,则跟随者节点须要查看是否领有最新的数据正本。如果没有,则从领导者节点申请数据。
import threading
import time

class LeaderFollowerReplication:
    def __init__(self, data):
        self.data = data
        self.leader_lock = threading.Lock()
        self.follower_lock = threading.Lock()
        self.leader = None
        self.follower = None

    def start_leader(self):
        while True:
            self.leader_lock.acquire()
            self.follower_lock.acquire()
            self.follower = self.data
            time.sleep(0.5)
            self.leader = self.data
            self.follower_lock.release()
            self.leader_lock.release()

    def start_follower(self):
        while True:
            self.follower_lock.acquire()
            if self.follower != self.data:
                self.data = self.follower
            self.follower_lock.release()
            time.sleep(1)

if __name__ == "__main__":
    lfr = LeaderFollowerReplication("original_data")
    leader_thread = threading.Thread(target=lfr.start_leader)
    follower_thread = threading.Thread(target=lfr.start_follower)

    leader_thread.start()
    follower_thread.start()

    leader_thread.join()
    follower_thread.join()

示例代码

  • LeaderFollowerReplication类 —— 存储要复制的数据并治理锁机制

    • 这个类有两个锁,leader_lockfollower_lock,以确保两个线程中一次只有一个能够拜访数据。
    • start_leader办法 —— 运行领导者线程,首先获取 leader_lockfollower_lock,以确保跟随者线程没有批改数据。而后将数据设置为与跟随者的数据相等,并在将数据设置为与本人的数据相等之前休眠 0.5 秒,最初开释锁。
    • start_follower办法 —— 运行跟随者线程,获取 follower_lock 并查看跟随者的数据是否与以后数据不同。如果是,将数据设置为等于跟随者的数据,而后开释 follower_lock 并休眠 1 秒。
  • 创立 LeaderFollowerReplication 实例,初始数据值为 ”original_data”。而后创立两个线程,一个用于领导者,一个用于跟随者。

长处

  • 即便跟随者节点呈现故障,领导者节点也能够持续解决写申请并保持数据的一致性
  • 随着跟随者节点数量的减少,零碎能够在不影响领导者节点性能的状况下解决大量读申请
  • 确保所有节点都有统一的数据正本

毛病

  • 如果领导者节点故障,零碎将无奈解决写申请,直到选出新的领导者节点
  • 因为每次更新都必须播送到所有跟随者节点,因而会产生更多网络流量

实用场景

  • 须要在多个地点保持一致库存的电子商务网站
  • 须要在多个分支机构之间放弃账户余额统一的金融机构
  • 须要在多个服务器上放弃用户材料统一的社交媒体平台

挑战

  • 很难确保所有节点在任何时候都有雷同的数据正本
  • 如果领导者节点必须解决大量写申请,那么有可能成为瓶颈
  • 零碎必须可能检测并从节点故障中复原,以保持数据一致性

参考文献

How does MVCC (Multi-Version Concurrency Control) work

What is MVCC? How multi-version concurrency control works

Chapter 13. Concurrency Control

Multiversion Concurrency Control (MVCC)

Setting Multi-Version Concurrency Control (MVCC)

Open Source Database (RDBMS) for the Enterprise

Distributed Snapshots

Distributed Snapshots

Distributed Snapshot Problem – Distributed Systems for Practitioners

Distributed Snapshots

Reading Group. Distributed Snapshots: Determining Global States of Distributed Systems

Distributed snapshots for YSQL

An example run of the Chandy-Lamport snapshot algorithm

DB Replication (I): Introduction to database replication

Chapter 5. Replication – Shichao’s Notes

Leader and Followers

Understanding Database Replication

Leaders and Followers

Multi-master vs leader-follower

Data Replication – Grokking Modern System Design Interview for Engineers & Managers

Build software better, together


你好,我是俞凡,在 Motorola 做过研发,当初在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓重的趣味,平时喜爱浏览、思考,置信继续学习、一生成长,欢送一起交流学习。微信公众号:DeepNoMind

本文由 mdnice 多平台公布

正文完
 0