乐趣区

订阅 + 定时任务重构后台主机操作任务

问题描述
主机状态一直显示有问题,去向动态链接库请求数据时,除了第一台主机访问成功外,以后的每一台主机返回的结果都是 9(HOST_NOT_FOUND)。

研究了很久也没研究明白,最后求助潘老师。

潘老师指出是指针有问题,主机的指针是我们构造出来的,虽然该指针指向对象的 name 和 context 与动态链接库服务那边的都一样,但是他那里可能是按地址处理的,不是同一个地址,就报主机找不到了。
经过测试,确实是这样。
不能直接构造指针,需要使用同步计算机数据时返回的指针进行操作。

所以,获取计算机状态,操作计算机都需要重新同步一下计算机,将指针同步过来。
假设用户操作很频繁的话,对动态链接库的压力就很大。指不定啥时候就炸了。
虽然我想到了这点,但也没想到好的解决方案。
潘老师最终的设计方案非常好,采用定时任务,当有计算机的操作时,不实时进行操作,而是先存起来,每隔一段时间统一进行操作,从而减轻服务器压力。
实现
设计
对主机进行关机或重启时,不立即执行,而是将其添加到一个要执行的任务列表中,当定时任务触发后,再统一执行。

定时任务
定时任务很简单,之前在计量中写过一个每天晚上定时统计数据的定时任务,这次写这个没什么难度,都不需要看文档了。
注释很详尽,相信聪明的你完全可以理解。
这里的存储计算机状态设置了一个主机名到计算机状态映射的 HashMap,这里没有用 ConcurrentHashMap,因为只有定时任务一个线程进行 put,其他接口调用该 Map 只负责查询,不会出现冲突。
@Async
@Scheduled(fixedRate = 10000) // 每隔 10s 执行
public void hostHandle() {
logger.info(“— 开始执行定时任务 —“);

logger.debug(“ 获取关机重启的订阅者 ”);
Set<String> shutdownSubscribers = this.cloneStringSetAndClear(hostService.getShutdownSubscribers());
Set<String> rebootSubscribers = this.cloneStringSetAndClear(hostService.getRebootSubscribers());

logger.debug(“ 获取主机结构体指针 Map”);
List<HostStruct.ByReference> byReferenceList = baseService.getHostStructReferenceList();
Map<String, HostStruct.ByReference> byReferenceMap = baseService.getHostStructReferenceMap(byReferenceList);

if (!shutdownSubscribers.isEmpty()) {
logger.info(“ 存在关机订阅,执行关机操作 ”);

for (String name : shutdownSubscribers) {
logger.debug(“ 获取结构体指针并关机 ”);
HostStruct.ByReference byReference = byReferenceMap.get(name);
baseService.shutdown(byReference);
}
}

if (!rebootSubscribers.isEmpty()) {
logger.info(“ 存在重启订阅,执行重启操作 ”);

for (String name : rebootSubscribers) {
logger.debug(“ 获取结构体指针并重启 ”);
HostStruct.ByReference byReference = byReferenceMap.get(name);
baseService.reboot(byReference);
}
}

logger.debug(“ 查询主机列表 ”);
for (HostStruct.ByReference byReference : byReferenceList) {
logger.debug(“ 查询主机指针,同时获取主机信息 ”);
Integer status = baseService.getHostStatus(byReference);

logger.debug(“ 添加到 Map 中 ”);
hostService.getHostStatusMap().put(Native.toString(byReference.name), status);
}

logger.info(“— 定时任务执行完毕 —“);
}

/**
* 克隆字符串集合并清空原集合
* @param primarySet 原集合
* @return 克隆的字符串集合
*/
private Set<String> cloneStringSetAndClear(Set<String> primarySet) {
logger.debug(“ 新建集合 ”);
Set<String> set = new HashSet<>(primarySet);

logger.debug(“ 清空原集合 ”);
primarySet.clear();

logger.debug(“ 返回 ”);
return set;
}
todo
目前是用 Set 存储要操作的主机,但是经过查询,HashSet、LinkedHashSet、TreeSet 都是线程不安全的。
这里就怕执行定时任务的时候,突然来了一个关机的指令,两个线程同时访问 Set,定时任务要克隆一个 Set 然后把这个清空,关机需要在 Set 中添加元素,两个线程如果交替执行,结果就很难说了。

去 concurrent 包下找,也没找到合适的。看了看,有第三方库实现的线程安全的 Set,以后引入进来。

这里不知道是不是我用的有问题,是并发场景下不推荐用 Set 吗?为什么 concurrent 包下没有相关的实现类?
总结
感叹一句:C++ 还是难啊!
C++ 老师对我的评价是字写得还不错,哈哈哈。佩服 C ++ 工程师!

退出移动版