在之前分析Nacos寻址机制
时分享了文件寻址,其中实现了对本地文件的监听,当本地文件变更时做出对应的变化。在分布式潮流下,本地文件监听不常用,但当我们写像DevOps
中间件,本地文件监听功能会变得非常重要。
FileChangeEvent
: 文件变更事件,用于当监听到文件变化时传递给处理类的实体
/**
* 文件变更事件
* @author Tarzan写bug
* @since 2022/09/13
*/
public class FileChangeEvent {
private String path;
private Object context;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Object getContext() {
return context;
}
public void setContext(Object context) {
this.context = context;
}
public static FileChangeEventBuilder builder() {
return new FileChangeEventBuilder();
}
@Override
public String toString() {
return "FileChangeEvent{" +
"path='" + path + '\'' +
", context='" + context + '\'' +
'}';
}
public static class FileChangeEventBuilder {
private String path;
private Object context;
public FileChangeEventBuilder path(String path) {
this.path = path;
return this;
}
public FileChangeEventBuilder context(Object context) {
this.context = context;
return this;
}
public FileChangeEvent build() {
FileChangeEvent event = new FileChangeEvent();
event.setPath(path);
event.setContext(context);
return event;
}
}
}
FileWatcher
:文件监听器处理抽象类,监听到文件变化时执行对应的操作。可继承这个抽象类实现具体的逻辑
/**
* 文件监听处理抽象类
* @author Tarzan写bug
* @date 2022/09/13
*/
public abstract class FileWatcher {
/**
* 线程池,子类可重写该方法,自定义执行线程池
* @return
*/
public ExecutorService executor() {
return null;
}
/**
* 监听文件名
* @param context
* @return
*/
abstract boolean watchFile(String context);
/**
* 文件变更操作
* @param event
*/
abstract void onChange(FileChangeEvent event);
}
FileWatchManager
: 文件监听管理器,用于注册监听路径和初始化文件监听任务
/**
* 文件监听管理器
* @author Tarzan写bug
* @since 2022/09/13
*/
public class FileWatchManager {
private static final Logger LOGGER = LoggerFactory.getLogger(FileWatchManager.class);
/**
* 最大文件监听数量
*/
private static final int MAX_WATCH_FILE_COUNT = 16;
/**
* 文件监听任务集合
*/
private static Map<String, FileWatchJob> MANAGER = new HashMap<>(MAX_WATCH_FILE_COUNT);
/**
* 用于关闭线程池时的CAS操作
*/
private static final AtomicBoolean CLOSED = new AtomicBoolean(false);
/**
* 初始监听文件数量
*/
private static int WATCH_FILE_CNT = 0;
private static final FileSystem FILE_SYSTEM = FileSystems.getDefault();
static {
// 应用关闭回调
ThreadUtil.addShutdownHook(new Runnable() {
@Override
public void run() {
shutdown();
}
});
}
/**
* 注册文件监听处理器
* @param path
* @param watcher
*/
public static void registerWatcher(String path, FileWatcher watcher) {
checkState();
if (WATCH_FILE_CNT == MAX_WATCH_FILE_COUNT) {
return;
}
FileWatchJob fileWatchJob = MANAGER.get(path);
if (fileWatchJob == null) {
// 初始化文件监听任务
fileWatchJob = new FileWatchJob(path);
fileWatchJob.start();
MANAGER.put(path, fileWatchJob);
WATCH_FILE_CNT++;
}
// 监听任务加入文件监听处理器
fileWatchJob.addSubscribe(watcher);
}
/**
* 检查状态
*/
private static void checkState() {
if (CLOSED.get()) {
throw new IllegalStateException("FileWatchManager already shutdown");
}
}
/**
* 优雅关闭
*/
public static void shutdown() {
if (!CLOSED.compareAndSet(false, true)) {
return;
}
for (FileWatchJob value : MANAGER.values()) {
value.shutdown();
}
MANAGER.clear();
WATCH_FILE_CNT = 0;
}
/**
* 文件监听任务
*/
public static class FileWatchJob extends Thread {
/**
* 文件路径
*/
private String path;
/**
* 监听处理线程池
*/
private ExecutorService callBackExecutor;
/**
* 监听处理器集合
*/
private List<FileWatcher> fileWatchers;
private WatchService watchService;
/**
* 用于优雅关闭线程时不在循环监听标识,所以要用volatile修饰,及时获取最新值
*/
private volatile boolean watch = true;
public FileWatchJob(String path) {
setName(path);
this.path = path;
// 监听路径
Path watchPath = Paths.get(path);
// 初始化线程池
callBackExecutor = ExecutorFactory
.newSingleExecutorService(new NameThreadFactory("com.mountain.file.FileWatchManager-" + path));
try {
// 注册文件监听事件
watchService = FILE_SYSTEM.newWatchService();
watchPath.register(watchService, StandardWatchEventKinds.OVERFLOW, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
} catch (Exception e) {
throw new RuntimeException("监听文件异常:" + e.getMessage());
}
}
/**
* 添加文件监听处理器
* @param watcher
*/
public void addSubscribe(FileWatcher watcher) {
fileWatchers.add(watcher);
}
/**
* 关闭线程池
*/
public void shutdown() {
watch = false;
ThreadUtil.shutdownThreadPool(callBackExecutor);
}
@Override
public void run() {
while (watch) {
try {
// 获取文件变更事件
WatchKey watchKey = watchService.take();
List<WatchEvent<?>> watchEvents = watchKey.pollEvents();
watchKey.reset();
if (watchEvents == null || watchEvents.isEmpty()) {
continue;
}
callBackExecutor.execute(new Runnable() {
@Override
public void run() {
for (WatchEvent<?> watchEvent : watchEvents) {
WatchEvent.Kind<?> kind = watchEvent.kind();
if (StandardWatchEventKinds.OVERFLOW.equals(kind)) {
overflowEventProcess();
} else {
eventProcess(watchEvent.context());
}
}
}
});
} catch (InterruptedException e) {
Thread.interrupted();
} catch (Throwable ex) {
LOGGER.error("FileWatchJob run exception: {}", ex.getMessage());
}
}
}
private void eventProcess(Object context) {
FileChangeEvent event = FileChangeEvent.builder().path(path).context(context).build();
String name = String.valueOf(context);
// 循环处理器
for (FileWatcher watcher : fileWatchers) {
// 是否是监听的文件名
if (watcher.watchFile(name)) {
Runnable job = new Runnable() {
@Override
public void run() {
// 调用处理器onChange方法
watcher.onChange(event);
}
};
ExecutorService executor = watcher.executor();
if (executor == null) {
try {
job.run();
} catch (Exception e) {
LOGGER.error("FileWatcher process exception: {}", e.getMessage());
}
} else {
executor.execute(job);
}
}
}
}
private void overflowEventProcess() {
File dir = Paths.get(path).toFile();
for (File file : Objects.requireNonNull(dir.listFiles())) {
if (file.isDirectory()) {
continue;
}
eventProcess(file.getName());
}
}
}
}
使用
实现FileWatcher
private FileWatcher watcher = new FileWatcher() {
@Override
boolean watchFile(String context) {
return context.equals("gitops.conf");
}
@Override
void onChange(FileChangeEvent event) {
LOGGER.info("receive file change");
}
};
注册到文件监听管理器
FileWatchManager.registerWatcher("/home/", watcher);
上述实现基于INotify
机制实现,通过监听回调可以思考下Nacos
客户端对配置的监听是否类似实现呢?上述代码收录在https://gitee.com/ouwenrts/tuyere.git
谢谢阅读,就分享到这,未完待续…
欢迎同频共振的那一部分人
作者公众号:Tarzan写bug