Nacos1.4使用的都是http通信,nacos2.0之后改为使用grpc通信。
涉及到的核心方法:异步队列+写时复制(copyOnWrite)
1.客户端启动流程
1.验证服务实例
NacosNamingService(核心类)
2.用post调用http请求,注册实例(客户端向服务端发起请求,注册!)
/nacos/v1/ns/instance
(调用jdkHttpClient发起请求)
客户端启动流程:Auto自动装配->EventListener->bind->this.start->register(核心注册的方法)
2.nacos服务端
添加服务实例:InstanceController#register,代码如下:
@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {
final String namespaceId = WebUtils
.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
NamingUtils.checkServiceNameFormat(serviceName);
final Instance instance = parseInstance(request);
serviceManager.registerInstance(namespaceId, serviceName, instance);
return "ok";
}
也就是将服务实例添加到服务端的Map中,该Map结构如下:(key-value结构)
Nacos服务注册表结构:Map<namespace, Map<group::serviceName, Service>>
服务拆分后,相同的服务类型放到一个group。比如说将订单服务拆分为支付服务、退货服务等。
接下来,服务端通过createEmptyService中的addInstance添加实例。
DistroConsistencyServiceImpl#onPut(key, value);->tasks.offer(Pair.with(datumKey, action));// 注册的实例丢到内存队列 ArrayBlockingQueue。集群的话会进行节点之间的同步。
DistroConsistencyServiceImpl阿里的分布式一致性协议的实现类Distro
接下来,Notifier#run从队列中拿出实例-->onChange:真正通过这个线程的run方法来注册实例。调用handle方法注册实例,入口代码如下:
@Override
public void run() {
Loggers.DISTRO.info("distro notifier started");
for (; ; ) {// 死循环
try {
Pair<String, DataOperation> pair = tasks.take();// 拿出实例
handle(pair);// 注册
} catch (Throwable e) {
Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
}
}
}
之后会将服务实例注册到注册表中,AP架构的注册表为ephemeralInstances,代码如下:
@JsonIgnore
private Set<Instance> ephemeralInstances = new HashSet<>();
ephemeralInstances = toUpdateInstances;// 注册表 存放的实例的地方 是map中的内容
run是什么时候被执行的呢,run方法的类通过@PostConstruct注册成为一个bean,死循环会一直执行,用来检测是否有新实例出现。DistroConsistencyServiceImpl#
private volatile Notifier notifier = new Notifier();
@PostConstruct
public void init() {
GlobalExecutor.submitDistroNotifyTask(notifier);
}
总结:
准实时注册
客户端发起http请求,进行注册
服务端将其放到内存队列中,然后后台线程(单线程)的run方法将实例进行注册,放到set集合中(双层Map中的set)
(异步注册:满足高并发的需求,多个实例注册)业务操作都是基于内存的,set是在内存的
为了防止多节点读写并发冲突,nacos采用写时复制技术
CopyOnWrite:写时复制,写的时候写到备份表,写完后,将表数据合并
读的时候读的是真正的表
复制的时候,存不存在原数据过大的问题?不会的,因为复制的仅仅是ephemeralInstances(set集合),复制到list中,之后的增删改都操作list!
list首先是从cache中拿的,然后会用实际的注册表进行更新健康状态!
涉及到的代码如下:
Cluster#updateIps():
HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());// 复制出来的oldMap
ephemeralInstances = toUpdateInstances;// 替换注册表
写时复制的代码如下:
public void updateIps(List<Instance> ips, boolean ephemeral) {// ips新来的实例
Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;
HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());// 复制出来的oldMap
for (Instance ip : toUpdateInstances) {
oldIpMap.put(ip.getDatumKey(), ip);// 放到oldIpMap
}
List<Instance> updatedIPs = updatedIps(ips, oldIpMap.values());// 仅仅拿到list,增删改 都在这个额副本中
if (updatedIPs.size() > 0) {
for (Instance ip : updatedIPs) {// 查看 新的实例是不是老实例,是不是配置变化
Instance oldIP = oldIpMap.get(ip.getDatumKey());
// do not update the ip validation status of updated ips
// because the checker has the most precise result
// Only when ip is not marked, don't we update the health status of IP:
if (!ip.isMarked()) {
ip.setHealthy(oldIP.isHealthy());
}
if (ip.isHealthy() != oldIP.isHealthy()) {
// ip validation status updated
Loggers.EVT_LOG.info("{} {SYNC} IP-{} {}:{}@{}", getService().getName(),
(ip.isHealthy() ? "ENABLED" : "DISABLED"), ip.getIp(), ip.getPort(), getName());
}
if (ip.getWeight() != oldIP.getWeight()) {
// ip validation status updated
Loggers.EVT_LOG.info("{} {SYNC} {IP-UPDATED} {}->{}", getService().getName(), oldIP.toString(),
ip.toString());
}
}
}
List<Instance> newIPs = subtract(ips, oldIpMap.values());// subtract删除实例的逻辑
if (newIPs.size() > 0) {
Loggers.EVT_LOG
.info("{} {SYNC} {IP-NEW} cluster: {}, new ips size: {}, content: {}", getService().getName(),
getName(), newIPs.size(), newIPs.toString());
for (Instance ip : newIPs) {
HealthCheckStatus.reset(ip);
}
}
List<Instance> deadIPs = subtract(oldIpMap.values(), ips);
if (deadIPs.size() > 0) {
Loggers.EVT_LOG
.info("{} {SYNC} {IP-DEAD} cluster: {}, dead ips size: {}, content: {}", getService().getName(),
getName(), deadIPs.size(), deadIPs.toString());
for (Instance ip : deadIPs) {
HealthCheckStatus.remv(ip);
}
}
toUpdateInstances = new HashSet<>(ips);
if (ephemeral) {
ephemeralInstances = toUpdateInstances;// 替换注册表
} else {
persistentInstances = toUpdateInstances;
}
}