nacos服务注册与发现之客户端
- 服务注册
1.1 NamingService.registerInstance的方法为客户端提供的服务注册接口
1.2 客户端通过调用NamingService.registerService上报到nacos节点
1.3 客户端通过BeatReactor.addBeatInfo定时上报心跳信息到nacos节点
//客户端NamingService接口实现类
@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule")
public class NacosNamingService implements NamingService {
/**
*
* @param serviceName name of service; SpringCloud项目一般为spring.application.name
* @param groupName group of service;位置为“DEFAULT_GROUP”
* @param instance instance to register
* @throws NacosException
*/
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
if (instance.isEphemeral()) {
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setCluster(instance.getClusterName());
beatInfo.setWeight(instance.getWeight());
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
//启动心跳任务
beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
}
//向nacos节点注册服务
serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
}
public class NamingProxy {
/**
* 客户端调用此方法向nacos节点注册服务
* 构造请求参数
* @param serviceName name of service
* @param groupName group of service
* @param instance instance to register
* @throws NacosException
*/
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
final Map<String, String> params = new HashMap<String, String>(9);
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", JSON.toJSONString(instance.getMetadata()));
reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);
}
/**
* 从集群列表随机下标开始循环所有nacos集群节点注册服务,只要一个成功则返回
* 都不成功则按指定REQUEST_DOMAIN_RETRY_COUNT次数重试
* @param api = "/nacos/v1/ns/instance"
* @param params 请求参数
* @param body = String.empty
* @param servers = nacos集群节点集合
* @param method = HttpMethod.POST
* @return
* @throws NacosException
*/
public String reqAPI(String api, Map<String, String> params, String body, List<String> servers, String method) throws NacosException {
params.put(CommonParams.NAMESPACE_ID, getNamespaceId());
NacosException exception = new NacosException();
if (servers != null && !servers.isEmpty()) {
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 {
//nacos节点注册服务
return callServer(api, params, body, server, method);
} catch (NacosException e) {
exception = e;
}
index = (index + 1) % servers.size();
}
}
//如果上面失败则重试
if (StringUtils.isNotBlank(nacosDomain)) {
for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) {
try {
return callServer(api, params, body, nacosDomain, method);
} catch (NacosException e) {
exception = e;
}
}
}
throw new NacosException(exception.getErrCode(), "failed to req API:/api/" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());
}
/**
* 通过HttpMethod.POST请求当前节点的"/nacos/v1/ns/instance"地址注册服务
* @param api = "/nacos/v1/ns/instance"
* @param params 请求参数
* @param body = String.empty
* @param curServer 当前nacos节点
* @param method = HttpMethod.POST
*/
public String callServer(String api, Map<String, String> params, String body, String curServer, String method) throws NacosException {
List<String> headers = builderHeaders();
// https://127.0.0.1:8848/nacos/v1/ns/instance
String url = curServer + api;
HttpClient.HttpResult result = HttpClient.request(url, headers, params, body, UtilAndComs.ENCODING, method);
if (HttpURLConnection.HTTP_OK == result.code) {
return result.content;
}
if (HttpURLConnection.HTTP_NOT_MODIFIED == result.code) {
return StringUtils.EMPTY;
}
throw new NacosException(result.code, result.content);
}
1.3 客户端通过BeatReactor.addBeatInfo定时上报心跳信息到nacos节点
public class BeatReactor {
/**
* 注册服务的时候启动心跳
* @param serviceName 服务名
* @param beatInfo 心跳信息
*/
public void addBeatInfo(String serviceName, BeatInfo 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);
}
/**
* BeatReactor内部类:心跳上报
*/
class BeatTask implements Runnable {
@Override
public void run() {
long nextTime = beatInfo.getPeriod();
try {
JSONObject result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);
long interval = result.getIntValue("clientBeatInterval");
if (interval > 0) {
nextTime = interval;
}
...........................
} catch (NacosException ne) {
NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}", JSON.toJSONString(beatInfo), ne.getErrCode(), ne.getErrMsg());
}
//本次心跳发送完成启动下次心跳任务
executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
}
}
}
public class NamingProxy {
/**
* 发送心跳到nacos节点"/nacos/v1/ns/instance/beat"
* @param beatInfo 心跳信息
* @param lightBeatEnabled=false: 用body传数据
*/
public JSONObject sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) throws NacosException {
Map<String, String> params = new HashMap<String, String>(8);
String body = StringUtils.EMPTY;
if (!lightBeatEnabled) {
try {
body = "beat=" + URLEncoder.encode(JSON.toJSONString(beatInfo), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new NacosException(NacosException.SERVER_ERROR, "encode beatInfo error", e);
}
}
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.NACOS_URL_BASE + "/instance/beat", params, body, HttpMethod.PUT);
return JSON.parseObject(result);
}
}
- 服务发现
2.1 NamingService.subscribe()的方法为客户端提供的服务发现接口
2.2 客户端通过调用NamingService.subscribe()注册服务变更监听器
2.3 客户端通启动线程通过UDP协议接收服务的变更通知
2.4 客户端通启动线程通过HTTP协议拉取服务信息
2.5 解析报文判断服务是否变更,有EventDispatcher分发变更通知
public class NacosNamingService implements NamingService {
/**
* 客户端服务发现API
* @param serviceName name of service
* @param groupName group of service
* @param clusters list of cluster
* @param listener event listener
* @throws NacosException
*/
@Override
public void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener) throws NacosException {
eventDispatcher.addListener(hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName),
StringUtils.join(clusters, ",")), StringUtils.join(clusters, ","), listener);
}
}
/**
* addListener注册监听器
* serviceChanged方法添加事件到队列
* Notifier拉取事件队列分发到各个监听器
*/
public class EventDispatcher {
private ExecutorService executor = null;
//服务变更队列
private BlockingQueue<ServiceInfo> changedServices = new LinkedBlockingQueue<ServiceInfo>();
//单个服务监听列表
private ConcurrentMap<String, List<EventListener>> observerMap = new ConcurrentHashMap<String, List<EventListener>>();
public EventDispatcher() {
//初始化线程池
...............
//启动通知线程
executor.execute(new Notifier());
}
/**
* 客户端注册监听事件
* @param serviceInfo 要监听的服务信息
* @param clusters
* @param listener 事件回调对象
*/
public void addListener(ServiceInfo serviceInfo, String clusters, EventListener listener) {
List<EventListener> observers = Collections.synchronizedList(new ArrayList<EventListener>());
observers.add(listener);
observers = observerMap.putIfAbsent(ServiceInfo.getKey(serviceInfo.getName(), clusters), observers);
if (observers != null) {
observers.add(listener);
}
serviceChanged(serviceInfo);
}
/**
* 变更的服务对象添加到队列
* @param serviceInfo
*/
public void serviceChanged(ServiceInfo serviceInfo) {
if (serviceInfo == null) {
return;
}
changedServices.add(serviceInfo);
}
private class Notifier implements Runnable {
@Override
public void run() {
while (true) {
ServiceInfo serviceInfo = null;
try {
//从队列拉取变更的服务对象
serviceInfo = changedServices.poll(5, TimeUnit.MINUTES);
} catch (Exception ignore) {
}
try {
//通知每个监听对象
List<EventListener> listeners = observerMap.get(serviceInfo.getKey());
if (!CollectionUtils.isEmpty(listeners)) {
for (EventListener listener : listeners) {
List<Instance> 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);
}
}
}
}
}
2.3 客户端通启动线程通过UDP协议接收服务的变更通知
public class PushReceiver implements Runnable {
@Override
public void run() {
while (true) {
try {
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();
PushPacket pushPacket = JSON.parseObject(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\":" + "\"\"}";
}
....................
udpSocket.send(new DatagramPacket(ack.getBytes(Charset.forName("UTF-8")), ack.getBytes(Charset.forName("UTF-8")).length, packet.getSocketAddress()));
} catch (Exception e) {
NAMING_LOGGER.error("[NA] error while receiving push data", e);
}
}
}
}
2.4 客户端启动线程通过HTTP协议拉取服务信息
public class HostReactor {
/**
* 定时器根据条件从服务器拉取服务信息
*/
public class UpdateTask implements Runnable {
@Override
public void run() {
long delayTime = -1;
try {
ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
if (serviceObj == null) {
updateServiceNow(serviceName, clusters);
delayTime = DEFAULT_DELAY;
return;
}
if (serviceObj.getLastRefTime() <= lastRefTime) {
updateServiceNow(serviceName, clusters);
serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
} else {
// if serviceName already updated by push, we should not override it
// since the push data may be different from pull through force push
refreshOnly(serviceName, clusters);
}
} catch (Throwable e) {
NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
} finally {
if (delayTime > 0) {
executor.schedule(this, delayTime, TimeUnit.MILLISECONDS);
}
}
}
}
/**
* 从服务器拉取服务信息: 地址:"/nacos/v1/ns/instance/list"
* @param serviceName
* @param clusters
*/
public void updateServiceNow(String serviceName, String clusters) {
ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
try {
String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUDPPort(), false);
if (StringUtils.isNotEmpty(result)) {
processServiceJSON(result);
}
} catch (Exception e) {
NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
} finally {
if (oldService != null) {
synchronized (oldService) {
oldService.notifyAll();
}
}
}
}
}
2.5 解析报文判断服务是否变更,有EventDispatcher分发变更通知
/**
* 解析报文如果本地服务集合没有此服务或服务已变更测调用eventDispatcher.serviceChanged()通知所有监听器
* @param json
* @return
*/
public ServiceInfo processServiceJSON(String json) {
ServiceInfo serviceInfo = JSON.parseObject(json, ServiceInfo.class);
ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
boolean changed = false;
//如果已存在服务则判断是否变更
if (oldService != null) {
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
Map<String, Instance> oldHostMap = new HashMap<String, Instance>(oldService.getHosts().size());
for (Instance host : oldService.getHosts()) {
oldHostMap.put(host.toInetAddr(), host);
}
Map<String, Instance> newHostMap = new HashMap<String, Instance>(serviceInfo.getHosts().size());
for (Instance host : serviceInfo.getHosts()) {
newHostMap.put(host.toInetAddr(), host);
}
Set<Instance> modHosts = new HashSet<Instance>();
Set<Instance> newHosts = new HashSet<Instance>();
Set<Instance> remvHosts = new HashSet<Instance>();
List<Map.Entry<String, Instance>> newServiceHosts = new ArrayList<Map.Entry<String, Instance>>(newHostMap.entrySet());
for (Map.Entry<String, Instance> entry : newServiceHosts) {
Instance host = entry.getValue();
String key = entry.getKey();
if (oldHostMap.containsKey(key) && !StringUtils.equals(host.toString(),
oldHostMap.get(key).toString())) {
modHosts.add(host);
continue;
}
if (!oldHostMap.containsKey(key)) {
newHosts.add(host);
}
}
for (Map.Entry<String, Instance> entry : oldHostMap.entrySet()) {
Instance host = entry.getValue();
String key = entry.getKey();
if (newHostMap.containsKey(key)) {
continue;
}
if (!newHostMap.containsKey(key)) {
remvHosts.add(host);
}
}
if (newHosts.size() > 0) {
changed = true;
}
if (remvHosts.size() > 0) {
changed = true;
}
if (modHosts.size() > 0) {
changed = true;
}
serviceInfo.setJsonFromServer(json);
//
if (newHosts.size() > 0 || remvHosts.size() > 0 || modHosts.size() > 0) {
eventDispatcher.serviceChanged(serviceInfo);
DiskCache.write(serviceInfo, cacheDir);
}
} else {
//服务不存在则加入到服务集合
changed = true;
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
eventDispatcher.serviceChanged(serviceInfo);
serviceInfo.setJsonFromServer(json);
DiskCache.write(serviceInfo, cacheDir);
}
MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());
if (changed) {
NAMING_LOGGER.info("current ips:(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() +
" -> " + JSON.toJSONString(serviceInfo.getHosts()));
}
return serviceInfo;
}

浙公网安备 33010602011771号