分布式系统概念与设计读书笔记-进程间通信

Posted by keys961 on July 10, 2018

1. Overview

UDP: 提供消息传递(数据报)的抽象,即进程间通信的最简单形式

TCP:提供进程对之间双向流通信的抽象,通信的信息由没有消息边界的数据项组成。生产者产生数据项,当发送缓冲区满则要等待;消费者消费数据项,没可用数据时则要等待

2. 互联网协议API

2.1. 进程间通信特征

a) 同步 vs 异步

同步通信:

发送进程和接收进程在每个消息上同步,此时sendreceive都是阻塞的,即

  • 当前send后,只有这个消息被receive了,才能继续下一个send,否则阻塞
  • 每个receive发送后,若没有消息进来,则阻塞直到消息到达为止

异步通信:

  • send是非阻塞的,只要消息被复制到缓冲区即可(发送进程就可以继续下面的处理操作,而不被阻塞),消息传递与发送进程并行运行
  • receive可为阻塞或非阻塞
    • 不阻塞下:接收进程发出receive操作后,接收进程可继续执行下面的操作,而receive则在后台提供一个接收缓冲区,因此,需要通过轮询/中断通知接收进程缓冲区已满
    • 阻塞下:同同步通信,每个receive发送后,若没有消息进来,则阻塞直到消息到达为止

b) 消息目的地

消息发送到的目的地由:网络地址、端口组成

一个端口只能有一个接收者(组播例外),但可有多个发送者

进程可用多个端口来接收消息,但不能与其它进程共享端口(组播是例外)

c) 可靠性

有效性而言,若点对点消息服务在丢失了合理数量的数据包后,仍能保证发送消息

完整性而言,消息不能有损坏,且消息不能重复

d) 排序

有些应用要求消息按照发送放的顺序组织发送与接收,顺序不一致,则也被是认为失败的消息传递

2.2. 套接字

源于BSD UNIX,TCP与UDP都使用套接字(Socket)抽象。

套接字提供进程间通信的一个端点,每个套接字与某个协议有关(TCP/UDP)。

Java API互联网地址:InetAddress

InetAddress类用于表示网络地址,支持输入域名,通过DNS方式获取网络地址,不需要显式给出地址值,隐藏了底层细节

2.3. UDP

a) 有关数据报通信的问题:

  • 消息大小:接受进程要指定固定大小的接受的字节数组,若超出容量,则消息会被截断。因此若有应用程序发送很大的消息,则需要将消息进行分割。通常由应用(如DNS)决定消息大小——不用太大,适当即可
  • 阻塞:通常套接字使用非阻塞send和阻塞receive(也会使用非阻塞receive)。除非设置超时,receive将会阻塞直到有一个数据报出现为止。因此若要在等待消息时还有其它工作要做,需要安排它单独一个线程
  • 超时:receive不适合无限制等待下去,因为消息很可能丢失了。所以最好要设置一个合适的超时时间
  • 任意接收:receive不指定接收的消息源,即用receive可获得任何来源发送到该套接字上的消息,并返回发送方的网络地址和端口,允许接收方来检查。因此,可将UDP套接字连接到某个远程端口和网络地址,只接收/发送该地址的消息

b) **故障模型: **

  • 遗漏故障:消息可能丢失(3个地方都可能发生故障)
  • 排序:消息没有按照发送方顺序发送/接收

c) **使用例子: **

如DNS, VOIP。

它没有保证消息传递可靠的额外开销,来自下面3个方面

  • 需要在源和目的地存储状态消息
  • 传输额外消息
  • 发送方延迟

d) UDP Java API: DatagramPacket&DatagramSocket

DatagramPacket: 数据报本身的实例

DatagramSocket: UDP的套接字,包含方法

  • send&receive: 发送和接收
  • setSentTimeout: 为接收设置超时
  • connect: 连接远程的网络地址和端口,这时套接字只能从/向该地址接收/发送消息

2.4. TCP

a) TCP流抽象隐藏了下列特征:

  • 消息大小:应用能选择它写到流和从流中读取的数据量,因为TCP是基于字节流的
  • 丢失消息:消息丢失后,接收方是不回复ACK的,发送方在超时时间窗口内接收不了这个回应后,会重传。而滑动窗口方案减少了所需确认的消息个数。
  • 流控制:TCP试图匹配读写流的进程的速度
  • 消息重复和排序:每个IP数据包与消息标识符相关联,这能让接收方能检测和丢弃重复消息,以及排序消息
  • 消息目的地:需要建立连接才能通信,然后通信就不需要使用网络地址和端口了

b) 原语:

服务端:create -> bind -> listen -> accept -> send/recv -> close

客户端:create -> bind -> connect -> send/recv -> close

一端close指自己不写消息(即不发送消息了,将剩余的消息全部发出),但还可以接收对方的消息

c) 流通信重要问题:

  • 数据项匹配:两个通信进程需要对流上数据内容达成一致
  • 阻塞:写入(接收)流的数据保存在缓冲队列中,阻塞下,进程从队列获取数据,若没数据则阻塞。
  • 线程:服务端接收连接时常创建一个新线程用于客户通信,不延误其它客户。在不提供线程环境下,另一种方式是试图在读取数据前测试流输入是否可用,select机制就是这个目的(在Java NIO中Selector有所应用,轮询测试可用的Channel

d) 故障模型:

尽管TCP是可靠通信,但是当连接上的数据丢失超过限制,并且网络很不稳定或拥塞严重,那么发送端收不到接收端的确认消息,这种情况下一段时间后,TCP会声明连接中断,这时TCP通信不可靠。

而中断后,进程还试图写/读,会有一下后果

  • 不能区分是网络故障还是连接另一端进程故障
  • 不能区分最近发送的消息是否被接收

e) 使用例子:

如HTTP, FTP, telnet, SMTP等

f) TCP Java API: ServerSocketSocket

ServerSocket: 服务端套接字,用于监听和接收,接收后返回一个Socket实例

Socket: 供连接的一对进程/线程使用的套接字

交互则是往流中写/读消息(用Java IO,都是基于流的,如BIO)

具体略过,比较简单

TCP NIO需要使用Channel,通信时使用ServerSocketChannelSocketChannel,然后通过注册Selector并经过其测试后,在Buffer中进行交互,提供非阻塞模式

UDP NIO则使用DatagramChannel

3. 外部数据的表示与编码

3.1. 相关概念

  • 编码 & 解码:
    • 编码(Marshalling): 将多个数据项组装成适合消息传送的格式过程
    • 解码(Unmarshalling): 消息到达后分解消息,在目的地生成相等的数据项的过程
  • 外部数据表示:一种表示数据结构和简单值一致的标准,例子有:
    • CORBA
    • Java Serialization
    • XML
    • Protobuf
    • JSON

3.2. 远程对象引用

这只适用于如Java和CORBA这样的支持分布对象模型的语言,而XML不支持。

当要调用远程对象的一个方法,需要一个标志符,即远程对象引用,它在这分布式系统中有效。

而远程对象引用在调用消息中传递,以指定调用哪一个对象。

一种引用格式可参照下面的格式,保证以时间和空间的唯一性方法生成:

  • 网络地址(32bit)
  • 端口号(32bit)
  • 时间(32bit)
  • 对象编号(32bit)
  • 对象方法/接口

此外远程对象引用不应该作为远程对象地址使用,因为为了使远程对象在不同计算机的不同进程中重定位

4. 组播

组播:将单个消息从一个进程发送到一组进程的每个成员的操作,组的成员对发送方透明

组播为构造下列特征的分布式系统提供基础设施:

  • 基于复制服务的容错:相同服务复制到一组服务器上以容错
  • 在自发网络中发现服务:客户端和服务端能使用组播消息找到可用的发现服务,以便于在分布式系统中注册服务接口或查找其它服务的接口
  • 通过复制的数据获取更好的性能:组播用于复制数据到其它各个进程
  • 事件通知的传播:如发布-订阅协议,使用组播发布消息

4.1. IP组播——组播通信实现

a) IP组播

其在IP协议上层实现,让发送方将单个IP包发给组成组播组的一组计算机。

组播组:由D类互联网地址指定,即IPv4中前四位是1110的地址。

组的成员是动态的,即可随时加入和离开,成员也可以加入任意数量的组。

同样无需称为某个组的成员,即可向该组组播消息。

应用层上,IP组播只能通过UDP进行。

对于IPv4, 组播有下列特点:

  • 组播路由器:IP包既能在局域网,又能在互联网上组播,后者利用组播路由器

TTL: 存活时间,消息经过一次路由,TTL减一,到0后消息将被丢弃

  • 组播地址分配:由IANA全局管理,将地址空间分为:

    • 本地网络控制块(224.0.0.0 ~ 224.0.0.225)
    • 互联网控制块(224.0.1.0 ~ 224.0.1.225)
    • Ad Hoc控制块,用于不适合其它任何网络的通信(224.0.2.0 ~ 224.0.225.0)
    • 管理块,用于实现组播通信的作用域机制(239.0.0.0 ~ 239.255.255.255)

    地址可以是永久的,也可以是暂时的。

b) 故障模型

同UDP数据报故障模型,IP组播也称为不可靠组播(也有可靠的组播,如最严格的全排序组播

c) IP组播 Java API: MulticastSocket

MulticastSocketDatagramSocket的子类,使用了UDP协议。

在父类的基础上又:

  • joinGroup: 给一个给定组播地址加入到一个组播组中
  • leaveGroup: 进程离开指定的组播组
  • setTimeToLive: 设置TTL,默认1,即仅在当前局域网中传播

5. 网络虚拟化:覆盖网络

网络虚拟化:涉及在一个已有的网络上,构造多个不同的虚拟网络,每个虚拟网络被设计成支持一个特定的分布式应用。

如一个虚拟网络可支持多媒体流,它支持与多人游戏的虚拟网络共存。而它们都在相同的底层网络上运行——即虚拟网络能建立在已有的网络上,并对其适当优化,而不改变底层网络特性

计算机网络又自己的寻址模式、协议和路由算法,虚拟网络也有自己特定的这些内容

5.1. 覆盖网络

覆盖网络:由一组结点和虚拟连接组成的虚拟网络,建立在底层网络的之上,提供独有的功能,常被称为应用层网络

它的好处:

  • 不改变底层网络特性即可进行新的网络服务定义
  • 鼓励对面向特定应用的定制(如分布式散列、组播等等)
  • 能定义多个覆盖网,能同时存在,相互覆盖,从而让网络体系结构更开放,更可扩展

不足是:引入额外一层(标准体系结构外的一层),存在性能损失,增加了网络服务的复杂性

覆盖网络例子:Skype,一个对等应用,提供VoIP服务

6. MPI

MPI (Message Passing Interface),即消息传递接口,是一个标准,为一组具有同步和异步支持的消息传递操作提供API

同步版本使用阻塞sendreceive; 异步版本使用非阻塞sendreceive无要求

6.1. 底层体系模型

相对简单,类似于2.1小节的进程通信模型,但显式在发送方和接收方添加了MPI缓冲区,这些缓冲区由MPI库管理,并用于在传送时保留数据。

“安全返回”多种不同解释,对于同步发送而言:

  • 通用层次,即在传送中或已经传递,发送者的缓冲区可被再次使用(如MPI_send
  • 已经传递到(如MPI_Ssend
  • 被拷贝到MPI缓冲区且正在传送中(如MPI_Bsend
  • 被拷贝到MPI缓冲区,发送者应用缓冲区能被重用(如MPI_Rsend

标准支持阻塞接收和非阻塞接收,发送和接收的变种在任一组合中成对出现。

此外该标准定义了多种多路通信模式(即群通信),如一对多(散步),多对一(聚集)