¡Acostúmbrate a escribir juntos! Este es el día 11 de mi participación en el "Nuevo plan diario de Nuggets · Desafío de actualización de abril", haga clic para ver los detalles del evento .
Directorio de artículos de la serie Microservicios
- Microservicios detallados: principio de ensamblaje automático SpringBoot
- Microservicios detallados: componente de llamada de SpringCloud Fingir
- Microservicios en profundidad: registro de servicios y descubrimiento de la base de SpringCloud Eureka
- Microservicios detallados: registro de servicios y descubrimiento de alta disponibilidad y principios básicos de Spring Cloud Eureka
- Fundamentos detallados de Nacos de microservicios y construcción de servidores Nacos
- Microservicios en profundidad: conceptos básicos de Nacos y práctica de descubrimiento de servicios
- Microservicios detallados: conceptos y prácticas básicos del centro de configuración de Nacos
- Microservicios detallados: compilación y funcionamiento del código fuente de Nacos
- Análisis detallado del código fuente de la instancia de registro de Microservice-Nacos
prefacio
Esta serie lo profundiza en el uso básico y los principios subyacentes de cada marco del sistema Spring de microservicios. El último artículo lo llevó a aprender cómo compilar y ejecutar el código fuente de Nacos.Este artículo explicará en detalle el análisis del código fuente de la instancia de registro de Nacos.
Análisis del código fuente de la instancia de registro de Nacos
Seguimiento del código fuente del cliente
Registre una instancia, el cliente encapsulará la información de una instancia para iniciar una solicitud al servidor Nacos y registrarse
1. Inicie la clase de configuración desde sping.factories
Comience por registrar sping.factories de spring-cloud-starter-alibaba-nacos-discovery con el servicio de atención al cliente
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. Busque la clase de configuración principal 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. Actualice la información de la instancia registrada
Actualice la instancia registrada temporal al atributo ephemeralInstances del clúster. El descubrimiento del servicio encuentra la instancia temporal y finalmente encuentra este atributo de la memoria. De acuerdo con la idea de copyOnWrite, cree una tabla de copia para comparar y luego asigne el valor 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;
}
}
复制代码