nacos集群
本章分析一下nacos集群之间nacos服务器上线,下线原理
- 每5秒运行定时任务ServerListManager.ServerListUpdater获取新上线的节点或下线的节点
- 每2秒运行定时任务ServerListManager.ServerStatusReporter发送心跳数据到集群中的每个节点
- 每5秒运行定时任务ServerStatusManager.ServerStatusUpdater检测和控制本地服务器的工作状态
- 当前节点收到其他节点发送心跳数据后更新该节点最后心跳时间戳的Map对象
- 执行ServerStatusReporter时候运行checkDistroHeartbeat方法更新健康的服务器节点集合
Nacos集群代码主要相关类:
// Nacos集群的成员节点
com.alibaba.nacos.naming.cluster.servers.Server;
// 用于全局刷新和操作服务器列表的管理器。
com.alibaba.nacos.naming.cluster.ServerListManager;
// 检测和控制本地服务器的工作状态
com.alibaba.nacos.naming.cluster.ServerStatusManager
//向其他服务器报告本地服务器状态
com.alibaba.nacos.naming.misc.ServerStatusSynchronizer
- 启动定时任务
/**
* The manager to globally refresh and operate server list.
*/
@Component("serverListManager")
public class ServerListManager {
//如果服务器节点有变化需要通知的对象
private List<ServerChangeListener> listeners = new ArrayList<>();
//存储集群中所有服务器节点
private List<Server> servers = new ArrayList<>();
//存储集群中健康的服务器节点
private List<Server> healthyServers = new ArrayList<>();
//根据site=key集群节点集合
private Map<String, List<Server>> distroConfig = new ConcurrentHashMap<>();
//存储各个节点最后一次心跳时间戳
private Map<String, Long> distroBeats = new ConcurrentHashMap<>(16);
//服务启动时候启动两个定时任务
@PostConstruct
public void init() {
//下面代码相当于:executorService.scheduleAtFixedRate(new ServerListUpdater(), 0, NACOS_SERVER_LIST_REFRESH_INTERVAL=5000, TimeUnit.MILLISECONDS);
GlobalExecutor.registerServerListUpdater(new ServerListUpdater());
//下面代码相当于:SERVER_STATUS_EXECUTOR.schedule(new ServerStatusReporter(), 2000, TimeUnit.MILLISECONDS);
GlobalExecutor.registerServerStatusReporter(new ServerStatusReporter(), 2000);
}
}
@Service
public class ServerStatusManager {
private ServerStatus serverStatus = ServerStatus.STARTING;
@PostConstruct
public void init() {
//executorService.scheduleAtFixedRate(runnable, 0, SERVER_STATUS_UPDATE_PERIOD=5000, TimeUnit.MILLISECONDS);
GlobalExecutor.registerServerStatusUpdater(new ServerStatusUpdater());
}
}
- ServerListManager.ServerListUpdater定时刷新本机集群节点列表的变化
/**
* ServerListManager的内部类,定时刷新本机集群节点列表的变化
*/
public class ServerListUpdater implements Runnable {
@Override
public void run() {
try {
/**
* refreshServerList方法:
* 1. 如果是STANDALONE_MODE返回当前节点实例
* 2. 从cluster.conf配置文件中读取节点列表 (readClusterConf())返回
* 3. 如果为空则从System.getenv("naming_self_service_cluster_ips") 读取节点列表返回
*/
List<Server> refreshedServers = refreshServerList();
List<Server> oldServers = servers;
if (CollectionUtils.isEmpty(refreshedServers)) {
Loggers.RAFT.warn("refresh server list failed, ignore it.");
return;
}
boolean changed = false;
//新增加集群节点
List<Server> newServers = (List<Server>) CollectionUtils.subtract(refreshedServers, oldServers);
if (CollectionUtils.isNotEmpty(newServers)) {
servers.addAll(newServers);
changed = true;
}
//移除的集群节点
List<Server> deadServers = (List<Server>) CollectionUtils.subtract(oldServers, refreshedServers);
if (CollectionUtils.isNotEmpty(deadServers)) {
servers.removeAll(deadServers);
changed = true;
}
//如果服务器节点有变化,通知其他类
if (changed) {
notifyListeners();
}
} catch (Exception e) {
Loggers.RAFT.info("error while updating server list.", e);
}
}
- ServerListManager.ServerStatusReporter定时发送心跳到集群中的其他节点
报文格式:SITE#IP:port#currentTimeMillis#weight\r\n
/**
* ServerListManager的内部类,定时发送心跳到集群中的其他节点
*/
private class ServerStatusReporter implements Runnable {
@Override
public void run() {
try {
//
checkDistroHeartbeat();
int weight = Runtime.getRuntime().availableProcessors() / 2;
if (weight <= 0) weight = 1;
long curTime = System.currentTimeMillis();
String status = LOCALHOST_SITE + "#" + NetUtils.localServer() + "#" + curTime + "#" + weight + "\r\n";
//send status to itself
onReceiveServerStatus(status);
//集群所有节点(除了本机)发送心跳
List<Server> allServers = getServers();
for (com.alibaba.nacos.naming.cluster.servers.Server server : allServers) {
if (server.getKey().equals(NetUtils.localServer())) {
continue;
}
Message msg = new Message();
msg.setData(status);
synchronizer.send(server.getKey(), msg);
}
} catch (Exception e) {
Loggers.SRV_LOG.error("[SERVER-STATUS] Exception while sending server status", e);
} finally {
GlobalExecutor.registerServerStatusReporter(this, switchDomain.getServerStatusSynchronizationPeriodMillis());
}
}
}
}
/**
* Report local server status to other server
* @author nacos
*/
public class ServerStatusSynchronizer implements Synchronizer {
@Override
public void send(final String serverIP, Message msg) {
final Map<String, String> params = new HashMap<String, String>(2);
params.put("serverStatus", msg.getData());
//上报地址
String url = "http://serverIP:8848 + "/nacos/v1/ns/operator/server/status";
HttpClient.asyncHttpGet(url, null, params, new AsyncCompletionHandler() {
@Override
public Integer onCompleted(Response response) throws Exception {
if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
return 1;
}
return 0;
}
});
}
}
- 本地节点接收其他节点发送心跳后处理,主要代码
@RequestMapping("/server/status")
public String serverStatus(@RequestParam String serverStatus) {
serverListManager.onReceiveServerStatus(serverStatus);
return "ok";
}
public synchronized void onReceiveServerStatus(String config) {
List<Server> tmpServerList = new ArrayList<>();
//site:ip:lastReportTime:weight
String[] params = config.split("#");
Server server = new Server();
server.setSite(params[0]);
server.setIp(params[1].split(UtilsAndCommons.IP_PORT_SPLITER)[0]);
server.setServePort(Integer.parseInt(params[1].split(UtilsAndCommons.IP_PORT_SPLITER)[1]));
server.setLastRefTime(Long.parseLong(params[2]));
//如果服务器的两个报告间隔大于distroServerExpiredMillis,则认为服务器已过期。
Long lastBeat = distroBeats.get(server.getKey());
long now = System.currentTimeMillis();
if (null != lastBeat) {
server.setAlive(now - lastBeat < switchDomain.getDistroServerExpiredMillis());
}
//更新当前节点最后一次心跳时间
distroBeats.put(server.getKey(), now);
Date date = new Date(Long.parseLong(params[2]));
server.setLastRefTimeStr(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
server.setWeight(params.length == 4 ? Integer.parseInt(params[3]) : 1);
//如果当前节点在列表中更新为现在数据
List<Server> list = distroConfig.get(server.getSite());
for (Server s : list) {
String serverId = s.getKey() + "_" + s.getSite();
String newServerId = server.getKey() + "_" + server.getSite();
if (serverId.equals(newServerId)) {
tmpServerList.add(server);
continue;
}
tmpServerList.add(s);
}
//如果没在列表中则添加到列表中
if (!tmpServerList.contains(server)) {
tmpServerList.add(server);
}
distroConfig.put(server.getSite(), tmpServerList);
}
- 检查心跳时间戳确定节点是否Alive
private void checkDistroHeartbeat() {
List<Server> newHealthyList = new ArrayList<>(servers.size());
long now = System.currentTimeMillis();
for (Server s: servers) {
Long lastBeat = distroBeats.get(s.getKey());
if (null == lastBeat) {
continue;
}
s.setAlive(now - lastBeat < switchDomain.getDistroServerExpiredMillis());
}
//local site servers
List<String> allLocalSiteSrvs = new ArrayList<>();
for (Server server : servers) {
server.setAdWeight(switchDomain.getAdWeight(server.getKey()) == null ? 0 : switchDomain.getAdWeight(server.getKey()));
for (int i = 0; i < server.getWeight() + server.getAdWeight(); i++) {
if (!allLocalSiteSrvs.contains(server.getKey())) {
allLocalSiteSrvs.add(server.getKey());
}
if (server.isAlive() && !newHealthyList.contains(server)) {
newHealthyList.add(server);
}
}
}
Collections.sort(newHealthyList);
if (!CollectionUtils.isEqualCollection(healthyServers, newHealthyList)) {
healthyServers = newHealthyList;
notifyListeners();
}
}
- ServerStatusManager.ServerStatusUpdater 检测和控制本地服务器的工作状态
public class ServerStatusUpdater implements Runnable {
@Override
public void run() {
refreshServerStatus();
}
}
private void refreshServerStatus() {
if (StringUtils.isNotBlank(switchDomain.getOverriddenServerStatus())) {
serverStatus = ServerStatus.valueOf(switchDomain.getOverriddenServerStatus());
return;
}
if (consistencyService.isAvailable()) {
serverStatus = ServerStatus.UP;
} else {
serverStatus = ServerStatus.DOWN;
}
}
nacos集群的更多相关文章
- Nacos集群环境的搭建与配置
Nacos集群环境的搭建与配置 集群搭建 一.环境: 服务器环境:CENTOS-7.4-64位 三台服务器IP:192.168.102.57:8848,192.168.102.59:8848,192. ...
- Spring Cloud Alibaba | Nacos集群部署
目录 Spring Cloud Alibaba | Nacos集群部署 1. Nacos支持三种部署模式 2. 集群模式下部署Nacos 2.1 架构图 2.2 下载源码或者安装包 2.3 配置集群配 ...
- Nacos(九):Nacos集群部署和遇到的问题
前言 前面的系列文章已经介绍了Nacos的如何接入SpringCloud,以及Nacos的基本使用方式 之前的文章中都是基于单机模式部署进行讲解的,本文对Nacos的集群部署方式进行说明 环境准备 J ...
- Nacos 集群部署
关于nacos 集群部署,网上的示例往往不全或不可用,而官方的教程太简单了.官方也提供了一个 docker + nacos 的伪集群的 部署示例.但毕竟是 伪, 不能实际生产使用. 全网就几乎就没有 ...
- nacos集群搭建
nacos介绍 Nacos 支持基于 DNS 和基于 RPC 的服务发现(可以作为springcloud的注册中心).动态配置服务(可以做配置中心).动态 DNS 服务. 1.从官网下载nacos压缩 ...
- java架构之路-(微服务专题)nacos集群精讲实战
上次回顾: 上次博客,我们主要说了微服务的发展历程和nacos集群单机的搭建,单机需要-m standalone启动,集群建议使用nginx做一下反向代理,自行保证mysql和ngxin的高可用. 本 ...
- CentOS 7 Nacos 集群搭建
环境 CentOS 7.4 MySQL 5.7 nacos-server-1.1.2 本次安装的软件全部在 /home/javateam 目录下. MySQL 安装 首先下载 rpm 安装包,地址:h ...
- windows下Nacos集群搭建与nginx集成
前言: nacos集群至少需要三个(一般为奇数个)nacos实 例,其前面顶nginx,外界入口从nginx入 一.windows下Nacos集群搭建 将Nacos的解压包复制分成3份,分别是: na ...
- nacos 集群搭建
nacos 集群搭建 1.单机部署 从nacos官网下载zip/tar包,https://github.com/alibaba/nacos/releases/tag/2.0.2 解压后即可启动 外置数 ...
随机推荐
- CPU中的程序是怎么运行起来的(预告篇)
总述 最近一位朋友问我,我开发的代码是怎么运行起来的,我就开始给他介绍代码的预编译.汇编.编译.链接然后到一般的文件属性,再到代码运行.但是大佬问了我一句,CPU到底是怎么执行到每一个逻辑的, ...
- Toxophily HDU - 2298 三分+二分
代码+解析: 1 //题意: 2 //有一个大炮在(0,0)位置,为你可不可以把炮弹射到(x,y)这个位置 3 //题目给你炮弹初始速度,让你求能不能找出来一个炮弹射出时角度满足题意 4 //题解: ...
- linux无需root挂载iso镜像文件
引言 起初,我在针对deepin制作一款appimage安装工具,想要其实现的功能就是自动获取图标,只需要输入软件名称和分类即可,当然以后也会寻找方案省去手动输入的麻烦. 后来我发现一个有趣的问题 o ...
- sql 手注 语法
mysql中的information_schema 结构用来存储数据库系统信息 information_schema 结构中这几个表存储的信息,在注射中可以用到的几个表. | SCHEMATA ―― ...
- zsh terminal set infinity scroll height
zsh terminal set infinity scroll height zsh Terminal 开启无限滚动 https://stackoverflow.com/questions/2761 ...
- npm publish bug & solution
npm publish bug & solution npm ERR! Unexpected token < in JSON at position 0 while parsing ne ...
- Promise thenable All In One
Promise thenable All In One Promise thenable 是指一个函数或一个对象的里面定义了一个 then 方法 Promises/A+ https://promise ...
- Virtual Reality In Action
Virtual Reality In Action VR WebXR immersive 沉浸式 https://github.com/immersive-web/webxr https://imme ...
- Web Components & HTML template & HTML slot
Web Components & HTML template & HTML slot https://github.com/xgqfrms/es-next/issues/2 live ...
- Nodejs file path to url path
import * as path from 'path'; import * as url from 'url'; const savePath = path.join('public', 'imag ...