1. 服务注册

    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);
}
}
  1. 服务发现

    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;
}

nacos服务注册与发现之客户端的更多相关文章

  1. 2021升级版微服务教程4—Nacos 服务注册和发现

    2021升级版SpringCloud教程从入门到实战精通「H版&alibaba&链路追踪&日志&事务&锁」 默认文件1610014380163 教程全目录「含视 ...

  2. Spring Cloud Alibaba | Nacos服务注册与发现

    目录 Spring Cloud Alibaba | Nacos服务注册与发现 1. 服务提供者 1.1 pom.xml项目依赖 1.2 配置文件application.yml 1.3 启动类Produ ...

  3. Spring Cloud Alibaba(一) 如何使用nacos服务注册和发现

    Nacos介绍 Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现.服务配置.服务元数据及流量管理. Nacos 帮助您更敏捷和容易地构 ...

  4. Alibaba Nacos 学习(三):Spring Cloud Nacos Discovery - FeignClient,Nacos 服务注册与发现

    Alibaba Nacos 学习(一):Nacos介绍与安装 Alibaba Nacos 学习(二):Spring Cloud Nacos Config Alibaba Nacos 学习(三):Spr ...

  5. Spring Cloud Alibaba Nacos 服务注册与发现功能实现!

    Nacos 是 Spring Cloud Alibaba 中一个重要的组成部分,它提供了两个重要的功能:服务注册与发现和统一的配置中心功能. 服务注册与发现功能解决了微服务集群中,调用者和服务提供者连 ...

  6. Spring Cloud Alibaba 实战 之 Nacos 服务注册和发现

    服务注册与发现,服务发现主要用于实现各个微服务实例的自动化注册与发现,是微服务治理的核心,学习 Spring Cloud Alibaba,首先要了解框架中的服务注册和发现组件——Nacos. 一.Sp ...

  7. 微服务架构 | *3.5 Nacos 服务注册与发现的源码分析

    目录 前言 1. 客户端注册进 Nacos 注册中心(客户端视角) 1.1 Spring Cloud 提供的规范标准 1.2 Nacos 的自动配置类 1.3 监听服务初始化事件 AbstractAu ...

  8. Nacos服务注册与发现的2种实现方法!

    Spring Cloud Alibaba 技术体系中的 Nacos,提供了两个重要的功能:注册中心(服务注册与发现)功能和配置中心功能. 其中注册中心解决了微服务调用中,服务提供者和服务调用者的解耦, ...

  9. nacos服务注册与发现原理解析

    前言:nacos 玩过微服务的想必不会陌生,它是阿里对于springcloud孵化出来的产品,用来完成服务之间的注册发现和配置中心,其核心作用我就不废话了 大致流程:每个服务都会有一个nacos cl ...

随机推荐

  1. 翻译:《实用的Python编程》01_01_Python

    目录 | 下一节 (1.2 第一个程序) 1.1 Python Python 是什么? Python 是一种解释型(译者注:区别于编译型)的高级语言, 通常被归类为 "脚本语言"  ...

  2. 2019 ICPC Asia Nanjing Regional

    2019 ICPC Asia Nanjing Regional A - Hard Problem 计蒜客 - 42395 若 n = 10,可以先取:6,7,8,9,10.然后随便从1,2,3,4,5 ...

  3. 2019HDU多校 Round4

    08 K-th Closest Distance 题意:询问区间l,r中与数p的距离为第k大的数 求这个距离 题解:很裸的主席树 二分答案 然后可以用主席数判断在这个区间内 一段值域内出现的数 之前没 ...

  4. Codeforces #Round 632 div2 A~C

                                       A. Little Artem   Young boy Artem tries to paint a picture, and h ...

  5. Strategic game POJ - 1463 树型dp

    //题意:就是你需要派最少的士兵来巡查每一条边.相当于求最少点覆盖,用最少的点将所有边都覆盖掉//题解://因为这是一棵树,所以对于每一条边的两个端点,肯定要至少有一个点需要放入士兵,那么对于x-&g ...

  6. Light Bulb ZOJ - 3203 三分

    三分: 和二分非常类似的一个算法,与二分不同的是 二分是单调的,而三分是一个先增后减或者先减后增 三分可以求出峰值. 注意三分一定是严格单调的,不能有相等的情况. 讲个例题: 题目 题意: 一个人发现 ...

  7. C# Dictionary(字典)源码解析&效率分析

    通过查阅网上相关资料和查看微软源码,我对Dictionary有了更深的理解. Dictionary,翻译为中文是字典,通过查看源码发现,它真的内部结构真的和平时用的字典思想一样. 我们平时用的字典主要 ...

  8. PHP的常用函数 持续更新

    PHP的常用函数 前言: 由于害怕遗忘,故在此记录下常用的php函数,以便复习 1 define函数 作用:定义常量 用法 <?php define('a',100); ?> 2 intv ...

  9. Zabbix 配置监控 & 触发器

    Zabbix 自定义监控 zabbix-agent 获取数据,然后定义,交给 zabbix-server 端 Zabbix 配置监控项 监控的内容 # 监控服务器登录用户的数量 [root@web01 ...

  10. Linux下/bin和/sbin的区别

    bin: bin为binary的简写主要放置一些系统的必备执行档例如:cat.cp.chmod df.dmesg.gzip.kill.ls.mkdir.more.mount.rm.su.tar等./u ...