Nacos 客户端源码分析——服务注册
一、代码位置
![](https://img-blog.csdnimg.cn/img_convert/9e7d1924b59a02c2d3362f9395c262c4.png)
二、源码分析
![](https://img-blog.csdnimg.cn/img_convert/3be827e424c46fd9116d7e2805f7059e.png)
2.1 NamingFactory#createNamingService
/**
* 创建NamingService
* @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);
}
}
2.2 NacosNamingService#registerInstance :注册实例
@Override
public void registerInstance(String serviceName, Instance instance) throws NacosException {
registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
}
// 注册主要做了两件事,第一件事:为注册的服务设置一个定时心跳任务。 第二件事:将服务注册到服务端。
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
//心跳检测-检查有关保持活动状态的实例参数。
NamingUtils.checkInstanceIsLegal(instance);
// 获取组名 groupName@@Service
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
// 实例是临时的
// 如果是临时实例,则不会在 Nacos 服务端持久化存储,需要通过上报心跳的方式进行
if (instance.isEphemeral()) {
//为注册服务设置一个定时任务获取心跳信息,默认为5s汇报一次
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
//注册服务
serverProxy.registerService(groupedServiceName, groupName, instance);
}
2.2.1 NamingUtils#checkInstanceIsLegal
/**
* <p>Check instance param about keep alive.</p>
*
* <pre>
//实例“心跳间隔”必须小于“心跳超时”和“IP 删除超时”。
* </pre>
*
* @param instance need checked instance
* @throws NacosException if check failed, throw exception
*/
public static void checkInstanceIsLegal(Instance instance) throws NacosException {
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'.");
}
}
2.2.2 NamingProxy#registerService
/**
* 注册实例服务根据属性
*
* @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<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);
}
2.2.3 NamingProxy#reqApi :请求API方法
/**
* Request api.
*
* @param api api
* @param params parameters
* @param body body
* @param servers servers
* @param method http method
* @return result
* @throws NacosException nacos exception
*/
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();
// 重试3次
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());
}
2.2.4 NamingProxy#callServer :调用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<String, String> params, Map<String, String> body, String curServer,
String method) throws NacosException {
// 当前时间
long start = System.currentTimeMillis();
long end = 0;
//添加校验参数token 和aksk
injectSecurityInfo(params);
// 添加Header
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 {
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 (NacosException ex) {
NAMING_LOGGER.warn("[NA] failed to request cause of inner exception");
throw ex;
} catch (ConnectException connectException) {
NAMING_LOGGER.warn("[NA] failed to request cause of connection exception");
throw new NacosException(NacosException.SERVER_ERROR, connectException);
} catch (SocketTimeoutException timeoutException) {
NAMING_LOGGER.warn("[NA] failed to request cause of connection time out exception");
throw new NacosException(NacosException.SERVER_ERROR, timeoutException);
} catch (Exception unknownEx) {
NAMING_LOGGER.error("[NA] failed to request cause of unknown reason", unknownEx);
throw new NacosException(NacosException.SERVER_ERROR, unknownEx);
}
}
2.2.5 BeatReactor#addBeatInfo :注册心跳服务
* 客户端心跳发送
*
* @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);
// 构建心跳的key
String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
BeatInfo existBeat = null;
// 防止高并发
if ((existBeat = dom2Beat.put(key, beatInfo)) != null) {
existBeat.setStopped(true);
}
// 利用线程池,定期执行心跳任务,周期为 beatInfo.getPeriod()
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
2.2.5 BeatReactor :心跳监测类
// 定时线程池
private final ScheduledExecutorService executorService;
private final NamingProxy serverProxy;
// 是否是轻心跳——》客户端第一次注册是重注册,其他时的心跳是轻xin'tiao
private boolean lightBeatEnabled = false;
public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>();
public BeatReactor(NamingProxy serverProxy) {
this(serverProxy, UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT);
}
// BeatInfoReactor 就是个线程
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;
}
});
}
2.2.6 BeatInfo: 心跳实体类
private int port;
private String ip;
private double weight;
private String serviceName;
private String cluster;
private Map<String, String> metadata;
private volatile boolean scheduled;
// 周期
private volatile long period;
private volatile boolean stopped;
2.2.7 Reactor#BeatTask :心跳任务
class BeatTask implements Runnable {
BeatInfo beatInfo;
public BeatTask(BeatInfo beatInfo) {
this.beatInfo = beatInfo;
}
@Override
public void run() {
if (beatInfo.isStopped()) {
return;
}
long nextTime = beatInfo.getPeriod();
try {
// 向服务端发送请求(首次注册为注册时心跳,会将整个beatInfo对象发送给服务端),api地址:/nacos/v1/ns/instance/beat
// 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();
}
// 首次注册时心跳,服务端会返回lightBeatEnabled=true,标注下次心跳为轻量级心跳
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 {
serverProxy.registerService(beatInfo.getServiceName(),
NamingUtils.getGroupName(beatInfo.getServiceName()), instance);
} catch (Exception ignore) {
}
}
} catch (NacosException ex) {
NAMING_LOGGER.warn("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}",
JacksonUtils.toJson(beatInfo), ex.getErrCode(), ex.getErrMsg());
} catch (Exception unknownEx) {
NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, unknown exception msg: {}",
JacksonUtils.toJson(beatInfo), unknownEx.getMessage(), unknownEx);
} finally {
// 周期进行心跳包发送(默认5s)
executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
}
}
}