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. Web APP和原生 APP的不同

    我们现在手机中的APP,大部分都是混合APP,也就是既用到了原生APP的基础,又用到了Web APP的基础,混合的比例从0%到100%之间不等.更好的了解APP的类型,有助于我们学则合适的测试策略.今 ...

  2. mitmproxy使用详解

    mitmproxy 相比Charles.fiddler的优点在于,它可以命令行方式或脚本的方式进行mock mitmproxy不仅可以像Charles那样抓包,还可以对请求数据进行二次开发,进入高度二 ...

  3. Codeforces Round #529 (Div. 3) E. Almost Regular Bracket Sequence (思维,模拟栈)

    题意:给你一串括号,每次仅可以修改一个位置,问有多少位置仅修改一次后所有括号合法. 题解:我们用栈来将这串括号进行匹配,每成功匹配一对就将它们消去,因为题目要求仅修改一处使得所有括号合法,所以栈中最后 ...

  4. Medium Free

    fetch(window.location.href,{credentials:"omit",redirect:"follow",mode:"no-c ...

  5. WSL安装

    默认的我们可以看到并没有安装任何发行版本: 访问:https://aka.ms/wslstore 安装后我们如何进入linux系统呢,我们使用windows terminal

  6. OpenStack Train版-3.安装glance镜像服务

    安装glance镜像服务 创建数据库并授权 mysql -u root create database glance; GRANT ALL PRIVILEGES ON glance.* TO 'gla ...

  7. Jenkins 安装与部署详细教程

    一.概述 Jenkins 的前身是 Hudson 是一个可扩展的持续集成引擎.Jenkins 是一款开源 CI&CD 软件,用于自动化各种任务,包括构建.测试和部署软件.Jenkins 支持各 ...

  8. SPOJ SUBST1 New Distinct Substrings(后缀数组 本质不同子串个数)题解

    题意: 问给定串有多少本质不同的子串? 思路: 子串必是某一后缀的前缀,假如是某一后缀\(sa[k]\),那么会有\(n - sa[k] + 1\)个前缀,但是其中有\(height[k]\)个和上一 ...

  9. linux下新建用户

    新建用户的两种方式: 一步步创建 useradd -m user1 #-m 是建立家目录 passwd user1 #设置密码 usermod -a -G root user1 #加入管理员 chsh ...

  10. flex 布局占位符

    flex 布局占位符 空 span bug .popover-custom-class .system-guide-container .buttons-box { display: flex; fl ...