Netconf集群最近故障爆发,其根源利用了controller的ownership功能,急需梳理逻辑,所以有了这篇文章。
针对Netconf任一节点,其candidate和owner信息都存储在ODL DataStore 操作库中的ownership分片中。
简单介绍下candidate和owner的含义,如图,A、B、C为三个控制器,都与设备进行了连接,无ownership功能,任何针对设置的操作,可以通过任一控制器进行下发,而如果有了ownership功能,比如认为A控制器与设备的连接中,设备标记A为owner,则在进行处理请求时,则可以根据节点是否为指定节点的owner区别处理,增加了业务的灵活性。
owner信息,可以通过URL http://localhost:8181/restconf/operational/entity-owners:entity-owners/ 获取:
{
"entity-owners": {
"entity-type": [
{
"type": "netconf-node/NSQdNGQB72wyDGHiR4NQ",
"entity": [
{
"id": "/general-entity:entity[general-entity:name='NSQdNGQB72wyDGHiR4NQ']",
"owner": ""
}
]
},
{
"type": "netconf-node/nww3XwdYZpeyfn7mC4hZ",
"entity": [
{
"id": "/general-entity:entity[general-entity:name='nww3XwdYZpeyfn7mC4hZ']",
"candidate": [
{
"name": "member-1"
},
{
"name": "member-256"
},
{
"name": "member-2"
}
],
"owner": "member-1"
}
]
},
{
"type": "topology-netconf",
"entity": [
{
"id": "/general-entity:entity[general-entity:name='topology-manager']",
"candidate": [
{
"name": "member-2"
}
],
"owner": "member-2"
}
]
}
]
}
}
其中type和entity字段通过代码中创建的Entity实例
new Entity(entityType, entityName)
ODL针对OwnerShip的使用,提供了两个方便的接口,分别是注册Candidate和ownershipChanged监听事件
candidateRegistration = entityOwnershipService.registerCandidate(entity);
ownershipListenerRegistration = entityOwnershipService.registerListener(entityType, this);
以Netconf集群为例,以topology-netcon为例介绍,介绍其ownership的逻辑?
触发源为TopologyRoleChangeStrategy#onDataTreeChange
datastoreListenerRegistration = dataBroker.registerDataTreeChangeListener(
new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, createTopologyId(entityType).child(Node.class)), this);
ClusteredNetconfTopology#onSessionInitiated方法中创建了BaseTopologyManager(Actor),该Actor的preStart方法中
使用传入的TopologyRoleChangeStrategy的注册Candidate,
而TopologyRoleChangeStrategy#registerRoleCandidate
则使用到了上面介绍EntityOwnershipService的两个主要接口
entityOwnershipService#registerCandidate
DistributedEntityOwnershipService#registerCandidate
在注册真正逻辑中:
RegisterCandidateLocal registerCandidate = new RegisterCandidateLocal(entity);
查询分片Actor
Future<ActorRef> future = context.findLocalShardAsync(ENTITY_OWNERSHIP_SHARD_NAME);
将RegisterCandidateLocal发送给enitty-ownership
至此代码逻辑来到EntityOwnershipShard中
private void onRegisterCandidateLocal(RegisterCandidateLocal registerCandidate) {
LOG.debug("{}: onRegisterCandidateLocal: {}", persistenceId(), registerCandidate);
listenerSupport.setHasCandidateForEntity(registerCandidate.getEntity());
NormalizedNode<?, ?> entityOwners = entityOwnersWithCandidate(registerCandidate.getEntity().getType(),
registerCandidate.getEntity().getId(), localMemberName);
commitCoordinator.commitModification(new MergeModification(ENTITY_OWNERS_PATH, entityOwners), this);
getSender().tell(SuccessReply.INSTANCE, getSelf());
}
构建MergeModification进行数据写入,完成Entity与Candidate数据的写入,注意写入数据库,会产生变更通知,会CandidateListChangeListener捕获,发送消息(CandidateAdd)回EntityOwnershipShard(绕一圈),这里则会进行选主判断。
社区关于EntityOwnershipShard相关的内容,在后序Cardon版本改动较多,有几点需要注意的是:
1.何时可能进行选主(有Candidate加入且entity无主时,或有Candidate移除且移除的正好是原主)
2.针对topology-manager这个entity,candidate社区后面版本是采用不删除策略(原因是其将Akka的MemberRemoved和MemberExited以及MemberUnreachable都作为peerDown处理,这样就无法区别节点是真的down了还是暂时的unreachable。考虑到这,采用一律不删除策略。但member的信息全部在内存中进行维护。所以peerDown的那个节点是无法选成主的。
选ownership时,默认提供2种策略:
LeastLooadedCandidateSelectionStrategy
FirstCandidateSelectionStrategy(默认)
Netconf的设备节点的策略是根据节点连接状态进行candidate随时增删:
在NetconfNodeManagerCallbac中
实现了注册与注销动作
而通过netconf节点的连接状态信息,维护的一个重要参数即是isMaster,应该Netconf节点的状态更新只在master节点是进行聚合更新入库,其它节点只作内存状态的更新。该master对应的即是topology-manager中的owner,那何时会进行该owner的通知(即ownershipchaged)
TopologyRoleChangeStrategy#ownershipChanged监听后修改isMaster的值
附:节点上线流程
NetconfTopologyManagerCallback#onNodeCreate流程
此处会创建NodeManager,注意在创建NodeManger的时候,在其构造方法中
roleChangeStrategy.registerRoleCandidate((NodeManager) TypedActor.self());
使用NodeRoleChangeStrategy#regiserRoleCandidate,实现了Node的Candidate的注册
@Override
public void registerRoleCandidate(NodeListener electionCandidate) {
LOG.debug("Registering role candidate type: {} , name: {}", entityType, entityName);
this.ownershipCandidate = electionCandidate;
try {
if (candidateRegistration != null) {
unregisterRoleCandidate();
}
candidateRegistration = entityOwnershipService.registerCandidate(entity);
ownershipListenerRegistration = entityOwnershipService.registerListener(entityType, this);
} catch (CandidateAlreadyRegisteredException e) {
LOG.error("Candidate already registered for election", e);
throw new IllegalStateException("Candidate already registered for election", e);
}
}
程序继续进行:
BaseNodeManager#onNodeCreated
NetconfNodeManagerCallback#onNodeCreate
注意由于本节点不是Master,所以并不会进行真正的写库,所以信息只会缓存,等待Master的触发处理
而beryllium版本,不论是Netconf还是Controller中EntityOwnership都存在不小的问题,针对商用支持明显不够。