当前位置: 首页 > news >正文

Nacos源码—1.Nacos服务注册发现分析一

大纲

1.客户端如何发起服务注册 + 发送服务心跳

2.服务端如何处理客户端的服务注册请求

3.注册服务—如何实现高并发支撑上百万服务注册

4.内存注册表—如何处理注册表的高并发读写冲突

1.客户端如何发起服务注册 + 发送服务心跳

(1)Nacos客户端项目启动时为什么会自动注册服务

(2)Nacos客户端通过什么方式注册服务

(3)Nacos客户端如何发送服务心跳

(1)Nacos客户端项目启动时为什么会自动注册服务

Nacos客户端就是引入了nacos-discovery + nacos-client依赖的项目。引入spring-cloud-starter-alibaba-nacos-discovery后,才自动注册服务。查看这个依赖包中的spring.factories文件,发现有一些Configuration类。

Spring Boot启动时会扫描spring.factories文件,然后创建里面的配置类。

在spring.pactories文件中,与注册相关的类就是:NacosServiceRegistryAutoConfiguration这个Nacos服务注册自动配置类。

Nacos服务注册自动配置类NacosServiceRegistryAutoConfiguration如下,该配置类创建了三个Bean。

第一个Bean:NacosServiceRegistry

这个Bean在创建时,会传入加载了yml配置文件内容的类NacosDiscoveryProperties。

第二个Bean:NacosRegistration

这个Bean在创建时,会传入加载了yml配置文件内容的类NacosDiscoveryProperties。

第三个Bean:NacosAutoServiceRegistration

这个Bean在创建时,会传入NacosServiceRegistry和NacosRegistration两个Bean。然后该Bean继承了AbstractAutoServiceRegistration抽象类。该抽象类实现了ApplicationListener接口,所以项目启动时便是利用了Spring的监听事件来实现自动注册服务的。因为在Spring容器启动的最后会执行finishRefresh()方法,然后会发布一个事件,该事件会触发调用onApplicationEvent()方法。

调用AbstractAutoServiceRegistration的onApplicationEvent()方法时,首先会调用AbstractAutoServiceRegistration的bind()方法,然后调用AbstractAutoServiceRegistration的start()方法,接着调用AbstractAutoServiceRegistration的register()方法发起注册,也就是调用this.serviceRegistry的register()方法完成服务注册的具体工作。

其中,AbstractAutoServiceRegistration的serviceRegistry属性,是在服务注册自动配置类NacosServiceRegistryAutoConfiguration,创建第三个Bean—NacosAutoServiceRegistration时,通过传入其创建的第一个Bean—NacosServiceRegistry进行赋值的。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class, AutoServiceRegistrationAutoConfiguration.class, NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {@Beanpublic NacosServiceRegistry nacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {//传入NacosDiscoveryProperties作为参数return new NacosServiceRegistry(nacosDiscoveryProperties);}@Bean@ConditionalOnBean(AutoServiceRegistrationProperties.class)public NacosRegistration nacosRegistration(ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers, NacosDiscoveryProperties nacosDiscoveryProperties, ApplicationContext context) {//传入NacosDiscoveryProperties作为参数return new NacosRegistration(registrationCustomizers.getIfAvailable(), nacosDiscoveryProperties, context);}@Bean@ConditionalOnBean(AutoServiceRegistrationProperties.class)public NacosAutoServiceRegistration nacosAutoServiceRegistration(NacosServiceRegistry registry, AutoServiceRegistrationProperties autoServiceRegistrationProperties, NacosRegistration registration) {//传入NacosServiceRegistry和NacosRegistration作为参数return new NacosAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration);}
}@ConfigurationProperties("spring.cloud.nacos.discovery")
public class NacosDiscoveryProperties {//nacos discovery server address.private String serverAddr;//the nacos authentication username.private String username;//the nacos authentication password.private String password;//namespace, separation registry of different environments.private String namespace;//service name to registry.@Value("${spring.cloud.nacos.discovery.service:${spring.application.name:}}")private String service;//cluster name for nacos.private String clusterName = "DEFAULT";//group name for nacos.private String group = "DEFAULT_GROUP";//The ip address your want to register for your service instance, needn't to set it if the auto detect ip works well.private String ip;//The port your want to register for your service instance, needn't to set it if the auto detect port works well.private int port = -1;//Heart beat interval. Time unit: millisecond.private Integer heartBeatInterval;//Heart beat timeout. Time unit: millisecond.private Integer heartBeatTimeout;//If instance is ephemeral.The default value is true.private boolean ephemeral = true;...
}public class NacosAutoServiceRegistration extends AbstractAutoServiceRegistration<Registration> {...private NacosRegistration registration;public NacosAutoServiceRegistration(ServiceRegistry<Registration> serviceRegistry,AutoServiceRegistrationProperties autoServiceRegistrationProperties,NacosRegistration registration) {super(serviceRegistry, autoServiceRegistrationProperties);this.registration = registration;}...
}public abstract class AbstractAutoServiceRegistration<R extends Registration>implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> {...private final ServiceRegistry<R> serviceRegistry;private AutoServiceRegistrationProperties properties;protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry, AutoServiceRegistrationProperties properties) {this.serviceRegistry = serviceRegistry;this.properties = properties;}...@Override@SuppressWarnings("deprecation")public void onApplicationEvent(WebServerInitializedEvent event) {bind(event);}public void bind(WebServerInitializedEvent event) {ApplicationContext context = event.getApplicationContext();if (context instanceof ConfigurableWebServerApplicationContext) {if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {return;}}this.port.compareAndSet(0, event.getWebServer().getPort());this.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 belowif (!this.running.get()) {this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));//发起注册register();if (shouldRegisterManagement()) {registerManagement();}this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));this.running.compareAndSet(false, true);}}protected void register() {//调用创建NacosAutoServiceRegistration时传入的NacosServiceRegistry实例的register()方法this.serviceRegistry.register(getRegistration());}...
}public class NacosServiceRegistry implements ServiceRegistry<Registration> {private final NacosDiscoveryProperties nacosDiscoveryProperties;public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {this.nacosDiscoveryProperties = nacosDiscoveryProperties;}@Overridepublic void register(Registration registration) {if (StringUtils.isEmpty(registration.getServiceId())) {log.warn("No service to register for nacos client...");return;}NamingService namingService = namingService();String serviceId = registration.getServiceId();String group = nacosDiscoveryProperties.getGroup();Instance instance = getNacosInstanceFromRegistration(registration);try {//把当前的服务实例注册到Nacos中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/1132rethrowRuntimeException(e);}}private NamingService namingService() {return nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());}...
}

Nacos客户端项目启动时自动触发服务实例注册的流程总结:Spring监听器调用onApplicationEvent()方法 -> bind()方法 -> start()方法 -> register()方法,最后register()方法会调用serviceRegistry属性的register()方法进行注册。

整个流程具体来说就是:首先通过spring.factories文件,找到一个注册相关的Configuration配置类,这个配置类里面定义了三个Bean对象。创建第三个Bean对象时,需要第一个、第二个Bean对象作为参数传进去。第一个Bean对象里面就有真正进行服务注册的register()方法,并且第一个Bean对象会赋值给第三个Bean对象中的serviceRegistry属性。在第三个Bean对象的父类会实现Spring的监听器方法。所以在Spring容器启动时会发布监听事件,从而触发执行Nacos注册逻辑。

(2)Nacos客户端通过什么方式注册服务

项目启动时是通过NacosServiceRegistry的register()方法发起服务注册的,然后会调用NacosNamingService的registerInstance()方法注册服务实例,接着调用NamingProxy的registerService()方法组装参数发起服务注册请求,接着调用NamingProxy的reqApi()方法向Nacos服务端发起服务注册请求,也就是调用NamingProxy的callServer()方法向Nacos服务端发送注册请求。

在NamingProxy的callServer()方法中,首先会调用NacosRestTemplate的exchangeForm()方法发起HTTP请求,然后会调用this.requestClient()的execute()方法执行HTTP请求的发送,接着会调用DefaultHttpClientRequest的execute()方法处理请求的发送,也就是通过Apache的CloseableHttpClient组件来处理发送HTTP请求。

注意:NacosServiceRegistry是属于nacos-discovery包中的类,NacosNamingService是属于nacos-client包中的类。

public class NacosServiceRegistry implements ServiceRegistry<Registration> {private final NacosDiscoveryProperties nacosDiscoveryProperties;public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {this.nacosDiscoveryProperties = nacosDiscoveryProperties;}@Overridepublic void register(Registration registration) {if (StringUtils.isEmpty(registration.getServiceId())) {log.warn("No service to register for nacos client...");return;}NamingService namingService = namingService();//服务名称String serviceId = registration.getServiceId();//服务分组String group = nacosDiscoveryProperties.getGroup();//服务实例,包含了IP、Port等信息Instance instance = getNacosInstanceFromRegistration(registration);try {//调用NacosNamingService.registerInstance()方法把当前的服务实例注册到Nacos中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);rethrowRuntimeException(e);}}private NamingService namingService() {return nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());}private Instance getNacosInstanceFromRegistration(Registration registration) {Instance instance = new Instance();instance.setIp(registration.getHost());instance.setPort(registration.getPort());instance.setWeight(nacosDiscoveryProperties.getWeight());instance.setClusterName(nacosDiscoveryProperties.getClusterName());instance.setEnabled(nacosDiscoveryProperties.isInstanceEnabled());instance.setMetadata(registration.getMetadata());instance.setEphemeral(nacosDiscoveryProperties.isEphemeral());return instance;}...
}public class NacosNamingService implements NamingService {private BeatReactor beatReactor;private NamingProxy serverProxy;...@Overridepublic 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);}//接下来调用NamingProxy的注册方法registerService()来注册服务实例serverProxy.registerService(groupedServiceName, groupName, instance);}...
}public class NamingProxy implements Closeable {private final NacosRestTemplate nacosRestTemplate = NamingHttpClientManager.getInstance().getNacosRestTemplate();...//register a instance to service with specified instance properties.//@param serviceName name of service//@param groupName   group of service//@param instance    instance to registerpublic void registerService(String serviceName, String groupName, Instance instance) throws NacosException {NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName, instance);//创建一个Map组装注册请求参数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()));//下面UtilAndComs常量类拼装的请求url是: /Nacos/v1/ns/instancereqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);}public String reqApi(String api, Map<String, String> params, String method) throws NacosException {return reqApi(api, params, Collections.EMPTY_MAP, method);}public String reqApi(String api, Map<String, String> params, Map<String, String> body, String method) throws NacosException {return reqApi(api, params, body, getServerList(), method);}//Request api.public String reqApi(String api, Map<String, String> params, Map<String, String> body, List<String> servers, String method) throws NacosException {params.put(CommonParams.NAMESPACE_ID, getNamespaceId());if (CollectionUtils.isEmpty(servers) && StringUtils.isBlank(nacosDomain)) {throw new NacosException(NacosException.INVALID_PARAM, "no server available");}NacosException exception = new NacosException();if (StringUtils.isNotBlank(nacosDomain)) {for (int i = 0; i < maxRetry; i++) {try {return callServer(api, params, body, nacosDomain, method);} catch (NacosException e) {exception = e;if (NAMING_LOGGER.isDebugEnabled()) {NAMING_LOGGER.debug("request {} failed.", nacosDomain, e);}}}} else {Random random = new Random(System.currentTimeMillis());int index = random.nextInt(servers.size());for (int i = 0; i < servers.size(); i++) {String server = servers.get(index);try {return callServer(api, params, body, server, method);} catch (NacosException e) {exception = e;if (NAMING_LOGGER.isDebugEnabled()) {NAMING_LOGGER.debug("request {} failed.", server, e);}}index = (index + 1) % servers.size();}}NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(), exception.getErrMsg());throw new NacosException(exception.getErrCode(), "failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());}//Call server.public String callServer(String api, Map<String, String> params, Map<String, String> body, String curServer, String method) throws NacosException {long start = System.currentTimeMillis();long end = 0;injectSecurityInfo(params);Header header = builderHeader();String url;if (curServer.startsWith(UtilAndComs.HTTPS) || curServer.startsWith(UtilAndComs.HTTP)) {url = curServer + api;} else {if (!IPUtil.containsPort(curServer)) {curServer = curServer + IPUtil.IP_PORT_SPLITER + serverPort;}url = NamingHttpClientManager.getInstance().getPrefix() + curServer + api;}try {//调用NacosRestTemplate.exchangeForm()方法发起HTTP请求HttpRestResult<String> restResult = nacosRestTemplate.exchangeForm(url, header, Query.newInstance().initParams(params), body, method, String.class);end = System.currentTimeMillis();MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(restResult.getCode())).observe(end - start);if (restResult.ok()) {return restResult.getData();}if (HttpStatus.SC_NOT_MODIFIED == restResult.getCode()) {return StringUtils.EMPTY;}throw new NacosException(restResult.getCode(), restResult.getMessage());} catch (Exception e) {NAMING_LOGGER.error("[NA] failed to request", e);throw new NacosException(NacosException.SERVER_ERROR, e);}}...
}public class NacosRestTemplate extends AbstractNacosRestTemplate {private final HttpClientRequest requestClient;...//Execute the HTTP method to the given URI template, writing the given request entity to the request, and returns the response as {@link HttpRestResult}.public <T> HttpRestResult<T> exchangeForm(String url, Header header, Query query, Map<String, String> bodyValues, String httpMethod, Type responseType) throws Exception {RequestHttpEntity requestHttpEntity = new RequestHttpEntity(header.setContentType(MediaType.APPLICATION_FORM_URLENCODED), query, bodyValues);return execute(url, httpMethod, requestHttpEntity, responseType);}private <T> HttpRestResult<T> execute(String url, String httpMethod, RequestHttpEntity requestEntity, Type responseType) throws Exception {URI uri = HttpUtils.buildUri(url, requestEntity.getQuery());if (logger.isDebugEnabled()) {logger.debug("HTTP method: {}, url: {}, body: {}", httpMethod, uri, requestEntity.getBody());}ResponseHandler<T> responseHandler = super.selectResponseHandler(responseType);HttpClientResponse response = null;try {response = this.requestClient().execute(uri, httpMethod, requestEntity);return responseHandler.handle(response);} finally {if (response != null) {response.close();}}}private HttpClientRequest requestClient() {if (CollectionUtils.isNotEmpty(interceptors)) {if (logger.isDebugEnabled()) {logger.debug("Execute via interceptors :{}", interceptors);}return new InterceptingHttpClientRequest(requestClient, interceptors.iterator());}return requestClient;}...
}public class DefaultHttpClientRequest implements HttpClientRequest {private final CloseableHttpClient client;public DefaultHttpClientRequest(CloseableHttpClient client) {this.client = client;}@Overridepublic HttpClientResponse execute(URI uri, String httpMethod, RequestHttpEntity requestHttpEntity) throws Exception {HttpRequestBase request = build(uri, httpMethod, requestHttpEntity);//通过Apache的CloseableHttpClient组件执行HTTP请求CloseableHttpResponse response = client.execute(request);return new DefaultClientHttpResponse(response);}...
}

由此可知:Nacos客户端是通过HTTP的方式往Nacos服务端发起服务注册的,Nacos服务端会提供服务注册的API接口给Nacos客户端进行HTTP调用,Nacos官方Open API文档中注册服务实例的接口说明如下:

(3)Nacos客户端如何发送服务心跳

调用NacosNamingService的registerInstance()方法注册服务实例时,在调用NamingProxy的registerService()方法来注册服务实例之前,会根据注册的服务实例是临时实例来构建和添加心跳信息到beatReactor,也就是调用BeatReactor的buildBeatInfo()方法和addBeatInfo()方法。

在BeatReactor的buildBeatInfo()方法中,会通过beatInfo的setPeriod()方法设置心跳间隔时间,默认是5秒。

在BeatReactor的addBeatInfo()方法中,倒数第二行会开启一个延时执行的任务,执行的任务是根据心跳信息BeatInfo封装的BeatTask。该BeatTask任务会交给BeatReactor的ScheduledExecutorService来执行,并通过beatInfo的getPeriod()方法获取延时执行的时间为5秒。

在BeatTask的run()方法中,就会调用NamingProxy的sendBeat()方法发送心跳请求给Nacos服务端,也就是调用NamingProxy的reqApi()方法向Nacos服务端发起心跳请求。如果返回的心跳响应表明服务实例不存在则重新发起服务实例注册请求。无论心跳响应如何,继续根据心跳信息BeatInfo封装一个BeatTask任务,然后将该任务交给线程池ScheduledExecutorService来延时5秒执行。

public class NacosServiceRegistry implements ServiceRegistry<Registration> {private final NacosDiscoveryProperties nacosDiscoveryProperties;public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {this.nacosDiscoveryProperties = nacosDiscoveryProperties;}@Overridepublic void register(Registration registration) {if (StringUtils.isEmpty(registration.getServiceId())) {log.warn("No service to register for nacos client...");return;}NamingService namingService = namingService();//服务名称String serviceId = registration.getServiceId();//服务分组String group = nacosDiscoveryProperties.getGroup();//服务实例,包含了IP、Port等信息Instance instance = getNacosInstanceFromRegistration(registration);try {//调用NacosNamingService.registerInstance()方法把当前的服务实例注册到Nacos中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);rethrowRuntimeException(e);}}private NamingService namingService() {return nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());}private Instance getNacosInstanceFromRegistration(Registration registration) {Instance instance = new Instance();instance.setIp(registration.getHost());instance.setPort(registration.getPort());instance.setWeight(nacosDiscoveryProperties.getWeight());instance.setClusterName(nacosDiscoveryProperties.getClusterName());instance.setEnabled(nacosDiscoveryProperties.isInstanceEnabled());instance.setMetadata(registration.getMetadata());instance.setEphemeral(nacosDiscoveryProperties.isEphemeral());return instance;}...
}public class NacosNamingService implements NamingService {private BeatReactor beatReactor;private NamingProxy serverProxy;...@Overridepublic 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);}//接下来调用NamingProxy的注册方法registerService()来注册服务实例serverProxy.registerService(groupedServiceName, groupName, instance);}...
}public class BeatReactor implements Closeable {...//Build new beat information.public BeatInfo buildBeatInfo(String groupedServiceName, Instance instance) {BeatInfo beatInfo = new BeatInfo();beatInfo.setServiceName(groupedServiceName);beatInfo.setIp(instance.getIp());beatInfo.setPort(instance.getPort());beatInfo.setCluster(instance.getClusterName());beatInfo.setWeight(instance.getWeight());beatInfo.setMetadata(instance.getMetadata());beatInfo.setScheduled(false);//getInstanceHeartBeatInterval()的返回值是5000beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());return beatInfo;}...
}@JsonInclude(Include.NON_NULL)
public class Instance implements Serializable {...public long getInstanceHeartBeatInterval() {//Constants.DEFAULT_HEART_BEAT_INTERVAL,默认是5000return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL, Constants.DEFAULT_HEART_BEAT_INTERVAL);}...
}public class BeatReactor implements Closeable {private final ScheduledExecutorService executorService;private final NamingProxy serverProxy;public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>();public BeatReactor(NamingProxy serverProxy) {this(serverProxy, UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT);}public BeatReactor(NamingProxy serverProxy, int threadCount) {this.serverProxy = serverProxy;this.executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r);thread.setDaemon(true);thread.setName("com.alibaba.nacos.naming.beat.sender");return thread;}});}...//Add beat information.public void addBeatInfo(String serviceName, BeatInfo beatInfo) {NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());BeatInfo existBeat = null;if ((existBeat = dom2Beat.remove(key)) != null) {existBeat.setStopped(true);}dom2Beat.put(key, beatInfo);//开启一个延时执行的任务,执行的任务是BeatTaskexecutorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());}...class BeatTask implements Runnable {BeatInfo beatInfo;public BeatTask(BeatInfo beatInfo) {this.beatInfo = beatInfo;}@Overridepublic void run() {//判断是否需要停止if (beatInfo.isStopped()) {return;}//获取下一次执行的时间,同样还是5slong nextTime = beatInfo.getPeriod();try {//调用NamingProxy.sendBeat()方法发送心跳请求给Nacos服务端JsonNode result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);long interval = result.get("clientBeatInterval").asLong();boolean lightBeatEnabled = false;if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) {lightBeatEnabled = result.get(CommonParams.LIGHT_BEAT_ENABLED).asBoolean();}BeatReactor.this.lightBeatEnabled = lightBeatEnabled;if (interval > 0) {nextTime = interval;}//获取Nacos服务端返回的code状态码int code = NamingResponseCode.OK;if (result.has(CommonParams.CODE)) {code = result.get(CommonParams.CODE).asInt();}//如果code = RESOURCE_NOT_FOUND,没有找到资源,那么表示之前注册的信息,已经被Nacos服务端移除了if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {//然后重新组装参数,重新发起注册请求Instance instance = new Instance();instance.setPort(beatInfo.getPort());instance.setIp(beatInfo.getIp());instance.setWeight(beatInfo.getWeight());instance.setMetadata(beatInfo.getMetadata());instance.setClusterName(beatInfo.getCluster());instance.setServiceName(beatInfo.getServiceName());instance.setInstanceId(instance.getInstanceId());instance.setEphemeral(true);try { //调用NamingProxy.registerService()方法发送服务实例注册请求到Nacos服务端serverProxy.registerService(beatInfo.getServiceName(), NamingUtils.getGroupName(beatInfo.getServiceName()), instance);} catch (Exception ignore) {}}} catch (NacosException ex) {NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}", JacksonUtils.toJson(beatInfo), ex.getErrCode(), ex.getErrMsg());}//把beatInfo又重新放入延迟任务当中,并且还是5秒,所以一直是个循环的状态executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);}}
}public class NamingProxy implements Closeable {...//Send beat.public JsonNode sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) throws NacosException {    if (NAMING_LOGGER.isDebugEnabled()) {NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", namespaceId, beatInfo.toString());}Map<String, String> params = new HashMap<String, String>(8);Map<String, String> bodyMap = new HashMap<String, String>(2);if (!lightBeatEnabled) {bodyMap.put("beat", JacksonUtils.toJson(beatInfo));}params.put(CommonParams.NAMESPACE_ID, namespaceId);params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());params.put(CommonParams.CLUSTER_NAME, beatInfo.getCluster());params.put("ip", beatInfo.getIp());params.put("port", String.valueOf(beatInfo.getPort()));String result = reqApi(UtilAndComs.nacosUrlBase + "/instance/beat", params, bodyMap, HttpMethod.PUT);return JacksonUtils.toObj(result);}...
}

由此可见,在客户端在发起服务注册期间,会开启一个心跳健康检查的延时任务,这个任务每间隔5s执行一次。任务内容就是通过HTTP请求调用发送Nacos提供的服务实例心跳接口。Nacos官方Open API文档中服务实例心跳接口说明如下:

如下是客户端发起服务注册 + 发送服务心跳的整个流程图:

相关文章:

Nacos源码—1.Nacos服务注册发现分析一

大纲 1.客户端如何发起服务注册 发送服务心跳 2.服务端如何处理客户端的服务注册请求 3.注册服务—如何实现高并发支撑上百万服务注册 4.内存注册表—如何处理注册表的高并发读写冲突 1.客户端如何发起服务注册 发送服务心跳 (1)Nacos客户端项目启动时为什么会自动注册服…...

NHANES指标推荐:CTI

文章题目&#xff1a;Association between the C-reactive protein-triglyceride-glucose index and endometriosis: a cross-sectional study using data from the national health and nutrition examination survey, 1996-2006 DOI&#xff1a;10.1186/s12905-024-03541-x 中…...

开源模型应用落地-全能音频新纪元-Kimi-Audio-7B-Instruct-重塑多模态交互边界

一、前言 在AI技术持续突破的2025年,音频交互正从单一任务处理迈向全场景融合的新阶段。4月27日,月之暗面(Moonshot AI)开源的​​Kimi-Audio-7B-Instruct​​,以“全能音频通才”之姿,为这一进程树立了里程碑式标杆。这款基于70亿参数架构的模型,首次在单一框架内整合语…...

mtrace和memleak源码分析

文章目录 1. 内存泄漏2. 定位工具2.1 memleak 工具定位内存泄漏2.1.1 源码解读 2.2 mtrace 工具定位内存泄漏2.2.1 源码解读 嵌入式内存泄漏定位工具mtrace和memleak源码分析 1. 内存泄漏 内存泄漏&#xff08;Memory Leak&#xff09;指程序中已动态分配的堆内存因未正确释放或…...

Python爬虫技术全解析:从入门到实战的终极指南大纲(深度解读与扩展)

Python爬虫技术全解析&#xff1a;从入门到实战的终极指南大纲&#xff08;深度解读与扩展&#xff09; 文章目录 Python爬虫技术全解析&#xff1a;从入门到实战的终极指南大纲&#xff08;深度解读与扩展&#xff09;前言&#xff1a;数据时代的爬虫使命第一章&#xff1a;Py…...

【网络入侵检测】基于源码分析Suricata的统计模块

【作者主页】只道当时是寻常 【专栏介绍】Suricata入侵检测。专注网络、主机安全,欢迎关注与评论。 1. 概要 👋 在 Suricata 的配置文件中,stats 节点用于配置统计信息相关的参数,它的主要作用是控制 Suricata 如何收集和输出统计数据,帮助用户了解 Suricata 的运行状态和…...

JDBC之Blob类型使用的实现

目录 一、 MySql Blob类型简介 1. Mysql中的Blob类型 2. Blob类型使用的注意事项 二. 插入Blob类型数据 1. 创建表 2. 通过PreparedStatement存储Blob类型数据 三. 解除文件大小限制 四、 读取Blob类型数据 前言 本文来讲解JDBC中的Blob类型 个人主页&#xff1a;艺杯羹…...

truffle

文章目录 truffle目录结构各文件作用在本地测试合约 truffle 项目来自https://github.com/Dapp-Learning-DAO/Dapp-Learning/blob/main/basic/04-web3js-truffle/README-CN.md Truffle 是基于 Solidity 语言的一套开发框架&#xff0c;它简化了去中心化应用&#xff08;Dapp&…...

网盘不限速

引言 哈喽小伙伴们&#xff01;说到网盘下载&#xff0c;是不是感觉心头一紧&#xff1f;特别是像某度那样不开会员就限速到怀疑人生&#xff01;就连之前号称不限速的阿里云盘&#xff0c;现在也是限的死死的。 随着阿里网盘开始限速&#xff0c;很多小伙伴开始转战其他平台。…...

TVM中的Pass两种实现方法?如何选择?

以下是TVM中基于DFPatternCallback和VisitDFPattern两种编写Pass的方法的详细对比与示例总结&#xff1a; 1. 核心概念对比 特性DFPatternCallbackVisitDFPattern (DFPatternFunctor)抽象层级声明式模式匹配命令式访问者模式适用场景简单/中等复杂度的模式匹配需要精细控制匹配…...

JAVA EE_网络原理_UDP与TCP

人海中未遇见时&#xff0c;我将独自前行... ----------陳長生. 1.UDP协议 1.1.UDP协议端格式 UDP&#xff08;用户数据报协议&#xff09;是由 源端口&#xff0c;目标端口&#xff0c;长度&#xff0c;校验和&#xff0c;数据 5种结构组成。16位是UDP报文中字段的长度&#…...

智能Python开发工具PyCharm v2025.1——AI层级功能重磅升级

JetBrains PyCharm是一种Python IDE&#xff0c;其带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具。此外&#xff0c;该IDE提供了一些高级功能&#xff0c;以用于Django框架下的专业Web开发。 立即获取PyCharm v2025.1正式版 具体更新内容&#xff1a; PyCh…...

15、项目搭建:绘制城堡蓝图——React 19 工程配置

一、魔法结界初始化 1. 召唤项目骨架 npx create-next-applatest hogwarts-castle --ts --tailwind 核心咒语&#xff1a; • --ts&#xff1a;激活预言水晶球&#xff08;TypeScript类型安全&#xff09; • --tailwind&#xff1a;注入飞天扫帚级原子样式&#xff08;…...

docker搭建swarm集群

环境准备 主机名 IP 角色 manger1 192.168.111.47 管理节点 worker1 192.168.111.48 工作节点 worker2 192.168.111.49 工作节点 注&#xff1a;三台主机都已经拉去完swarm和…...

普通IT的股票交易成长史--20250428晚

声明&#xff1a;本文章的内容只是自己学习的总结&#xff0c;不构成投资建议。文中观点基本来自yt站Andylee&#xff0c;美股Alpha姐&#xff0c;综合自己的观点得出。感谢他的无私分享。 仓位就是生命&#xff0c;绝对不能满仓&#xff01;&#xff01;&#xff01;&#xf…...

【React Native】精通 react native

活到老,学到老。 一、基础核心 JavaScript/TypeScript 基础 掌握 ES6+ 语法(箭头函数、解构、Promise、async/await)。熟悉 TypeScript(类型系统、接口、泛型)以提高代码质量。React 核心概念 组件化开发(函数组件、类组件)。状态管理(useState, useEffect, useContex…...

微信小程序-van-uploader的preview-size

preview-size支持数组格式 修改前修改后1、升级微信小程序里面的van版本:2、 重新构建npm3、重启微信开发工具 修改前 引用van组件的上传文件&#xff0c;设置预览图尺寸&#xff0c;刚开始设置的是preview-size“140”&#xff0c;出来的效果就是一个正方形。 修改后 1、升级…...

成员方法的详细说明(结合Oracle官方文档)

在Java的对象创建过程中&#xff0c;成员方法的地址并不存储在对象的堆内存中。Java虚拟机的设计说明&#xff08;包括Oracle的Java虚拟机规范、OpenJDK文档、以及HotSpot的设计文档&#xff09;都明确区分了对象的实例数据&#xff08;存储在堆内存中&#xff09;和类的元数据…...

[蓝桥杯刷题]---模拟法[2]日期问题

题目如下: 题目的意思是&#xff1a; 给出一个日期&#xff08;格式是yy mm dd&#xff0c;注意年份只有两位数&#xff09;&#xff0c;要找出所有可能的真实日期&#xff08;合法的yyyy-mm-dd格式&#xff09;。 需要考虑&#xff1a; 年份范围在1960到2059。 输入的yy、mm、…...

阿里开源图生动画模型AnimateAnyone2

项目背景 近年来&#xff0c;基于扩散模型&#xff08;diffusion models&#xff09;的人物图像动画化方法取得了显著进展&#xff0c;例如 Animate Anyone 在生成一致性和泛化性方面表现优异。然而&#xff0c;这些方法在处理人物与环境之间的空间关系和人-物体交互&#xff0…...

02_使用 AES 算法实现文件加密上传至阿里云、解密下载

02_使用 AES 算法实现文件加密上传至阿里云、解密下载 一、文件上传下载接口 controller 层 RestController RequestMapping("/api/common/file") Api(tags "公共文件上传") AllArgsConstructor Slf4j public class FileV2Controller {private final Os…...

Linux运维——Vim基础

Vim基础 一、移动光标1.1、基础移动1.2、屏幕滚动 二、编辑操作2.1、插入模式2.2、删除与修改2.3、复制粘贴 三、搜索与替换3.1、搜索3.2、替换 4、分屏与窗口管理4.1、分屏操作4.2、窗口调整 五、宏与批量操作六、效率技巧 一、移动光标 1.1、基础移动 快捷键作用h j k l左/…...

从外卖大战看O2O新趋势:上门私厨平台系统架构设计解析

京东高调进军外卖市场&#xff0c;美团全力防守&#xff0c;两大巨头的竞争让整个行业风起云涌。但在这场外卖大战之外&#xff0c;一个更具潜力的细分市场正在悄然兴起——上门私厨服务。 与标准化外卖不同&#xff0c;上门私厨提供的是个性化定制服务。厨师带着新鲜食材上门现…...

【网络编程】ARP协议与主机之间的通信

1. 什么是ARP协议&#xff1f; ARP&#xff08;地址解析协议&#xff0c;Address Resolution Protocol&#xff09; 是一种用于在网络中将IP地址转换为MAC地址的协议。它属于TCP/IP协议栈中的网络层协议&#xff0c;通常在局域网&#xff08;LAN&#xff09;环境下使用&#x…...

JVM模型、GC、OOM定位

JVM模型 程序计数器 程序计数器是一块较小的内存空间&#xff0c;可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令&#xff0c;分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来…...

什么是数据链路层的CRC检测以及为什么要放到帧尾?

数据链路层在封装过程中添加CRC&#xff08;循环冗余校验&#xff09;帧尾&#xff0c;主要目的是为了检测数据传输过程中可能出现的比特错误&#xff0c;确保数据的完整性和可靠性。具体原因如下&#xff1a; 1. 错误检测 物理层传输的不可靠性&#xff1a;数据在物理介质&am…...

Electron 入门指南

Electron 入门指南 Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用的框架。通过 Electron&#xff0c;你可以利用 Web 技术开发出功能强大的桌面应用程序&#xff0c;并且能够运行在 Windows、Mac 和 Linux 系统上。 本文将带你从零开始构建一个简单的 Ele…...

目标检测YOLO实战应用案例100讲- 无人机平台下露天目标检测与计数

目录 知识储备 基于YOLOv8改进的无人机露天目标检测与计数 一、环境配置与依赖安装 二、核心代码实现(带详细注释) 1. 改进YOLOv8模型定义(添加注意力机制) 2. 无人机视角数据增强(drone_augment.py ) 3. 多目标跟踪与计数(tracking_counter.py ) 4. 完整推理流…...

ArkTS基础实验 (二)

任务一&#xff1a;使用模板字符串相关知识&#xff0c;实现多个变量的拼接。同学们可以把自己的姓名、年纪和爱好这三个变量进行拼接。把代码和日志中console.log的打印结果截图保留。 预期效果&#xff1a; 任务二&#xff1a;使用状态变量和点击事件相关知识实现计数器案例…...

【计算机视觉】Bayer Pattern与Demosaic算法详解:从传感器原始数据到彩色图像

Bayer Pattern与Demosaic算法详解&#xff1a;从传感器原始数据到彩色图像 一、引言 在现代数码相机和手机摄像头中&#xff0c;我们能够拍摄到丰富多彩的彩色图像。然而&#xff0c;你可能不知道的是&#xff0c;图像传感器本身并不能直接感知颜色——它们只能感知光的强度。…...

媒体查询使用

一、引言 为了确保网页在不同设备上都能提供良好的用户体验&#xff0c;响应式设计变得至关重要。而媒体查询&#xff08;Media Queries&#xff09;就是前端开发中实现响应式设计的核心技术之一。 二、媒体查询的概念 媒体查询是 CSS3 引入的一项强大功能&#xff0c;它允许开…...

deepseek对IBM MQ SSL 证书算法的建议与解答

在IBM MQ配置SSL TLS的命令中&#xff0c;如果参数SSLCIPH使用TLS_RSA_WITH_AES_128_CBC_SHA256&#xff0c;如下所示&#xff1a; DEFINE CHANNEL(QM1.TO.QM2) CHLTYPE(SDR) TRPTYPE(TCP) CONNAME(QM1.MACH.COM) XMITQ(QM2) SSLCIPH(TLS_RSA_WITH_AES_128_CBC_SHA256) DESCR(S…...

服务器文件同步工具有哪些?

服务器文件同步工具的选择取决于你的具体需求(如实时同步、单向/双向同步、跨平台支持、安全性等)。以下是几款主流的服务器文件同步工具推荐,适用于不同场景: 1. 实时同步工具(适合高频率、低延迟需求) rsync 特点:经典增量同步工具,支持本地/远程同步,高效节省带宽。…...

Numpy数组与矩阵——python学习

我前面提到过Numpy函数&#xff0c;但是不够全&#xff0c;在这里我顺便做一些补充。先说明一下我用的是Notebook。 一、数组的创建与操作 1、把列表转换为数组 np.array([1,2,3,4,5]) 2、把元组转换为数组 np.array((1,2,3,4,5)) 3、把range对象转换为数组 np.array(rang…...

CasaOS上部署1Panel开源运维面板远程在线访问配置实操指南

文章目录 前言1. 添加镜像源2. 部署1Panel3. 本地访问测试4. 安装内网穿透工具5. 配置公网地址6. 配置固定公网地址 前言 很多时候在尝试远程管理服务器时&#xff0c;常常会遇到各种各样的麻烦&#xff0c;尤其是缺乏公网IP或者路由器设置过于复杂时&#xff0c;更是让人感到…...

深入理解缓存淘汰策略:LRU 与 LFU 算法详解及 Java 实现

一、LRU (Least Recently Used - 最近最少使用) LRU 策略的核心思想是&#xff1a;当缓存空间不足时&#xff0c;优先淘汰最近最长时间未被访问的数据。它基于“时间局部性”原理&#xff0c;即最近被访问的数据&#xff0c;在未来被访问的概率也更高。 LeetCode 146. LRU 缓…...

小智项目架构分析

小智代码架构 .github 这就是github项目上拉下来的一些信息 没什么好看的&#xff0c;这跟项目代码无关 .build 编译时生成的文件&#xff0c;没什么可看的&#xff0c;与项目代码无关 .main 主要的代码都在这里面了 .managed_components 这里是小智用到的一些第三方移植…...

基于 SSE 和分块传输的 Uniapp 微信小程序 实现 流式传输 对话

最近的项目是做微信小程序的一个对话框&#xff0c;接入DeepSeek&#xff0c;实现实时对话一个功能。 主要用到的技术点为&#xff1a; 1. Server-Sent Events (SSE) 技术&#xff1a; 在请求头中设置了 ‘X-DashScope-SSE’: ‘enable’&#xff0c;启用了SSE协议 服务器以事…...

[OS] POSIX C库介绍

POSIX C 库可以理解为 Unix/Linux系统的"标准化工具包"&#xff0c;用一句话概括就是&#xff1a; &#x1f449; 它提供了一套跨Unix系统的统一编程接口&#xff0c;让开发者用同一份代码能在不同系统&#xff08;如Linux、macOS&#xff09;中运行。 核心组成&…...

<uniapp><插件><UTS>在uniapp中,创建自己的插件并发布到uni插件市场

前言 本专栏是基于uniapp实现手机端各种小功能的程序&#xff0c;并且基于各种通讯协议如http、websocekt等&#xff0c;实现手机端作为客户端&#xff08;或者是手持机、PDA等&#xff09;&#xff0c;与服务端进行数据通讯的实例开发。 发文平台 CSDN 环境配置 系统&…...

深度学习前沿探秘:Transformer 模型与多领域应用

技术点目录 注意力&#xff08;Attention&#xff09;机制详解自然语言处理&#xff08;NLP&#xff09;领域的Transformer模型详解计算视觉&#xff08;CV&#xff09;领域的Transformer模型详解时间序列建模与预测的大语言模型目标检测算法详解目标检测的大语言模型语义分割的…...

介绍下Nginx的作用与请求转发机制

引言 最近笔者在业务中遇到了Nginx轮训策略使用不当导致后端服务的压力增加&#xff0c;从而导致容器CPU资源不足&#xff0c;响应超时的问题&#xff1b; 但由于对Nginx的了解仅限与作为反向代理使用&#xff0c;所以借用GPT工具整理了Nginx的作用以及请求转发机制&#xff…...

Sql刷题日志(day6)

一、笔试 1、insert ignore&#xff1a;在插入数据时忽略主键冲突或其他唯一性约束冲突。 如果插入的记录会导致主键冲突&#xff08;如 actor_id 已存在&#xff09;&#xff0c;该语句不会报错&#xff0c;而是直接忽略插入操作 语法&#xff1a; INSERT IGNORE INTO tab…...

Ajax 提交表单与文件上传

目录 一、Ajax 提交表单1.1 基本原理1.2 HTML 表单示例1.3 JavaScript 示例(使用 fetch API)二、Ajax 文件上传2.1 基本原理2.2 HTML 文件上传表单示例2.3 JavaScript 示例(使用 fetch API)三、后端处理示例(以 Node.js + Express 为例)3.1 安装依赖3.2 创建服务器文件四…...

【零基础入门】ASP.NET Core快速搭建第一个Web应用

一、为什么选择ASP.NET Core&#xff1f; 跨平台支持&#xff1a;可在Windows/macOS/Linux系统运行 高性能&#xff1a;比传统ASP.NET框架快10倍以上 开源生态&#xff1a;活跃的开发者社区和丰富的NuGet包 云原生支持&#xff1a;完美适配Docker和Kubernetes部署 二、开发…...

盒子模型

1.1看透网页布局的本质 1.2盒子模型的组成部分 css盒子模型本质是一个盒子&#xff0c;封装周围的html元素&#xff0c;它包括边框 外边距 内边距和实际内容。 padding&#xff1a;盒子与内容间的距离margin&#xff1a;盒子与盒子间的距离 1.3border边框 1.border-width 边…...

厚铜PCB如何兼顾质量与成本?供应商设计规范执行的黄金平衡点

厚铜电路板供应商需要遵循一系列设计规范&#xff0c;以确保所提供的电路板符合行业标准和客户要求。以下是一些需要遵循的设计规范&#xff1a; 1. 电路板尺寸和形状&#xff1a;厚铜电路板供应商需要按照客户提供的规格和要求来设计电路板的尺寸和形状。一般来说&#xff0c;…...

Kafka 配置参数性能调优建议

文章目录 1、生产者调优batch.size&#xff08;重要&#xff09;linger.mscompression.typeacks&#xff08;重要&#xff09;buffer.memorymax.in.flight.requests.per.connection&#xff08;重要&#xff09;message.max.bytes&#xff08;重要&#xff09; 2、消费者调优fe…...

Java读Excel:解析阿里云easyExcel导入文件的行号

文章目录 引言I 解析阿里云easyExcel导入文件的行号声明解析对象的基类判断Excel解析对象类型是否包含继承某个类 isAssignableFromJava 转换list类型并设置下标到元素对象属性II 封装excel 文件读取excel 文件读取用法文件导入上下文III 参数校验工具类校验参数是否合法 (jaka…...

Vuex持续保存数据 页面刷新不丢失 vuex-persistedstate

vuex可以进行全局的状态管理&#xff0c;但刷新后刷新后数据会消失&#xff0c;这是我们不愿意看到的。怎么解决呢&#xff0c;我们可以结合本地存储做到数据状态持久化&#xff0c;但是太麻烦每次都要操作&#xff0c;强烈建议使用插件利用vuex-persistedstate插件. 安装 npm …...