官网的文档一直在变化,可能翻译时的文档跟目前文档有出入,请以官网文档为准。
集群是使多个进程和程序作为一个整体工作的机制。举例,当在google.com网站上查询资料时,查询请求好像只有一台web服务器在处理。实际上,查询请求是被一个集群中多台相互连接的web服务器处理的。类似地,OpenDaylight也可以有多个实例作为一个整体对外提供服务。
集群的优势:
- 可伸缩性:如果有多个OpenDaylight实例在运行,一般情况下比仅有一台实例会做更多的任务和存储更多的数据。也可以把数据分解成块(片),然后把数据散布到整个集群,或者在集群中的某个实例执行某个操作。
高可用性:如果有多个OpenDaylight实例在运行,其中一个实例宕机,其它实例仍然可以工作和提供服务。
数据持久化:手动重启或者宕机,不会丢失存储在OpenDaylight的任何数据.
下面这部分描述如何搭建集群。
以下部分描述了如何用OpenDaylight搭建多节点集群。
实现集群,需要注意如下事项:
推荐最少3台机子来搭建多节点集群。也可以搭建只有两个节点的集群。但是,如果2台中有一台宕机,则集群就不可用。
注意:这是因为OpenDaylight要求大部分节点需要运行,而一个节点显然不能称作是大部分节点。
集群中的每个实例都需要有一个标识符。OpenDaylight通过使用节点role来达到这个目的。当在akka.conf文件中定义第一个节点的role为member-1时,OpenDaylight就使用member-1来标识该节点。
数据片(译注:这个概念对于文章理解很重要,请查看wiki Share)用于存储OpenDaylight MD-SAL数据库所有数据或者某个片段的数据。例如,一个数据片可以存储所有的inventory数据,而另外一个数据片可以存储所有的topology数据。
如果没有在modules.conf文件中指定一个module,且在module-shards.conf文件中也没有指定一个分片。默认所有的数据存放在default数据片中(该default分片必须在module-shares.conf文件预先定义好)。每个分片都有一个可配置的replicas。在module-shares.conf中可以指定replicas详情,replicas表示分片位于哪个节点(译注:比如memeber-1,memeber-2)。
如果有一个3节点的集群,想要能够容忍任意节点的宕机,那么每个数据片的副本必须运行在3节点的集群上。(译注:意思是如果想在集群中某台机子宕机的情况下还能正常服务,没有数据缺失,那么你必须把每一个数据片复制到3个节点上)
注意:这是因为OpenDaylight的集群实现要求大部分数据片都运行才能提供服务。如果只在集群中的2个节点定义数据片副本(译注:即该数据片只有两个复制)且其中一个节点宕机,相应的该数据片无法运作(译注:一个数据片无法成为大部分。虽然还有一个节点正常运行,但是也不运行的意思)
如果有3个节点的集群且某个数据片在所有节点上定义副本,即使该集群只剩下两个节点在运行,该数据片仍然能够运行。注意如果剩下的两个节点又宕机一个,则该数据片将不可操作。
推荐配置多个节点。集群中的某个节点启动,它会发送一个消息给所有的节点。然后该节点会发送一个加入命令给第一个响应它的节点。如果没有节点回应,该节点重复这个过程直到成功建立一个连接或者自己关闭这个过程。
集群中某个节点持续一段时间(默认10秒,可配置)还是不可达后(译注:不能连接),该节点自动下线。一旦节点下线,你需要重新启动它才能再次加入集群。一旦重新启动的节点加入集群,它将自动从主节点同步数据。
OpenDaylight包含一些用于配置集群的脚本。
注意:脚本是位于OpenDaylight distribution/bin 目录下,该脚本在仓库的位置是distribution-karaf/src/main/assembly/bin/
这个脚本被用于设置控制器集群中某个节点的集群参数(指akka.conf,module-shares.conf中的参数)。用户重启节点才能生效设置的参数。
注意:这个脚本可以在任意时刻使用,甚至在控制器第一次被启动之前。
用法:
bin/configure_cluster.sh <index> <seed_nodes_list>
index:1..N之间的整数,N是节点的数量。这个表示脚本配置的是哪一个控制器节点。
seed_nodes_list:集群中所有节点组成的ip地址列表,用逗号或者空格分隔。
index指向的IP地址应该是正在执行脚本的节点的ip地址。当在多个节点运行这个脚本时,保证seed_node_list这个参数值一样,而index在1到N之间改变。
可选,数据片可以通过修改“custom_shard_configs.txt”文件,在更多粒度进行配置,该文件和该脚本在同一个目录下。想要更多信息可以查看此文件。
举例:
bin/configure_cluster.sh 2 192.168.0.1 192.168.0.2 192.168.0.3
上面的命令将配置节点2(ip地址192.168.0.2),该节点位于由192.168.0.1 192.168.0.2 192.168.0.3三台机子组成的集群中
译注:这里需要注意一下步骤顺序,第10个步骤需要移到第2个步骤的下面进行,不然你将找不到第3步所说的配置文件。即你需要先启动karaf,然后运行feature:install odl-mdsal-clustering,退出karaf命令行界面后,在继续进行下面的步骤。
为了运行3节点集群的OpenDaylight,执行如下步骤:
首先选择3台机子用于搭建集群。之后在每台机子下做如下操作:
1.拷贝OpenDaylight发布文件zip(译注:linux为tar.bz2)到机子上。
2.解压发布版本文件。
3.打开下面的.conf文件:
configuration/initial/akka.conf
configuration/initial/module-shards.conf
4.每个配置文件做如下修改(译注:这里说的是akka.conf这个文件):
每个文件找到如下行的情况,然后用该配置文件所在机子即OpenDaylight运行所在机子的主机名或者ip地址替换127.0.0.1:
netty.tcp {
hostname = "127.0.0.1"
注意:对于集群中的每个节点该值是不一样的。
5.找到如下所示的行,然后用集群中的所有节点的机子的主机名或ip地址替换127.0.0.1(译注:这里的意思是集群有几个节点,数组中就有几个记录,只是ip做相应的修改):
cluster {
seed-nodes = ["akka.tcp://[email protected]:2550"]
6.找到如下部分,给每一个节点指定一个角色名。这里我们用member-1赋值给第一个节点,第二节点用member-2,第三个节点用member-3:
roles = [
"member-1"
]
注意:这个步骤,在每个节点应该使用不同的节点名。
7.打开configuration/initial/module-shards.conf文件,修改replicas属性,让每一个数据片复制到所有的节点
replicas = [
"member-1",
"member-2",
"member-3"
]
仅供参考,可以查看下面给出的==配置文件示例==
8.切换到/bin目录下。
9.运行如下命令:
JAVA_MAX_MEM=4G JAVA_MAX_PERM_MEM=512m ./karaf
10.在karaf命令行中运行如下命令启用集群功能(译注:这步移到第三步)
feature:install odl-mdsal-clustering
OpenDaylight此时应该运行在3个节点的集群上。你可以通过任意的节点访问存储在数据库中的数据。
==akka.conf== 示例文件:
odl-cluster-data {
bounded-mailbox {
mailbox-type = "org.opendaylight.controller.cluster.common.actor.MeteredBoundedMailbox"
mailbox-capacity = 1000
mailbox-push-timeout-time = 100ms
}
metric-capture-enabled = true
akka {
loglevel = "DEBUG"
loggers = ["akka.event.slf4j.Slf4jLogger"]
actor {
provider = "akka.cluster.ClusterActorRefProvider"
serializers {
java = "akka.serialization.JavaSerializer"
proto = "akka.remote.serialization.ProtobufSerializer"
}
serialization-bindings {
"com.google.protobuf.Message" = proto
}
}
remote {
log-remote-lifecycle-events = off
netty.tcp {
hostname = "10.194.189.96"
port = 2550
maximum-frame-size = 419430400
send-buffer-size = 52428800
receive-buffer-size = 52428800
}
}
cluster {
seed-nodes = ["akka.tcp://opendaylight-cluster-data@10.194.189.96:2550",
"akka.tcp://opendaylight-cluster-data@10.194.189.98:2550",
"akka.tcp://opendaylight-cluster-data@10.194.189.101:2550"]
auto-down-unreachable-after = 10s
roles = [
"member-2"
]
}
}
}
odl-cluster-rpc {
bounded-mailbox {
mailbox-type = "org.opendaylight.controller.cluster.common.actor.MeteredBoundedMailbox"
mailbox-capacity = 1000
mailbox-push-timeout-time = 100ms
}
metric-capture-enabled = true
akka {
loglevel = "INFO"
loggers = ["akka.event.slf4j.Slf4jLogger"]
actor {
provider = "akka.cluster.ClusterActorRefProvider"
}
remote {
log-remote-lifecycle-events = off
netty.tcp {
hostname = "10.194.189.96"
port = 2551
}
}
cluster {
seed-nodes = ["akka.tcp://opendaylight-cluster-rpc@10.194.189.96:2551"]
auto-down-unreachable-after = 10s
}
}
}
==module-shards.conf== 示例文件:
module-shards = [
{
name = "default"
shards = [
{
name="default"
replicas = [
"member-1",
"member-2",
"member-3"
]
}
]
},
{
name = "topology"
shards = [
{
name="topology"
replicas = [
"member-1",
"member-2",
"member-3"
]
}
]
},
{
name = "inventory"
shards = [
{
name="inventory"
replicas = [
"member-1",
"member-2",
"member-3"
]
}
]
},
{
name = "toaster"
shards = [
{
name="toaster"
replicas = [
"member-1",
"member-2",
"member-3"
]
}
]
}
]
OpenDaylight通过MBeans暴露数据片信息,可以通过JConsole,VisualVM或者其他JMX客户端来查看数据片信息,也可以通过使用Jolokia的REST API来暴露数据片信息,该API是安装karaf feature ==odl-jolokia==组件才有。
列出所有的可用的MBeans方案的URI:
GET /jolokia/list
使用下面REST,查询OpenDaylight实例的本地数据片信息。
比如config数据库信息:
GET /jolokia/read/org.opendaylight.controller:type=DistributedConfigDatastore,Category=ShardManager,name=shard-manager-config
比如operational数据库信息:
GET /jolokia/read/org.opendaylight.controller:type=DistributedOperationalDatastore,Category=ShardManager,name=shard-manager-operational
以下输出显示了该节点数据片信息:
{
"request": {
"mbean": "org.opendaylight.controller:Category=ShardManager,name=shard-manager-operational,type=DistributedOperationalDatastore",
"type": "read"
},
"value": {
"LocalShards": [
"member-1-shard-default-operational",
"member-1-shard-entity-ownership-operational",
"member-1-shard-topology-operational",
"member-1-shard-inventory-operational",
"member-1-shard-toaster-operational"
],
"SyncStatus": true,
"MemberName": "member-1"
},
"timestamp": 1483738005,
"status": 200
}
当需要进一步的查看来自“LocalShards”列表中的某个分片信息,可以把该分片的完整名称当作URI的一部分,来查询该分片的详细信息。如下是member-1-shard-default-operational信息输出的例子:
{
"request": {
"mbean": "org.opendaylight.controller:Category=Shards,name=member-1-shard-default-operational,type=DistributedOperationalDatastore",
"type": "read"
},
"value": {
"ReadWriteTransactionCount": 0,
"SnapshotIndex": 4,
"InMemoryJournalLogSize": 1,
"ReplicatedToAllIndex": 4,
"Leader": "member-1-shard-default-operational",
"LastIndex": 5,
"RaftState": "Leader",
"LastCommittedTransactionTime": "2017-01-06 13:19:00.135",
"LastApplied": 5,
"LastLeadershipChangeTime": "2017-01-06 13:18:37.605",
"LastLogIndex": 5,
"PeerAddresses": "member-3-shard-default-operational: akka.tcp://[email protected]:2550/user/shardmanager-operational/member-3-shard-default-operational, member-2-shard-default-operational: akka.tcp://[email protected]:2550/user/shardmanager-operational/member-2-shard-default-operational",
"WriteOnlyTransactionCount": 0,
"FollowerInitialSyncStatus": false,
"FollowerInfo": [
{
"timeSinceLastActivity": "00:00:00.320",
"active": true,
"matchIndex": 5,
"voting": true,
"id": "member-3-shard-default-operational",
"nextIndex": 6
},
{
"timeSinceLastActivity": "00:00:00.320",
"active": true,
"matchIndex": 5,
"voting": true,
"id": "member-2-shard-default-operational",
"nextIndex": 6
}
],
"FailedReadTransactionsCount": 0,
"StatRetrievalTime": "810.5 μs",
"Voting": true,
"CurrentTerm": 1,
"LastTerm": 1,
"FailedTransactionsCount": 0,
"PendingTxCommitQueueSize": 0,
"VotedFor": "member-1-shard-default-operational",
"SnapshotCaptureInitiated": false,
"CommittedTransactionsCount": 6,
"TxCohortCacheSize": 0,
"PeerVotingStates": "member-3-shard-default-operational: true, member-2-shard-default-operational: true",
"LastLogTerm": 1,
"StatRetrievalError": null,
"CommitIndex": 5,
"SnapshotTerm": 1,
"AbortTransactionsCount": 0,
"ReadOnlyTransactionCount": 0,
"ShardName": "member-1-shard-default-operational",
"LeadershipChangeCount": 1,
"InMemoryJournalDataSize": 450
},
"timestamp": 1483740350,
"status": 200
}
输出信息标识分片状态(主/从,投票中/非投票中)、同级节点信息、从节点信息,分片是否是主节点、其他统计信息和计数信息。
集成团队正在维护一个基于Python开发的工具,这个工具通过Jolokia组件,使用了上文提到MBeans暴露的接口,且systemmetrics项目也提供了一个基于DLUX UI来显示相同的信息。
节点之间的延迟时间最小的时候OpenDaylight集群工作在最佳状态,实践表明它们应该在同一个数据中心(译注:指在同一个机房)。但是假设所有节点宕机,能够在不同的数据中心实现故障转移更满足需求。为了获的这种效果,在不影响主数据中心节点(译注:这里应该是主数据中心,即同一个机房的所有节点)的延迟时间的方法下,集群可以通过在不同数据中心扩展节点的方式来达到目的。如果这样做,备份数据中心节点必须处在“non-voting” 状态。
在controller项目cluster-admin.yang中定义了一个RPCs,用于修改分片的voting状态的API;该API已经很好地被文档化。该API的概要如下:
注意:除非另外指出,下面的POST请求都被发送到集群中的任意一个节点。
创建一个6个节点集群的active/backup设置(分别位于两个机房,3个活跃和3个备份节点)。这里有一个RPC可以根据给定状态设置指定节点中的所有分片的投票状态:
POST /restconf/operations/cluster-admin:change-member-voting-states-for-all-shards
这个RPC需要一个节点列表和期望的投票状态作为输入。为了创建备份节点,如下输入示例可以被使用:
{
"input": {
"member-voting-state": [
{
"member-name": "member-4",
"voting": false
},
{
"member-name": "member-5",
"voting": false
},
{
"member-name": "member-6",
"voting": false
}
]
}
}
当一个active/backup部署存在时,且备份节点处于non-voting状态,这些被用于从active子集群到backup子集群的故障转移的分片将翻转每一个分片(包含每一个节点,活跃和备份)的投票状态。通过下面的RPC调用可以很容易实现这个过程(无需参数):
POST /restconf/operations/cluster-admin:flip-member-voting-states-for-all-shards
如果是主投票节点(译注:这里应该指的是active数据中心机房)因意外断电导致宕机,“flip” RPC请求必须被发送到某个非投票状态的备份节点(译注:backup数据中心机房的某个节点)。这种情况下,没有分片进行投票选举。但是这里有一个特例,如果收到RPC请求的这个节点处在non-voting状态,且即将改变为voting状态,且没有主节点,则这个节点将状态改为voting并尝试成为主节点。如果成功,该节点保存状态并复制这些变动信息给其余的节点。
当主中心被修复且你想要自动恢复到之前状态,你必须谨慎对待主中心的恢复。因为次级中心的投票状态被翻转的时候,主中心是处于下线状态,主中心的数据库并没有保存这些变化。如果主中心在这种状态被恢复,主中心的节点将仍然处于投票状态。如果主中心节点连接到次级中心,它们找出次级中心的主节点并同步。但是如果没有发生这种情况,主中心将可能选举出它们自己的主节点,从而被分割出2个集群,这种情况会导致不可预测的结果。因此推荐在恢复主中心时,先清除主中心节点数据库数据(例如,日志和快照目录)。另外一种选择是从次级中心一个最新备份中恢复主中心的数据(详情查阅Backing Up and Restoring the Datastore)
如果想要从集群中温和的移除一个节点也是可以的,查看如下RPC API:
POST /restconf/operations/cluster-admin:remove-all-shard-replicas
请求的示例数据:
{
"input": {
"member-name": "member-1"
}
}
或者仅仅是移除一个特定的分片:
POST /restconf/operations/cluster-admin:remove-shard-replica
请求的实例数据:
{
"input": {
"shard-name": "default",
"member-name": "member-2",
"data-store-type": "config"
}
}
注意在运行时某个故障节点被移除,也可以添加一个新的节点,不用修改正常节点的配置文件(需要重启)。
POST /restconf/operations/cluster-admin:add-replicas-for-all-shards
该请求不需要任何请求数据,但是这个RPC需要被发送到新的节点,指示该节点去集群中复制所有分片数据。
注意
虽然集群admin API允许动态地添加和移除分片,但是module-shard.conf和modules.conf文件仍然使用启动时定义的初始化配置。使用API修改并不会存储到这些静态文件中,但是会存到journal中。
Shard
Setting Up Clustering
OpenDaylight Lithium-SR2 Cluster集群搭建
Running and testing an OpenDaylight Cluster