源码阅读前的建议:
1.了解过Spring&SpringBoot源码(必须)。
源码分析Nacos的版本为:1.4
注册流程:
- 1.客户端发起注册
- 2.服务端完成注册
1. 前序
项目中使用Spring Boot与Nacos整合的包为
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
然后在配置文件中进行配置一下:
server:
port: 8085
spring:
application:
name: xxx
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
指定一下Nacos地址,用户名&密码,启动项目后即可注册到Nacos中,总的来说使用起来是比较简单的,导包—》配置–》启动,就结束了。
看到这里的同学可能会有疑问,这个过程究竟发生了什么事情,Ncaos中注册的服务实例究竟是什么东西,那就让我们先从启动流程分析开始!
1.启动流程分析
熟悉Spring Boot的同学可能都会知道,Spring Boot的老套路,用什么三方框架只要引入整合的starter即可,所以我们源码分析的源头也在于上面引入的starter。
我们找到starter,找到下面的spring.factories文件,这个是Spring Boot启动时会去读取的自动配置文件,EnableAutoConfiguration指向的是会去加载的自动配置类,其中NacosServiceRegistryAutoConfiguration是我们要重点分析的类。
2.NacosServiceRegistryAutoConfiguration
简要分析一下重要的类:
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
AutoServiceRegistrationAutoConfiguration.class,
// 对配置文件中的配置进行了映射加载 NacosDiscoveryProperties
NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {
@Bean
public NacosServiceRegistry nacosServiceRegistry(
NacosDiscoveryProperties nacosDiscoveryProperties) {
....
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosRegistration nacosRegistration(
ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers,
....
}
/**
* NacosAutoServiceRegistration 重要的类,把上面创建的Bean都进行了引用
**/
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosAutoServiceRegistration nacosAutoServiceRegistration(
NacosServiceRegistry registry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
return new NacosAutoServiceRegistration(registry,
autoServiceRegistrationProperties, registration);
}
}
2.1 NacosAutoServiceRegistration
分析类继承图
图中可看出,NacosAutoServiceRegistration 实现了 ApplicationListener接口,这说明了该类是一个事件监听类,我们直接找到事件触发时会调用的onApplicationEvent()方法:
public void onApplicationEvent(WebServerInitializedEvent event) {
this.bind(event);
}
最终在父类AbstractAutoServiceRegistration中发现了实现的该方法,并且监听的事件是WebServerInitializedEvent事件,那么该事件是何时触发的呢,这个地方就是关联了Spring源码了:
finishRefresh()是Spring容器启动完成的最后的一个方法,也就是说,Spring容器启动完成后,通过发布的事件就会走上面的事件监听者方法,走bind()逻辑。
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
this.start();
}
public void start() {
if (!this.running.get()) {
// 发布一个注册前的事件
this.context.publishEvent(
new InstancePreRegisteredEvent(this, getRegistration()));
// 客户端注册的方法
register();
// 注册完成后的事件
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
protected void register() {
// ServiceRegistry serviceRegistry 对应nacos的实现是:NacosServiceRegistry
this.serviceRegistry.register(getRegistration());
}
/**
* NacosServiceRegistry:register()
* Registration存放的是要注册的信息
**/
public void register(Registration registration) {
NamingService namingService = namingService();
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
// 实例类,真正要注册的内容
Instance instance = getNacosInstanceFromRegistration(registration);
// 注册实例到Nacos远程Server端
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
Instance 类:
@JsonInclude(Include.NON_NULL)
public class Instance implements Serializable {
private String instanceId;
private String ip;
private int port;
private double weight = 1.0D;
private boolean healthy = true;
private boolean enabled = true;
private boolean ephemeral = true;
private String clusterName;
private String serviceName;
}
NacosNamingService.registerInstance:
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) {
NamingUtils.checkInstanceIsLegal(instance);
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
// 没有显示设置就是为true
if (instance.isEphemeral()) {
// 心跳线程启动
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
// 注册
serverProxy.registerService(groupedServiceName, groupName, instance);
}
NamingProxy.registerService;
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
final Map<String, String> params = new HashMap<String, String>(16);
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, serviceName);
params.put(CommonParams.GROUP_NAME, groupName);
params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("weight", String.valueOf(instance.getWeight()));
params.put("enable", String.valueOf(instance.isEnabled()));
params.put("healthy", String.valueOf(instance.isHealthy()));
params.put("ephemeral", String.valueOf(instance.isEphemeral()));
params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
// 发送http请求到server端:POST:/nacos/v1/ns/instance
reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}
注册总结:
- 1.注册时机:利用Spring事件机制,解耦注册。
- 2.所谓的服务实例其实就是本机的IP,服务名称等信息。
- 3.注册是异步的直接发送Http请求给Server端,后续心跳保活。
以上是客户端注册部分的源码解析。
1.服务端接受注册
前面我们看到客户端注册的最后会发送一个 POST:/nacos/v1/ns/instance 的请求到Server端,官网上注册实例的路径也是这个,那么我们只要找到这个Controller中对应的接口,沿着这个接口请求进行分析即可。
下面分析的代码均在 Nacos Server端,需要下载 Nacos 1.4版本的源码,用Git拉下来导入项目即可。
com.alibaba.nacos.Nacos 是启动类。
com.alibaba.nacos.naming.controllers.InstanceController#register 是注册的接口,也是源码分析的入口:
/**
* Register new instance.
*/
@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {
...
// 请求解析成 Instance
final Instance instance = parseInstance(request);
// 服务实例注册
serviceManager.registerInstance(namespaceId, serviceName, instance);
return "ok";
}
把传过来的参数进行解析,包装成一个 Instance 对象,然后进行注册。
registerInstance(namespaceId, serviceName, instance):
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
// 1.创建空 service
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
// 2.注册实例
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
/**
* Nacos注册实例表:Map(namespace, Map(group::serviceName, Service)).
*/
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
throws NacosException {
// 先从注册实例表里面查一下
Service service = getService(namespaceId, serviceName);
// 第一次创建 service,肯定是null
if (service == null) {
service = new Service();
service.setName(serviceName);
service.setNamespaceId(namespaceId);
service.setGroupName(NamingUtils.getGroupName(serviceName));
// now validate the service. if failed, exception will be thrown
service.setLastModifiedMillis(System.currentTimeMillis());
service.recalculateChecksum();
// 1.1 添加到map & 初始化service
putServiceAndInit(service);
}
}
private void putServiceAndInit(Service service) throws NacosException {
// 添加到 实例注册表
putService(service);
// 1.1.1执行service的初始化方法
service.init();
}
public void init() {
// 健康检测 任务启动 5秒
HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
entry.getValue().setService(this);
entry.getValue().init();
}
}
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
throws NacosException {
// com.alibaba.nacos.naming.iplist.ephemeral.public##DEFAULT_GROUP@@web-consumer
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
Service service = getService(namespaceId, serviceName);
synchronized (service) {
List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
Instances instances = new Instances();
instances.setInstanceList(instanceList);
// *****重要*****
consistencyService.put(key, instances);
}
}
public void put(String key, Record value) throws NacosException {
// 持久 & 临时,默认 临时
// com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl#put
mapConsistencyService(key).put(key, value);
}
public void put(String key, Record value) throws NacosException {
// 核心方法 onPut
onPut(key, value);
// 集群同步方法
distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
globalConfig.getTaskDispatchPeriod() / 2);
}
注册流程已经走完,核心在于onPut方法怎么做后续处理的:
private volatile Notifier notifier = new Notifier();
public void onPut(String key, Record value) {
if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
Datum<Instances> datum = new Datum<>();
datum.value = (Instances) value;
datum.key = key;
datum.timestamp.incrementAndGet();
dataStore.put(key, datum);
}
// notifier 一个线程类,初始化的时候,线程启动
notifier.addTask(key, DataOperation.CHANGE);
}
public class Notifier implements Runnable {
private ConcurrentHashMap<String, String> services = new ConcurrentHashMap<>(10 * 1024);
/**
* 存放 注册实例的队列,,进行异步消费
*/
private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);
public void addTask(String datumKey, DataOperation action) {
if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
return;
}
if (action == DataOperation.CHANGE) {
services.put(datumKey, StringUtils.EMPTY);
}
// instance 放到队列中
tasks.offer(Pair.with(datumKey, action));
}
@Override
public void run() {
for (; ; ) {
try {
Pair<String, DataOperation> pair = tasks.take();
// 异步线程消费
handle(pair);
} catch (Throwable e) {
Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
}
}
}
private void handle(Pair<String, DataOperation> pair) {
try {
String datumKey = pair.getValue0();
DataOperation action = pair.getValue1();
services.remove(datumKey);
int count = 0;
if (!listeners.containsKey(datumKey)) {
return;
}
for (RecordListener listener : listeners.get(datumKey)) {
count++;
// 会走这个地方
if (action == DataOperation.CHANGE) {
// 消费通知
listener.onChange(datumKey, dataStore.get(datumKey).value);
continue;
}
if (action == DataOperation.DELETE) {
listener.onDelete(datumKey);
continue;
}
} catch (Throwable e) {
Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
}
}
}
com.alibaba.nacos.naming.core.Service#onChange
public void onChange(String key, Instances value) throws Exception {
// 注册实例
updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key));
recalculateChecksum();
}
public void updateIPs(Collection<Instance> instances, boolean ephemeral) {
Map<String, List<Instance>> ipMap = new HashMap<>(clusterMap.size());
for (String clusterName : clusterMap.keySet()) {
ipMap.put(clusterName, new ArrayList<>());
}
for (Map.Entry<String, List<Instance>> entry : ipMap.entrySet()) {
//make every ip mine
List<Instance> entryIPs = entry.getValue();
// 注册更新实例
clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral);
}
setLastModifiedMillis(System.currentTimeMillis());
// udp 事件推送
getPushService().serviceChanged(this);
}
// com.alibaba.nacos.naming.core.Cluster#updateIps
public void updateIps(List<Instance> ips, boolean ephemeral) {
// ephemeral. 默认是true,临时实例
// toUpdateInstances 要更新的实例列表
Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;
// copy on writer 写时复制,提高并发
HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());
// oldIpMap 旧的实例
for (Instance ip : toUpdateInstances) {
oldIpMap.put(ip.getDatumKey(), ip);
}
// 更新实例列表
List<Instance> updatedIPs = updatedIps(ips, oldIpMap.values());
if (updatedIPs.size() > 0) {
for (Instance ip : updatedIPs) {
Instance oldIP = oldIpMap.get(ip.getDatumKey());
}
}
// 改变后的实例
toUpdateInstances = new HashSet<>(ips);
if (ephemeral) {
ephemeralInstances = toUpdateInstances;
} else {
persistentInstances = toUpdateInstances;
}
}
注册总结:
- 注册信息包装成Service对象,注册到实例表里面进行维护。
- 注册过程是异步的,后台线程处理注册,提高吞吐量。
- 实例表在更新时,用了copy on writer思想,防止在更改过程中,无法进行读请求。
至此,一次注册的大致流程就全部走完了!