[从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理

目录

0x00 摘要

SOFARegistry 是蚂蚁金服开源的一个生产级、高时效、高可用的服务注册中心。

本系列文章重点在于分析设计和架构,即利用多篇文章,从多个角度反推总结 DataServer 或者 SOFARegistry 的实现机制和架构思路,让大家借以学习阿里如何设计。

本文为第三篇,介绍SOFARegistry网络操作之连接管理。

0x01 业务领域

上文我们讲解了SOFARegistry的网络封装和操作,本文继续网络相关部分。

虽然SOFABolt底层已经做了连接管理,比如有Manager,pool,但是SOFARegistry在上层结合业务也做了连接管理,很有特色,所以我们专文讲解。

1.1 应用场景

这里我们集中从DataServer角度出发讲解,此处和业务紧密结合。

让我们大致想想DataServer需要管理哪些连接或者类似概念。

  • MetaServer Connection:本DataServer与Meta Server的连接,用来和Meta Server交互;
  • DataServer Connection:本DataServer与其他dataServer的连接,用来数据同步;
  • 扩展开来,其他Data Server节点也需要管理;
  • SessionServer Connection,本DataServer与Session server的连接,这个非常复杂,这里会重点讲解;
  • 在SessionServer方面,又需要区分具体每个Publisher;

这就让我们来思考几个问题:

  • 究竟什么可以唯一标示一个SessionServer?
  • 什么可以唯一标示一个Publisher?ip : port?或者其他?
  • 业务上有没有特殊考虑的需要?

具体我们在后文会详述阿里的思路。

0x02 管理内容

2.1 连接管理

首先讲讲普遍意义的连接管理。

连接管理是网络操作中的核心。我们知道,一次 tcp 请求大致分为三个步骤:建立连接、通信、关闭连接。每次建立新连接都会经历三次握手,中间包含三次网络传输,对于高并发的系统,这是一笔不小的负担;关闭连接同样如此。为了减少每次网络调用请求的开销,对连接进行管理、复用,可以极大的提高系统的性能。

为了提高通信效率,我们需要考虑复用连接,减少 TCP 三次握手的次数,因此需要有连接管理的机制。

关于连接管理,SOFARegistry有两维度层次的连接管理,分别是 Connection 和 Node

2.2 管理内容

普遍意义的连接管理,通常需要处理:

  • 连接创建与销毁
  • 心跳管理
  • 空闲连接管理
  • 断线重连
  • 慢连接处理
  • 作为一个框架,当然还需要把各种连接事件分派给用户进行定制

因为SOFABolt底层已经做了底层连接管理,所以SOFARegistry只要做顶层部分连接管理即可,就是从业务角度区分保存连接

0x03 Connection管理

3.1 Connection对象

这里说的Connection我们特指sofa-bolt的Connection对象com.alipay.remoting.Connection。前文提到,SOFARegistry把sofa-bolt的Connection对象直接暴露出来。

面向连接的TCP协议要求每次peer间通信前建立一条TCP连接,该连接可抽象为一个4元组(four-tuple,有时也称socket pair):socket(localIp, localPort, remoteIp, remotePort ),这4个元素唯一地代表一条TCP连接。

在Netty中用Channel来表示一条TCP连接,在sofa-bolt使用Connection对象来抽象一个连接,一个连接在client跟server端各用一个connection对象表示

有了Connection这个抽象之后,自然的需要提供接口来管理Connection, 这个接口就是ConnectionFactory。

那么Connection是如何跟Netty进行联动呢。我们知道在Netty中,client连接到server后,server会回调initChannel方法,在这个方法我们会初始化各种事件handler,sofa-bolt就在这里创建Connection,并在Netty的Channel对象上打上Connection标,后续通过Channel就可以直接找到这个Connection。

3.2 Connection类定义

Connection其删减版定义如下,可以看到其主要成员就是 Netty channel 实例

public class Connection {

    private Channel                                                               channel;

    private final ConcurrentHashMap<Integer, InvokeFuture>                        invokeFutureMap  = new ConcurrentHashMap<Integer, InvokeFuture>(4);

    /** Attribute key for connection */
public static final AttributeKey<Connection> CONNECTION = AttributeKey.valueOf("connection"); /** Attribute key for heartbeat count */
public static final AttributeKey<Integer> HEARTBEAT_COUNT = AttributeKey.valueOf("heartbeatCount"); /** Attribute key for heartbeat switch for each connection */
public static final AttributeKey<Boolean> HEARTBEAT_SWITCH = AttributeKey.valueOf("heartbeatSwitch"); /** Attribute key for protocol */
public static final AttributeKey<ProtocolCode> PROTOCOL = AttributeKey.valueOf("protocol"); /** Attribute key for version */
public static final AttributeKey<Byte> VERSION = AttributeKey.valueOf("version"); private Url url; private final ConcurrentHashMap<Integer/* id */, String/* poolKey */> id2PoolKey = new ConcurrentHashMap<Integer, String>(256); private Set<String> poolKeys = new ConcurrentHashSet<String>(); private final ConcurrentHashMap<String/* attr key*/, Object /*attr value*/> attributes = new ConcurrentHashMap<String, Object>();
}

省去 AtributeKey 类型定义以及 Log 配置,以上是Connection中主要的成员变量。包括几个方面:

  • 连接:Channel、Url
  • 版本:protocolCode、version
  • 调用:invokeFutureMap
  • 附着:attributes
  • 引用:referenceCount、id2PoolKey、poolKeys

这里提一下 protocolCode 和 version,版本信息会被携带至对端,用于连接的协商。总的来说,通过对于 Channel 的包装,Connection 提供了丰富的上下文及引用信息,是 SOFABolt 连接管理的直接对象

3.3 ConnectionFactory

SOFARegistry建立了ConnectionFactory 连接工厂,负责创建连接、检测连接等。

这里我对Connection进行了种类,分类是我从业务角度出发,强行分为三种Connection,只是为了讲解方便。

  • MetaServerConnectionFactory,是Meta Server的连接,用来和Meta Server交互。
  • DataServerConnectionFactory ,是其他dataServer的连接,用来数据同步;
  • SessionServerConnectionFactory,是Session server的连接,这个非常复杂,后续会重点讲解。

3.4 MetaServerConnectionFactory

MetaServerConnectionFactory 就是用来对com.alipay.remoting.Connection进行连接管理。

其核心变量是一个双层Map,可以理解为一个矩阵,其维度是 Map<dataCenter, Map<ip, Connection>>

其内部函数比较简单,望名生意。

public class MetaServerConnectionFactory {

    private final Map<String, Map<String, Connection>> MAP = new ConcurrentHashMap<>();

    /**
* @param dataCenter
* @param ip
* @param connection
*/
public void register(String dataCenter, String ip, Connection connection) { Map<String, Connection> connectionMap = MAP.get(dataCenter);
if (connectionMap == null) {
Map<String, Connection> newConnectionMap = new ConcurrentHashMap<>();
connectionMap = MAP.putIfAbsent(dataCenter, newConnectionMap);
if (connectionMap == null) {
connectionMap = newConnectionMap;
}
} connectionMap.put(ip, connection);
} /**
* @param dataCenter
* @param ip
*/
public Connection getConnection(String dataCenter, String ip) {
if (MAP.containsKey(dataCenter)) {
Map<String, Connection> map = MAP.get(dataCenter);
if (map.containsKey(ip)) {
return map.get(ip);
}
}
return null;
} /**
* @param dataCenter
*/
public Map<String, Connection> getConnections(String dataCenter) {
if (MAP.containsKey(dataCenter)) {
return MAP.get(dataCenter);
}
return new HashMap<>();
} /**
* @param dataCenter
*/
public Set<String> getIps(String dataCenter) {
if (MAP.containsKey(dataCenter)) {
Map<String, Connection> map = MAP.get(dataCenter);
if (map != null) {
return map.keySet();
}
}
return new HashSet<>();
} /**
* @param dataCenter
*/
public void remove(String dataCenter) {
Map<String, Connection> map = getConnections(dataCenter);
if (!map.isEmpty()) {
for (Connection connection : map.values()) {
if (connection.isFine()) {
connection.close();
}
}
}
MAP.remove(dataCenter);
} /**
* @param dataCenter
* @param ip
*/
public void remove(String dataCenter, String ip) {
if (MAP.containsKey(dataCenter)) {
Map<String, Connection> map = MAP.get(dataCenter);
if (map != null) {
map.remove(ip);
}
}
} public Set<String> getAllDataCenters() {
return MAP.keySet();
}
}

3.5 DataServerConnectionFactory

DataServerConnectionFactory 就是用来对com.alipay.remoting.Connection进行连接管理。

其核心变量是以ip:port作为key,Connection作为value的一个Map

其内部函数比较简单,望名生意。

import com.alipay.remoting.Connection;

/**
* the factory to hold connections that other dataservers connected to local server
*/
public class DataServerConnectionFactory { /**
* collection of connections
* key:connectId ip:port
*/
private final Map<String, Connection> MAP = new ConcurrentHashMap<>(); /**
* register connection
*
* @param connection
*/
public void register(Connection connection) {
MAP.put(getConnectId(connection), connection);
} /**
* remove connection by specific ip+port
*
* @param connection
*/
public void remove(Connection connection) {
MAP.remove(getConnectId(connection));
} /**
* get connection by ip
*
* @param ip
* @return
*/
public Connection getConnection(String ip) {
return MAP.values().stream().filter(connection -> ip.equals(connection.getRemoteIP()) && connection.isFine()).findFirst().orElse(null);
} private String getConnectId(Connection connection) {
return connection.getRemoteIP() + ":" + connection.getRemotePort();
}
}

3.5.1 注册

当需要管理连接时候,可以通过如下来进行注册。

public void connected(Channel channel) throws RemotingException {
super.connected(channel);
dataServerConnectionFactory.register(((BoltChannel) channel).getConnection());
}

这样往ConcurrentHashMap client放入,是根据IP和port构建了url,然后url作为key。

3.5.2 获取

com.alipay.remoting.Connection 可以通过Channel进行获取。

DataNodeExchanger就采用如下方式获取Client。

conn = ((BoltChannel) dataNodeExchanger.connect(new URL(ip, dataServerConfig
.getSyncDataPort()))).getConnection();

3.5.3 DataSyncServerConnectionHandler

有了注册与获取,接下来我们看看连接事件响应。

DataSyncServerConnectionHandler 是 server 的handler。

前文提到了,DataSyncServerConnectionHandler是连接事件处理器 (ConnectionEventProcessor),用来监听建连事件(ConnectionEventType.CONNECT)与断连事件(ConnectionEventType.CLOSE)。

这里就是针对各种事件,简单的对Connection做相应维护。

public class DataSyncServerConnectionHandler extends AbstractServerHandler {
@Autowired
private DataServerConnectionFactory dataServerConnectionFactory; @Override
public ChannelHandler.HandlerType getType() {
return ChannelHandler.HandlerType.LISENTER;
} @Override
public void connected(Channel channel) throws RemotingException {
super.connected(channel);
dataServerConnectionFactory.register(((BoltChannel) channel).getConnection());
} @Override
public void disconnected(Channel channel) throws RemotingException {
super.disconnected(channel);
dataServerConnectionFactory.remove(((BoltChannel) channel).getConnection());
} @Override
protected Node.NodeType getConnectNodeType() {
return Node.NodeType.DATA;
}
}

3.6 SessionServerConnectionFactory

SessionServerConnectionFactory 包括复杂的逻辑。

3.6.1 问题

回顾前面问题:

  • 究竟什么可以唯一标示一个SessionServer?
  • 什么可以唯一标示一个Publisher?
  • ip : port?或者其他?
  • 业务上有没有特殊考虑的需要?

下面我们就一一看看阿里如何处理。

3.6.2 逻辑概念和关系

首先要讲讲阿里的几个逻辑概念:

  • process Id 代表了Session Server,格式是类似uid的构建,每个Session Server有一个唯一的process Id,Session Server与process Id是一对一的关系;
  • Connection 就是一个 Session Server 和 Data Server 之间的 Connection;
  • connect Id 代表了Publisher,格式是 ip : port。connect Id与Publisher是一对一的关系;
  • 一个Session Server包括许多Publiser,即许多connection id;
  • Session Server address 是一个 ip : port 的组合,代表一个 Connection 的 session server 那一端;
  • 一个 Session Server 可能对于一个data Server有多个连接;这个目前原因不知,没有发现业务原因,可能推测如下:因为连接敏感性,网络不稳定性,所以SOFABolt重连时候会选择一个新端口,所以会有多个Connection存在。所以一个processID对应多个sessionConnAddress;

具体就是,SOFARegistry 将服务数据 (PublisherRegister) 和 服务发布者 (Publisher) 的连接的生命周期绑定在一起:每个 PublisherRegister 定义属性 connId,connId 由注册本次服务的 Publisher 的连接标识 (IP 和 Port)构成,也就是只要该 Publisher 和 SessionServer 断连,服务信息数据即失效。客户端重新建连成功后重新注册服务数据,重新注册的服务数据会被当成新的数据,考虑更换长连接后 Publisher 的 connId 是 Renew 新生成的。

3.6.3 示例图

我们假设一个Session server内部有两个 Publisher,都连接到一个Data Server上。

这些 address 格式都是 ip : port,举例如下:

  • SessionServer address 1 是 :1.1.2.3 : 1

  • SessionServer address 2 是 :1.1.2.3 : 2

  • SessionServer address 3 是 :1.1.2.3 : 3

  • SessionServer address 4 是 :1.1.2.3 : 4

  • DataServer address 1 是 :2.2.2.3 : 1

  • DataServer address 2 是 :2.2.2.3 : 2

具体逻辑如图:

    +----------+                        +----------+
| Client | | Client |
+----+-----+ +----+-----+
| |
| |
| |
| |
| SessionServer address 1 | SessionServer address 2
v v
+--------+-----------------------------------+----------------+
| Session Server(process Id) |
| |
| +------------------------+ +-----------------------+ |
| | Publisher(connect Id) | ... | Publisher(connect Id) | |
| +------------------------+ +-----------------------+ |
+-------------------------------------------------------------+
| SessionServer address 3 | SessionServer address 4
| |
| |
| |
| |
+----------> +---------------+ <---------+
DataServer address 1 | Data Server | DataServer address 2
+---------------+

3.6.4 主要变量

所以,SessionServerConnectionFactory的几个变量就对应了上述这些逻辑关系,具体如下:

  • SESSION_CONN_PROCESS_ID_MAP : Map<SessionServer address, SessionServer processId>,这个代表了怎么从 SessionServer address 找到 SessionServer processId,是一对一的关系;
  • PROCESS_ID_CONNECT_ID_MAP : Map<SessionServer processId, Set<ip:port of clients> >,这个代表了一个Session Server 包括了哪些Publiser
  • PROCESS_ID_SESSION_CONN_MAP : Map<SessionServer processId, pair(SessionServer address, SessionServer connection)>,这代表了一个 Session Server 包括哪些 Connection,每个Connection 被其Session Server 端的address 唯一确定;

这些都代表了本 Data Server 和 其 Session Server 之间的关系

+-----------------------------------------------------------------------------------------+     +--------------------------------+
| SessionServerConnectionFactory | | SessionServer |
| | | |
| | | +-------------------------+ |
| +---------------------------------------------------------+ | | | SessionServer address | |
| | SESSION_CONN_PROCESS_ID_MAP | | | | | |
| | | | | | +----------------+ | |
| | | +----------------------------------->+ | process Id | | |
| | Map<SessionServer address, SessionServer processId> | | | +-------------------------+ |
| | | | | | | |
| +---------------------------------------------------------+ | | | Publisher | |
| | | +--+-------------+ |
| | | ^ |
| +---------------------------------------------------------+ | | | |
| | PROCESS_ID_CONNECT_ID_MAP | | +------------------------+-------+
| | | | | ^
| | Map<SessionServer processId, Set<ip:port of clients> > +-------------------------------------------+ |
| | | | |
| +---------------------------------------------------------+ | |
| | |
| | |
| +------------------------------------------------------------------------------------+ | +------------+ |
| |PROCESS_ID_SESSION_CONN_MAP +-------> | Connection +---------+
| | | | +------------+
| | | |
| |Map<SessionServer processId, pair(SessionServer address, SessionServer connection)> | |
| | | |
| +------------------------------------------------------------------------------------+ |
+-----------------------------------------------------------------------------------------+

手机上如下图:

具体类定义如下:

public class SessionServerConnectionFactory {

    private static final int               DELAY                       = 30 * 1000;
private static final Map EMPTY_MAP = new HashMap(0); /**
* key : SessionServer address
* value: SessionServer processId
*/
private final Map<String, String> SESSION_CONN_PROCESS_ID_MAP = new ConcurrentHashMap<>(); /**
* key : SessionServer processId
* value: ip:port of clients
*/
private final Map<String, Set<String>> PROCESS_ID_CONNECT_ID_MAP = new ConcurrentHashMap<>(); /**
* key : SessionServer processId
* value: pair(SessionServer address, SessionServer connection)
*/
private final Map<String, Pair> PROCESS_ID_SESSION_CONN_MAP = new ConcurrentHashMap<>(); @Autowired
private DisconnectEventHandler disconnectEventHandler;
}

3.6.5 Pair

这是SessionServerConnectionFactory的内部类。

PROCESS_ID_SESSION_CONN_MAP是 Map<SessionServer processId, pair(SessionServer address, SessionServer connection)>,代表了一个 Session Server 包括哪些Connection,每个Connection 被其Session Server 端的address 唯一确定。

Pair就是SessionServer address, SessionServer connection的组合,定义如下:

private static class Pair {
private AtomicInteger roundRobin = new AtomicInteger(-1);
private Map<String, Connection> connections;
private String lastDisconnectedSession; private Pair(Map<String, Connection> connections) {
this.connections = connections;
} @Override
public boolean equals(Object o) {
return connections.equals(((Pair) o).getConnections())
&& (((Pair) o).lastDisconnectedSession.equals(lastDisconnectedSession));
} /**
* Getter method for property <tt>connections</tt>.
* @return property value of connections
*/
private Map<String, Connection> getConnections() {
return connections;
}
}

当生成时,Session Server 端的address,这是由InetSocketAddress转换而来。此类用于实现 IP 套接字地址 (IP 地址+端口号),用于socket 通信;

public void registerSession(String processId, Set<String> connectIds, Connection connection) {
String sessionConnAddress = NetUtil.toAddressString(connection.getRemoteAddress()); SESSION_CONN_PROCESS_ID_MAP.put(sessionConnAddress, processId); Set<String> connectIdSet = PROCESS_ID_CONNECT_ID_MAP
.computeIfAbsent(processId, k -> ConcurrentHashMap.newKeySet());
connectIdSet.addAll(connectIds); Pair pair = PROCESS_ID_SESSION_CONN_MAP.computeIfAbsent(processId, k -> new Pair(new ConcurrentHashMap<>()));
pair.getConnections().put(sessionConnAddress, connection);
}

3.6.6 processId

processId是在Session Server之中生成,可以看出,是IP,时间戳,循环递增整数构建。这样就可以唯一确定一个SessionServer。

public class SessionProcessIdGenerator {
/**
* Generate session processId.
*/
public static String generate() {
String localIp = NetUtil.getLocalSocketAddress().getAddress().getHostAddress();
if (localIp != null && !localIp.isEmpty()) {
return getId(getIPHex(localIp), System.currentTimeMillis(), getNextId());
}
return EMPTY_STRING;
}
}

3.7 SessionServerConnectionFactory业务流程

因为高层连接管理与业务密切耦合,所以我们接下来分析业务。看看调用 SessionServerConnectionFactory的业务流程。

具体registerSession从何处调用,这就涉及到两个消息:SessionServerRegisterRequest 和PublishDataRequest。即有两个途径会调用。而且业务涉及到Session Server与DataServer

3.7.1 SessionServerRegisterRequest

当重新连接的时候,会统一注册 Session Server 本身包含的所有Publisher。对应在Session Server之中,如下可以看到:

  • 从sessionServer获取connectIds。
  • 建立SessionServerRegisterRequest,然后发送。

代码如下:

public class SessionRegisterDataTask extends AbstractSessionTask {
@Override
public void setTaskEvent(TaskEvent taskEvent) { //taskId create from event
if (taskEvent.getTaskId() != null) {
setTaskId(taskEvent.getTaskId());
} Object obj = taskEvent.getEventObj(); if (obj instanceof BoltChannel) {
this.channel = (BoltChannel) obj;
}
Server sessionServer = boltExchange.getServer(sessionServerConfig.getServerPort()); if (sessionServer != null) { Collection<Channel> chs = sessionServer.getChannels();
Set<String> connectIds = new HashSet<>();
chs.forEach(channel -> connectIds.add(NetUtil.toAddressString(channel.getRemoteAddress()))); sessionServerRegisterRequest = new SessionServerRegisterRequest(
SessionProcessIdGenerator.getSessionProcessId(), connectIds);
}
}
}

来到DataServer,SessionServerRegisterHandler会进行处理调用,用到了sessionServerConnectionFactory。

public class SessionServerRegisterHandler extends
AbstractServerHandler<SessionServerRegisterRequest> {
@Override
public Object doHandle(Channel channel, SessionServerRegisterRequest request) {
Set<String> connectIds = request.getConnectIds();
if (connectIds == null) {
connectIds = new HashSet<>();
}
sessionServerConnectionFactory.registerSession(request.getProcessId(), connectIds,
((BoltChannel) channel).getConnection());
return CommonResponse.buildSuccessResponse();
}
}

3.7.2 PublishDataRequest

当注册Publisher时候。在Session Server之中,可以看到建立了请求。

private Request<PublishDataRequest> buildPublishDataRequest(Publisher publisher) {
return new Request<PublishDataRequest>() {
private AtomicInteger retryTimes = new AtomicInteger(); @Override
public PublishDataRequest getRequestBody() {
PublishDataRequest publishDataRequest = new PublishDataRequest();
publishDataRequest.setPublisher(publisher);
publishDataRequest.setSessionServerProcessId(SessionProcessIdGenerator
.getSessionProcessId());
return publishDataRequest;
} @Override
public URL getRequestUrl() {
return getUrl(publisher.getDataInfoId());
} @Override
public AtomicInteger getRetryTimes() {
return retryTimes;
}
};
}

在data server之中,会调用处理,用到了sessionServerConnectionFactory。

public class PublishDataHandler extends AbstractServerHandler<PublishDataRequest> {
@Override
public Object doHandle(Channel channel, PublishDataRequest request) {
Publisher publisher = Publisher.internPublisher(request.getPublisher());
if (forwardService.needForward()) {
CommonResponse response = new CommonResponse();
response.setSuccess(false);
response.setMessage("Request refused, Server status is not working");
return response;
} dataChangeEventCenter.onChange(publisher, dataServerConfig.getLocalDataCenter()); if (publisher.getPublishType() != PublishType.TEMPORARY) {
String connectId = WordCache.getInstance().getWordCache(
publisher.getSourceAddress().getAddressString());
sessionServerConnectionFactory.registerConnectId(request.getSessionServerProcessId(),
connectId);
// record the renew timestamp
datumLeaseManager.renew(connectId);
} return CommonResponse.buildSuccessResponse();
}
}

3.7.3 DatumLeaseManager

上述代码提到了DatumLeaseManager,这里可以看到就是对connectId,即Publisher进行续约

  • connectIdRenewTimestampMap : 记录了renew时间;

  • locksForConnectId :只有一个task能够更新;

renew 函数记录本次renew时间戳,启动evict task,如果到期没有renew,就去除。

public class DatumLeaseManager implements AfterWorkingProcess {
/** record the latest heartbeat time for each connectId, format: connectId -> lastRenewTimestamp */
private final Map<String, Long> connectIdRenewTimestampMap = new ConcurrentHashMap<>(); /** lock for connectId , format: connectId -> true */
private ConcurrentHashMap<String, Boolean> locksForConnectId = new ConcurrentHashMap(); /**
* record the renew timestamp
*/
public void renew(String connectId) { // record the renew timestamp
connectIdRenewTimestampMap.put(connectId, System.currentTimeMillis());
// try to trigger evict task
scheduleEvictTask(connectId, 0);
} }

0x04 节点管理

除了具体连接之外,SOFARegistry也对Data 节点进行另一个维度的连接管理。具体在DataServerNodeFactory完成。

4.1 DataServerNodeFactory

4.1.1 DataServerNode

就是简单的数据结构,没有建立Bean。

public class DataServerNode implements HashNode {

    private String     ip;

    private String     dataCenter;

    private Connection connection;
}

4.1.2 DataServerNodeFactory

对应Node的连接管理 则是 DataServerNodeFactory。

在具体模块控制上,DataServerNodeFactory拥有自己的Bean。DataServerConnectionFactory 则全部是Static类型,直接static使用

DataServerNodeFactory的关键变量有两个:

  • MAP是以dataCenter和ip作为维度的一个Node矩阵,是数据节点相关数据;
  • CONSISTENT_HASH_MAP则是用dataCenter作为key,ConsistentHash作为value的Map;

具体定义如下:

public class DataServerNodeFactory {
/**
* row: dataCenter
* column: ip
* value dataServerNode
*/
private static final Map<String, Map<String, DataServerNode>> MAP = new ConcurrentHashMap<>(); /**
* key: dataCenter
* value: consistentHash
*/
private static final Map<String, ConsistentHash<DataServerNode>> CONSISTENT_HASH_MAP = new ConcurrentHashMap<>();
}

4.2 业务流程

4.2.1 注册

具体在LocalDataServerChangeEventHandler 和 DataServerChangeEventHandler 全都有涉及。

public class LocalDataServerChangeEventHandler extends
AbstractEventHandler<LocalDataServerChangeEvent> {
private void connectDataServer(String dataCenter, String ip) {
Connection conn = null;
for (int tryCount = 0; tryCount < TRY_COUNT; tryCount++) {
try {
conn = ((BoltChannel) dataNodeExchanger.connect(new URL(ip, dataServerConfig.getSyncDataPort()))).getConnection();
break;
}
} //maybe get dataNode from metaServer,current has not start! register dataNode info to factory,wait for connect task next execute
DataServerNodeFactory.register(new DataServerNode(ip, dataCenter, conn),
dataServerConfig);
}
}
}

以及

public class DataServerChangeEventHandler extends AbstractEventHandler<DataServerChangeEvent> {
private void connectDataServer(String dataCenter, String ip) {
Connection conn = null;
for (int tryCount = 0; tryCount < TRY_COUNT; tryCount++) {
try {
conn = ((BoltChannel) dataNodeExchanger.connect(new URL(ip, dataServerConfig
.getSyncDataPort()))).getConnection();
break;
} catch (Exception e) {
TimeUtil.randomDelay(3000);
}
}
//maybe get dataNode from metaServer,current has not start! register dataNode info to factory,wait for connect task next execute
DataServerNodeFactory.register(new DataServerNode(ip, dataCenter, conn), dataServerConfig);
}
}

4.2.2 使用

使用就是从MAP与CONSISTENT_HASH_MAP中提取Node,这里把从CONSISTENT_HASH_MAP提取的代码摘录如下:

/**
* get dataserver by specific datacenter and dataInfoId
*
* @param dataCenter
* @param dataInfoId
* @return
*/
public static DataServerNode computeDataServerNode(String dataCenter, String dataInfoId) {
ConsistentHash<DataServerNode> consistentHash = CONSISTENT_HASH_MAP.get(dataCenter);
if (consistentHash != null) {
return consistentHash.getNodeFor(dataInfoId);
}
return null;
} public static List<DataServerNode> computeDataServerNodes(String dataCenter, String dataInfoId,
int backupNodes) {
ConsistentHash<DataServerNode> consistentHash = CONSISTENT_HASH_MAP.get(dataCenter);
if (consistentHash != null) {
return consistentHash.getNUniqueNodesFor(dataInfoId, backupNodes);
}
return null;
}

0x05 总结

关于连接管理,SOFARegistry有两维度层次的连接管理,分别是 Connection 和 Node

因为SOFABolt底层已经做了底层连接管理,所以SOFARegistry只要做顶层部分连接管理即可,就是从业务角度区分注册,保存,获取连接。具体就是:

  • Connection 就是一个 Session Server 和 Data Server 之间的 Connection;
  • 一个 Session Server 可能对于一个data Server有多个连接;
  • 一个Session Server包括许多Publiser;
  • Connection与Publiser一一对应;

SOFARegistry 将服务数据 (PublisherRegister) 和 服务发布者 (Publisher) 的连接的生命周期绑定在一起:每个 PublisherRegister 定义属性 connId,connId 由注册本次服务的 Publisher 的连接标识 (IP 和 Port)构成。

只要该 Publisher 和 SessionServer 断连,服务信息数据即失效。客户端重新建连成功后重新注册服务数据,重新注册的服务数据会被当成新的数据,考虑更换长连接后 Publisher 的 connId 是 Renew 新生成的。

如下图所示:

                 +----------+                        +----------+
| Client | | Client |
+----+-----+ +----+-----+
| |
| |
| |
| |
| |
| |
+-------------------------------------------------------------+
| | Session Server(process Id) | |
| v v |
| +------+-----------------+ +--------+--------------+ |
| | Publisher(connect Id) | ... | Publisher(connect Id) | |
| +------------------------+ +-----------------------+ |
+-------------------------------------------------------------+
| |
| |
| Connection Connection |
| |
| |
| |
v v
+---------------------+------------------------------------------+--------------------+
| Data Server |
| |
| Map<SessionServer address, SessionServer processId> |
| |
| Map<SessionServer processId, Set<ip:port of clients> > |
| |
| Map<SessionServer processId, pair(SessionServer address, SessionServer connection)> |
| |
+-------------------------------------------------------------------------------------+

0xFF 参考

https://timyang.net/architecture/cell-distributed-system/

SOFABolt 源码分析12 - Connection 连接管理设计

SOFABolt 源码分析2 - RpcServer 服务端启动的设计

SOFABolt 源码分析3 - RpcClient 客户端启动的设计

[从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理的更多相关文章

  1. [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构

    [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构 0x00 摘要 之前我们通过三篇文章初步分析了 MetaServer 的基本架构,MetaServer 这三篇文章为我们接下来的工作做了 ...

  2. [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作

    [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 目录 [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 0x00 摘要 0x01 业务领域 1.1 SOFARegis ...

  3. [从源码学设计]蚂蚁金服SOFARegistry之消息总线

    [从源码学设计]蚂蚁金服SOFARegistry之消息总线 目录 [从源码学设计]蚂蚁金服SOFARegistry之消息总线 0x00 摘要 0x01 相关概念 1.1 事件驱动模型 1.1.1 概念 ...

  4. [从源码学设计]蚂蚁金服SOFARegistry之消息总线异步处理

    [从源码学设计]蚂蚁金服SOFARegistry之消息总线异步处理 目录 [从源码学设计]蚂蚁金服SOFARegistry之消息总线异步处理 0x00 摘要 0x01 为何分离 0x02 业务领域 2 ...

  5. [从源码学设计]蚂蚁金服SOFARegistry之存储结构

    [从源码学设计]蚂蚁金服SOFARegistry之存储结构 目录 [从源码学设计]蚂蚁金服SOFARegistry之存储结构 0x00 摘要 0x01 业务范畴 1.1 缓存 1.2 DataServ ...

  6. [从源码学设计]蚂蚁金服SOFARegistry之推拉模型

    [从源码学设计]蚂蚁金服SOFARegistry之推拉模型 目录 [从源码学设计]蚂蚁金服SOFARegistry之推拉模型 0x00 摘要 0x01 相关概念 1.1 推模型和拉模型 1.1.1 推 ...

  7. [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用

    [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 目录 [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 0x00 摘要 0x01 业务领域 1.1 应用场景 0x02 定 ...

  8. [从源码学设计]蚂蚁金服SOFARegistry 之 自动调节间隔周期性任务

    [从源码学设计]蚂蚁金服SOFARegistry 之 自动调节间隔周期性任务 目录 [从源码学设计]蚂蚁金服SOFARegistry 之 自动调节间隔周期性任务 0x00 摘要 0x01 业务领域 0 ...

  9. [从源码学设计]蚂蚁金服SOFARegistry 之 如何与Meta Server交互

    [从源码学设计]蚂蚁金服SOFARegistry 之 如何与Meta Server交互 目录 [从源码学设计]蚂蚁金服SOFARegistry 之 如何与Meta Server交互 0x00 摘要 0 ...

随机推荐

  1. C#连接Access

    连接数据库 string oleCon = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source= " + Application.Sta ...

  2. vue-cli2.0创建项目步骤

    Vue是近两年来比较火的一个前端框架(渐进式框架吧),与reactjs和angularjs三国鼎立,我不是职业前端,做过Vue,了解了一下React,听说过Angluar.我只能这么说,我来晚了,没经 ...

  3. jquery里面的一些方法使用

    prop("属性名");  //获取属性名 prop("属性名","属性值");  //设置属性名 change(fucntion(){  ...

  4. Learn day9 粘包\struct用法\hashlib校验\socketserver并发\模块引入\进程\join\守护进程

    1.粘包现象 总结 : 导致黏包现象的两种情况 hello,worl d (1) 在发送端,发送数据太快,频繁发送 (2) 在接收端,接收数据太慢,延迟截取 # ### 服务端 import sock ...

  5. 基于uni-app的微信小程序之分包

    作者:故事我忘了¢个人微信公众号:程序猿的月光宝盒 目录 0. 缘由 1. 关于分包 1.0 这是 官方文档 1.1 注意事项 2.使用方法 2.1 首先你得有个uniapp的微信小程序项目 2.2 ...

  6. (一)http协议介绍

    HTTP协议详解 (一) 介绍 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本 ...

  7. 记录一些API(持续更新)

    //对response进行编解码URLEncoder.encode(string,"UTF-8");//ts检查checkbox是否为选中状态$event.target.check ...

  8. itextpdf freemarker渲染

    现有需求涉及到打印pdf操作,简单找了俩种方式 在现有的模板上进行编辑,操作难度比较大 通过freemarker生成静态页面,在进行转换html,完美. 关于动态生成pdf,网上参考的挺多的,看来看去 ...

  9. ssh连接缓慢的问题分析

    之前遇到ssh连接缓慢的问题 一般会检查Server端 /etc/ssh/sshd_config配置文件的两个地方 1.设置UseDNS no 因为我们ssh连接服务器的话 如果UseDNS选项是打开 ...

  10. SpringBoot进阶教程(六十五)自定义注解

    在上一篇文章<SpringBoot进阶教程(六十四)注解大全>中介绍了springboot的常用注解,springboot提供的注解非常的多,这些注解简化了我们的很多操作.今天主要介绍介绍 ...