一致性协议的抽象

说明:本篇文章部分内容参考了网上的文章(在下文已给出原文链接),由于本人能力有限,如果有书写错误的地方,欢迎各位大佬批评指正!我们互相交流,学习,共同进步!

该项目的地址:https://github.com/xiaoheng1/nacos_read

一致性,即多个副本之间是否能保持一致的特性. 而副本的本质就是数据,对数据的操作,不是获取就是修改. 同时,一致性协议其实是针对分布式情况的,所以必然会涉及多个节点. 因此,需要有相应的接口能够调整一致性协议的协同工作节点.

所以,一致性协议其实更加关注的是读、写在集群间的一致性.

ConsistencyProtocol

public interface ConsistencyProtocol<T extends Config, P extends RequestProcessor> extends CommandOperations {

/**
 * Consistency protocol initialization: perform initialization operations based on the incoming.
 * Config 一致性协议初始化,根据Config 实现类
 *
 * @param config {@link Config}
 */
void init(T config);

/**
 * Add a request handler.
 *
 * @param processors {@link RequestProcessor}
 */
void addRequestProcessors(Collection<P> processors);

/**
 * Copy of metadata information for this consensus protocol.
 * 该一致性协议的元数据信息
 *
 * @return metaData {@link ProtocolMetaData}
 */
ProtocolMetaData protocolMetaData();

/**
 * Obtain data according to the request.
 *
 * @param request request
 * @return data {@link Response}
 * @throws Exception {@link Exception}
 */
Response getData(ReadRequest request) throws Exception;

/**
 * Get data asynchronously.
 *
 * @param request request
 * @return data {@link CompletableFuture}
 */
CompletableFuture<Response> aGetData(ReadRequest request);

/**
 * Data operation, returning submission results synchronously.
 * 同步数据提交,在 Datum 中已携带相应的数据操作信息
 *
 * @param request {@link com.alibaba.nacos.consistency.entity.WriteRequest}
 * @return submit operation result {@link Response}
 * @throws Exception {@link Exception}
 */
Response write(WriteRequest request) throws Exception;

/**
 * Data submission operation, returning submission results asynchronously.
 * 异步数据提交,在 Datum中已携带相应的数据操作信息,返回一个Future,自行操作,提交发生的异常会在CompleteFuture中
 *
 * @param request {@link com.alibaba.nacos.consistency.entity.WriteRequest}
 * @return {@link CompletableFuture} submit result
 * @throws Exception when submit throw Exception
 */
CompletableFuture<Response> writeAsync(WriteRequest request);

/**
 * New member list .
 * 新的成员节点列表,一致性协议自行处理相应的成员节点是加入还是离开
 *
 * @param addresses [ip:port, ip:port, ...]
 */
void memberChange(Set<String> addresses);

/**
 * Consistency agreement service shut down .
 * 一致性协议服务关闭![在这里插入图片描述](https://img-blog.csdnimg.cn/20210606214245859.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1J1bl9ieV9XaW5k,size_16,color_FFFFFF,t_70#pic_center)

 */
void shutdown();

}

基于一致性协议之上,可以进一步抽象出 APProtocol & CPProtocol.

在这里插入图片描述

目前,看到只有一个 Jraft 实现了 CPProtocol.

有一个问题:AP 协议的实现 DistroProtocol 其实并没有实现 APProtocol 接口的,而是单独弄了一个,这个破坏了底层的设计.

还有一个组件值得我们注意:ConsistencyService 一致性服务.

从注释上看,它的目标是为了将一致性服务和业务解耦,用户并不知道正在使用什么一致性服务,这样我们也为用户提供了扩展底层一致性服务的空间.

那么它和 ConsistencyProtocol 有什么区别了?

我的理解是 ConsistencyProtocol 就是底层干活的,而 ConsistencyService 是暴露给用户的. 同时 ConsistencyService 有一些不同的实现,比如基于 AP 实现的 DistroConsistencyServiceImpl,基于 CP 实现的 PersistentServiceProcessor. 这两者在底层干活的就是 Protocol.

我们重点关注下 Distro 协议.

数据存储结构:

@Component
public class DataStore {
private Map<String, Datum> dataMap = new ConcurrentHashMap<>(1024);
}

这个结构表明了 Distro 协议是内存存储数据的,这一点很重要.

接着看下 Datum 的结构.

public class Datum implements Serializable {

private static final long serialVersionUID = -2525482315889753720L;

public String key;

public T value;

}

这个表明 Datum 本身是一个 key-value 的单一结构,value 是 record 类型.

目前 Record 有三个实现:

Instances 存储所有的实例信息(某个服务下的实例信息).

public class Instances implements Record {

private List<Instance> instanceList = new ArrayList<>();    

}

Service 三级结构信息(Service - Cluster - Instance). 同时需要注意的是,service 中有哦一个定时任务 clientBeatCheckTask,这个定时任务表明 nacos 是 server 和 client 双向探活的.
客户端每隔 5 秒中向服务端探活,同样的,服务端每隔 5 秒也会向客户端探活.

public class Service extends com.alibaba.nacos.api.naming.pojo.Service implements Record, RecordListener {

private static final String SERVICE_NAME_SYNTAX = "[0-9a-zA-Z@\\.:_-]+";

@JsonIgnore
private ClientBeatCheckTask clientBeatCheckTask = new ClientBeatCheckTask(this);

private Map<String, Cluster> clusterMap = new HashMap<>();

}

Service 还有一个需要注意的点是 RecordListener. 这是一个监听器,说明当发生变更的时候,有些监听事件要进行处理. 比如通知客户端有消息发生变更.

SwitchDomain 同样实现 Record 接口,我理解这个是一个开关.

再次回到这个 ConsistencyService.

public interface ConsistencyService {

/**
 * Put a data related to a key to Nacos cluster.
 *
 * @param key   key of data, this key should be globally unique
 * @param value value of data
 * @throws NacosException nacos exception
 */
void put(String key, Record value) throws NacosException;

/**
 * Remove a data from Nacos cluster.
 *
 * @param key key of data
 * @throws NacosException nacos exception
 */
void remove(String key) throws NacosException;

/**
 * Get a data from Nacos cluster.
 *
 * @param key key of data
 * @return data related to the key
 * @throws NacosException nacos exception
 */
Datum get(String key) throws NacosException;

/**
 * Listen for changes of a data.
 *
 * @param key      key of data
 * @param listener callback of data change
 * @throws NacosException nacos exception
 */
void listen(String key, RecordListener listener) throws NacosException;

/**
 * Cancel listening of a data.
 *
 * @param key      key of data
 * @param listener callback of data change
 * @throws NacosException nacos exception
 */
void unListen(String key, RecordListener listener) throws NacosException;

/**
 * Get the error message of the consistency protocol.
 *
 * @return the consistency protocol error message.
 */
Optional<String> getErrorMsg();

/**
 * Tell the status of this consistency service.
 *
 * @return true if available
 */
boolean isAvailable();

}

总体上看,核心方法就是 put/get/listen 三个.

接着看下 Distro 协议的实现类:

@DependsOn(“ProtocolManager”)
@org.springframework.stereotype.Service(“distroConsistencyService”)
public class DistroConsistencyServiceImpl implements EphemeralConsistencyService, DistroDataProcessor {

// 初始化,主要是添加任务,处理任务.
@PostConstruct
public void init() {
    GlobalExecutor.submitDistroNotifyTask(notifier);
}

// 将数据保存到内存,然后向集群间同步
public void put(String key, Record value) throws NacosException {
    onPut(key, value);
    distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
            globalConfig.getTaskDispatchPeriod() / 2);
}

}

先不关注这个类中的一些细节, 我们先从 DistroConsistencyServiceImpl 它的实现类来看:EphemeralConsistencyService, DistroDataProcessor.

看下 DistroDataProcessor 这个,我的理解是处理集群间数据同步的时候,处理集群间发送过来的数据的一个接口.

目前是只看到了同步 change 事件,对于 delete 有处理吗?

这个可能真有点问题.

当收到集群中其他节点发送过来的数据同步请求后,直接存起来

see com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl#processData(com.alibaba.nacos.core.distributed.distro.entity.DistroData)

到此,Distro 基本分析完毕.

问题:

1.集群间数据的同步策略

在节点启动时第一次同步

com.alibaba.nacos.core.distributed.distro.DistroProtocol#DistroProtocol

说起这个加载数据,那得说下 dataStorageMap 这个.

现在目前 type 有如下几类:

com.alibaba.nacos.naming.iplist. --> dataStorageMap 中存储的 key 就是这个.

startVerifyTask 这个任务是读取本机负责的 Datum, 得到其校验和,然后封装成 DistroData,发送给其他节点. 其他节点在对比的时候,如果发现校验和不相同(checkSum),那么会自己从负责这个 service 的 nacos server 上拉取最新信息. 这个定时任务默认 5s 执行一次.

startLoadTask 会调用 com.alibaba.nacos.core.distributed.distro.task.load.DistroLoadDataTask#loadAllDataSnapshotFromRemote 这个方法.

在这个方法中,会向集群中发起请求 /distro/datums 获取该 nacos server 上存储的所有信息(俗称快照包) 这里会循环遍历,直到找到一个处理成功为止.

说到这个,那我们说下 com.alibaba.nacos.naming.controllers.ServiceController#remove 接口. 这个接口没有 @CanDistro 修饰,所以任意一台 nacos server 节点都可以处理.

如果是在非 Responser 上进行了删除,那么还是会从 Responser 上同步回来. 如果是在 Responser 上进行了删除,那么会导致其他非 Responser 上也会删除.

在更新过程中同步

DistroDelayTask

com.alibaba.nacos.core.distributed.distro.task.execute.DistroSyncChangeTask#DistroSyncChangeTask

com.alibaba.nacos.naming.consistency.ephemeral.distro.component.DistroHttpAgent#syncData(com.alibaba.nacos.core.distributed.distro.entity.DistroData, java.lang.String)

最后发送请求到 /distro/datum 这,向其他节点进行数据同步.

对于 Distro 协议,写请求转发到 Responser 处理,读请求本机处理.

网络分区恢复后同步

假设有三个 nacos server(S1, S2, S3), 三个 nacos client(C1, C2, C3), 三个服务(ServiceA, ServiceB, ServiceC)

ServiceA 由 S1 负责,ServiceB 由 S2 负责,ServiceC 由 S3 负责.

当注册完成后,由于数据在集群间同步,最终的效果是:

S1
ServiceA(C1, C2, C3)
ServiceB(C1, C2, C3)
ServiceC(C1, C2, C3)

S2
ServiceA(C1, C2, C3)
ServiceB(C1, C2, C3)
ServiceC(C1, C2, C3)

S3
ServiceA(C1, C2, C3)
ServiceB(C1, C2, C3)
ServiceC(C1, C2, C3)

当 S2 和 S3 断开连接的时候,会发生什么?

在 S1, S2 中会将 S3 标记为不健康的状态. S3 中会将 S1, S2 标记为不健康的状态.

此时 Responser 会重新计算,(ServiceA, ServiceB) 由 S2 负责,ServiceC 由 S1 负责. 在 S3 中,(ServiceA, ServiceB, ServiceC) 都由 S3 负责.

因为客户端发送心跳是随机的, 所以对于 C1 而言,可能向 S1 发送心跳,也可能向 S2, S3 发送心跳. 当 C1 向 S3 发送心跳,S3 收不到时,会将 C1 的流量摘除.

最终 S1, S2 的列表为:

S1
ServiceA(C1, C2)
ServiceB(C1, C2)
ServiceC(C1, C2)

S2
ServiceA(C1, C2)
ServiceB(C1, C2)
ServiceC(C1, C2)

S3
ServiceA(C3)
ServiceB(C3)
ServiceC(C3)

如果说 S3 突然又和 S1, S2 能否联通了,这是会发生什么?

会重新计算 Responser, 比如 ServiceA 由 S1 负责,ServiceB 由 S2 负责,ServiceC 由 S3 负责.

此时,由于 C1, C2 又能向 S3 发送心跳了, 那么此时更新 S3 的列表为 (C1, C2, C3). S1, S2 同理.

最终达到的一致效果和没有发生网络分区前一致.

参考:https://blog.csdn.net/Horizon_Zy/article/details/115655436

2.集群间健康节点如何感知

参见 com.alibaba.nacos.core.cluster.ServerMemberManager 这个的实现. 分为多种寻址方式:
(1) FileConfigMemberLookup
(2) AddressServerMemberLookup

还有一个 MemberInfoReportTask.
参考:com.alibaba.nacos.core.cluster.ServerMemberManager#onApplicationEvent

当 ServiceMemberManager 启动的时候,会注册一个 MemberInfoReportTask 任务,每隔 5s 执行一次. 这个任务会和除自己之外的其他 Member 进行通信,检测其他节点是否还存活. 如果通讯失败,会将该 Member 状态置为不健康,如果后续和该节点通讯成功,会将这个节点的状态置为健康,该 Task 与 Responser 的计算密切相关.

Responser: 对于每一个服务(比如:com.ly.OrderService)来说,在Nacos-Server集群中都会有一个专门的节点来负责。比如集群中有三个健康节点,这三个节点的IP:Port就是组成一个长度为3的List,对三个节点的IP:Port组成的addressList进行排序,这样在每一个节点中,addressList的顺序都是一致的。这时com.ly.OrderService服务注册上来,会根据服务名计算对应的hash值,然后对集群的节点数取余获得下标,从addressList中获取对应的IP:Port,这时这个IP:Port对应的节点就是该服务的Responser,负责该服务的健康检查,数据同步,心跳维持,服务注册。如果客户端服务注册请求到了某个节点,但是本次注册的服务不是由该节点负责,会将该请求重定向到responser的节点去进行处理。

注意: 这里的addressList是健康节点,一旦某个节点宕机或者网络发生故障,该节点会从addressList中移除,Service对应的Responser会发生变化.

这里的 Responser 的实现和 @Distro 注解有关.

参考:https://blog.csdn.net/Horizon_Zy/article/details/115655436?spm=1001.2014.3001.5501

3.client 如何感知服务端节点

对于临时节点而言:

client 端每隔 5 秒向集群发送心跳,用于更新 nacos server 上实例的最近更新时间. 服务端也会定时检查本纪上实例节点是否是正常状态(ClientBeatCheckTask).

对于持久化节点而言:

持久化实例不通过心跳维持健康状态,是服务端主动检测,可以通过tcp http mysql等不同协议进行探测,默认tcp,只要能建立连接就认为是健康的。

细节看com.alibaba.nacos.naming.healthcheck.HealthCheckProcessor

参考:https://github.com/alibaba/nacos/issues/3794

参考:https://nacos.io/zh-cn/blog/nacos-1.3.0-design.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值