[源码阅读] 阿里SOFA服务注册中心MetaServer(2)

目录

0x00 摘要

SOFARegistry 是蚂蚁金服开源的一个生产级、高时效、高可用的服务注册中心。本系列将带领大家一起分析其MetaServer的实现机制,本文为第二篇,介绍MetaServer基本功能,比如注册,存储,通知,续约等。

0x01 MetaServer 注册

1.1 Leader入口

MetaServer 的业务启动是从setLeaderProcessListener开始。

前面提到,MetaServer 集群内部基于 Raft 协议选举和复制,只要不超过 1⁄2 节点宕机,就可以对外服务。

Raft 协议由三个部分组成:

  • 领导人选举(Leader Election)
  • 日志复制(Log Replication)
  • 安全性(Safety)

如果使用JRaft, 需要实现其状态机,而在MetaServer之中, jraft FSM 的具体实现是 ServiceStateMachine类(后文中会有大量 Raft 相关内容)。

在 raft 选出了MetaServer leader之后,ServiceStateMachine会调用 setLeaderProcessListener,其中又调用到了registerCurrentNode,这样就在MetaServer中注册了当前Meta节点。

raftServer.setLeaderProcessListener(new LeaderProcessListener() {
@Override
public void startProcess() {
executorManager.startScheduler();
PeerId leader = new PeerId(NetUtil.getLocalAddress().getHostAddress(),
metaServerConfig.getRaftServerPort());
registerCurrentNode(); //
raftServer.sendNotify(leader, "leader");
}

其运行堆栈如下 :

register:51, MetaServerRegistry (com.alipay.sofa.registry.server.meta.registry)
registerCurrentNode:203, RaftExchanger (com.alipay.sofa.registry.server.meta.remoting)
access$200:56, RaftExchanger (com.alipay.sofa.registry.server.meta.remoting)
startProcess:105, RaftExchanger$1 (com.alipay.sofa.registry.server.meta.remoting)
lambda$onLeaderStart$2:234, ServiceStateMachine (com.alipay.sofa.registry.jraft.bootstrap)
run:-1, 318295790 (com.alipay.sofa.registry.jraft.bootstrap.ServiceStateMachine$$Lambda$191)
call:511, Executors$RunnableAdapter (java.util.concurrent)
run$$$capture:266, FutureTask (java.util.concurrent)
run:-1, FutureTask (java.util.concurrent)

1.2 注册

注册行为由 Registry 完成。注册接口实现为:

public interface Registry<T extends Node> {
NodeChangeResult setNodes(List<T> nodes);
NodeChangeResult register(T node);
void cancel(String connectId, NodeType nodeType);
void evict();
void renew(T node, int duration);
void getOtherDataCenterNodeAndUpdate(NodeType nodeType);
DataCenterNodes getDataCenterNodes(NodeType nodeType);
NodeChangeResult getAllNodes(NodeType nodeType);
void pushNodeListChange(NodeType nodeType);
}

具体实现举例是:

public class MetaServerRegistry implements Registry<Node> {
@Override
public NodeChangeResult register(Node node) {
StoreService storeService = ServiceFactory.getStoreService(node.getNodeType());
return storeService.addNode(node);
}
}

Registry 根据不同的节点类型,获取对应的StoreService完成添加节点服务,这里节点类型就是"META"。

node = {MetaNode@6342} "MetaNode{ip=192.168.1.2}"
nodeType = {Node$NodeType@6369} "META"
nodeUrl = {URL@6370} "URL{address='192.168.1.2:0'}"
dataCenter = "DefaultDataCenter"
name = "192.168.1.2"
regionId = null
nodeStatus = {Node$NodeStatus@6373} "INIT"

storeService 就是从上文 ServiceFactory 介绍的 storeServiceMap 中提取。

比如 MetaNode,其对应 storeService 实现为 MetaStoreService ,所以就会用 MetaStoreService 完成存储。

storeServiceMap = {HashMap@6394}  size = 3
{Node$NodeType@6454} "SESSION" -> {SessionStoreService@6455}
{Node$NodeType@6369} "META" -> {MetaStoreService@6456}
{Node$NodeType@6457} "DATA" -> {DataStoreService@6458}

1.3 存储服务

Node 然后由 StoreService 存储到 Repository 中,具体举例 MetaStoreService 实现为:

public class MetaStoreService implements StoreService<MetaNode> {
@RaftReference(uniqueId = "metaServer")
private RepositoryService<String, RenewDecorate<MetaNode>> metaRepositoryService; @Override
public NodeChangeResult addNode(MetaNode metaNode) {
NodeChangeResult nodeChangeResult;
String ipAddress = metaNode.getNodeUrl().getIpAddress();
write.lock();
try {
//存放到repository(自动通过jraft同步给集群)
metaRepositoryService.put(ipAddress, new RenewDecorate(metaNode,
RenewDecorate.DEFAULT_DURATION_SECS)); //触发通知(需要通知data/session)
nodeChangeResult = getNodeChangeResult();
firePushDataListTask(nodeChangeResult, "addMetaNode");
firePushSessionListTask(nodeChangeResult, "addMetaNode");
} finally {
write.unlock();
}
return nodeChangeResult;
}
}

预先把存储流程总结如下:

                                                                                      +------------------------------+
+-----------------------+ | Map(String, NodeRepository) |
+--->+ metaRepositoryService +------->+ registry |
| +-----------------------+ +------------------------------+
|
|
|
Register +-------------------+ addNode +-----+-----------+
+------> | MetaServerRegistry| +--------> | MetaStoreService|
+-------------------+ +-----+-----------+
|
|
| +-------------------+ +--------------+ +----------------------+
+----------> |TaskListenerManager+---> |TaskDispatcher| +---> |DataNodeChangePushTask|
sendTaskEvent +-------------------+ +--------------+ +----------------------+

手机上参见:

1.4 Repository服务

Repository算是一个比较经典的概念了,封装数据查询和存储逻辑。定义(来自Martin Fowler的《企业应用架构模式》):Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects。

Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,即提供一个类似集合的接口提供给领域层进行领域对象的访问。

Repository可以被认为是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。

在这里,Node并没有持久化,而是存储在内存中。于是就要 重点注意,虽然Repository存储一个Node是很简单的,但是在分布式状态下,如何保证 "在一个Repository中新Node的存储状态" 可以拓展到集群其他节点中?而且能保证 数据一致性呢?

1.4.1 MetaRepositoryService

MetaRepositoryService 内部的存储是在集群内部由Raft协议来保证数据一致性的

后端 Repository 可以看作 SOFAJRaft 的状态机,任何对 Map 的操作都会在集群内部,交由 Raft 协议进行同步,从而达到集群内部的一致。从代码中可以看到MetaRepositoryService加上了 RaftService 注解,这就是Raft的部分实现。

为了更好的说明,我们这里提前介绍下 Raft 的两个注解:RaftReferenceRaftService

这两个注解可以认为是封装好Raft的,呈现给Registry的接口。RaftReference 对应了客户端代理,RaftService对应着服务端的实现。为什么要这么做?因为需要维护数据一致性,所以必须把单纯的本地调用转换为异步网络调用,这样才能用raft协议保证数据一致性

  • RepositoryService 的具体实现类都加了 @RaftService 注解,这样就说明自己是一个服务端;
  • 凡是 引用 RepositoryService 的地方,都加了 @RaftReference,这样调用 RepositoryService 的函数就相当于客户端调用到服务端;
  • 凡是加了 @RaftReference 注解的属性,都会被动态代理类替换,其代理实现见 ProxyHandler 类,即将方法调用,封装为 ProcessRequest,通过 RaftClient 发送给 RaftServer。

回到MetaRepositoryService代码,具体如下:

@RaftService(uniqueId = "metaServer")
public class MetaRepositoryService extends AbstractSnapshotProcess
implements RepositoryService<String, RenewDecorate<MetaNode>> {
@Autowired
private NodeConfig nodeConfig;
/**
* meta node store and version
*/
private Map<String/*dataCenter*/, NodeRepository> registry = new ConcurrentHashMap<>();
private Set<String> snapShotFileNames = new HashSet<>();
}

1.4.2 put操作

当有新节点 时候,MetaRepositoryService 会进行 put 操作。在具体put函数调用中,实际上是将方法调用,封装为 ProcessRequest,通过 RaftClient 发送给 RaftServer。这样就由 Raft 协议保证了数据一致性

@Override
public RenewDecorate<MetaNode> put(String ipAddress, RenewDecorate<MetaNode> metaNode,
Long currentTimeMillis) {
try {
String dataCenter = metaNode.getRenewal().getDataCenter();
NodeRepository<MetaNode> metaNodeRepository = registry.get(dataCenter);
if (metaNodeRepository == null) {
NodeRepository<MetaNode> nodeRepository = new NodeRepository<>(dataCenter,
new ConcurrentHashMap<>(), currentTimeMillis);
// put操作实际上是调用到了服务端
metaNodeRepository = registry.put(dataCenter, nodeRepository);
if (metaNodeRepository == null) {
metaNodeRepository = nodeRepository;
}
}
metaNodeRepository.setVersion(currentTimeMillis);
Map<String/*ipAddress*/, RenewDecorate<MetaNode>> metaNodes = metaNodeRepository
.getNodeMap();
RenewDecorate oldRenewDecorate = metaNodes.get(ipAddress);
metaNodes.put(ipAddress, metaNode);
}
return metaNode;
}

1.4.3 节点数据存储

节点数据的存储,其本质上是存储在内存的哈希表中,其存储结构为:

// RepositoryService 底层存储
Map<String/*dataCenter*/, NodeRepository> registry; // NodeRepository 底层存储
Map<String/*ipAddress*/, RenewDecorate<T>> nodeMap;

RenewDecorate存储到该 Map 中,整个节点注册的流程就完成了,至于如何和 Raft 协议进行结合和数据同步,后续会介绍。

节点移除的逻辑类似,将节点信息从该 Map 中删除,也会存储一个变更事件到队列。

最后结果如下 :

this = {MetaRepositoryService@6905}
registry = {ConcurrentHashMap@6907} size = 1
"DefaultDataCenter" -> {NodeRepository@7251}
snapShotFileNames = {HashSet@6904} size = 1

1.5 jraft实现数据一致性

成员列表数据存储在 Repository 中,Repository 被一致性协议层进行包装,作为 SOFAJRaft 的状态机实现,所有对 Repository 的操作都会同步到其他节点,通过 Registry 来操作存储层。

在同步时候,jraft可以直接调用 MetaRepositoryService,实现内部数据一致性。

这里说的是其他节点的同步操作,和上节不同。上节是主动存储节点,这里是被动同步

put:45, MetaRepositoryService (com.alipay.sofa.registry.server.meta.repository.service)
put:31, RepositoryService (com.alipay.sofa.registry.server.meta.repository)
invokeInterface_L3_L:-1, 1412712349 (java.lang.invoke.LambdaForm$DMH)
reinvoke:-1, 1139092036 (java.lang.invoke.LambdaForm$BMH)
invoker:-1, 403463237 (java.lang.invoke.LambdaForm$MH)
invokeExact_MT:-1, 1568507411 (java.lang.invoke.LambdaForm$MH)
invokeWithArguments:627, MethodHandle (java.lang.invoke)
process:123, Processor (com.alipay.sofa.registry.jraft.processor)
onApply:133, ServiceStateMachine (com.alipay.sofa.registry.jraft.bootstrap)
doApplyTasks:534, FSMCallerImpl (com.alipay.sofa.jraft.core)
doCommitted:503, FSMCallerImpl (com.alipay.sofa.jraft.core)
runApplyTask:431, FSMCallerImpl (com.alipay.sofa.jraft.core)
access$100:72, FSMCallerImpl (com.alipay.sofa.jraft.core)
onEvent:147, FSMCallerImpl$ApplyTaskHandler (com.alipay.sofa.jraft.core)
onEvent:141, FSMCallerImpl$ApplyTaskHandler (com.alipay.sofa.jraft.core)
run:137, BatchEventProcessor (com.lmax.disruptor)
run:748, Thread (java.lang)

1.6 通知data, session

1.6.1 通知操作

前文 "1.3 存储服务" 中有 firePushDataListTask,firePushSessionListTask,最终目的是通知所有data/session有metaServer变动,这里把代码再贴出来温习下。

public class MetaStoreService implements StoreService<MetaNode> {

    @Override
public NodeChangeResult addNode(MetaNode metaNode) { //存放到repository(自动通过jraft同步给集群)
metaRepositoryService.put(ipAddress, new RenewDecorate(metaNode,
RenewDecorate.DEFAULT_DURATION_SECS)); //触发通知(需要通知data/session),我们说的就是这里
nodeChangeResult = getNodeChangeResult();
firePushDataListTask(nodeChangeResult, "addMetaNode");
firePushSessionListTask(nodeChangeResult, "addMetaNode");
}
}

firePushDataListTask 和 firePushSessionListTask 进而会往taskListenerManager发送消息,

private void firePushDataListTask(NodeChangeResult nodeChangeResult, String nodeOperate) {
TaskEvent taskEvent = new TaskEvent(nodeChangeResult, TaskType.DATA_NODE_CHANGE_PUSH_TASK);
taskEvent.setAttribute(Constant.PUSH_NEED_CONFIRM_KEY, false);
taskEvent.setAttribute(Constant.PUSH_TARGET_TYPE, NodeType.DATA);
taskEvent.setAttribute(Constant.PUSH_TARGET_OPERATOR_TYPE, nodeOperate);
taskListenerManager.sendTaskEvent(taskEvent);
} private void firePushSessionListTask(NodeChangeResult nodeChangeResult, String nodeOperate) {
//notify all session node
TaskEvent taskEvent = new TaskEvent(nodeChangeResult, TaskType.DATA_NODE_CHANGE_PUSH_TASK);
taskEvent.setAttribute(Constant.PUSH_TARGET_TYPE, NodeType.SESSION);
taskEvent.setAttribute(Constant.PUSH_TARGET_OPERATOR_TYPE, nodeOperate);
taskListenerManager.sendTaskEvent(taskEvent);
}

此处是触发通知,于是data 节点, session节点会得到通知。

如何通知data节点, session节点?这里用到了Listener,即调用了 DefaultTaskListenerManager # sendTaskEvent。

堆栈如下 :

sendTaskEvent:45, DefaultTaskListenerManager (com.alipay.sofa.registry.task.listener)
firePushDataListTask:355, MetaStoreService (com.alipay.sofa.registry.server.meta.store)
addNode:127, MetaStoreService (com.alipay.sofa.registry.server.meta.store)
addNode:54, MetaStoreService (com.alipay.sofa.registry.server.meta.store)
register:52, MetaServerRegistry (com.alipay.sofa.registry.server.meta.registry)
registerCurrentNode:203, RaftExchanger (com.alipay.sofa.registry.server.meta.remoting)
access$200:56, RaftExchanger (com.alipay.sofa.registry.server.meta.remoting)
startProcess:105, RaftExchanger$1 (com.alipay.sofa.registry.server.meta.remoting)
lambda$onLeaderStart$2:234, ServiceStateMachine (com.alipay.sofa.registry.jraft.bootstrap)
run:-1, 50941601 (com.alipay.sofa.registry.jraft.bootstrap.ServiceStateMachine$$Lambda$192)

1.6.2 分发通知消息

TaskListenerManager 是用来分发各种通知消息的类。

public class DefaultTaskListenerManager implements TaskListenerManager {
@Override
public void sendTaskEvent(TaskEvent taskEvent) {
Collection<TaskListener> taskListeners = this.taskListeners.get(taskEvent.getTaskType());
for (TaskListener taskListener : taskListeners) {
taskListener.handleEvent(taskEvent);
}
}
}

在 taskListenerManager.addTaskListener(taskListener); 中注册了很多处理消息的handler,从下面运行变量的文字中可以看出其逻辑意义。

this.taskListeners = {ArrayListMultimap@7221}
expectedValuesPerKey = 3
map = {HashMap@7227} size = 4
{TaskEvent$TaskType@7235} "RECEIVE_STATUS_CONFIRM_NOTIFY_TASK" -> {ArrayList@7236} size = 1
key = {TaskEvent$TaskType@7235} "RECEIVE_STATUS_CONFIRM_NOTIFY_TASK"
value = {ArrayList@7236} size = 1
0 = {ReceiveStatusConfirmNotifyTaskListener@7248}
{TaskEvent$TaskType@7237} "PERSISTENCE_DATA_CHANGE_NOTIFY_TASK" -> {ArrayList@7238} size = 1
key = {TaskEvent$TaskType@7237} "PERSISTENCE_DATA_CHANGE_NOTIFY_TASK"
value = {ArrayList@7238} size = 1
0 = {PersistenceDataChangeNotifyTaskListener@7254}
{TaskEvent$TaskType@7239} "SESSION_NODE_CHANGE_PUSH_TASK" -> {ArrayList@7240} size = 1
key = {TaskEvent$TaskType@7239} "SESSION_NODE_CHANGE_PUSH_TASK"
value = {ArrayList@7240} size = 1
0 = {SessionNodeChangePushTaskListener@7252}
{TaskEvent$TaskType@7241} "DATA_NODE_CHANGE_PUSH_TASK" -> {ArrayList@7242} size = 1
key = {TaskEvent$TaskType@7241} "DATA_NODE_CHANGE_PUSH_TASK"
value = {ArrayList@7242} size = 1
0 = {DataNodeChangePushTaskListener@7250}

于是在 sendTaskEvent 之中会调用相关的消息handler来进行处理。

1.6.3 异步处理消息

具体处理消息,我们举例如下:

DataNodeChangePushTaskListener 就是用来处理DataNode相关消息的类。

public class DataNodeChangePushTaskListener implements TaskListener {

    private TaskDispatcher<String, MetaServerTask> dataSingleTaskDispatcher;
private TaskDispatcher<String, MetaServerTask> sessionSingleTaskDispatcher; @Override
public void handleEvent(TaskEvent event) { NodeType nodeType = (NodeType) event.getAttribute(Constant.PUSH_TARGET_TYPE);
switch (nodeType) {
case SESSION:
MetaServerTask sessionNodeChangePushTask = new DataNodeChangePushTask(
NodeType.SESSION, metaServerConfig);
sessionNodeChangePushTask.setTaskEvent(event);
sessionSingleTaskDispatcher.dispatch(sessionNodeChangePushTask.getTaskId(),
sessionNodeChangePushTask, sessionNodeChangePushTask.getExpiryTime());
break;
case DATA:
MetaServerTask dataNodeChangePushTask = new DataNodeChangePushTask(NodeType.DATA,
metaServerConfig);
dataNodeChangePushTask.setTaskEvent(event);
dataSingleTaskDispatcher.dispatch(dataNodeChangePushTask.getTaskId(),
dataNodeChangePushTask, dataNodeChangePushTask.getExpiryTime());
break;
default:
break;
}
}
}

我们可以看到,TaskDispatcher 是一个分发异步消息,随之通过TaskExecutors进行异步操作的类。

假如是DataNode,最后会调用到 DataNodeChangePushTask,其是由 DataNodeSingleTaskProcessor 来执行。

其调用栈如下:

pushDataNodes:73, DataNodeServiceImpl (com.alipay.sofa.registry.server.meta.node.impl)
execute:86, DataNodeChangePushTask (com.alipay.sofa.registry.server.meta.task)
process:41, DataNodeSingleTaskProcessor (com.alipay.sofa.registry.server.meta.task.processor)
process:32, DataNodeSingleTaskProcessor (com.alipay.sofa.registry.server.meta.task.processor)
run:136, TaskExecutors$WorkerRunnable (com.alipay.sofa.registry.task.batcher)
run:748, Thread (java.lang)

具体到 DataNodeChangePushTask 来进行与各个DataNode进行交互的操作。

public class DataNodeChangePushTask extends AbstractMetaServerTask {
private final SessionNodeService sessionNodeService;
private final DataNodeService dataNodeService;
final private MetaServerConfig metaServerConfig;
final private NodeType nodeType;
private NodeChangeResult nodeChangeResult;
private Boolean confirm;
private String confirmNodeIp; private Map<String, DataNode> targetNodes; @Override
public void execute() {
switch (nodeType) {
case SESSION:
sessionNodeService.pushDataNodes(nodeChangeResult);
break;
case DATA:
dataNodeService
.pushDataNodes(nodeChangeResult, targetNodes, confirm, confirmNodeIp);
break;
default:
break;
}
}
}

以DataNodeServiceImpl为例,可以看到最后调用了 dataNodeExchanger 完成了节点间通讯。

public class DataNodeServiceImpl implements DataNodeService {

    @Autowired
private NodeExchanger dataNodeExchanger; @Autowired
private StoreService dataStoreService; @Autowired
private AbstractServerHandler dataConnectionHandler; @Override
public void pushDataNodes(NodeChangeResult nodeChangeResult, Map<String, DataNode> targetNodes,
boolean confirm, String confirmNodeIp) { if (nodeChangeResult != null) { List<Throwable> exceptions = new ArrayList<>();
NodeConnectManager nodeConnectManager = getNodeConnectManager(); Collection<InetSocketAddress> connections = nodeConnectManager.getConnections(null); // add register confirm
StoreService storeService = ServiceFactory.getStoreService(NodeType.DATA);
DataCenterNodes dataCenterNodes = storeService.getDataCenterNodes();
Map<String, DataNode> registeredNodes = dataCenterNodes.getNodes(); for (InetSocketAddress address : connections) {
try {
if (targetNodes != null && !targetNodes.isEmpty()) {
if (!targetNodes.keySet().contains(address.getAddress().getHostAddress())) {
continue;
}
} else {
if (!registeredNodes.keySet().contains(
address.getAddress().getHostAddress())) {
continue;
}
} Request<NodeChangeResult> nodeChangeRequestRequest = new Request<NodeChangeResult>() {
@Override
public NodeChangeResult getRequestBody() {
return nodeChangeResult;
} @Override
public URL getRequestUrl() {
return new URL(address);
}
};
// 节点间通讯
Response response = dataNodeExchanger.request(nodeChangeRequestRequest); if (confirm) {
Object result = response.getResult();
if (result instanceof CommonResponse) {
CommonResponse genericResponse = (CommonResponse) result;
if (genericResponse.isSuccess()) {
confirmStatus(address, confirmNodeIp);
}
}
}
}
}
}
}
}

1.6.4 另一种产生通知方式

ExecutorManager的pushNodeListChange会定期检查,如果有必要,则产生通知

对应后文的 DataConfirmStatusService 节点变更事件。

scheduler.schedule(
new TimedSupervisorTask("CheckDataNodeListChangePush", scheduler, checkNodeListChangePushExecutor,
metaServerConfig.getSchedulerCheckNodeListChangePushTimeout(), TimeUnit.SECONDS,
metaServerConfig.getSchedulerCheckNodeListChangePushExpBackOffBound(),
() -> metaServerRegistry.pushNodeListChange(NodeType.DATA)),
metaServerConfig.getSchedulerCheckNodeListChangePushFirstDelay(), TimeUnit.SECONDS);

其堆栈是:

pushNodeListChange:278, DataStoreService (com.alipay.sofa.registry.server.meta.store)
pushNodeListChange:103, MetaServerRegistry (com.alipay.sofa.registry.server.meta.registry)
lambda$startScheduler$4:158, ExecutorManager (com.alipay.sofa.registry.server.meta.executor)
run:-1, 758751909 (com.alipay.sofa.registry.server.meta.executor.ExecutorManager$$Lambda$202)
call:511, Executors$RunnableAdapter (java.util.concurrent)
run$$$capture:266, FutureTask (java.util.concurrent)
run:-1, FutureTask (java.util.concurrent)

比如

public class DataStoreService implements StoreService<DataNode> {
@Override
public void pushNodeListChange() {
NodeOperator<DataNode> fireNode;
if ((fireNode = dataConfirmStatusService.peekConfirmNode()) != null) {
NodeChangeResult nodeChangeResult = getNodeChangeResult();
Map<String, Map<String, DataNode>> map = nodeChangeResult.getNodes();
Map<String, DataNode> addNodes = map.get(nodeConfig.getLocalDataCenter());
if (addNodes != null) {
Map<String, DataNode> previousNodes = dataConfirmStatusService.putExpectNodes(
fireNode.getNode(), addNodes); if (!previousNodes.isEmpty()) {
// 产生通知
firePushDataListTask(fireNode, nodeChangeResult, previousNodes, true);
}
}
// 产生通知
firePushSessionListTask(nodeChangeResult, fireNode.getNodeOperate().toString());
}
}
}

0x02 节点注册

2.1 DataApplication

2.1.1 DataConnectionHandler

当一个DataApplication启动,首先DataConnectionHandler会响应。

connected:40, DataConnectionHandler (com.alipay.sofa.registry.server.meta.remoting.connection)
onEvent:69, ConnectionEventAdapter (com.alipay.sofa.registry.remoting.bolt)
onEvent:44, ConnectionEventListener (com.alipay.remoting)
run:201, ConnectionEventHandler$1 (com.alipay.remoting)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

感觉只是设置了一下。

public class DataConnectionHandler extends AbstractServerHandler implements NodeConnectManager {
private Map<String/*connectId*/, InetSocketAddress> connections = new ConcurrentHashMap<>(); @Override
public void connected(Channel channel) throws RemotingException {
super.connected(channel);
addConnection(channel);
}
}

2.1.2 DataNodeHandler

然后是 DataNodeHandler 会响应。

reply:43, DataNodeHandler (com.alipay.sofa.registry.server.meta.remoting.handler)
handleRequest:54, SyncUserProcessorAdapter (com.alipay.sofa.registry.remoting.bolt)
dispatchToUserProcessor:239, RpcRequestProcessor (com.alipay.remoting.rpc.protocol)
doProcess:145, RpcRequestProcessor (com.alipay.remoting.rpc.protocol)
run:366, RpcRequestProcessor$ProcessTask (com.alipay.remoting.rpc.protocol)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

具体代码如下。

public class DataNodeHandler extends AbstractServerHandler<DataNode> {
@Autowired
private Registry metaServerRegistry; @Override
public Object reply(Channel channel, DataNode dataNode) {
NodeChangeResult nodeChangeResult;
nodeChangeResult = metaServerRegistry.register(dataNode);
return nodeChangeResult;
}
}

接下来是调用Store服务进行添加节点。

@Override
public NodeChangeResult register(Node node) {
StoreService storeService = ServiceFactory.getStoreService(node.getNodeType());
return storeService.addNode(node);
}

代码来到 DataStoreService,依然是调用 RepositoryService 来进行节点的注册和存储。

public class DataStoreService implements StoreService<DataNode> {
@Autowired
private TaskListenerManager taskListenerManager; @RaftReference(uniqueId = "dataServer")
private RepositoryService<String, RenewDecorate<DataNode>> dataRepositoryService; @RaftReference(uniqueId = "dataServer")
private NodeConfirmStatusService<DataNode> dataConfirmStatusService; @Override
public NodeChangeResult addNode(DataNode dataNode) {
NodeChangeResult nodeChangeResult;
String ipAddress = dataNode.getNodeUrl().getIpAddress();
write.lock();
try {
dataRepositoryService.put(ipAddress, new RenewDecorate(dataNode,
RenewDecorate.DEFAULT_DURATION_SECS));
renew(dataNode, 30);
nodeChangeResult = getNodeChangeResult();
dataConfirmStatusService.putConfirmNode(dataNode, DataOperator.ADD);
} finally {
write.unlock();
}
return nodeChangeResult;
}
}

dataConfirmStatusService.putConfirmNode 同时会存储一个变更事件到队列中,主要用于数据推送,消费处理。

@Override
public void putConfirmNode(DataNode node, DataOperator nodeOperate) {
expectNodesOrders.put(new NodeOperator(node, nodeOperate));
}

2.1.3 DataConfirmStatusService

DataConfirmStatusService 也是注解了RaftService,这说明是一个由 Raft 协议进行同步的存储。

以下的这些存储结构会被同步。

  • expectNodesOrders 用来存储节点变更事件;
  • expectNodes 用来存储变更事件需要确认的节点,也就是说 NodeOperator 只有得到了其他节点的确认,才会从 expectNodesOrders 移除;
  • snapShotFileNames 是快照文件名;
@RaftService(uniqueId = "dataServer")
public class DataConfirmStatusService extends AbstractSnapshotProcess
implements NodeConfirmStatusService<DataNode> { private ConcurrentHashMap<DataNode/*node*/, Map<String/*ipAddress*/, DataNode>> expectNodes = new ConcurrentHashMap<>();
private BlockingQueue<NodeOperator> expectNodesOrders = new LinkedBlockingQueue();
private Set<String> snapShotFileNames @Override
public void putConfirmNode(DataNode node, DataOperator nodeOperate) {
expectNodesOrders.put(new NodeOperator(node, nodeOperate));
} public NodeOperator<DataNode> peekConfirmNode() {
return expectNodesOrders.peek();
}
}

2.1.4 消费

事件存储到 BlockingQueue expectNodesOrders 里,哪里去消费呢? 看源码发现,并不是想象中的使用一个线程阻塞的读。

ExecutorManager中会启动一个定时任务,轮询该队列有没有数据。即周期性的调用Registry#pushNodeListChange方法,获取队列的头节点并消费。Data 和 Session 各对应一个任务。

具体流程如下图所示:

  1. 首先获取队列(expectNodesOrders)头节点,如果为Null直接返回;
  2. 获取当前数据中心的节点列表,并存储到确认表(expectNodes);
  3. 提交节点变更推送任务(firePushXxListTask);
  4. 处理任务,即调用 XxNodeService 的 pushXxxNode 方法,即通过 ConnectionHandler 获取所有的节点连接,发送节点列表;
  5. 收到回复后,如果需要确认,则会调用StoreService#confirmNodeStatus 方法,将该节点从expectNodes中移除;
  6. 待所有的节点从 expectNodes 中移除,则将此次操作从 expectNodesOrders 移除,处理完毕;

具体可以细化如下。

2.1.4.1 定时提交节点变更推送任务

本小节对应上面步骤的前三步。

比如关于Data 定时任务如下,里面定期调用了MetaServerRegistry 的 pushNodeListChange函数来处理任务 :

scheduler.schedule(
new TimedSupervisorTask("CheckDataNodeListChangePush", scheduler, checkNodeListChangePushExecutor,
metaServerConfig.getSchedulerCheckNodeListChangePushTimeout(), TimeUnit.SECONDS,
metaServerConfig.getSchedulerCheckNodeListChangePushExpBackOffBound(),
() -> metaServerRegistry.pushNodeListChange(NodeType.DATA)),
metaServerConfig.getSchedulerCheckNodeListChangePushFirstDelay(), TimeUnit.SECONDS);

MetaServerRegistry 中的 pushNodeListChange 定义如下:

@Override
public void pushNodeListChange(NodeType nodeType) {
StoreService storeService = ServiceFactory.getStoreService(nodeType);
if (storeService != null) {
storeService.pushNodeListChange();
}
}

DataStoreService 这里做了以下工作。

  1. 首先获取队列(expectNodesOrders)头节点,如果为Null直接返回;
  2. 获取当前数据中心的节点列表,并存储到确认表(expectNodes);
  3. 提交节点变更推送任务(firePushXxListTask);
@Override
public void pushNodeListChange() {
NodeOperator<DataNode> fireNode;
// 首先获取队列(expectNodesOrders)头节点,如果为Null直接返回;
if ((fireNode = dataConfirmStatusService.peekConfirmNode()) != null) {
NodeChangeResult nodeChangeResult = getNodeChangeResult();
Map<String, Map<String, DataNode>> map = nodeChangeResult.getNodes();
Map<String, DataNode> addNodes = map.get(nodeConfig.getLocalDataCenter());
if (addNodes != null) {
// 获取当前数据中心的节点列表,并存储到确认表(expectNodes);
Map<String, DataNode> previousNodes = dataConfirmStatusService.putExpectNodes(
fireNode.getNode(), addNodes);
// 提交节点变更推送任务(firePushXxListTask);
if (!previousNodes.isEmpty()) {
firePushDataListTask(fireNode, nodeChangeResult, previousNodes, true);
}
}
// 提交节点变更推送任务(firePushXxListTask);
firePushSessionListTask(nodeChangeResult, fireNode.getNodeOperate().toString());
}
}

这样就把任务提交到了Task任务。

2.1.4.2 定时异步处理任务

本小节对应上面步骤的后三步。

下面就是定时异步处理任务 。

  1. 处理任务,即调用 XxNodeService 的 pushXxxNode 方法,即通过 ConnectionHandler 获取所有的节点连接,发送节点列表;
  2. 收到回复后,如果需要确认,则会调用StoreService#confirmNodeStatus 方法,将该节点从expectNodes中移除;
  3. 待所有的节点从 expectNodes 中移除,则将此次操作从 expectNodesOrders 移除,处理完毕;
public class DataNodeServiceImpl implements DataNodeService {
@Override
public void pushDataNodes(NodeChangeResult nodeChangeResult, Map<String, DataNode> targetNodes,
boolean confirm, String confirmNodeIp) { if (nodeChangeResult != null) {
NodeConnectManager nodeConnectManager = getNodeConnectManager();
Collection<InetSocketAddress> connections = nodeConnectManager.getConnections(null); // add register confirm
StoreService storeService = ServiceFactory.getStoreService(NodeType.DATA);
DataCenterNodes dataCenterNodes = storeService.getDataCenterNodes();
Map<String, DataNode> registeredNodes = dataCenterNodes.getNodes(); // 通过 ConnectionHandler 获取所有的节点连接,发送节点列表;
for (InetSocketAddress address : connections) {
try {
Request<NodeChangeResult> nodeChangeRequestRequest = new Request<NodeChangeResult>() {
@Override
public NodeChangeResult getRequestBody() {
return nodeChangeResult;
} @Override
public URL getRequestUrl() {
return new URL(address);
}
};
// 发送节点列表;
Response response = dataNodeExchanger.request(nodeChangeRequestRequest); if (confirm) {
Object result = response.getResult();
if (result instanceof CommonResponse) {
CommonResponse genericResponse = (CommonResponse) result;
if (genericResponse.isSuccess()) {
// 收到回复后,如果需要确认,则会调用`StoreService#confirmNodeStatus` 方法,将该节点从expectNodes中移除;
confirmStatus(address, confirmNodeIp);
}
}
} }
}
}
}
}

其堆栈如下:

pushDataNodes:73, DataNodeServiceImpl (com.alipay.sofa.registry.server.meta.node.impl)
execute:86, DataNodeChangePushTask (com.alipay.sofa.registry.server.meta.task)
process:41, DataNodeSingleTaskProcessor (com.alipay.sofa.registry.server.meta.task.processor)
process:32, DataNodeSingleTaskProcessor (com.alipay.sofa.registry.server.meta.task.processor)
run:136, TaskExecutors$WorkerRunnable (com.alipay.sofa.registry.task.batcher)
run:748, Thread (java.lang)
2.1.4.3 确认步骤

进一步细化上文提到的 “确认步骤”

private void confirmStatus(InetSocketAddress address, String confirmNodeIp) {
String ipAddress = address.getAddress().getHostAddress();
dataStoreService.confirmNodeStatus(ipAddress, confirmNodeIp);
}

然后是 DataStoreService

  1. 收到回复后,如果需要确认,则会调用StoreService#confirmNodeStatus 方法,将该节点从expectNodes中移除;
  2. 待所有的节点从 expectNodes 中移除,则将此次操作从 expectNodesOrders 移除,处理完毕;
@Override
public void confirmNodeStatus(String ipAddress, String confirmNodeIp) {
NodeOperator<DataNode> fireNode = dataConfirmStatusService.peekConfirmNode();
if (fireNode != null) {
String fireNodeIp = fireNode.getNode().getIp();
Map<String/*ipAddress*/, DataNode> waitNotifyNodes = dataConfirmStatusService
.getExpectNodes(fireNode.getNode()); if (waitNotifyNodes != null) {
Set<String> removeIp = getRemoveIp(waitNotifyNodes.keySet());
removeIp.add(ipAddress); // 将该节点从expectNodes中移除;
waitNotifyNodes = dataConfirmStatusService.removeExpectConfirmNodes(
fireNode.getNode(), removeIp); if (waitNotifyNodes.isEmpty()) {
//all node be notified,or some disconnect node be evict
try {
// 待所有的节点从 expectNodes 中移除,则将此次操作从 expectNodesOrders 移除,处理完毕;
if (null != dataConfirmStatusService
.removeExpectNodes((dataConfirmStatusService.pollConfirmNode())
.getNode())) {
//add init status must notify
if (fireNode.getNodeOperate() == DataOperator.ADD) {
notifyConfirm(fireNode.getNode());
}
}
}
}
} else {
try {
//wait node not exist,
dataConfirmStatusService.pollConfirmNode();
}
}
}
}
2.1.4.4 总结消费流程

对于消费流程,总结如下图:

 +-------------------+        +-----------+         +------------------------+                                       +----------------+
|ServiceStateMachine+------> | Processor | +-----> |DataConfirmStatusService| | loop |
+-------------------+ +-----------+ +-----+------------------+ v |
| +------+------+ |
| put |TaskExecutors| |
v +------+------+ |
+--------+--------+ remo^e | |
+------------------> |expectNodesOrders| <-------+ | |
| loop +--------+--------+ | v |
| | | +------------+--------------+ |
| | peekConfirmNode | |DataNodeSingleTaskProcessor| |
| | | +------------+--------------+ |
| | | | |
+---------------------------+--------------------+ v | | |
| ExecutorManager | +----+---+ | v |
| +--------------------------------------------+ | |fireNode| | +----------+-----------+ |
| | TimedSuper^isorTask | | +----+---+ | |DataNodeChangePushTask| |
| | +---------------------------------------+ | | | | +----------+-----------+ |
| | | metaServerRegistry.pushNodeListChange | | | | putExpectNodes | | |
| | +---------------------------------------+ | | | | | |
| +--------------------------------------------+ | v | v |
+------------------------------------------------+ +------+------+ remo^e | +---------+---------+ |
^ | expectNodes | <---------+------------------<--------+ |DataNodeServiceImpl| |
| +------+------+ | +---------+---------+ |
| | | | |
| v | | |
| +-------------+--------+ | v |
| | firePushDataListTask +-----------------------------------> | +------+-------+ |
| +-------------+--------+ taskListenerManager.sendTaskEvent | | StoreService | |
| | | +------+-------+ ^
| v | | |
| +-------------+-----------+ | v |
^<--------------+ firePushSessionListTask +--------------------------------> | +-----+-------+ |
+-------------------------+taskListenerManager.sendTaskEvent +-----+confirmStatus| |
+-----+-------+ |
| |
>----------------+

手机上参见如下

2.1.5 总结

数据节点注册流程总结如下图。

                                                                                                       +----------------------------+
put +----------------------+ | Map(String, NodeRepository)|
+----------> |dataRepositoryService +----> | registry |
| +----------------------+ +----------------------------+
|
register addNode |
+----------------+ +------------------+ +-------+--------+
| DataNodeHandler+----->+metaServerRegistry+------->+DataStoreService| TimedSupervisorTask
+----------------+ +------------------+ +-------+--------+
| +------------------------+ +------------------+
+----------> |dataConfirmStatusService| +------> |pushNodeListChange|
putConfirmNode +------------------------+ +------------------+

手机上参见如下

2.2 SessionNodeHandler

Session节点的注册和Data节点几乎类似。

SessionNodeHandler 定义如下 :

public class SessionNodeHandler extends AbstractServerHandler<SessionNode> {

    private static final Logger LOGGER = LoggerFactory.getLogger(SessionNodeHandler.class);

    @Autowired
private Registry metaServerRegistry; @Override
public Object reply(Channel channel, SessionNode sessionNode) {
NodeChangeResult nodeChangeResult;
try {
nodeChangeResult = metaServerRegistry.register(sessionNode);
}
return nodeChangeResult;
}
}

然后是进行业务处理的SessionStoreService。

public class SessionStoreService implements StoreService<SessionNode> {

    @Override
public NodeChangeResult addNode(SessionNode sessionNode) { write.lock();
try {
String ipAddress = sessionNode.getNodeUrl().getIpAddress(); sessionRepositoryService.put(ipAddress, new RenewDecorate(sessionNode,
RenewDecorate.DEFAULT_DURATION_SECS)); sessionVersionRepositoryService.checkAndUpdateVersions(nodeConfig.getLocalDataCenter(),
System.currentTimeMillis()); renew(sessionNode, 30);
sessionConfirmStatusService.putConfirmNode(sessionNode, DataOperator.ADD); } finally {
write.unlock();
} return dataStoreService.getNodeChangeResult();
}
}

然后是SessionRepositoryService。

注意,这里map的key是ip,这是与Data不一致的地方。

@RaftService(uniqueId = "sessionServer")
public class SessionRepositoryService extends AbstractSnapshotProcess
implements
RepositoryService<String, RenewDecorate<SessionNode>> {
/**
* session node store
*/
private ConcurrentHashMap<String/*ipAddress*/, RenewDecorate<SessionNode>> registry = new ConcurrentHashMap<>(); @Override
public RenewDecorate<SessionNode> put(String ipAddress, RenewDecorate<SessionNode> sessionNode,
Long currentTimeMillis) {
try {
RenewDecorate oldRenewDecorate = registry.get(ipAddress);
registry.put(ipAddress, sessionNode);
}
return sessionNode;
}
}

由上节可知,DataServer 和 SessionServer 都有处理节点注册请求的 Handler。注册行为由 Registry 完成。

比如 metaServerHandlers。

@Bean(name = "metaServerHandlers")
public Collection<AbstractServerHandler> metaServerHandlers() {
Collection<AbstractServerHandler> list = new ArrayList<>();
list.add(metaConnectionHandler());
list.add(getNodesRequestHandler());
return list;
}

0x03 注册信息续约

3.1 关键类 RenewDecorate

节点注册的时候,节点信息被 RenewDecorate 包装起来了,这个就是实现注册信息续约和驱逐的关键,该类定义 如下:

public class RenewDecorate<T> implements Serializable {
public static final int DEFAULT_DURATION_SECS = 15;
private T renewal; // 节点对象封装
private long beginTimestamp; // 注册事件
private volatile long lastUpdateTimestamp; // 续约时间
private long duration; // 超时时间 public boolean isExpired() {
return System.currentTimeMillis() > lastUpdateTimestamp + duration;
} public void renew() {
lastUpdateTimestamp = System.currentTimeMillis() + duration;
} public void renew(long durationSECS) {
lastUpdateTimestamp = System.currentTimeMillis() + durationSECS * 1000;
}
}

该对象为注册节点信息,附加了注册时间、上次续约时间、过期时间。

续约操作就是修改lastUpdateTimestamp,是否过期就是判断System.currentTimeMillis() - lastUpdateTimestamp > duration 是否成立,成立则认为节点超时进行驱逐。

3.2 执行路径

可以看到,renew可以从多条执行路径调用。

路径一:

renew:70, RenewDecorate (com.alipay.sofa.registry.server.meta.store)
replace:176, DataRepositoryService (com.alipay.sofa.registry.server.meta.repository.service)
replace:45, DataRepositoryService (com.alipay.sofa.registry.server.meta.repository.service)
replace:39, RepositoryService (com.alipay.sofa.registry.server.meta.repository)
invokeInterface_L3_L:-1, 1889671331 (java.lang.invoke.LambdaForm$DMH)
reinvoke:-1, 171310548 (java.lang.invoke.LambdaForm$BMH)
invoker:-1, 559310456 (java.lang.invoke.LambdaForm$MH)
invokeExact_MT:-1, 426124479 (java.lang.invoke.LambdaForm$MH)
invokeWithArguments:627, MethodHandle (java.lang.invoke)
process:123, Processor (com.alipay.sofa.registry.jraft.processor)
onApply:133, ServiceStateMachine (com.alipay.sofa.registry.jraft.bootstrap)
doApplyTasks:534, FSMCallerImpl (com.alipay.sofa.jraft.core)
doCommitted:503, FSMCallerImpl (com.alipay.sofa.jraft.core)
runApplyTask:431, FSMCallerImpl (com.alipay.sofa.jraft.core)
access$100:72, FSMCallerImpl (com.alipay.sofa.jraft.core)
onEvent:147, FSMCallerImpl$ApplyTaskHandler (com.alipay.sofa.jraft.core)
onEvent:141, FSMCallerImpl$ApplyTaskHandler (com.alipay.sofa.jraft.core)
run:137, BatchEventProcessor (com.lmax.disruptor)
run:748, Thread (java.lang)

路径二:

renew:171, DataStoreService (com.alipay.sofa.registry.server.meta.store)
addNode:111, DataStoreService (com.alipay.sofa.registry.server.meta.store)
addNode:60, DataStoreService (com.alipay.sofa.registry.server.meta.store)
register:52, MetaServerRegistry (com.alipay.sofa.registry.server.meta.registry)
reply:43, DataNodeHandler (com.alipay.sofa.registry.server.meta.remoting.handler)
reply:32, DataNodeHandler (com.alipay.sofa.registry.server.meta.remoting.handler)
handleRequest:54, SyncUserProcessorAdapter (com.alipay.sofa.registry.remoting.bolt)
dispatchToUserProcessor:239, RpcRequestProcessor (com.alipay.remoting.rpc.protocol)
doProcess:145, RpcRequestProcessor (com.alipay.remoting.rpc.protocol)
run:366, RpcRequestProcessor$ProcessTask (com.alipay.remoting.rpc.protocol)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

比如在 DataStoreService 之中

@Override
public NodeChangeResult addNode(DataNode dataNode) {
NodeChangeResult nodeChangeResult;
String ipAddress = dataNode.getNodeUrl().getIpAddress();
write.lock();
try {
dataRepositoryService.put(ipAddress, new RenewDecorate(dataNode,
RenewDecorate.DEFAULT_DURATION_SECS));
renew(dataNode, 30); // 续约
nodeChangeResult = getNodeChangeResult();
dataConfirmStatusService.putConfirmNode(dataNode, DataOperator.ADD);
} finally {
write.unlock();
}
return nodeChangeResult;
}

进而调用renew

@Override
public void renew(DataNode dataNode, int duration) {
write.lock();
try {
String ipAddress = dataNode.getNodeUrl().getIpAddress();
RenewDecorate renewer = dataRepositoryService.get(ipAddress);
if (renewer == null) {
addNode(dataNode); // 新增
} else {
// 续约
if (duration > 0) {
dataRepositoryService.replace(ipAddress, new RenewDecorate(dataNode, duration));
} else {
dataRepositoryService.replace(ipAddress, new RenewDecorate(dataNode,
RenewDecorate.DEFAULT_DURATION_SECS));
} }
} finally {
write.unlock();
}
}

因为是Raft对Repository进行数据一致性维护,所以 dataRepositoryService.replace 会被 Proxy 替换,进而来到了ProxyHandler。

@Override
public Object invoke(Object proxy, Method method, Object[] args) {
try {
ProcessRequest request = new ProcessRequest();
request.setMethodArgSigs(createParamSignature(method.getParameterTypes()));
request.setMethodName(method.getName());
request.setMethodArgs(args);
request.setServiceName(serviceId); if (Processor.getInstance().isLeaderReadMethod(method)) {
return doInvokeMethod(request);
}
return client.sendRequest(request);
}
}

当 Raft 来到 服务端,堆栈如下 :

replace:165, DataRepositoryService (com.alipay.sofa.registry.server.meta.repository.service)
replace:45, DataRepositoryService (com.alipay.sofa.registry.server.meta.repository.service)
replace:39, RepositoryService (com.alipay.sofa.registry.server.meta.repository)
invokeInterface_L3_L:-1, 1177311202 (java.lang.invoke.LambdaForm$DMH)
reinvoke:-1, 960369282 (java.lang.invoke.LambdaForm$BMH)
invoker:-1, 198860519 (java.lang.invoke.LambdaForm$MH)
invokeExact_MT:-1, 2035225037 (java.lang.invoke.LambdaForm$MH)
invokeWithArguments:627, MethodHandle (java.lang.invoke)
process:123, Processor (com.alipay.sofa.registry.jraft.processor)
onApply:133, ServiceStateMachine (com.alipay.sofa.registry.jraft.bootstrap)
doApplyTasks:534, FSMCallerImpl (com.alipay.sofa.jraft.core)
doCommitted:503, FSMCallerImpl (com.alipay.sofa.jraft.core)
runApplyTask:431, FSMCallerImpl (com.alipay.sofa.jraft.core)
access$100:72, FSMCallerImpl (com.alipay.sofa.jraft.core)
onEvent:147, FSMCallerImpl$ApplyTaskHandler (com.alipay.sofa.jraft.core)
onEvent:141, FSMCallerImpl$ApplyTaskHandler (com.alipay.sofa.jraft.core)
run:137, BatchEventProcessor (com.lmax.disruptor)
run:748, Thread (java.lang)

具体renew代码如下,这样就最终完成了更新:

@Override
public RenewDecorate<DataNode> replace(String ipAddress, RenewDecorate<DataNode> dataNode,
Long currentTimeMillis) {
try {
String dataCenter = dataNode.getRenewal().getDataCenter();
NodeRepository<DataNode> dataNodeRepository = registry.get(dataCenter); if (dataNodeRepository != null) {
Map<String/*ipAddress*/, RenewDecorate<DataNode>> dataNodes = dataNodeRepository
.getNodeMap();
RenewDecorate<DataNode> oldRenewDecorate = dataNodes.get(ipAddress);
if (oldRenewDecorate != null && oldRenewDecorate.getRenewal() != null) {
oldRenewDecorate.setRenewal(dataNode.getRenewal());
oldRenewDecorate.renew();
}
}
return dataNode;
}
}

具体如下图

+---------------------------------------+        +---------------------------------------------+
| +------------------------------+ | | +----------------------------------+ |
| | +----------------+ registry |Client| | Server| +----------------------+registry | |
| | |DataStoreService| | | | | | DataRepositoryService| | |
| | +-----+----------+ | | | | +---------+------------+ | |
| | | replace | | | | ^ replace | |
| | | | | | | | | |
| | v | | | | +------+----+ | |
| | +-----+--------------------+ | | | | | Processor | | |
| | |DataRepositoryService stub| | | | | +------+----+ | |
| | +-----+--------------------+ | | | | ^ onApply | |
| | | | | | | | | |
| | v | | | | +-------+------+ | |
| | +-+---+ | | | | | StateMachine | | |
| | |Proxy| | | | | +-------+------+ | |
| | +-+---+ | | | | ^ process | |
| | | invoke | | | | | | |
| | v | | | | | | |
| | +----+-------+ | | | | +------+------+ | |
| | |ProxyHandler| | | | | |FSMCallerImpl| | |
| | +----+-------+ | | | | +------+------+ | |
| | | sendRequest | | | | ^ | |
| | v | | | | | received | |
| | +---+------+ | | | | | | |
| | |RaftClient| | | | | +-----------------+ | |
| | +----------+ | | network| | |RaftServerHandler| | |
| | | +-------------------> | +-----------------+ | |
| +------------------------------+ | | +----------------------------------+ |
| | | |
+---------------------------------------+ +---------------------------------------------+

手机上如下

3.3 ReNewNodesRequestHandler

和注册一样,续约请求的处理 Handler 为ReNewNodesRequestHandler,最终交由 StoreService 进行续约操作。另外一点,续约的时候如果没有查询到注册节点,会触发节点注册的操作。

在初始化时候,就设置了ReNew handler。

        @Bean(name = "sessionServerHandlers")
public Collection<AbstractServerHandler> sessionServerHandlers() {
Collection<AbstractServerHandler> list = new ArrayList<>();
list.add(sessionConnectionHandler());
list.add(sessionNodeHandler());
list.add(renewNodesRequestHandler()); // 注册到Server handler
list.add(getNodesRequestHandler());
list.add(fetchProvideDataRequestHandler());
return list;
} @Bean(name = "dataServerHandlers")
public Collection<AbstractServerHandler> dataServerHandlers() {
Collection<AbstractServerHandler> list = new ArrayList<>();
list.add(dataConnectionHandler());
list.add(getNodesRequestHandler());
list.add(dataNodeHandler());
list.add(renewNodesRequestHandler()); // 注册到Server handler
list.add(fetchProvideDataRequestHandler());
return list;
}

具体定义如下:

public class RenewNodesRequestHandler extends AbstractServerHandler<RenewNodesRequest> {
@Autowired
private Registry metaServerRegistry; @Override
public Object reply(Channel channel, RenewNodesRequest renewNodesRequest) {
Node renewNode = null;
renewNode = renewNodesRequest.getNode();
metaServerRegistry.renew(renewNode, renewNodesRequest.getDuration());
return null;
} @Override
public Class interest() {
return RenewNodesRequest.class;
} @Override
public HandlerType getType() {
return HandlerType.PROCESSER;
}
}

0x04 驱除

驱出的操作是由定时任务完成,MetaServer 在启动时会启动多个定时任务,详见ExecutorManager#startScheduler,其中一个任务会调用Registry#evict,其实现为遍历存储的 Map, 获得过期的列表,调用StoreService#removeNodes方法,将他们从 Repository 中移除,这个操作也会触发变更通知。该任务默认每3秒执行一次。

4.1 配置

public void startScheduler() {
scheduler.schedule(new TimedSupervisorTask("HeartbeatCheck", scheduler, heartbeatCheckExecutor,
metaServerConfig.getSchedulerHeartbeatTimeout(), TimeUnit.SECONDS,
metaServerConfig.getSchedulerHeartbeatExpBackOffBound(), () -> metaServerRegistry.evict()),
metaServerConfig.getSchedulerHeartbeatFirstDelay(), TimeUnit.SECONDS);
}

4.2 驱除

这里就是遍历各种StoreService,获取其中的过期节点,然后进行驱除。

public class MetaServerRegistry implements Registry<Node> {
@Override
public void evict() {
for (NodeType nodeType : NodeType.values()) {
StoreService storeService = ServiceFactory.getStoreService(nodeType);
if (storeService != null) {
Collection<Node> expiredNodes = storeService.getExpired();
if (expiredNodes != null && !expiredNodes.isEmpty()) {
storeService.removeNodes(expiredNodes);
}
}
}
}
}

具体又会继续调用 DataStoreService。

@Override
public void removeNodes(Collection<DataNode> nodes) {
write.lock();
try {
if (nodes != null && !nodes.isEmpty()) {
for (DataNode dataNode : nodes) {
String ipAddress = dataNode.getNodeUrl().getIpAddress();
RenewDecorate<DataNode> dataNodeRemove = dataRepositoryService
.remove(ipAddress);
if (dataNodeRemove != null) {
dataConfirmStatusService.putConfirmNode(dataNode, DataOperator.REMOVE);
}
}
}
} finally {
write.unlock();
}
}

最后调用到 DataRepositoryService

@Override
public RenewDecorate<DataNode> remove(Object key, Long currentTimeMillis) {
try {
String ipAddress = (String) key;
String dataCenter = nodeConfig.getLocalDataCenter(); NodeRepository<DataNode> dataNodeRepository = registry.get(dataCenter);
if (dataNodeRepository != null) {
Map<String/*ipAddress*/, RenewDecorate<DataNode>> dataNodes = dataNodeRepository
.getNodeMap();
if (dataNodes != null) {
RenewDecorate<DataNode> oldRenewDecorate = dataNodes.remove(ipAddress);
dataNodeRepository.setVersion(currentTimeMillis);
return oldRenewDecorate;
}
}
}
}

0x05 节点列表查询

Data,Meta, Session Server 都提供 getNodesRequestHandler ,用于处理查询当前节点列表的请求,其本质上从底层存储 Repository 读取数据返回。返回的结果的具体结构见 NodeChangeResult 类,包含各个数据中心的节点列表以及版本号。

public class NodeChangeResult<T extends Node> implements Serializable {
private final NodeType nodeType;
private Map<String/*dataCenter id*/, Map<String /*ipAddress*/, T>> nodes;
private Long version;
private Map<String/*dataCenter*/, Long /*version*/> dataCenterListVersions;
/** local dataCenter id */
private String localDataCenter;
}

5.1 配置

Data,Meta,Session Server 都提供 getNodesRequestHandler。具体如下:

@Bean(name = "sessionServerHandlers")
public Collection<AbstractServerHandler> sessionServerHandlers() {
Collection<AbstractServerHandler> list = new ArrayList<>();
list.add(sessionConnectionHandler());
list.add(sessionNodeHandler());
list.add(renewNodesRequestHandler());
list.add(getNodesRequestHandler()); // 做了配置
list.add(fetchProvideDataRequestHandler());
return list;
} @Bean(name = "dataServerHandlers")
public Collection<AbstractServerHandler> dataServerHandlers() {
Collection<AbstractServerHandler> list = new ArrayList<>();
list.add(dataConnectionHandler());
list.add(getNodesRequestHandler()); // 做了配置
list.add(dataNodeHandler());
list.add(renewNodesRequestHandler());
list.add(fetchProvideDataRequestHandler());
return list;
} @Bean(name = "metaServerHandlers")
public Collection<AbstractServerHandler> metaServerHandlers() {
Collection<AbstractServerHandler> list = new ArrayList<>();
list.add(metaConnectionHandler());
list.add(getNodesRequestHandler()); // 做了配置
return list;
}

getNodesRequestHandler的Bean配置生成如下:

@Configuration
public static class MetaServerRemotingConfiguration {
@Bean
public AbstractServerHandler getNodesRequestHandler() {
return new GetNodesRequestHandler();
}
}

5.2 响应handler

NodesRequest 是通过Bolt来响应消息。

public class GetNodesRequestHandler extends AbstractServerHandler<GetNodesRequest> {
@Autowired
private Registry metaServerRegistry; @Override
public Object reply(Channel channel, GetNodesRequest getNodesRequest) {
NodeChangeResult nodeChangeResult;
try {
nodeChangeResult = metaServerRegistry.getAllNodes(getNodesRequest.getNodeType());
}
return nodeChangeResult;
}
}

堆栈为:

getNodeChangeResult:188, MetaStoreService (com.alipay.sofa.registry.server.meta.store)
getAllNodes:96, MetaServerRegistry (com.alipay.sofa.registry.server.meta.registry)
reply:44, GetNodesRequestHandler (com.alipay.sofa.registry.server.meta.remoting.handler)
reply:33, GetNodesRequestHandler (com.alipay.sofa.registry.server.meta.remoting.handler)
handleRequest:54, SyncUserProcessorAdapter (com.alipay.sofa.registry.remoting.bolt)
dispatchToUserProcessor:239, RpcRequestProcessor (com.alipay.remoting.rpc.protocol)
doProcess:145, RpcRequestProcessor (com.alipay.remoting.rpc.protocol)
run:366, RpcRequestProcessor$ProcessTask (com.alipay.remoting.rpc.protocol)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

具体如下图所示:

                      +-------------------------+
| SyncUserProcessorAdapter|
+-----------+-------------+
|
|
v handleRequest
+----------+-----------+
|GetNodesRequestHandler|
+----------+-----------+
|
|
v getAllNodes
+--------+---------+
|MetaServerRegistry|
+--------+---------+
|
|
v getNodeChangeResult
+-------+--------+
|MetaStoreService|
+----+------+----+
| |
+-----------+ +------------+
| |
getNodeMap v v getNodeMap
+------------------+-----------+ +----------+-------------------+
|dataCenter, metaNodeRepository| ... |dataCenter, metaNodeRepository|
+------------------------------+ +------------------------------+

我们可以再具体深入下。

5.3 Registry操作

Registry 只是简单调用StoreService。

public class MetaServerRegistry implements Registry<Node> {
@Override
public NodeChangeResult getAllNodes(NodeType nodeType) {
StoreService storeService = ServiceFactory.getStoreService(nodeType);
return storeService.getNodeChangeResult();
}
}

5.4 StoreService操作

Service 会遍历数据中心,获取对应的Node列表以及version,最终返回。

public class MetaStoreService implements StoreService<MetaNode> {
@Override
public NodeChangeResult getNodeChangeResult() { NodeChangeResult nodeChangeResult = new NodeChangeResult(NodeType.META);
String localDataCenter = nodeConfig.getLocalDataCenter();
Map<String/*dataCenter*/, NodeRepository> metaRepositoryMap = metaRepositoryService.getNodeRepositories();
ConcurrentHashMap<String/*dataCenter*/, Map<String/*ipAddress*/, MetaNode>> pushNodes = new ConcurrentHashMap<>();
Map<String/*dataCenter*/, Long> versionMap = new ConcurrentHashMap<>(); metaRepositoryMap.forEach((dataCenter, metaNodeRepository) -> { if (localDataCenter.equalsIgnoreCase(dataCenter)) {
nodeChangeResult.setVersion(metaNodeRepository.getVersion());
}
versionMap.put(dataCenter, metaNodeRepository.getVersion()); Map<String, RenewDecorate<MetaNode>> dataMap = metaNodeRepository.getNodeMap();
Map<String, MetaNode> newMap = new ConcurrentHashMap<>();
dataMap.forEach((ip, dataNode) -> newMap.put(ip, dataNode.getRenewal()));
pushNodes.put(dataCenter, newMap);
}); nodeChangeResult.setLocalDataCenter(localDataCenter);
nodeChangeResult.setNodes(pushNodes);
nodeChangeResult.setDataCenterListVersions(versionMap);
return nodeChangeResult;
}
}

最后结果是:

nodeChangeResult =
nodeType = {Node$NodeType@7190} "DATA"
nodes = {ConcurrentHashMap@8267} size = 1
"DefaultDataCenter" -> {ConcurrentHashMap@8276} size = 0
key = "DefaultDataCenter"
value = {ConcurrentHashMap@8276} size = 0
version = {Long@8268} 1601126414990
dataCenterListVersions = {ConcurrentHashMap@8269} size = 1
"DefaultDataCenter" -> {Long@8268} 1601126414990
key = "DefaultDataCenter"
value = {Long@8268} 1601126414990
localDataCenter = "DefaultDataCenter"
value = {char[17]@8280}
hash = 761435552

5.5 节点变更时的数据同步

MetaServer 会通过网络连接感知到新节点上线或者下线,所有的 DataServer 中运行着一个定时刷新连接的任务 ConnectionRefreshTask,该任务定时去轮询 MetaServer,获取数据节点的信息。需要注意的是,除了 DataServer 主动去 MetaServer 拉取节点信息外,MetaServer 也会主动发送 NodeChangeResult 请求到各个节点,通知节点信息发生变化,推拉获取信息的最终效果是一致的。

本文提到了很多Raft相关信息,下篇文章就具体探究下究竟MetaServer是如何使用 Raft。

0xFF 参考

服务注册中心 MetaServer 功能介绍和实现剖析 | SOFARegistry 解析

服务注册中心如何实现 DataServer 平滑扩缩容 | SOFARegistry 解析

服务注册中心数据一致性方案分析 | SOFARegistry 解析

服务注册中心如何实现秒级服务上下线通知 | SOFARegistry 解析

服务注册中心 Session 存储策略 | SOFARegistry 解析

服务注册中心数据分片和同步方案详解 | SOFARegistry 解析

服务注册中心 SOFARegistry 解析 | 服务发现优化之路

海量数据下的注册中心 - SOFARegistry 架构介绍

服务端部署

客户端使用

全面理解Raft协议

详解蚂蚁金服 SOFAJRaft | 生产级高性能 Java 实现

从JRaft来看Raft协议实现细节

SOFAJRaft—初次使用

JRaft 用户指南 & API 详解

怎样打造一个分布式数据库——rocksDB, raft, mvcc,本质上是为了解决跨数据中心的复制

sofa-bolt源码阅读(5)-日志

Raft 为什么是更易理解的分布式一致性算法

SOFAJRaft 源码分析一(启动流程和节点变化)

SOFAJRaft 实现原理 - 生产级 Raft 算法库存储模块剖析

客户端使用

[源码阅读] 阿里SOFA服务注册中心MetaServer(2)的更多相关文章

  1. [源码阅读] 阿里SOFA服务注册中心MetaServer(1)

    [源码阅读] 阿里SOFA服务注册中心MetaServer(1) 目录 [源码阅读] 阿里SOFA服务注册中心MetaServer(1) 0x00 摘要 0x01 服务注册中心 1.1 服务注册中心简 ...

  2. [源码阅读] 阿里SOFA服务注册中心MetaServer(3)

    [源码阅读] 阿里SOFA服务注册中心MetaServer(3) 目录 [源码阅读] 阿里SOFA服务注册中心MetaServer(3) 0x00 摘要 0x01 概念 1.1 分布式一致性 1.2 ...

  3. Nacos源码结构和AP模式注册中心实现介绍

    前言 NacosAP模式源码分析目录 微服务下的注册中心如何选择 Nacos使用和注册部分源码介绍 Nacos服务心跳和健康检查源码介绍 Nacos服务发现 Nacos源码结构介绍 Nacos版本基于 ...

  4. InfluxDB源码阅读之snapshotter服务

    操作系统 : CentOS7.3.1611_x64 go语言版本:1.8.3 linux/amd64 InfluxDB版本:1.1.0 服务模块介绍 源码路径: github.com/influxda ...

  5. InfluxDB源码阅读之httpd服务

    操作系统 : CentOS7.3.1611_x64 go语言版本:1.8.3 linux/amd64 InfluxDB版本:1.1.0 服务模块介绍 源码路径: github.com/influxda ...

  6. 【Dubbo源码阅读系列】服务暴露之远程暴露

    引言 什么叫 远程暴露 ?试着想象着这么一种场景:假设我们新增了一台服务器 A,专门用于发送短信提示给指定用户.那么问题来了,我们的 Message 服务上线之后,应该如何告知调用方服务器,服务器 A ...

  7. 【Dubbo源码阅读系列】服务暴露之本地暴露

    在上一篇文章中我们介绍 Dubbo 自定义标签解析相关内容,其中我们自定义的 XML 标签 <dubbo:service /> 会被解析为 ServiceBean 对象(传送门:Dubbo ...

  8. SpringCloud 源码系列(1)—— 注册中心 Eureka(上)

    Eureka 是 Netflix 公司开源的一个服务注册与发现的组件,和其他 Netflix 公司的服务组件(例如负载均衡.熔断器.网关等)一起,被 Spring Cloud 整合为 Spring C ...

  9. xxl-job源码阅读二(服务端)

    1.源码入口 xxl-job-admin是一个简单的springboot工程,简单翻看源码,可以很快发现XxlJobAdminConfig入口. @Override public void after ...

随机推荐

  1. C++11中一个使用for+auto时容易发生的bug

    C++11中一个使用for+auto时容易发生的bug 一个小坑,那就是忘记在for循环中使用auto时加引用. 例如: for(auto num : nums){ // do some thing ...

  2. 转:brpc的研发经历

    转载自:https://www.jianshu.com/p/124dc2c7d9d3 RPC是个老概念,五花八门的实现非常多.在14年我刚转到基础架构部时,其实是不想做RPC框架的.我的想法可能和很多 ...

  3. Linux:crond(crontab)定时任务

    一..定义 Crond 是linux系统中用来定期执行命令或指定程序任务的一种服务或者软件.一般在安装完系统时,crond会默认存在. crond默认每分钟会检查系统中是否有需要执行的定时任务.如果有 ...

  4. 不用写代码也能做表单 —— 加载meta即可

    做增删改查要写多少代码? 一个表单一套代码,十个表单十套代码吗? 我这么懒,怎么会写这么多代码? 我想做到:即使一百个表单也只需要一套代码(而且不需要复制粘贴).实现多个表单,只需要加载不同的meta ...

  5. SpringBoot版不需要配置文件注解获取当前登录用户

    本文讯(2019年3月30日 飞快的蜗牛博客)   我是一个懒人,很久不写博客,想起来看到也不一定会写,只有心血来潮的时候写写,"钱塘江上潮信来,今日方知我是我"...... 空杯 ...

  6. 【Flutter 实战】菜单(Menu)功能

    老孟导读:今天介绍下Flutter中的菜单功能. PopupMenuButton 使用PopupMenuButton,点击时弹出菜单,用法如下: PopupMenuButton<String&g ...

  7. 11.redis cluster的hash slot算法和一致性 hash 算法、普通hash算法的介绍

    分布式寻址算法 hash 算法(大量缓存重建) 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡) redis cluster 的 hash slot 算法 一.hash 算法 来了一 ...

  8. swift基本数据类型使用-字典使用

    目录 1.定义的定义 2.对可变字典的基本操作 3.遍历字典 4.字典合并 5.示例 1.定义的定义 1> 不可变字典: let 2> 可变字典: var 2.对可变字典的基本操作 增删改 ...

  9. .Net EF 学习之model first

    新建一个控制台项目,然后点击添加新建项,选择ADO.Net 实体数据模型 选择空模型 右击设计器,新增,实体 右击新增,标量属性, 右侧可以设置最大长度和一些属性信息: 建好对象后右击根据模型生成数据 ...

  10. JWT理论理解

    什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的 ...