并行系统Parallelism System(三)

48 阅读6分钟

并行系统Parallelism System(三)

并发控制 (Concurrency Control)分布式事务 (Distributed Transactions)

单机系统中,学习了锁(Locks)、信号量(Semaphores)等机制处理并发,在分布式环境中,这些机制不够用,甚至无法适用

q1:为什么并发控制在分布式系统中会变得更加复杂

  1. No Shared Memory(缺乏共享内存):
    1. 单机: 所有线程/进程都运行在同一台机器上,共享同一份物理内存。可以直接通过内存地址访问共享数据,并通过内存级的原子指令来实现锁
    2. 分布式: 不同的节点(机器)拥有自己独立的内存空间。它们之间不能直接访问对方的内存。所有通信都必须通过网络(如 RPC)进行消息传递。这意味着你无法直接用一个简单的 lock() 语句来保护一个跨多台机器的数据
  2. 独立故障(Independent Failures)的常态化
    1. 单机: 如果某个线程在持有锁时崩溃,整个进程可能随之崩溃,操作系统通常会清理资源,释放锁
    2. 分布式: 一个节点可能在持有“分布式锁”时突然崩溃(电源故障、网络中断、软件崩溃)。如果其他节点不知道这个节点已经崩溃或者无法有效释放这个锁,那么被锁定的资源可能永远无法被其他节点访问,导致死锁 (deadlock)活锁 (livelock),从而影响系统的可用性
  3. 网络的不确定性 (Network Unreliability/Latency/Partitions):
    1. 单机: 函数调用几乎没有延迟
    2. 分布式: 网络通信存在不可预测的延迟。一个锁请求可能因为网络拥堵而延迟到达;一个锁释放消息可能在传输过程中丢失
    3. 网络分区 (Network Partitions): 这是最糟糕的情况。整个网络可能会断裂成多个互不相连的子网。在分区发生时,两个子网的节点可能都认为自己拥有对共享资源的独占访问权,导致**“脑裂 (Split-Brain)”**问题,从而产生严重的数据不一致或数据损坏
  4. 没有全局时钟 (No Global Clock):
    1. 单机: 很容易确定事件的发生顺序(例如,线程 A 在线程 B 之前访问了数据)
    2. 分布式: 不同的机器有自己独立的本地时钟,这些时钟可能不同步。由于网络延迟的存在,很难精确判断一个事件在另一个事件之前还是之后发生。这使得在分布式环境中协调操作顺序变得极其困难

事物 Transactions

事务是一个非常重要的概念,它源于数据库领域,但其原则同样适用于分布式系统中的数据管理

What is a Transaction?

  1. 一个逻辑上的操作序列,这个序列中的所有操作要么全部成功完成,要么全部失败回滚,就好像它们是一个不可分割的原子操作一样
  2. 数据库系统(以及分布式系统中的事务系统)通常需要满足以下四个核心属性,被称为 ACID 特性
    1. 原子性 (Atomicity):
      1. 事务是最小的执行单位,要么所有操作都成功提交,要么所有操作都失败回滚(Rollback)。不存在部分完成的状态
      2. 确保数据的完整性
    2. 一致性 (Consistency):
      1. 含义: 事务执行前后,数据库或系统的数据从一个有效状态转换到另一个有效状态。它遵循所有预定义的规则、完整性约束(如非空约束、唯一性约束、外键约束)和业务逻辑
      2. 目标: 确保数据在逻辑上是正确的
    3. 隔离性 (Isolation):
      1. 含义: 多个并发事务的执行互不干扰,就好像它们是串行执行的一样。一个事务在执行过程中,不会看到其他并发事务的中间状态
      2. 目标: 避免并发操作导致的数据读写冲突,例如脏读 (Dirty Read)、不可重复读 (Non-Repeatable Read)、幻读 (Phantom Read) 等
    4. 持久性 (Durability):
      1. 含义: 一旦事务成功提交,它对数据的修改就是永久性的,即使系统发生故障(如电源故障、系统崩溃),这些修改也不会丢失
      2. 目标: 确保数据不会丢失

存根 (Stub)

RPC 中,存根 (Stub) 是一个特殊的代码模块,它扮演着代理 (Proxy) 或适配器 (Adapter) 的角色,目的是隐藏网络通信的复杂性,让远程调用看起来像本地调用;分为客户端存根 (Client Stub)服务端存根 (Server Stub)

  1. 客户端存根 (Client Stub)

    1. 运行在客户端机器上

    2. 客户端应用程序直接调用的那个“远程函数”

    3. 职责:

      1. 封装调用: 客户端程序调用它时,它会截获这个调用
      2. 参数打包 (Marshaling / Serialization): 将客户端传入的函数参数打包(即序列化)成适合通过网络传输的格式(字节流)
      3. 寻找服务器: 确定远程服务的地址(IP 地址和端口)
      4. 网络发送: 将打包好的请求通过网络发送到服务器
      5. 等待响应: 阻塞等待服务器的响应(或者在异步 RPC 中处理回调)
      6. 结果解包 (Unmarshaling / Deserialization): 收到服务器的响应后,将其解包(即反序列化)成客户端程序能理解的数据结构
      7. 返回结果: 将结果返回给客户端调用者

      eg:把它想象成一个本地的“电话机”,对着它说话(调用函数),它负责把声音(参数)转换成信号(字节流)通过电话线(网络)发送出去

  2. 服务端存根 (Server Stub) 或 骨架 (Skeleton)

    1. 运行在服务器端机器上

    2. 远程服务和 RPC 运行时系统之间的桥梁

      1. 接收请求: 从网络中接收到客户端发送过来的请求字节流
      2. 参数解包 (Unmarshaling / Deserialization): 将接收到的字节流解包(反序列化)成服务器程序能理解的函数参数
      3. 调用本地函数: 使用这些参数,调用服务器上真正的业务逻辑函数
      4. 结果打包 (Marshaling / Serialization): 将业务逻辑函数的返回值打包(序列化)成字节流
      5. 网络发送: 将打包好的结果通过网络发送回客户端

      eg:想象成一个服务器上的“电话机”,它接收到信号(字节流)后,转换成听得懂的话(参数),然后交给实际的“人”(业务逻辑函数)去处理,再把“人”的回复(结果)转换成信号(字节流)发回去

  3. 存根就是 RPC 框架自动生成的一段代码,作为客户端和服务端之间通信的“中间人”,隐藏了底层的网络通信和数据转换细节,使得远程调用看起来就像本地调用一样