前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
Nacos是一个基于云原生的动态服务发现、配置管理和服务治理平台,由阿里巴巴开源。它提供了服务注册与发现、配置管理、动态DNS、流量管理、服务降级、负载均衡、限流、路由管理等一系列核心功能,可以帮助企业构建弹性可扩展的微服务架构。
本文将从源码的角度介绍一下Nacos服务注册原理。
服务注册是指将某个服务的相关信息(如服务名称、IP地址、端口号等)注册到服务注册中心中,以便其他服务或客户端能够发现和访问该服务。
在微服务架构中,由于服务的数量众多,服务之间的调用关系也变得复杂且动态,因此需要一种统一和自动化的方式来管理和发现服务。而服务注册中心就是这样一种机制,它可以帮助服务消费者快速地找到可用的服务提供者,并完成服务调用。
本文将从客户端和服务端的角度介绍Nacos服务注册的原理。
客户端 | 服务端 | |
---|---|---|
版本 | Spring Cloud Alibaba 2021.0.1.0 | 1.4.0 |
Spring Cloud Alibaba Nacos客户端是基于Springboot自动装配功能实现的,了解Springboot自动装配原理的应该知道首先要查看 spring.factories
文件有哪些自动装配的类。
从名字可以看出NacosServiceRegistryAutoConfiguration
类是进行服务注册的,源码如下:
@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 {
@Bean
public NacosServiceRegistry nacosServiceRegistry(
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosServiceRegistry(nacosDiscoveryProperties);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosRegistration nacosRegistration(
ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers,
NacosDiscoveryProperties nacosDiscoveryProperties,
ApplicationContext context) {
return new NacosRegistration(registrationCustomizers.getIfAvailable(),
nacosDiscoveryProperties, context);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosAutoServiceRegistration nacosAutoServiceRegistration(
NacosServiceRegistry registry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
return new NacosAutoServiceRegistration(registry,
autoServiceRegistrationProperties, registration);
}
}
@ConditionalOnNacosDiscoveryEnabled
注解用于使@EnableDiscoveryClient
注解生效,但其实有没有在启动类使用@EnableDiscoveryClient
都能让客户端生效。因为@ConditionalOnProperty
注解的属性matchIfMissing = true
,也就是有没有对应的value值,自动配置都会生效。
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.enabled",
matchIfMissing = true)
public @interface ConditionalOnNacosDiscoveryEnabled {
}
从源码看到NacosServiceRegistryAutoConfiguration
类会注入以下3个类:
NacosServiceRegistry :实现了Springcloud的ServiceRegistry
接口,ServiceRegistry
接口是Springcloud提供的一个服务注册的接口,如果想集成Springcloud注册中心必须要实现的接口。NacosServiceRegistry实现了它,并完成nacos服务注册功能。
NacosRegistration :存储nacos服务端信息,它实现了Registration接口,Registration接口中没有任何东西,只是实现了ServiceInstance 接口,ServiceInstance 接口存储一些服务实例的信息。
NacosAutoServiceRegistration:继承了AbstractAutoServiceRegistration
抽象类,AbstractAutoServiceRegistration
抽象类实现了AutoServiceRegistration
, ApplicationContextAware
, ApplicationListener
,通过spring的事件监听机制来完成服务的注册。
先来看下NacosAutoServiceRegistration的父类AbstractAutoServiceRegistration
抽象类部分源码:
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);
}
// 绑定WebServerInitializedEvent事件
@Deprecated
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 below
if (!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);
}
}
//销毁服务
@PreDestroy
public void destroy() {
stop();
}
/**
* 注册服务
* Register the local service with the {@link ServiceRegistry}.
*/
protected void register() {
//真正注册服务的地方
this.serviceRegistry.register(getRegistration());
}
}
从源码可以看到 AbstractAutoServiceRegistration实现了ApplicationListener 监听接口,并监听了WebServerInitializedEvent 事件,WebServerInitializedEvent 事件在容器启动时会进行事件发布。
AbstractAutoServiceRegistration 启动流程是这样的:
容器启动时,发布了WebServerInitializedEvent 事件,AbstractAutoServiceRegistration会调用onApplicationEvent(WebServerInitializedEvent)
方法,onApplicationEvent()
方法会调用绑定了WebServerInitializedEvent 事件的bind(WebServerInitializedEvent)
方法,同时后续调用start()
方法启动注册流程,start()
方法会调用ServiceRegistry#register()
开始进行注册。
NacosServiceRegistry实现了ServiceRegistry
接口,所以 注册从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();
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
// 通过配置文件获取实例相关的信息
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) {
if (nacosDiscoveryProperties.isFailFast()) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
rethrowRuntimeException(e);
}
else {
log.warn("Failfast is false. {} register failed...{},", serviceId,
registration.toString(), e);
}
}
}
NacosServiceRegistry#register()
方法中,会先通过客户端的配置文件创建一个 NamingService ,然后把封装过服务实例的基本信息的 Registration 对象(Registration 接口只是空继承了 ServiceInstance 接口,类中没有任何方法)生成一个 Instance 对象,最后通过 NamingService#registerInstance()
将服务实例注册到Nacos注册中心去。
追溯源码可以发现 NamingService 是通过**NamingFactory#createNamingService(Properties)**方法 创建的:
public static NamingService createNamingService(Properties properties) throws NacosException {
try {
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
NamingService vendorImpl = (NamingService) constructor.newInstance(properties);
return vendorImpl;
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
createNamingService 方法通过反射调用**NacosNamingService(Properties)**构造方法创建对象,
public NacosNamingService(Properties properties) throws NacosException {
init(properties);
}
//初始化操作
private void init(Properties properties) throws NacosException {
//验证properties参数是否正确
ValidatorUtils.checkInitParam(properties);
//初始化namespace
this.namespace = InitUtils.initNamespaceForNaming(properties);
InitUtils.initSerialization();
//初始化server 地址
initServerAddr(properties);
InitUtils.initWebRootContext();
//初始化缓存目录
initCacheDir();
//初始化日志名
initLogName(properties);
//创建事件转发器
this.eventDispatcher = new EventDispatcher();
this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);
//初始化心跳反应
this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));
this.hostReactor = new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir,
isLoadCacheAtStart(properties), initPollingThreadCount(properties));
}
再看一下 NacosNamingService#registerInstance()
方法:
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
// 临时节点
if (instance.isEphemeral()) {
//封装心跳信息
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
//加入定时任务
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
//实际进行代理注册服务
serverProxy.registerService(groupedServiceName, groupName, instance);
}
//添加定时任务
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
// 心跳key,形如: DEFAULT_GROUP@@provider#192.168.71.70#9093
String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
BeatInfo existBeat = null;
//fix #1733
if ((existBeat = dom2Beat.remove(key)) != null) {
existBeat.setStopped(true);
}
dom2Beat.put(key, beatInfo);
//心跳定时任务 默认5s一次
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
NacosNamingService#registerInstance()
方法会对临时节点一个心跳封装,创建一个 BeatTask 心跳任务以保证客户端的健康,默认5s执行一次,最后通过NamingProxy#registerService
执行服务注册。
进行服务注册的是
com.alibaba.nacos.client.naming.net.NamingProxy#registerService
方法,且看源码:
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
instance);
// 请求参数
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()));
// 封装请求
reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}
NamingProxy#registerService
方法会将服务实例的信息放到Map 集合中,然后传递给reqApi
方法。
reqApi
方法调用callServer
方法,callServer
方法最终会把这些包含客户端信息的参数封装起来并生成一个url,通过Nacos提供的openAPI形式调用服务端接口完成注册。
总结一下客户端注册流程,客户端启动时,会向Nacos注册中心发送注册请求。注册请求中包含了服务名、IP地址、端口号和其他元数据信息。客户端将自己的信息注册到Nacos中心,这样服务消费者才能够发现它。
在Nacos中,服务提供者注册的信息被称为服务实例(Instance),一个服务可以有多个实例,每个实例都有一个唯一的ID来标识自己。
客户端注册完成之后,服务端在接到注册请求之后会做什么呢?
通过actuator找到到客户端请求注册之后服务端的controller:
InstanceController
类用于处理服务注册和发现的HTTP请求,包括注册、注销和查询服务实例列表等操作。
@Autowired
private ServiceManager serviceManager;
@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);
//检查服务格式是否正确
checkServiceNameFormat(serviceName);
//将请求中的参数转化为实例信息
final Instance instance = parseInstance(request);
//服务端注册实例信息
serviceManager.registerInstance(namespaceId, serviceName, instance);
return "ok";
}
InstanceController#register
方法将客户端传过来的参数进行解析并进行一系列操作之后,通过ServiceManager#registerInstance
方法进行实例注册。
ServiceManager 类提供了一些注册和管理服务的方法。来看下ServiceManager#registerInstance
方法将一个服务实例注册到Nacos注册中心中,且看源码:
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
// 创建一个服务
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
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);
}
createEmptyService
方法会初始化一个服务,使服务能注册到nacos注册中心。
public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
createServiceIfAbsent(namespaceId, serviceName, local, null);
}
/**
* Create service if not exist.
*
* @param namespaceId namespace
* @param serviceName service name
* @param local whether create service by local
* @param cluster cluster
* @throws NacosException nacos exception
*/
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
throws NacosException {
//根据namespace和服务名称获取服务,获取不到新建一个服务。
Service service = getService(namespaceId, serviceName);
if (service == null) {
Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
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();
if (cluster != null) {
cluster.setService(service);
service.getClusterMap().put(cluster.getName(), cluster);
}
//验证服务名是否正确
service.validate();
//将服务放入serviceMap本地缓存中并进行初始化
putServiceAndInit(service);
if (!local) {
addOrReplaceService(service);
}
}
}
通过 createServiceIfAbsent
会进行创建服务,并在最后将创建好的服务放到本地缓存中。
/**
* Map(namespace, Map(group::serviceName, Service)).
* 同一个namespace下放到一起,同一个组的服务
*/
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
private void putServiceAndInit(Service service) throws NacosException {
// 放到本地缓存中
putService(service);
//初始化服务,对客户端进行心跳检查和服务集群进行初始化
service.init();
//监听数据的变化保持集群中数据的一致性
consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
}
/**
* Put service into manager.
*
* @param service service
*/
public void putService(Service service) {
if (!serviceMap.containsKey(service.getNamespaceId())) {
// 同步,确保只有一个线程进行操作
synchronized (putServiceLock) {
if (!serviceMap.containsKey(service.getNamespaceId())) {
serviceMap.put(service.getNamespaceId(), new ConcurrentHashMap<>(16));
}
}
}
serviceMap.get(service.getNamespaceId()).put(service.getName(), service);
}
可以看到最后nacos服务端将服务缓存到private final Map
中,nacos通过不同的 namespace 来管理服务,在同一个 namespace 下又使用不同的group来管理服务。
至此,服务端接收到客户端的简单流程就结束了,至于服务端怎么更新缓存、服务集群怎么更新实例、还有诸如心跳健康检查之类的后续再介绍。
总结一下服务端注册流程,Nacos注册中心接收到服务提供者的注册请求后,将服务实例的信息保存到本地缓存或持久化存储中。同时,Nacos还会根据服务名和实例的标识来生成一个全局唯一的服务实例ID。
最后总结一下服务注册的流程:
spring.factories
文件自动注入 spring-cloud-common
包下的 AutoServiceRegistrationConfiguration 类,同时注入 Nacos的 NacosServiceRegistry 和 NacosRegistration类。start()
方法开始注册流程。start()
方法会调用 ServiceRegistry 接口的实现类 NacosServiceRegistry 的register()
进行注册。NacosServiceRegistry#register()
方法中会创建一个 NamingService 和 Instance 对象,然后通过NacosNamingService#registerInstance()
方法将Instance 对象注册到注册中心。NacosNamingService#registerInstance()
方法会给客户端NamingProxy#registerService
将客户端服务名、IP地址、端口号和其他元数据信息以openAPI的形式请求给服务端,完成客户端的服务注册。InstanceController#register
方法中处理客户端的注册请求。ServiceManager#registerInstance
方法会将客户端注册进来,并将客户端缓存到一个名为 serviceMap 数据结构为 ConcurrentHashMap
的本地缓存中。