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. MDK中用C++开发STM32

    ​作者:良知犹存 转载授权以及围观:欢迎添加微信:Allen-Iverson-me-LYN 前言     最近想开发一段单片机的代码,代码本身有很多的重复元素,这重复定义的一些结构体使用起来有些繁琐, ...

  2. 2288.【POJ Challenge】生日礼物 链表+堆+贪心

    BZOJ2288 [POJ Challenge]生日礼物 题意: 给一个长度为\(n\)的数组,最多可以选\(m\)个连续段,问选取的最大值是多少 题解: 先把连续的符号相同的值合并,头和尾的负数去掉 ...

  3. HDU6331 Problem M. Walking Plan【Floyd + 矩阵 + 分块】

    HDU6331 Problem M. Walking Plan 题意: 给出一张有\(N\)个点的有向图,有\(q\)次询问,每次询问从\(s\)到\(t\)且最少走\(k\)条边的最短路径是多少 \ ...

  4. hdu4126Genghis Khan the Conqueror (最小生成树+树形dp)

    Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 327680/327680 K (Java/Others) Total Submiss ...

  5. pbds初探

    今年暑假外校集训的时候一道题标算是最短路扩展,然而std用的是pbds,于是就产生了研究的兴趣.结果那个标程我现在死都找不到了233 定义: 在知乎上看到有oier去年向CCF发了邮件,得到的回复是p ...

  6. 【bzoj 3232】圈地游戏(算法效率--01分数规划+图论--最小割)

    题目:DZY家的后院有一块地,由N行M列的方格组成,格子内种的菜有一定的价值,并且每一条单位长度的格线有一定的费用.DZY喜欢在地里散步.他总是从任意一个格点出发,沿着格线行走直到回到出发点,且在行走 ...

  7. JavaScript函数console、this关键字

    console.dir()打印出来的东西不加任何操作 console.log()打印出来东西实现进行了操作,打印出来的东西可能不是原型 具体对象的类型可以在"_proto_"中看, ...

  8. 主动降噪,通话降噪及AI降噪之辨

    近日,三星发布的Buds Pro 耳机中,宣传有以下功能.其中涉及到噪声的,有主动降噪,通话降噪及智能降噪,很多人对他们的具体用途容易混淆,今天我们来辨析一下. 主动降噪和通话降噪完全不是一个概念,无 ...

  9. kubernetes进阶(三)服务发现-coredns

    服务发现,说白了就是服务(应用)之间相互定位的过程. 服务发现需要解决的问题: 1.服务动态性强--容器在k8s中ip变化或迁移 2.更新发布频繁--版本迭代快 3.支持自动伸缩--大促或流量高峰 我 ...

  10. poj-3046 Ant Counting【dp】【母函数】

    题目链接:戳这里 题意:有A只蚂蚁,来自T个家族,每个家族有ti只蚂蚁.任取n只蚂蚁(S <= n <= B),求能组成几种集合? 这道题可以用dp或母函数求. 多重集组合数也是由多重背包 ...