很多人在学习开源的时候,无从下手,代码那么多,从哪个地方开始呢?我们学习nacos,首先去到nocas的github源码的地方链接: https://github.com/alibaba/nacos下载源码到我们的idea,打开example项目,
进入APP,可以看到如下代码:
public static void main(String[] args) throws NacosException {
Properties properties = new Properties();
properties.setProperty("serverAddr", "21.34.53.5:8848,21.34.53.6:8848");
properties.setProperty("namespace", "quickStart");
NamingService naming = NamingFactory.createNamingService(properties);
naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1");
naming.registerInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT");
System.out.println(naming.getAllInstances("nacos.test.3"));
}
这里我们可以看到,这里构建了一个NamingService实例,同时设置了我们的nacos服务端的地址和端口,设置namespace。
我们进入createNamingService方法
NamingService
/**
* Create a new naming service.
*
* @param properties naming service properties
* @return new naming service
* @throws NacosException nacos exception
*/
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);
}
}
这里通过反射创建了一个NamingService实例,实际的实现类是在api项目里面的NacosNamingService,随后我们进入NacosNamingService看看
NacosNamingService
1.先看里面的属性
namespace 名字空间
endpoint 服务管理服务端地址管理服务器地址,获取服务管理服务端地址(当 nacos server 集群需要扩缩容时,客户端需要有一种能力能够及时感知到集群发生变化,及时感知到集群的变化是通过 endpoint 来实现的。也即客户端会定时的向endpoint发送请求来更新客户端内存中的集群列表。)
serverList 服务管理服务端地址,可直接配置,或从endpoint获取
cacheDir 调用服务信息本地文件缓存地址
logName 暂未使用
HostReactor 客户端关心的服务的实例信息,推拉模式的更新,failover服务实例信息读写管理
BeatReactor 本地实例信息心跳
EventDispatcher 服务信息变更监听回调处理
NamingProxy 服务管理服务端地址列表更新管理,接口调用负载均衡,失败重试
/**
* Each Naming service should have different namespace.
* 名字空间
*/
private String namespace;
/**
* 当 nacos server 集群需要扩缩容时,客户端需要有一种能力能够及时感知到集群发生变化。
* 及时感知到集群的变化是通过 endpoint 来实现的。也即客户端会定时的向 endpoint 发送请求来更新客户端内存中的集群列表。
* 服务管理服务端地址管理服务器地址,获取服务管理服务端地址
*/
private String endpoint;
/**
* 服务管理服务端地址管理服务器地址,获取服务管理服务端地址
*/
private String serverList;
/**
* 服务管理服务端地址管理服务器地址,获取服务管理服务端地址
*/
private String cacheDir;
private String logName;
/**
* 客户端关心的服务的实例信息,推拉模式的更新,failover服务实例信息读写管理
*/
private HostReactor hostReactor;
/**
* 本地实例信息心跳
*/
private BeatReactor beatReactor;
/**
* 服务信息变更监听回调处理
*/
private EventDispatcher eventDispatcher;
/**
* 服务管理服务端地址列表更新管理,接口调用负载均衡,失败重试
*/
private NamingProxy serverProxy;
了解了相关字段的意思我们来看看构造方法
public NacosNamingService(Properties properties) throws NacosException {
init(properties);
}
这里其实就是执行init初始化方法
private void init(Properties properties) throws NacosException {
ValidatorUtils.checkInitParam(properties); //检查contextPath格式 可为空
this.namespace = InitUtils.initNamespaceForNaming(properties); //初始化命名空间
//子类实现类中的静态代码串中已经向Jackson进行了注册,但是由于classloader的原因,只有当 该子类被使用的时候,才会加载该类。
// 这可能会导致Jackson先进性反序列化,再注册子类,从而导致 反序列化失败。
//所以这里将NoneSelector、ExpressionSelector这两个类进行注册或者销毁
InitUtils.initSerialization();
//这里进行nacos服务端地址初始化
//这里面会涉及到是否启用endpoint
initServerAddr(properties);
//如果应用由EDAS部署,则支持阿里云的web上下文
InitUtils.initWebRootContext();
//这里初始化本地缓存的路径及存放的registerInstance的内容
initCacheDir();
//初始化LogName,未设置用naming.log
initLogName(properties);
/**
*初始化ExecutorService线程池,创建名字为com.alibaba.nacos.naming.client.listener的daemon线程Notifier
* EventDispatcher中有一个LinkedBlockingQueue队列,放的是ServiceInfo
* EventDispatcher中有ConcurrentMap>放入的是EventListener
*Notifier中run方法解析
* 先去队列中弹出队顶元素(poll方法)
* 如果为空进行下一次循环
* 如果不为空则去ConcurrentMap取listeners
* 去除listener去监听NamingEvent
*
*/
this.eventDispatcher = new EventDispatcher();
/**
* 初始化服务代理,用户名密码服务地址及initRefreshTask任务的线程池,创建com.alibaba.nacos.client.naming.updater名字的daemon线程
*/
this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);
/**
* initClientBeatThreadCount(properties):Runtime.getRuntime().availableProcessors()返回到Java虚拟机的可用的处理器数量
* 创建一个此案城池com.alibaba.nacos.naming.beat.sender的daemon线程
*/
this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));
/**
* 同上
*/
this.hostReactor = new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir,
isLoadCacheAtStart(properties), initPollingThreadCount(properties));
}
innit方法里面在初始化各个模块,具体的步骤是
1.检查contextPath格式
2.将NoneSelector、ExpressionSelector这两个类进行注册或者销毁
3.nacos服务端地址初始化
4.如果应用由EDAS部署,则支持阿里云的web上下文
5.这里初始化本地缓存
6.初始化LogName
7.初始化服务信息变更监听回调处理
8.初始化服务管理服务端地址列表更新管理,接口调用负载均衡,失败重试
9.初始化本地实例信息心跳
10.初始化客户端关心的服务的实例信息
说明:7-10都会初始化线程池,创建daemon线程
总的来说,init方法为我们初始化各种本地信息,下面来看具体初始化方法
ValidatorUtils.checkInitParam(properties)
public static final String CONTEXT_PATH = "contextPath";
private static final Pattern CONTEXT_PATH_MATCH = Pattern.compile("(\\/)\\1+");
public static void checkInitParam(Properties properties) throws NacosException {
checkContextPath(properties.getProperty(PropertyKeyConst.CONTEXT_PATH));
}
/**
* Check context path.
*
* @param contextPath context path
*/
public static void checkContextPath(String contextPath) {
if (contextPath == null) {
return;
}
Matcher matcher = CONTEXT_PATH_MATCH.matcher(contextPath);
if (matcher.find()) {
throw new IllegalArgumentException("Illegal url path expression");
}
}
这里的代码比较简单,只是检查了一下contextPath
InitUtils.initNamespaceForNaming(properties)
/**
* Add a difference to the name naming. This method simply initializes the namespace for Naming. Config
* initialization is not the same, so it cannot be reused directly.
*
* 为名称命名添加差异。此方法简单地初始化命名空间以进行命名。配置初始化不一样,所以不能直接重用。
*
* @param properties properties
* @return namespace
*/
public static String initNamespaceForNaming(Properties properties) {
String tmpNamespace = null;
String isUseCloudNamespaceParsing = properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING)));//默认是true
System.out.println("isUseCloudNamespaceParsing:" + isUseCloudNamespaceParsing);
if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) {
tmpNamespace = TenantUtil.getUserTenantForAns();//这里是ans,据说是注册中心,未设置tenant.id和ans.namespace 返回为空
/**
* 这里检查是否为空,如果不为空发返回tmpNamespace,如果为空执行Callable.call()方法,
* call()方法里面去取ans.namespace属性,返回namespace
*/
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable() {
@Override
public String call() {
String namespace = System.getProperty(SystemPropertyKeyConst.ANS_NAMESPACE);
LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
return namespace;
}
});
/**
* 这里检查是否为空,如果不为空发返回tmpNamespace,如果为空执行Callable.call()方法,
* call()方法里面去取ALIBABA_ALIWARE_NAMESPACE环境变量
*/
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable() {
@Override
public String call() {
String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);
LogUtils.NAMING_LOGGER.info("initializer namespace from System Environment :" + namespace);
return namespace;
}
});
}
/**
* 这里检查是否为空,如果不为空发返回tmpNamespace,如果为空执行Callable.call()方法,
* call()方法里面去取NAMESPACE属性
*/
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable() {
@Override
public String call() {
String namespace = System.getProperty(PropertyKeyConst.NAMESPACE);
LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
return namespace;
}
});
if (StringUtils.isEmpty(tmpNamespace) && properties != null) {
/**
* 这里拿到我们外面设置的namespace
*/
tmpNamespace = properties.getProperty(PropertyKeyConst.NAMESPACE);
}
/**
* 这里如果前面tmpNamespace都是null,则返回默认的NAMESPACE:public
*/
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable() {
@Override
public String call() {
return UtilAndComs.DEFAULT_NAMESPACE_ID;
}
});
return tmpNamespace;
}
这个方法里面先去会判断是否使用isUseCloudNamespaceParsing,默认是true,然后回去检查是否用ans,ALIBABA_ALIWARE。同时会拿到我们最开始设置的namespace,如果为设置,则用默认的public。
InitUtils.initSerialization()
/**
* Register subType for serialization.
*
*
* Now these subType implementation class has registered in static code. But there are some problem for classloader.
* The implementation class will be loaded when they are used, which will make deserialize before register.
*
*
*
* 子类实现类中的静态代码串中已经向Jackson进行了注册,但是由于classloader的原因,只有当 该子类被使用的时候,才会加载该类。这可能会导致Jackson先进性反序列化,再注册子类,从而导致 反序列化失败。
*
*/
public static void initSerialization() {
// TODO register in implementation class or remove subType
JacksonUtils.registerSubtype(NoneSelector.class, SelectorType.none.name());
JacksonUtils.registerSubtype(ExpressionSelector.class, SelectorType.label.name());
}
这里很简单,主要是为了防止反序列化失败
initServerAddr(properties)
private void initServerAddr(Properties properties) {
//这里拿到我们前面填写的nacos服务端地址
serverList = properties.getProperty(PropertyKeyConst.SERVER_ADDR);
endpoint = InitUtils.initEndpoint(properties);
if (StringUtils.isNotEmpty(endpoint)) {
serverList = "";
}
}
/**
* Init end point.
*
* @param properties properties
* @return end point
*/
public static String initEndpoint(final Properties properties) {
if (properties == null) {
return "";
}
// Whether to enable domain name resolution rules 是否启用域名解析规则
/**
* 这里是去取end point的解析规则,即对传入的endpoint参数规则解析的能力可以是一个具体的值,也可以是一个占位符的形式
* 1.endpoint.options 是一个具体的变量。支持从系统属性,系统环境变量中读取。
* 2.defaultValue 是给出的一个默认值。当从具体的变量中没有被正确初始化时,会使用给出的默认值来初始化。
*
*/
String isUseEndpointRuleParsing = properties.getProperty(PropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE,
System.getProperty(SystemPropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE,
String.valueOf(ParamUtil.USE_ENDPOINT_PARSING_RULE_DEFAULT_VALUE)));
//isUseEndpointParsingRule的值决定是否启用endpoint解析规则
boolean isUseEndpointParsingRule = Boolean.parseBoolean(isUseEndpointRuleParsing);
String endpointUrl;
if (isUseEndpointParsingRule) {//如果启用解析规则
// Get the set domain name information
endpointUrl = ParamUtil.parsingEndpointRule(properties.getProperty(PropertyKeyConst.ENDPOINT));
if (StringUtils.isBlank(endpointUrl)) {
return "";
}
} else {//不启用
endpointUrl = properties.getProperty(PropertyKeyConst.ENDPOINT);
}
if (StringUtils.isBlank(endpointUrl)) {
return "";
}
String endpointPort = TemplateUtils
.stringEmptyAndThenExecute(System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_PORT),
new Callable() {
@Override
public String call() {
return properties.getProperty(PropertyKeyConst.ENDPOINT_PORT);
}
});
endpointPort = TemplateUtils.stringEmptyAndThenExecute(endpointPort, new Callable() {
@Override
public String call() {
return "8080";
}
});
return endpointUrl + ":" + endpointPort;
}
第一部分是设置serverList为我们最开始设置的服务端地址
第二部分设置我们的endpoint规则
InitUtils.initWebRootContext()
/**
* Init web root context.
*/
public static void initWebRootContext() {
// support the web context with ali-yun if the app deploy by EDAS
final String webContext = System.getProperty(SystemPropertyKeyConst.NAMING_WEB_CONTEXT);
TemplateUtils.stringNotEmptyAndThenExecute(webContext, new Runnable() {
@Override
public void run() {
UtilAndComs.webContext = webContext.indexOf("/") > -1 ? webContext : "/" + webContext;
UtilAndComs.nacosUrlBase = UtilAndComs.webContext + "/v1/ns";
UtilAndComs.nacosUrlInstance = UtilAndComs.nacosUrlBase + "/instance";
}
});
}
这里如果应用由EDAS部署,则支持阿里云的web上下文
initCacheDir()
private void initCacheDir() {
cacheDir = System.getProperty("com.alibaba.nacos.naming.cache.dir");
if (StringUtils.isEmpty(cacheDir)) {
cacheDir = System.getProperty("user.home") + "/nacos/naming/" + namespace;
}
}
这里初始化本地实例信息,在本地你会看到这样的文件
C:\Users\nacos\naming\quickStart
[图片上传失败...(image-bbdedd-1600251861321)]
initLogName(properties)
private void initLogName(Properties properties) {
logName = System.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME);
if (StringUtils.isEmpty(logName)) {
if (properties != null && StringUtils
.isNotEmpty(properties.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME))) {
logName = properties.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME);
} else {
logName = "naming.log";
}
}
}
这里设置logname,目前没看到哪里用
new EventDispatcher()
public EventDispatcher() {
this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "com.alibaba.nacos.naming.client.listener");
thread.setDaemon(true);
return thread;
}
});
this.executor.execute(new Notifier());
}
我们看到构造方法里面初始化了一个线程池,并且加入了Notifier这个线程,我们来看看Notifier的run方法
@Override
public void run() {
while (!closed) {
ServiceInfo serviceInfo = null;
try {
serviceInfo = changedServices.poll(5, TimeUnit.MINUTES);
} catch (Exception ignore) {
}
if (serviceInfo == null) {
continue;
}
try {
List listeners = observerMap.get(serviceInfo.getKey());
if (!CollectionUtils.isEmpty(listeners)) {
for (EventListener listener : listeners) {
List hosts = Collections.unmodifiableList(serviceInfo.getHosts());
listener.onEvent(new NamingEvent(serviceInfo.getName(), serviceInfo.getGroupName(),
serviceInfo.getClusters(), hosts));
}
}
} catch (Exception e) {
NAMING_LOGGER.error("[NA] notify error for service: " + serviceInfo.getName() + ", clusters: "
+ serviceInfo.getClusters(), e);
}
}
}
先去队列中弹出队顶元素(poll方法)
如果为空进行下一次循环
如果不为空则去ConcurrentMap取listeners
取出listener去监听NamingEvent
new NamingProxy(this.namespace, this.endpoint, this.serverList, properties)
public NamingProxy(String namespaceId, String endpoint, String serverList, Properties properties) {
this.securityProxy = new SecurityProxy(properties, nacosRestTemplate);
this.properties = properties;
this.setServerPort(DEFAULT_SERVER_PORT);
this.namespaceId = namespaceId;
this.endpoint = endpoint;
if (StringUtils.isNotEmpty(serverList)) {
this.serverList = Arrays.asList(serverList.split(","));
if (this.serverList.size() == 1) {
this.nacosDomain = serverList;
}
}
this.initRefreshTask();
}
这里的初始化动作很多 我们一个一个看
首先看new SecurityProxy(properties, nacosRestTemplate)
/**
* Construct from properties, keeping flexibility.
*
* @param properties a bunch of properties to read
*/
public SecurityProxy(Properties properties, NacosRestTemplate nacosRestTemplate) {
username = properties.getProperty(PropertyKeyConst.USERNAME, StringUtils.EMPTY);
password = properties.getProperty(PropertyKeyConst.PASSWORD, StringUtils.EMPTY);
contextPath = properties.getProperty(PropertyKeyConst.CONTEXT_PATH, "/nacos");
contextPath = contextPath.startsWith("/") ? contextPath : "/" + contextPath;
this.nacosRestTemplate = nacosRestTemplate;
}
这里设置了用户名和密码同时初始化了nacosRestTemplate,nacosRestTemplate是客户端发送信息到服务端的类,里面用HttpClient实现,有兴趣的可以去看看
再看看initRefreshTask方法
private void initRefreshTask() {
this.executorService = new ScheduledThreadPoolExecutor(2, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.naming.updater");
t.setDaemon(true);
return t;
}
});
this.executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
refreshSrvIfNeed();
}
}, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);
this.executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
securityProxy.login(getServerList());
}
}, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
refreshSrvIfNeed();
this.securityProxy.login(getServerList());
}
首先初始化一个线程池,同时refreshSrvIfNeed去拿服务端serverList,同时securityProxy.login登陆到拿到的服务端列表。
new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties))
我们先看看initClientBeatThreadCount方法
public static final int DEFAULT_CLIENT_BEAT_THREAD_COUNT =
Runtime.getRuntime().availableProcessors() > 1 ? Runtime.getRuntime().availableProcessors() / 2 : 1;
private int initClientBeatThreadCount(Properties properties) {
if (properties == null) {
return UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT;
}
return ConvertUtils.toInt(properties.getProperty(PropertyKeyConst.NAMING_CLIENT_BEAT_THREAD_COUNT),
UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT);
}
通过 Runtime.getRuntime().availableProcessors()方法拿到Java虚拟机的可用的处理器数量,下面我们看看构造方法
public BeatReactor(NamingProxy serverProxy, int threadCount) {
this.serverProxy = serverProxy;
this.executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.beat.sender");
return thread;
}
});
}
这里只是初始化了线程池,本身这个BeatReactor有一个内部类BeatTask执行本地实例注册到服务端做心跳检测
new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir, isLoadCacheAtStart(properties), initPollingThreadCount(properties))
这里先看看isLoadCacheAtStart
private boolean isLoadCacheAtStart(Properties properties) {
boolean loadCacheAtStart = false;
if (properties != null && StringUtils
.isNotEmpty(properties.getProperty(PropertyKeyConst.NAMING_LOAD_CACHE_AT_START))) {
loadCacheAtStart = ConvertUtils
.toBoolean(properties.getProperty(PropertyKeyConst.NAMING_LOAD_CACHE_AT_START));
}
return loadCacheAtStart;
}
这个方法比较简单,只是设置了是否加载本地缓存,下面我们看看构造方法
public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, BeatReactor beatReactor,
String cacheDir, boolean loadCacheAtStart, int pollingThreadCount) {
// init executorService
this.executor = new ScheduledThreadPoolExecutor(pollingThreadCount, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.client.naming.updater");
return thread;
}
});
this.eventDispatcher = eventDispatcher;
this.beatReactor = beatReactor;
this.serverProxy = serverProxy;
this.cacheDir = cacheDir;
if (loadCacheAtStart) {
this.serviceInfoMap = new ConcurrentHashMap(DiskCache.read(this.cacheDir));
} else {
this.serviceInfoMap = new ConcurrentHashMap(16);
}
this.updatingMap = new ConcurrentHashMap();
this.failoverReactor = new FailoverReactor(this, cacheDir);
this.pushReceiver = new PushReceiver(this);
}
这里初始化了一些本地缓存的内容,我们主要看看FailoverReactor和PushReceiver
FailoverReactor
public FailoverReactor(HostReactor hostReactor, String cacheDir) {
this.hostReactor = hostReactor;
this.failoverDir = cacheDir + "/failover";
// init executorService
this.executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.failover");
return thread;
}
});
this.init();
}
public void init() {
executorService.scheduleWithFixedDelay(new SwitchRefresher(), 0L, 5000L, TimeUnit.MILLISECONDS);
executorService.scheduleWithFixedDelay(new DiskFileWriter(), 30, DAY_PERIOD_MINUTES, TimeUnit.MINUTES);
// backup file on startup if failover directory is empty.
executorService.schedule(new Runnable() {
@Override
public void run() {
try {
File cacheDir = new File(failoverDir);
if (!cacheDir.exists() && !cacheDir.mkdirs()) {
throw new IllegalStateException("failed to create cache dir: " + failoverDir);
}
File[] files = cacheDir.listFiles();
if (files == null || files.length <= 0) {
new DiskFileWriter().run();
}
} catch (Throwable e) {
NAMING_LOGGER.error("[NA] failed to backup file on startup.", e);
}
}
}, 10000L, TimeUnit.MILLISECONDS);
}
这里是操作本地实例信息的一些线程,FailoverReactor通过一个文件配置激活failover模式。该模式下,会从本地文件中读取服务 列表信息。
PushReceiver
这里主要看run方法
@Override
public void run() {
while (!closed) {
try {
// byte[] is initialized with 0 full filled by default
byte[] buffer = new byte[UDP_MSS];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
udpSocket.receive(packet);
String json = new String(IoUtils.tryDecompress(packet.getData()), UTF_8).trim();
NAMING_LOGGER.info("received push data: " + json + " from " + packet.getAddress().toString());
PushPacket pushPacket = JacksonUtils.toObj(json, PushPacket.class);
String ack;
if ("dom".equals(pushPacket.type) || "service".equals(pushPacket.type)) {
hostReactor.processServiceJson(pushPacket.data);
// send ack to server
ack = "{\"type\": \"push-ack\"" + ", \"lastRefTime\":\"" + pushPacket.lastRefTime + "\", \"data\":"
+ "\"\"}";
} else if ("dump".equals(pushPacket.type)) {
// dump data to server
ack = "{\"type\": \"dump-ack\"" + ", \"lastRefTime\": \"" + pushPacket.lastRefTime + "\", \"data\":"
+ "\"" + StringUtils.escapeJavaScript(JacksonUtils.toJson(hostReactor.getServiceInfoMap()))
+ "\"}";
} else {
// do nothing send ack only
ack = "{\"type\": \"unknown-ack\"" + ", \"lastRefTime\":\"" + pushPacket.lastRefTime
+ "\", \"data\":" + "\"\"}";
}
udpSocket.send(new DatagramPacket(ack.getBytes(UTF_8), ack.getBytes(UTF_8).length,
packet.getSocketAddress()));
} catch (Exception e) {
NAMING_LOGGER.error("[NA] error while receiving push data", e);
}
}
}
run方法使用while true循环来执行udpSocket.receive(packet),之后将接收到的数据解析为PushPacket,然后根据不同pushPacket.type做不同处理
当pushPacket.type为dom或者service的时候会调用hostReactor.processServiceJSON(pushPacket.data);当pushPacket.type为dump的时候会将hostReactor.getServiceInfoMap()序列化到ack中,最后将ack返回回去
至此,初始化工作就完成了,下面我们来看看如何注册namespace
registerInstance
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
/**
*ephemeral
*短暂的
*/
if (instance.isEphemeral()) {
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
/**
* BeatTask加入到线程池中,线程池中线程注册实例到服务端
*
* 利用prometheus监控
*/
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
//这里也是注册实例到服务端,beatReactor里面也是本地实例心跳
serverProxy.registerService(groupedServiceName, groupName, instance);
}
registerInstance方法第一步获取GroupedName,然后看instance是否短暂的,如果是执行beatReactor.addBeatInfo方法,注册及监控,最后也是通过serverProxy注册namespace
我们直接来看addBeatInfo方法
/**
* Add beat information.
*
* @param serviceName service name
* @param beatInfo 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;
//fix #1733
if ((existBeat = dom2Beat.remove(key)) != null) {
existBeat.setStopped(true);
}
dom2Beat.put(key, beatInfo);
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
先是buildKey生成key,查看beatinfo是否存在,不存在put;然后执行BeatTask,我们来看看BeatTask的run方法
public void run() {
if (beatInfo.isStopped()) {
return;
}
long nextTime = beatInfo.getPeriod();
try {
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;
}
int code = NamingResponseCode.OK;
if (result.has(CommonParams.CODE)) {
code = result.get(CommonParams.CODE).asInt();
}
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 {
/**
* 注册实例到服务端 reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
*/
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());
}
executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
}
这个方法里面最重要两步就是第一步sendBeat发送心跳,第二步通过reqApi注册实例到服务端
然后看看registerService方法
/**
* register a instance to service with specified instance properties.
*
* @param serviceName name of service
* @param groupName group of service
* @param instance instance to register
* @throws NacosException nacos exception
*/
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 params = 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());
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);
}
这里主要配置参数,然后通过reqApi注册实例到服务端,注册实例到服务端代码如下
/**
* Call server.
*
* @param api api
* @param params parameters
* @param body body
* @param curServer ?
* @param method http method
* @return result
* @throws NacosException nacos exception
*/
public String callServer(String api, Map params, Map 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 (!curServer.contains(UtilAndComs.SERVER_ADDR_IP_SPLITER)) {
curServer = curServer + UtilAndComs.SERVER_ADDR_IP_SPLITER + serverPort;
}
url = NamingHttpClientManager.getInstance().getPrefix() + curServer + api;
}
try {
HttpRestResult 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);
}
}
我们看到,这里是通过最开始初始化的nacosRestTemplate发送的。