Get into the habit of writing together! This is the 11th day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .
Microservices Series Article Directory
- In-depth microservices - SpringBoot automatic assembly principle
- In-depth microservices - SpringCloud call component Feign
- In-depth microservices - service registration and discovery of the basis of SpringCloud Eureka
- In-depth Microservices - Service Registration and Discovery of High Availability and Core Principles of Spring Cloud Eureka
- In-depth Nacos Foundation of Microservices and Nacos Server Construction
- In-depth Microservices-Nacos Core Concepts and Service Discovery Practice
- In-depth Microservices - Core Concepts and Practices of Nacos Configuration Center
- In-depth microservices - Nacos source code compilation and operation
- In-depth Microservice-Nacos Registration Instance Source Code Analysis
foreword
This series takes you deep into the basic use and underlying principles of each framework of the microservice Spring system. The last article took you to learn how to compile and run the Nacos source code. This article will explain the source code analysis of the Nacos registration instance in detail.
Nacos registration instance source code analysis
Client source code tracking
Register an instance, the client will encapsulate an instance information to initiate a request to the Nacos server and register
1. Start the configuration class from sping.factories
Start by registering spring-cloud-starter-alibaba-nacos-discovery's sping.factories with the client service
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.NacosDiscoveryAutoConfiguration,\
com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientAutoConfiguration,\
com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration
复制代码
2. Find the core configuration class NacosDiscoveryAutoConfiguration
向下走分表是绑定,发布事件 com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#bind org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#start
public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get()) {
// 发布InstancePreRegisteredEvent事件
this.context.publishEvent(
new InstancePreRegisteredEvent(this, getRegistration()));
// 注册逻辑
register();
if (shouldRegisterManagement()) {
registerManagement();
}
// 发布InstanceRegisteredEvent事件
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
复制代码
3、客户端进行服务注册逻辑
com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration#register
@Override
protected void register() {
if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
log.debug("Registration disabled.");
return;
}
if (this.registration.getPort() < 0) {
this.registration.setPort(getPort().get());
}
// 调用父类AbstractAutoServiceRegistration
super.register();
}
复制代码
org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#register com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
NamingService namingService = namingService();
// 获取serviceId
String serviceId = registration.getServiceId();
// 获取服务发现配置的分组
String group = nacosDiscoveryProperties.getGroup();
// 封装实例对象,分别从registration和nacosDiscoveryProperties配置获取
Instance instance = getNacosInstanceFromRegistration(registration);
try {
//注册实例
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
// rethrow a RuntimeException if the registration is failed.
// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
rethrowRuntimeException(e);
}
}
复制代码
注册实例 代码 com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
//判断是否为临时实例
if (instance.isEphemeral()) {
// 封装心跳信息
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
// 开启心跳续约
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
// serverProxy注册
serverProxy.registerService(groupedServiceName, groupName, instance);
}
复制代码
4、封装向服务端发起http请求,进行服务注册
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
Map<String, String> params = new HashMap(9);
params.put("namespaceId", this.namespaceId);
params.put("serviceName", serviceName);
params.put("groupName", groupName);
params.put("clusterName", 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", JSON.toJSONString(instance.getMetadata()));
this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, (String)"POST");
}
复制代码
服务端源码解析
1、客户端调用服务端注册接口
com.alibaba.nacos.naming.controllers.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";
}
复制代码
com.alibaba.nacos.naming.core.ServiceManager#registerInstance
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
// 初始化Service Map 和初始化定时任务
// Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>()
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
// 根据namespaceId 和 serviceName从serviceMap中获取Service
Service service = getService(namespaceId, serviceName);
if (service == null) {
throw new NacosException(NacosException.INVALID_PARAM,
"service not found, namespace: " + namespaceId + ", service: " + serviceName);
}
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
复制代码
2、添加实例
addInstance方法它会获取service以及instanceList
com.alibaba.nacos.naming.core.ServiceManager#addInstance
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
throws NacosException {
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
// 1.获取Service
Service service = getService(namespaceId, serviceName);
synchronized (service) {
// 获取instanceList
List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
Instances instances = new Instances();
instances.setInstanceList(instanceList);
consistencyService.put(key, instances);
}
}
复制代码
com.alibaba.nacos.naming.consistency.DelegateConsistencyServiceImpl#put
3、将实例信息更新到内存注册表以及同步实例信息到集群其他节点
@Override
public void put(String key, Record value) throws NacosException {
// 将注册实例更新到内存注册表
onPut(key, value);
// 同步实例信息到nacos Server 集群其他节点
distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
globalConfig.getTaskDispatchPeriod() / 2);
}
复制代码
com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl#put
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);
}
if (!listeners.containsKey(key)) {
return;
}
notifier.addTask(key, DataOperation.CHANGE);
}
复制代码
4、往阻塞队列task里面放入注册实例数据
com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl.Notifier#addTask
public void addTask(String datumKey, DataOperation action) {
...
// private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024)
tasks.offer(Pair.with(datumKey, action));
}
复制代码
5、初始化Notifier 任务,从队列拉取数据进行处理
5、DistroConsistencyServiceImpl init()方法会初始化一个Notifier 任务,循环从阻塞队列拉取数据进行处理 com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl#init
@PostConstruct
public void init() {
GlobalExecutor.submitDistroNotifyTask(notifier);
}
复制代码
com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl.Notifier#run
@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);
}
}
}
复制代码
com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl.Notifier#handle
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++;
try {
// 判断是什么类型
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 notifying listener of key: {}", datumKey, e);
}
}
...
} catch (Throwable e) {
Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
}
}
复制代码
com.alibaba.nacos.naming.core.Service#onChange
@Override
public void onChange(String key, Instances value) throws Exception {
Loggers.SRV_LOG.info("[NACOS-RAFT] datum is changed, key: {}, value: {}", key, value);
...
updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key));
recalculateChecksum();
}
复制代码
com.alibaba.nacos.naming.core.Service#updateIPs
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());
getPushService().serviceChanged(this);
StringBuilder stringBuilder = new StringBuilder();
for (Instance instance : allIPs()) {
stringBuilder.append(instance.toIpAddr()).append("_").append(instance.isHealthy()).append(",");
}
Loggers.EVT_LOG.info("[IP-UPDATED] namespace: {}, service: {}, ips: {}", getNamespaceId(), getName(),
stringBuilder.toString());
}
复制代码
6. Update registered instance information
Update the temporary registered instance to the ephemeralInstances attribute of the cluster. The service discovery finds the temporary instance and finally finds this attribute from the memory. According to the copyOnWrite idea, create a copy table for comparison, and then assign the value com.alibaba.nacos.naming .core.Cluster#updateIps
public void updateIps(List<Instance> ips, boolean ephemeral) {
Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;
// 创建赋值表
HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());
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());
// 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());
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;
}
}
复制代码