在HostReactor类的getServiceInfo 方法的开头有一段代码 在当时代码讲解中有意飘过,大意是如果开启了failover 策略 就从failoverReactor获取服务信息,这篇单独解读一下FailoverReactor类
public class HostReactor {
...
public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {
NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch());
String key = ServiceInfo.getKey(serviceName, clusters);
if (failoverReactor.isFailoverSwitch()) {
return failoverReactor.getService(key);
}
...
}
FailoverReactor
属性说明
private String failoverDir;
private HostReactor hostReactor;
private Map<String, ServiceInfo> serviceMap = new ConcurrentHashMap<String,
ServiceInfo>();
private Map<String, String> switchParams = new ConcurrentHashMap<String, String>();
private static final long DAY_PERIOD_MINUTES = 24 * 60;
public FailoverReactor(HostReactor hostReactor, String cacheDir) {
this.hostReactor = hostReactor;
this.failoverDir = cacheDir + "/failover";
this.init();
}
private ScheduledExecutorService executorService =
Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.failover");
return thread;
}
});
failoverDir: 服务备份的文件目录,每个服务一个备份文件
serviceMap:存储服务的map ,是一个并发Map: ConcurrentHashMap
switchParams:存储 failover-mode 变量 值为false 或者 true 。代表是否启用failover
executorService:执行定时备份和定时读取服务信息任务的线程池
初始化
public void init() {
//5秒钟查询一次failover 开关
executorService.scheduleWithFixedDelay(new SwitchRefresher(), 0L, 5000L,
TimeUnit.MILLISECONDS);
//30分钟后执行备份一次 然后一天备份一次。
executorService.scheduleWithFixedDelay(new DiskFileWriter(), 30,
DAY_PERIOD_MINUTES, TimeUnit.MINUTES);
// backup file on startup if failover directory is empty.
executorService.schedule(new Runnable() {
@Override
public void run() {
try {
File cacheDir = new File(failoverDir);
// 如果目录不存在创建目录
if (!cacheDir.exists() && !cacheDir.mkdirs()) {
throw new IllegalStateException("failed to create cache dir: " +
failoverDir);
}
File[] files = cacheDir.listFiles();
if (files == null || files.length <= 0) {
new DiskFileWriter().run();
}
} catch (Throwable e) {
NAMING_LOGGER.error("[NA] failed to backup file on startup.", e);
}
}
}, 10000L, TimeUnit.MILLISECONDS);
}
定义了3个定时任务,
SwitchRefresher 每隔5秒钟读取一次failover 开关文件内容并根据内容读取failover 目录下的服务列表
DiskFileWriter:每隔24小时根据服务信息缓存到failover 目录下文件
服务启动10秒后备份服务列表到failover 目录下文件
SwitchRefresher(failover 开关读取任务)
class SwitchRefresher implements Runnable {
long lastModifiedMillis = 0L;
@Override
public void run() {
try {
File switchFile = new File(failoverDir + UtilAndComs.FAILOVER_SWITCH);
if (!switchFile.exists()) {
switchParams.put("failover-mode", "false");
NAMING_LOGGER.debug("failover switch is not found, " +
switchFile.getName());
return;
}
long modified = switchFile.lastModified();
if (lastModifiedMillis < modified) {
lastModifiedMillis = modified;
String failover = ConcurrentDiskUtil.getFileContent(failoverDir +
UtilAndComs.FAILOVER_SWITCH,
Charset.defaultCharset().toString());
if (!StringUtils.isEmpty(failover)) {
List<String> lines =
Arrays.asList(failover.split(DiskCache.getLineSeparator()));
for (String line : lines) {
String line1 = line.trim();
if ("1".equals(line1)) {
switchParams.put("failover-mode", "true");
NAMING_LOGGER.info("failover-mode is on");
new FailoverFileReader().run();
} else if ("0".equals(line1)) {
switchParams.put("failover-mode", "false");
NAMING_LOGGER.info("failover-mode is off");
}
}
} else {
switchParams.put("failover-mode", "false");
}
}
} catch (Throwable e) {
NAMING_LOGGER.error("[NA] failed to read failover switch.", e);
}
}
}
开关文件如果不存在说明failover-mode 是false;
否则根据 【lastModifiedMillis < modified】来判断文件是否有变更,如果有变更则读取文件内容
逐行内容判断如果读取到的值是1就设置failove-mode为true 并读取服务的failover file 到 serviceMap钟 ,如果读取到的值是0设置failove-mode为false.
提个小问题:为什么这里要多行循环去读取,不知直接读取最后一行或者第一行吗?
FailoverFileReader(failover 文件读取任务)
class FailoverFileReader implements Runnable {
@Override
public void run() {
Map<String, ServiceInfo> domMap = new HashMap<String, ServiceInfo>(16);
BufferedReader reader = null;
try {
File cacheDir = new File(failoverDir);
if (!cacheDir.exists() && !cacheDir.mkdirs()) {
throw new IllegalStateException("failed to create cache dir: " +
failoverDir);
}
File[] files = cacheDir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (!file.isFile()) {
continue;
}
//如果是failover 开关文件则不处理
if (file.getName().equals(UtilAndComs.FAILOVER_SWITCH)) {
continue;
}
// 文件名为服务名
ServiceInfo dom = new ServiceInfo(file.getName());
try {
String dataString = ConcurrentDiskUtil.getFileContent(file,
Charset.defaultCharset().toString());
reader = new BufferedReader(new StringReader(dataString));
String json;
if ((json = reader.readLine()) != null) {
try {
//反序列化 文件内容为 ServiceInfo
dom = JSON.parseObject(json, ServiceInfo.class);
} catch (Exception e) {
...
}
}
} catch (Exception e) {
...
} finally {
...
}
// 读取到了有效的内容就设置
if (!CollectionUtils.isEmpty(dom.getHosts())) {
domMap.put(dom.getKey(), dom);
}
}
} catch (Exception e) {
...
}
//只有新的读取列表不为空时 才赋值给serviceMap
if (domMap.size() > 0) {
serviceMap = domMap;
}
}
}
代码解释都写在注释上了。逻辑非常简单就是循环读取备份文件写入到serviceMap
DiskFileWriter (failover 文件写任务)
class DiskFileWriter extends TimerTask {
@Override
public void run() {
Map<String, ServiceInfo> map = hostReactor.getServiceInfoMap();
//遍历当前的服务列表 逐个写入到 failoverDir 目录
for (Map.Entry<String, ServiceInfo> entry : map.entrySet()) {
ServiceInfo serviceInfo = entry.getValue();
if (StringUtils.equals(serviceInfo.getKey(), UtilAndComs.ALL_IPS) ||
StringUtils.equals(
serviceInfo.getName(), UtilAndComs.ENV_LIST_KEY)
|| StringUtils.equals(serviceInfo.getName(), "00-00---000-ENV_CONFIGS-
000---00-00")
|| StringUtils.equals(serviceInfo.getName(), "vipclient.properties")
|| StringUtils.equals(serviceInfo.getName(), "00-00---000-ALL_HOSTS-
000---00-00")) {
continue;
}
DiskCache.write(serviceInfo, failoverDir);
}
}
}
上面的代码逐个把当前的服务信息写到failover 文件里。
其中 if 语句块里面的内容 目前我们只需要知道这是几种特殊类型的service,不需要备份到文件中,等讲到服务端的serviceInfo时会涉及到这块。
最后
记得上次HostReactor 类里面也有一个cacheDir 用来缓存当前的服务信息。
其实FailoverReactorl里面的cacheDir 就是在HostReactor的cacheDir目录后面加了一个/failover 目录,前者时在服务启动时加载到内存中,后者是在failover 开关设置为true时 服务的查询数据来自 failover 中的服务信息。所以使用的场景时不一样的。