这里的通信是指计算实体之间的数据交换。在每个计算实体的本地计算完毕后,相互之间交换消息数据。
Hama 中的每个计算实体称为BSPPeer. BSPPeer持有MessageManager对象,该对象负责两方面工作:
这两工作先后处理。
BSPPeer在初始化时会做一些通信准备工作:设置MessageManager的通信地址与端口;获取其他BSPPeers的通行地址端口;启动RPC服务器,提供远程调用功能。
整体的时序图如图1-1:
图1-1 Hama通信模块时序图
消息通信中最重要的数据结构就是消息数据的的组织。Hama实现的BSP模型采用“计算-通信-同步”的步骤串行执行,一共三种消息:本地计算所需的消息,在计算过程中会产生新的消息,计算结束后将产生的消息发送至对应的远端BSPPeer和接收远端BSPPeer发送来的消息。因此其消息存储分为三部分:msgQueue, localQueueForNextIteration和outgoing队列。Hama在具体实现中,这三种消息组织如图1-2,1-3:
图1-2本地计算所需的消息
在图的处理是以顶点为中心,即按照vertex的ID依次计算,每次计算,需要该vertex对应的上一步发送来的消息,因此local Queues的消息组织为Map<vertexID, Queue>。
图1-3 消息发送队列与接收队列
假设有n个peer,那么,每个peer在本地需要n个outgoing队列用来存放本地计算产生的消息;需要1个localQueueForNextIteration用来接收n个peers通过RPC传送过来的消息。因此这两种消息组织形式相同,均为Map<PartitionID, Queue>形式。
当新的一次迭代开始,需要将localQueueForNextIteration中的消息转换为msgQueue格式的消息,以供计算所用。
从上可知,在内存中,各消息队列在内存中转换情况如下:
首先看Hama在本地计算迭代逻辑中与通信相关的关键代码,这里是消息数据产生的源头。
while (NeedContinued) { peer.sync(); ... ... Map<V, LinkedList<M>> messages = parseMessages(peer); ... ... for (vertex : Vertices) { bsp.compute(msgs); } }
while循环表明bsp是一个迭代模型,条件NeedContinued决定迭代何时终止。第2行peer.sync()中进行和其他peers之间的消息数据交换和同步。第4行进行消息数据的格式转换,将localQueueForNextIteration( Map<PartitionID,Queue>)转换为localQueue(Map<VertexID, Queue>)。第6行开始的for循环将围绕每一个顶点和其对应的上一步接收到的数据进行本地计算。
在用户自定义的bsp.compute(msgs)中,产生的消息将交给图1-4所示的相关类处理。
图1-4 hama通信相关类
如1.1提到的,BSPPeer持有MessageManager对象是整个消息通信的关键对象,图1-4中MessageManager接口规定的两个重要方法send 和 transfer是它承担两方面工作的具体行为:前者供本地计算时调用,将产生的message 发送到本地的outgoig队列中;后者在本地计算结束后,将outgoing队列整体传送到目的地。图1-4中其他的类分别围绕这两方面的工作继承并实现。
具体来说,最上面的接口MessageManager规定了消息服务的两个主要方法:向outgoing队列缓存数据的send方法 (在bsp.compute(msgs)方法中被调用),和向其他peer发送数据的transfer方法。抽象类AbstractMessageManager主要实现了以send方法为中心的服务,比如三种消息队列的实体以及send方法。CompressableMessageManager在上面的基础上继续扩展,使得outgoing队列在具有可压缩的特性。
右边的接口HadoopMessager规定了RPC服务器、客户端的方法。hadoopMessager A可以作为RPC客户端远程使用服务器端hadoopMessager B的put方法将本地的outoging队列存放至B的localQueueForNextIteration队列中。
最下面的HadoopMessagerManagerImpl是最终实现以上接口、抽象类的类,作为BSPpeer的属性与其他BSPpeer交互。
outgoging队列被发送前,可以压缩,以减少通信代价。首先将消息队列转换为字节流,再用指定的压缩方法压缩,这里提供了两种:Bzip2和Sanpy。Snap是google 开源的压缩工具。若压缩比率小于1,则使用RPC发送压缩队列,否则,发送原队列。
Hama提供了DiskQueue作为localQueueForNextIteration的磁盘版本。即其他peers通过RPC发送的消息数据均存入DiskQueue中,可以减轻内存压力。
DiskQueue的实现简要如下:
使用Hadoop提供的io类 FSDataOutputStream writer和FSDataInputStream reader对消息进行磁盘读写。
消息存放在bsp.disk.queue.dir文件下的同一个文件中。基本的操作如下:
add(M item){ size ++; new ObjectWritable(item).write(writer); }
M poll(){ size--; writable.readFields(reader); return (M) writable.get(); }
远程过程调用对底层通信细节封装,对外提供RPC服务,远端的进程可以获取本地localQueueForNextIteration队列的句柄,并将远端的消息传给句柄,发送到本地。
Hama使用两种PRC,一种是hadoop提供的RPC服务,一种是Apache开源项目Avro。Avro是一个数据序列化框架,支持RPC调用。