转载

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

问题描述

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

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

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

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

潘老师指出是指针有问题,主机的指针是我们构造出来的,虽然该指针指向对象的 namecontext 与动态链接库服务那边的都一样,但是他那里可能是按地址处理的,不是同一个地址,就报主机找不到了。

经过测试,确实是这样。

不能直接构造指针,需要使用同步计算机数据时返回的指针进行操作。

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

所以,获取计算机状态,操作计算机都需要重新同步一下计算机,将指针同步过来。

假设用户操作很频繁的话,对动态链接库的压力就很大。指不定啥时候就炸了。

虽然我想到了这点,但也没想到好的解决方案。

潘老师最终的设计方案非常好,采用定时任务,当有计算机的操作时,不实时进行操作,而是先存起来,每隔一段时间统一进行操作,从而减轻服务器压力。

实现

设计

对主机进行关机或重启时,不立即执行,而是将其添加到一个要执行的任务列表中,当定时任务触发后,再统一执行。

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

定时任务

定时任务很简单,之前在计量中写过一个每天晚上定时统计数据的定时任务,这次写这个没什么难度,都不需要看文档了。

注释很详尽,相信聪明的你完全可以理解。

这里的存储计算机状态设置了一个主机名到计算机状态映射的 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 存储要操作的主机,但是经过查询, HashSetLinkedHashSetTreeSet 都是线程不安全的。

这里就怕执行定时任务的时候,突然来了一个关机的指令,两个线程同时访问 Set ,定时任务要克隆一个 Set 然后把这个清空,关机需要在 Set 中添加元素,两个线程如果交替执行,结果就很难说了。

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

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

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

这里不知道是不是我用的有问题,是并发场景下不推荐用 Set 吗?为什么 concurrent 包下没有相关的实现类?

总结

感叹一句: C++ 还是难啊!

C++ 老师对我的评价是字写得还不错,哈哈哈。佩服 C++ 工程师!

原文  https://segmentfault.com/a/1190000018440803
正文到此结束
Loading...