ONOS集群原理及流程分析

作者简介:陈晓帆,毕业于中山大学,博士,目前是深信服的技术专家,主要负责SDN/NFV,云计算相关的预研工作。这份文档主要是依据我和另外两位小伙伴(hgl,zll)最近对ONOS的原理和代码流程的分析整理而成。本文所有观点仅代表作者个人观点,与作者目前所在的公司无关。

一、ONOS集群原理简介

ONOS是一个分布式的控制器,为了提高数据的读写效率,采用自实现的基于In-Memory的Key-Value数据存储系统。针对实际的需要,不同的数据模型采用不同的数据一致性方法,即强一致性(strong consistency)和最终一致性(eventually consistency)。ONOS使用raft协议实现强一致性,使用Gossip协议实现最终一致性。

ONOS在后面的版本中使用自研的基于raft协议的分布式存储系统,ONOS使用的是基于Java实现的CopyCat版本,采用基于raft协议的分布式协同框架Atomix。

为了提高数据的访问效率,ONOS数据采用了分片存储,在ONOS形成集群后,会在$ONOS_ROOT/下生产一个config文件夹,文件夹里面有个cluster.json文件,里面就是该ONOS的分片信息。ONOS启动后,PartitionManager会根据分片信息来创建相应的目录和文件,如$KARAF_ROOT/data/partitions/目录下的文件夹及文件。

二、Partition形成

ONOS开启后,PartitionManager会作为服务而被加载,加载时就会调用它的activate()方法,它会根据分片信息(也就是本机存储的Metadata)来创建partitions目录下的信息,该文件夹会作为参数传入StoragePartition的构造函数。StoragePartition是ONOS自实现的Key-Value存储系统中Value的一种类型。

PartitionManager接下来会调用第三方的框架去实现集群的管理。首先是创建相应的StoragePartitionClient,StoragePartitionServer来为集群的运行和管理做准备。程序会根据每个Partition的members的组成来决定相应的Server和Client的开启。

开启Server:

在开启Server的过程中会在该Server的文件夹下创建几个文件来辅助集群的运行,这几个文件可以在$KARAF_ROOT/data/partitions/1(示例实验是单个节点,所以只有一个partition)中看到,分别是该raft集群的:meta;log;snapshot。(ONOS的raft是实现是用了第三方的框架,该三个文件夹的具体作用,以及存储了哪些东西,或是自己想要开发新的东西,都可以去Coptcat的官网查看具体说明)。

下图是CopycatServer相关文件,管理等的创建,具体可以参考CopycatServer的builder()方法(可以单步调试看看ServerContext的创建过程):


以上是进入ServerContext的创建过程,红色框框即是Partition中存储的文件,Copycat中都有对应的类对应。Raft的存储和一致性就是通过日志来实现的。Snapshot只要是辅助Log Compaction。

开启Client:

DistributedClusterStore中没有用到分布式原语,所以它只是保存单机的数据,不会被同步到其他机器上面。

三、集群形成

使用onos-form-cluter命令形成集群时,会调用ClusterManager的formCluster()方法。

这个方法会调用buildDefaultPartitions()来创建Partitions。

首先对node进行排序,设置框大小为partitionSize,每次在框里面的node就是该Partition的member,框每次向右移动一个node,partition的命名从1开始依次递增至n(n为集群中onos的总数)。

formCluster方法会创建形成集群后的Metadata,并设置该数据,该数据会写到硬盘的$ONOS_ROOT/config/cluster.json文件里面,然后onos会删除partitions文件夹和它里面的内容,重新启动后,onos会按照cluster.json文件里面的信息来启动onos,PartitionManager会根据Metadata里面的partitions信息来创建partition。

onos原始的形成集群流程如下:

四、copycat日志分析

Java8提供了一种函数风格的异步和事件驱动编程模型CompletableFuture。

日志是raft一致性算法的核心,当命令提交到群集时,表示状态更改的条目被写入磁盘上的有序日志中。日志提供了实现持久性和一致性的机制。

但是日志在管理磁盘消费方面有特殊的挑战。随着命令逐渐写入每个服务器上的日志中,日志文件会消耗越来越多的磁盘空间。最终,每个服务器上的磁盘空间会被日志文件耗尽。

raft一致性算法的典型实现使用基于快照的方法来压缩服务器日志。但是为了寻找更一致的性能,并且由于Copycat会话事件算法的独特需求,Copycat选择了一种增量日志压缩算法。

Copycat的日志被分成若干段,日志的每个段都由磁盘上的一个文件(或内存块)表示,每个段都包含一系列条目。一旦某个段变得完整,要么取决于它的大小,要么取决于条目的数量——日志会滚到一个新的段中。每个段都有一个64字节的标题,用来描述段的起始索引、时间戳、版本以及与日志压缩和恢复相关的其他信息。

日志中的每个条目都是用16位无符号长度、32位无符号偏移量和可选的64位术语编写的。因为raft保证日志中的术语是单调递增的,所以这个术语只写在某个给定段中的第一个条目中,所有后面的条目都继承这个术语。当附加一个新项的条目时,该条目用新术语编写,后面的条目继承这个术语。

五、强弱一致性分析

ONOS提供了一些分布式数据结构(distributed primitive)来实现数据的强一致性和最终一致性存储。应用开发者可以运用它们来开发相应的应用。强一致性是通过raft来实现,而弱一致性是通过事件乐观异步复制和anti-entropy(gossip)协议实现最终一致性。

弱一致性如EventuallyConsistentMap用来存贮一个最终一致性map,当有节点的map值发生更新时,ONOS会广播更新时间,其它的节点会通过比较时间戳来更新map的值。另外,当有新节点加入或有节点的数据突然丢失时,ONOS使用anti-entropy(gossip)协议来确保数据的最终一致性。这些状态都是存储在ONOS的内存里面,所以当整个集群重新启动时,数据会丢失。

5.1 强一致性分布式存储实例分析

强一致性分布式存储实例主要通过Network/config的API接口下发配置。配置信息保存在DistributedNetworkConfigStore中,该配置信息的保存使用了强一致性的分布式原语ConsistentMap。

上图第一个红框是存储配置信息ConsistentMap的声明,第二个红框是ConsistentMap的创建初始化,这个过程是在DistributedNetworkConfigStore的acvivate()方法中进行的。

这个过程调用storageService来创建我们的分布式数据结构。

onos调用storageService服务来创建ConsistentMap,DefaultConsistentMapBuilder会提供一些方法来设置ConsistentMap的属性,设置完了属性之后,调用build()方法来创建符合要求的ConsistentMap。

数据如何映射到分片信息,Hasher是系统写的对象映射PartitionId的接口,他的实现具体如下:

具体的调用过程如下:

Hasher是系统写的对象映射PartitionId的接口,该接口决定了配置数据是如何映射到分片信息。

5.2 弱一致性分析

ONOS提供了一些分布式数据结构(distributed primitive)来实现数据的强一致性和最终一致性存储。下面来讨论一下ONOS的弱一致性。

EventuallyConsistentMap是ONOS提供的用来实现弱一致性的分布式原语,它的实现类中提供了一系列参数来设置它的属性,其中就有一个是设置该Map的值是否存储在硬盘上,下面就流程做一个简单的说明。以下例子是以网络拓扑为例。

在DistributedTopologyStore的activate()方法中,我们在创建存储网络拓扑EventuallyConsistentMap的地方打上断点。

上图第一个红框是DistributedTopologyStore中EventuallyConsistentMap的声明,第二个红框是它的创建初始化。初始化时,会调用StorageService去创建,上图红框是要传递的参数persistentService,弱一致性的数据想要存储在硬盘上面,必须通过该服务来创建相应的persistentMap或者persistentSet,该项服务会把数据存储在由PersistenceManager提供的localDB中。

上图是EventuallyConsistentMap的创建过程。这个过程通过EventuallyConsistentMapbuilderImpl来控制,上面两个红框是我们今天重点关注的两个属性,一个就是我们的persistentService,还有一个就是设置是否将弱一致性数据保存在硬盘上面。

EventuallyConsistentMapbuilderImpl提供了一些方法来设置属性。

属性等设置完毕后,要调用build()方法来创建EventuallyConsistentMap。

最后通过EventuallyConsistentMapImpl去创建EventuallyConsistentMap,过程中会根据persistent(boolean)的值来决定是否调用persistentService这个服务。

参考资料

CompletableFuture官网的API:http://docs.oracle.com/javase/8/docs/api/
CopyCat可参考官网网址:http://atomix.io/copycat/
CopyCat的Log网址:http://atomix.io/copycat/docs/log-architecture/
CopyCat的Log Compaction网址:http://atomix.io/copycat/docs/log-compaction/


  • 本站原创文章仅代表作者观点,不代表SDNLAB立场。所有原创内容版权均属SDNLAB,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用,转载须注明来自 SDNLAB并附上本文链接。 本站中所有编译类文章仅用于学习和交流目的,编译工作遵照 CC 协议,如果有侵犯到您权益的地方,请及时联系我们。
  • 本文链接https://www.sdnlab.com/19692.html
分享到:
相关文章
条评论

登录后才可以评论

sailchen 发表于17-08-07
1