首先我们在看Nacos源码之前,要先想想为什么我们要读源码?是为了装杯?还是为了在心仪的女神面前给她娓娓道来展示自己的代码功底?当然不全是!
这都不是我们读源码的最终目的。作为一名技术人,上面的都是浮云,真正激励我们的应该是能够提升我们技术功底和整体技术大局观。此乃大道也!闲言少叙,接下来我们就来看一看,看源码究竟有什么好处。
首先我们需要将Nacos的源码下载下来,下载地址:https://github.com/alibaba/nacos。
我们将源码下下来以后,导入到idea中。
当我们导入成功以后,会出现程序包com.alibaba.nacos.consistency.entity不存在的错误提示,这是因为Nacos底层的数据通信会基于protobuf对数据做序列化和反序列化,需要先将proto文件编译为对应的Java代码。
最简单的 不安装任何的东西 idea2021.2已经捆绑安装了这个。
可以通过mvn copmpile来在target自动生成他们。
我们只需要在文件根目录下执行以下命令即可:
mvn clean package -Dmaven.test.skip=true -Dcheckstyle.skip=true
做完以上两步,我们就可以启动Nacos的了。
首先我们找到 nacos-console 这个模块,这个就是我们的管理后台,找到它的启动类,因为Nacos默认为集群启动,所以我们要设置它为单机启动,方便演示。
设置命令:
-Dnacos.standalone=true -Dnacos.home=E:\test\nacos。
启动成功后,账号密码:nacos/nacos。
到这里我们Nacos的源码启动就完成了。
我们先从客户端服务的注册开始说起,我们可以先想一想如果Nacos客户端要注册,会把什么信息传递给服务器?这里我们可以看到在 nacos-client下的NamingTest有这么一些信息。
@Ignore
public class NamingTest {
@Test
public void testServiceList() throws Exception {
//Nacos Server连接信息
Properties properties = new Properties();
//Nacos服务器地址
properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
//连接Nacos服务的用户名
properties.put(PropertyKeyConst.USERNAME, "nacos");
//连接Nacos服务的密码
properties.put(PropertyKeyConst.PASSWORD, "nacos");
//实例信息
Instance instance = new Instance();
//实例IP,提供给消费者进行通信的地址
instance.setIp("1.1.1.1");
//端口,提供给消费者访问的端口
instance.setPort(800);
//权重,当前实例的权限,浮点类型(默认1.0D)
instance.setWeight(2);
Mapmap = new HashMap ();
map.put("netType", "external");
map.put("version", "2.0");
instance.setMetadata(map);
//关键代码 创建自己的实例
NamingService namingService = NacosFactory.createNamingService(properties);
namingService.registerInstance("nacos.test.1", instance);
ThreadUtils.sleep(5000L);
Listlist = namingService.getAllInstances("nacos.test.1");
System.out.println(list);
ThreadUtils.sleep(30000L);
// ExpressionSelector expressionSelector = new ExpressionSelector();
// expressionSelector.setExpression("INSTANCE.metadata.registerSource = 'dubbo'");
// ListViewserviceList = namingService.getServicesOfServer(1, 10, expressionSelector);
}
}
上面就是客户端注册的一个测试类,模仿了真实的服务注册到Nacos的过程,包括NacosServer连接、实例的创建、实例属性的赋值、注册实例,所以在这个其中包含了服务注册的核心代码,从这里我们可以大致看出,它包含了两个类的信息:Nacos Server连接信息和实例信息。
从上述中我们可以看到有关于Nacos Server连接信息是存储在Properties中。
从上述测试中我们可以看到注册实例信息用instance进行承载,而实例信息又分为两部分,一个是基础实例信息,一个是元数据信息。
元数据类型为HashMap,从当前Demo我们能够看到的数据只有两个。
除此之外,我们在Instance类中还可以看到一些默认信息,这些方法都是通过get方法进行提供的。
//心跳间隙的key,默认为5s,也就是默认5秒进行一次心跳
public long getInstanceHeartBeatInterval() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL,
Constants.DEFAULT_HEART_BEAT_INTERVAL);
}
//心跳超时的key,默认为15s,也就是默认15秒收不到心跳,实例将会标记为不健康;
public long getInstanceHeartBeatTimeOut() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,
Constants.DEFAULT_HEART_BEAT_TIMEOUT);
}
//实例IP被删除的key,默认为30s,也就是30秒收不到心跳,实例将会被移除;
public long getIpDeleteTimeout() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
Constants.DEFAULT_IP_DELETE_TIMEOUT);
}
//实例ID生成器key,默认为simple;
public String getInstanceIdGenerator() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.INSTANCE_ID_GENERATOR,
Constants.DEFAULT_INSTANCE_ID_GENERATOR);
}
为什么要说这个呢?从这些参数中我们就可以了解到,我们服务的心跳间隙是多少以及超时时间,传递什么参数配置什么参数,以此来了解我们的实例是否健康。同时我们也可以看到一个比较关键且核心的类,是真正创建实例的类 ——NamingService。
NamingService是Nacos对外提供的一个统一的接口,当我们点进去查看,可以看到大概以下几个方法,这些方法提供了不同的重载方法,方便我们用于不同的场景。
//服务实例注册
void registerInstance(...) throws NacosException;
//服务实例注销
void deregisterInstance(...) throws NacosException;
//获取服务实例列表
ListgetAllInstances(...) throws NacosException;
//查询健康服务实例
ListselectInstances(...) throws NacosException;
//查询集群中健康的服务实例
ListselectInstances(....List clusters....)throws NacosException;
//使用负载均衡策略选择一个健康的服务实例
Instance selectOneHealthyInstance(...) throws NacosException;
//订阅服务事件
void subscribe(...) throws NacosException;
//取消订阅服务事件
void unsubscribe(...) throws NacosException;
//获取所有(或指定)服务名称
ListViewgetServicesOfServer(...) throws NacosException;
//获取所有订阅的服务
ListgetSubscribeServices() throws NacosException;
//获取Nacos服务的状态
String getServerStatus();
//主动关闭服务
void shutDown() throws NacosException;
NamingService的实例化是通过NacosFactory.createNamingService(properties);实现的,内部源码是通过反射来实现实例化过程。
NamingService namingService = NacosFactory.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);
return (NamingService) constructor.newInstance(properties);
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
接下来我们就来看一看NamingService的具体实现。
//调用registerInstance方法
namingService.registerInstance("nacos.test.1", instance);
@Override
public void registerInstance(String serviceName, Instance instance) throws NacosException {
//默认的分组为“DEFAULT_GROUP”
registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
}
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
//检查心跳时间是否正常
NamingUtils.checkInstanceIsLegal(instance);
//通过代理注册服务
clientProxy.registerService(serviceName, groupName, instance);
}
//心跳间隙超过限制 返回错误
if (instance.getInstanceHeartBeatTimeOut() < instance.getInstanceHeartBeatInterval()
|| instance.getIpDeleteTimeout() < instance.getInstanceHeartBeatInterval()) {
throw new NacosException(NacosException.INVALID_PARAM,
"Instance 'heart beat interval' must less than 'heart beat timeout' and 'ip delete timeout'.");
}
通过代理注册服务,我们了解到clientProxy代理接口是通过NamingClientProxyDelegate来完成,我们可以在init构造方法中得出,具体的实例对象。
private void init(Properties properties) throws NacosException {
//使用NamingClientProxyDelegate来完成
this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);
}
NamingClientProxyDelegate实现在NamingClientProxyDelegate中,真正调用注册服务的并不是代理实现类,而且先判断当前实例是否为瞬时对象后,来选择对应的客户端代理来进行请求。
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}
如果当前实例是瞬时对象,则采用gRPC协议(NamingGrpcClientProxy)进行请求,否则采用Http协议(NamingHttpClientProxy),默认为瞬时对象,在2.0版本中默认采用gRPC协议进行与Nacos服务进行交互。
//判断当前实例是否为瞬时对象
private NamingClientProxy getExecuteClientProxy(Instance instance) {
return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
instance);
//数据的缓存
redoService.cacheInstanceForRedo(serviceName, groupName, instance);
//gRPC进行服务调用
doRegisterService(serviceName, groupName, instance);
}
我们想要让某一个服务注册到Nacos中,首先要引入一个依赖:
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
在依赖中,去查看SpringBoot自动装配文件自动装配文件META-INF/spring.factories。
通过SpringBoot的自动装配来加载EnableAutoConfiguration对应的类,这里我们可以看到很多有关于Nacos相关的类,怎么知道哪个是我们真正需要关心的类,服务在注册的时候走的是哪个,一般自动装配,我们都会找到带有“Auto”关键字的文件进行查看,然后再结合我们需要找的,我们是客户端注册服务,所以我们大体可以定位到NacosServiceRegistryAutoConfiguration这个文件。
查看NacosServiceRegistryAutoConfiguration源码,在这里我们只需要关注最核心的nacosAutoServiceRegistration方法
而我们真正关心的只有三个类NacosAutoServiceRegistration类是注册的核心,我们来看一下它的继承关系。
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosAutoServiceRegistration nacosAutoServiceRegistration(
NacosServiceRegistry registry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
return new NacosAutoServiceRegistration(registry,
autoServiceRegistrationProperties, registration);
}
从上述内容中我们可以知道,Nacos服务自动注册是从NacosServiceRegistryAutoConfiguration类开始的,并自动注册到NacosAutoServiceRegistration类中。
在下图中我们可以看到,主要是调用了super 方法,所以我们继续查看该类的构造方法:AbstractAutoServiceRegistration。
public class NacosAutoServiceRegistration
extends AbstractAutoServiceRegistration{
public NacosAutoServiceRegistration(ServiceRegistryserviceRegistry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
super(serviceRegistry, autoServiceRegistrationProperties);
this.registration = registration;
}
}
AbstractAutoServiceRegistration 实现了ApplicationListener接口,用来监听Spring容器启动过程中WebServerInitializedEvent事件,一般如果我们实现这个类的时候,会实现一个方法onApplicationEvent(),这个方法会在我们项目启动的时候触发。
@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
}
由此我们可以看到bind里面的这个方法。
@Deprecated
public void bind(WebServerInitializedEvent event) {
//获取 ApplicationContext对象
ApplicationContext context = event.getApplicationContext();
//判断服务的 Namespace
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
return;
}
}
//记录当前服务的端口
this.port.compareAndSet(0, event.getWebServer().getPort());
//【核心】启动注册流程
this.start();
}
start()方法调用register();方法来注册服务。
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);
}
}
NacosServiceRegistry.register()方法,如下所示:
@Override
public void register(Registration registration) {
//判断ServiceId是否为空
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
//获取Nacos的服务信息
NamingService namingService = namingService();
//获取服务ID和分组
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
//构建instance实例(IP/Port/Weight/clusterName.....)
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);
}
}
}
NacosNamingService.registerInstance()方法,如下:
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
//检查超时参数是否异常,心跳超时时间(15s)必须大于心跳间隙(5s)
NamingUtils.checkInstanceIsLegal(instance);
//拼接服务名,格式:groupName@@serviceName
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
//判断是否为临时实例,默认为true
if (instance.isEphemeral()) {
//临时实例,定时向Nacos服务发送心跳
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
//【核心】发送注册服务实例请求
serverProxy.registerService(groupedServiceName, groupName, instance);
}
在registerService中我们可以看到Nacos服务注册接口需要的完整参数。
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
instance);
final Mapparams = new HashMap (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());
//当前实例IP
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);
}
补充在这里我们会发现我们请求实例接口的地址为/nacos/v1/ns/instance,其实这个在官网中也有提供对应的地址给我们,并且是对应的。
以上就是Nacos的客户端注册流程,阅读源码并没有我们想象中的那么难,道阻且长,行之将至,当你开始行动的时候,你就已经开始进步了,别管学多少。
名称栏目:Nacos源码系列—关于服务注册的那些事
当前地址:http://www.hantingmc.com/qtweb/news17/358267.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联