6.任务分配与执行总体设计实现

1.设计

执行任务找一个落地场景:连接设备采集参数。设备有不同的协议,如:modbus rtu、modbus tcp、opc ua、simens s7等。协议多种多样,需要的参数也不同,连接及任务执行参数存放在t_job表的link_spec中,任务的配置存储在job_spec中,存储格式都是json。

1.1 任务的流转

1.2 类设计

1.2.1 Job类设计

JobAllotStrategy是接口,目前只定义了allotJob(分配任务)业务方法,后面会根据情况增加,下面AbstractAllotStrategy是抽象类,实现了JobAllotStrategy接口,将底层的具体实现的公共方法放在这里。

AverageJobAllotStrategy:平均分配任务策略实现类

WeightJobAllotStrategy:权重分配任务策略实现类

... 后面可以有很多

1.2.2 辅助类设计

ExecuteDttaskJobContext:任务相关参数的封装

JobAllotManager:对不同任务分配策略的集中管理

1.2.3 协议相关类的设计

IProtocol:顶层接口,定义协议的类型、开始方法和处理配置方法

AbstractProtocol:抽象类,对下面实际协议的实现的方式进行再细化以及共用逻辑的统一

ModbusRTUProtocol:针对Modbus RTU协议的处理

ModbusTCPProtocol:针对Modbus TCP协议的处理

VirtualProtocol:针对测试环境,虚拟化的协议处理

.... 可以自定义扩充

1.2.4 协议相关辅助类

CollectDataService:采集数据的最上层服务类

ICollectDataWorker:采集数据的接口定义,此处提供接口,方便后续不同采集数据实现的扩充

CommonCollectDataWorker:一般采集数据接口实现,使用java原生的ScheduledExecutorService实现周期性采集任务

2. 实现

2.1 Job相关类实现

2.1.1 JobAllotStrategy

目前此类很简单,只有分配job的类型和分配job两个方法,后面再去实现rebalanceJob的功能

public interface JobAllotStrategy {
    
    JobAllotStrategyType getType();
    
    void allotJob();
    
}

2.1.2 AbstractJobAllotStrategy

JobAllotStrategy的抽象实现,后续所有Strategy的通用实现,以及一些复杂逻辑的组合可以放在这里

public abstract class AbstractJobAllotStrategy implements JobAllotStrategy {
    
    
    public List<Job> getAllJob() {
        return BeanUseHelper.entityHelpService().getAllJob();
    }

    public List<DttaskJob> getAllCrawlerJob() {
        return BeanUseHelper.entityHelpService().getAllDttaskJob();
    }
    
    public List<DttaskJob> getByCrawlerId(long dttaskId) {
        return BeanUseHelper.entityHelpService().queryDttaskJob(dttaskId);
    }
    
}

2.1.3 AverageJobAllotStrategy

平均分配job的策略实现; WeightJobAllotStrategy可以模仿此类实现,主要逻辑是从每个Dttask节点解析配置的不同weight指标,默认是1:1:1,

可以配置成2:1:1,这样如果有8个任务,那么1号节点分配4个,2号和3号节点各分配2个

@Component
@Slf4j
public class AverageJobAllotStrategy extends AbstractJobAllotStrategy {

    @Autowired
    private CollectDataService collectDataService;

    @Override
    public JobAllotStrategyType getType() {
        return JobAllotStrategyType.AVERAGE;
    }

    @Override
    public void allotJob() {
        EntityHelpService entityHelpService = BeanUseHelper.entityHelpService();
        Map<Long, List<Job>> allotJobMap = getAllotJobMap();
        log.info("allotJobMap={}", allotJobMap);

        entityHelpService.invalidAllDttaskJob();
        entityHelpService.saveAllDttaskJob(allotJobMap);
        Map<Long, List<DttaskJob>> dttaskJobMap = entityHelpService.queryDttaskJob();
        executeDttaskJob(new ExecuteDttaskJobContext(dttaskJobMap, true));
    }

    private void executeDttaskJob(ExecuteDttaskJobContext executeDttaskJobContext) {
        Map<Long, List<DttaskJob>> dttaskJobMap = executeDttaskJobContext.getDttaskJobMap();
        boolean startFlag = executeDttaskJobContext.getStartFlag();
        dttaskJobMap.forEach((dttaskId, dttaskJobList) -> {
            if (!Objects.equals(ServerInfo.getServerId(), dttaskId)) {
                // 向其它节点发送 任务控制 命令
                Set<Long> dttaskJobIdList = dttaskJobList.stream().map(DttaskJob::getId).collect(Collectors.toSet());
                DttaskMessage controlCollectMessage = DttaskMessage.buildControlCollectMessage(
                        dttaskJobIdList, startFlag, dttaskId);
                log.info("向nodeId={}发送采集控制指令={}", controlCollectMessage);
                ServerInfo.getChannelByServerId(dttaskId).writeAndFlush(controlCollectMessage);
            } else {
                log.info("{}分配给自己的采集任务={}", startFlag ? "执行" : "停止", dttaskJobList);
                Set<Long> dttaskJobIds = dttaskJobList.stream().map(DttaskJob::getId).collect(Collectors.toSet());
                if (startFlag) {
                    collectDataService.startCollectData(dttaskJobIds);
                } else {
                    collectDataService.stopCollectData(dttaskJobIds);
                }
            }
        });
    }

    private Map<Long, List<Job>> getAllotJobMap() {
        List<Job> allJob = getAllJob();
        return average(allJob);
    }

    private <T> Map<Long, List<T>> average(List<T> list) {
        List<NodeInfo> nodeInfoList = ServerInfo.getNodeInfoList();
        int nodeCount = nodeInfoList.size();
        Map<Long, List<T>> allotJobMap = new HashMap<>();
        int averageJobCount = list.size() / nodeCount;
        int remainingJobCount = list.size() % nodeCount;
        int currentIndex = 0;
        for (NodeInfo nodeInfo : nodeInfoList) {
            allotJobMap.put(nodeInfo.getServerId(), list.subList(currentIndex, currentIndex + averageJobCount));
            currentIndex += averageJobCount;
        }
        while (remainingJobCount != 0) {
            for (Map.Entry<Long, List<T>> entry : allotJobMap.entrySet()) {
                entry.getValue().addAll(list.subList(currentIndex, currentIndex + 1));
                currentIndex++;
                remainingJobCount--;
            }
        }
        return allotJobMap;
    }

}

2.1.4 ExecuteDttaskJobContext

执行任务传递的上下文信息,这里还比较简单,只是一个Map和执行的是启动还是停止,后续可以根据业务扩展,它也可以自带逻辑

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExecuteDttaskJobContext {

    private Map<Long, List<DttaskJob>> dttaskJobMap;
    private Boolean startFlag;
    
}

2.1.5 JobAllotStrategyType

分配job的策略枚举类

public enum JobAllotStrategyType {
    
    AVERAGE(0), WEIGHT(1), SPECIFIC(2);
    
    int code;

    JobAllotStrategyType(int code) {
        this.code = code;
    }
    
    public static JobAllotStrategyType from(int code) {
        for (JobAllotStrategyType value : values()) {
            if (value.code == code) {
                return value;
            }
        }
        throw new BusinessException(CharSequenceUtil.format("code={}不在JobAllotStrategyType中", code));
    }
}

2.1.6 JobAllotManager

和前面的netty任务处理策略一样,这里我也借助Spring容器管理所有JobAllotStrategy的策略,并根据配置,选择当前任务的策略

@Component
@Slf4j
public class JobAllotManager {
    
    @Autowired
    private List<JobAllotStrategy> jobAllotStrategies;
    
    private static final Map<JobAllotStrategyType, JobAllotStrategy> map = new EnumMap<>(JobAllotStrategyType.class);
    
    @PostConstruct
    public void init() {
        if (jobAllotStrategies != null) {
            for (JobAllotStrategy jobAllotStrategy : jobAllotStrategies) {
                map.put(jobAllotStrategy.getType(), jobAllotStrategy);
            }
        }
    }
    
    public static JobAllotStrategy getStrategy() {
        JobAllotStrategyType allotJobStrategyType = JobAllotStrategyType.from(
                BeanUseHelper.dttaskServerConfig().getAllotJobStrategyType());
        if (map.containsKey(allotJobStrategyType)) {
            return map.get(allotJobStrategyType);
        }
        throw new BusinessException(CharSequenceUtil.format("allotJobStrategyType={} 配置有误", allotJobStrategyType));
    }
    
}

2.2 Protocol相关类实现

Protocol后面的变化会很多,不同协议需要的参数,处理的逻辑都不相同,目前仅进行较简化的封装

2.2.1 IProtocol

协议接口,主要是启动协议处理,我暂时将协议关闭的逻辑放到了Job这一侧去完成,也可以在这里提供一个stop接口,自己协议完成

public interface IProtocol {
    
    int getType();

    
    void start(ProtocolContext protocolContext);
    void parseConfig(ProtocolContext protocolContext);
    
}

2.2.2 AbstractProtocol

@Slf4j
public abstract class AbstractProtocol implements IProtocol {

    protected Long dttaskId;
    protected Long dttaskJobId;
    protected Long deviceId;
    protected ProtocolContext protocolContext;
    
    public void parseConfig(ProtocolContext protocolContext) {
        this.dttaskId = protocolContext.getDttaskId();
        this.dttaskJobId = protocolContext.getDttaskJobId();
        this.deviceId = protocolContext.getDeviceId();
        this.protocolContext = protocolContext;
        doParseConfig(protocolContext);
    }
    
    protected abstract void doParseConfig(ProtocolContext protocolContext);
    
    
    public abstract void doStart();
    
    public void start(ProtocolContext protocolContext) {
        log.info("进入 AbstractProtocol.start, protocolContext={}", protocolContext);
        parseConfig(protocolContext);
        doStart();
    }
    
}

2.2.3 Modbus RTU相关类实现

2.2.3.1 ModbusRTUProtocol

@Data
@Slf4j
@Component
public class ModbusRTUProtocol extends AbstractProtocol {

    private ModbusRTUSpecModel modbusRTUSpecModel;
    @Autowired
    private ICollectDataWorker collectDataWorker;
    
    @Override
    public void doParseConfig(ProtocolContext protocolContext) {
        JSONObject jsonObject = protocolContext.getParam();
        this.modbusRTUSpecModel = ModbusRTUSpecModel.getFromParam(jsonObject);
    }

    @Override
    public int getType() {
        return 0;
    }

    @Override
    public void doStart() {
        try {
            SerialParameters serialParameters = new SerialParameters();
            serialParameters.setPortName(modbusRTUSpecModel.getPortName());
            serialParameters.setBaudRate(modbusRTUSpecModel.getBaudRate());
            serialParameters.setDatabits(modbusRTUSpecModel.getDatabits());
            serialParameters.setStopbits(modbusRTUSpecModel.getStopbits());
            serialParameters.setParity(modbusRTUSpecModel.getParity());
            serialParameters.setEncoding(modbusRTUSpecModel.getEncoding());
            serialParameters.setEcho(modbusRTUSpecModel.getEcho());
            serialParameters.setPortName(modbusRTUSpecModel.getPortName());

            ModbusSerialMaster modbusSerialMaster = new ModbusSerialMaster(serialParameters);
            modbusSerialMaster.connect();

            ModbusRTUCollectWorker modbusRTUCollectWorker = new ModbusRTUCollectWorker(
                    this.dttaskId, this.dttaskJobId, deviceId, modbusSerialMaster, modbusRTUSpecModel);
            collectDataWorker.addCollectTask(dttaskJobId, modbusRTUCollectWorker, 5, 1, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("DttaskId={}采集config={}出现异常", this.dttaskId, protocolContext, e);
        }
    }
}

2.2.3.2 ModbusRTUCollectWorker

@Slf4j
public class ModbusRTUCollectWorker implements Runnable {

    private Long dttaskId;
    private Long dttaskJobId;
    private Long deviceId;
    private ModbusSerialMaster master;
    private ModbusRTUSpecModel modbusRTUSpecModel;
    private Map<String, Long> lastReadMap = new HashMap<>();

    public ModbusRTUCollectWorker(Long dttaskId, Long dttaskJobId, Long deviceId, ModbusSerialMaster master, ModbusRTUSpecModel modbusRTUSpecModel) {
        this.dttaskId = dttaskId;
        this.dttaskJobId = dttaskJobId;
        this.deviceId = deviceId;
        this.master = master;
        this.modbusRTUSpecModel = modbusRTUSpecModel;
    }

    @Override
    public void run() {
        long current = new Date().getTime();
        for (ModbusRTUSpecModel.PointDetail pointDetail : modbusRTUSpecModel.getPointDetailList()) {
            String key = pointDetail.getKey();
            if (lastReadMap.containsKey(key) &&
                    (current - lastReadMap.get(key)) % pointDetail.getSamplingInterval() >= 1) {
                try {
                    Register[] registers = master.readMultipleRegisters(modbusRTUSpecModel.getUnitId(),
                            pointDetail.getOffset(),
                            pointDetail.getNumOfRegisters());
                    for (Register register : registers) {
                        log.info("Register value:{}", register.getValue());
                    }
                } catch (Exception e) {
                    log.error("DttaskId={}采集registerConfig={}出现异常", this.dttaskId, pointDetail, e);
                }
            }
        }
    }
}

2.2.3.3 ModbusRTUSpecModel

@Data
public class ModbusRTUSpecModel {
    private Integer mode;
    private Integer unitId;
    private String portName;
    private Integer baudRate;
    private Integer databits;
    private String parity;
    private Integer stopbits;
    private String encoding = "RTU";
    private Boolean echo = false;
    private List<PointDetail> pointDetailList;

    @Data
    public static class PointDetail {
        private String key;
        private Integer offset;
        private Integer numOfRegisters;
        private Integer samplingInterval;
    }

    public static ModbusRTUSpecModel getFromParam(JSONObject param) {
        JSONObject linkSpec = param.getJSONObject("linkSpec");
        ModbusRTUSpecModel modbusRTUSpecModel = new ModbusRTUSpecModel();
        modbusRTUSpecModel.setMode(linkSpec.getInteger("mode"));
        modbusRTUSpecModel.setUnitId(linkSpec.getInteger("unitId"));
        modbusRTUSpecModel.setPortName(linkSpec.getString("portName"));
        modbusRTUSpecModel.setBaudRate(linkSpec.getInteger("baudRate"));
        modbusRTUSpecModel.setDatabits(linkSpec.getInteger("databits"));
        modbusRTUSpecModel.setParity(linkSpec.getString("parity"));
        modbusRTUSpecModel.setStopbits(linkSpec.getInteger("stopbits"));
        JSONArray pointDetailJsonArray = linkSpec.getJSONArray("pointDetailList");

        List<PointDetail> rtuPointDetailList = new ArrayList<>();
        for (Object pointDetailObject : pointDetailJsonArray) {
            JSONObject pointDetail = (JSONObject)pointDetailObject;
            PointDetail rtuPointDetail = new PointDetail();
            rtuPointDetail.setKey(pointDetail.getString("key"));
            rtuPointDetail.setOffset(pointDetail.getInteger("offset"));
            rtuPointDetail.setNumOfRegisters(pointDetail.getInteger("numOfRegisters"));
            rtuPointDetail.setSamplingInterval(pointDetail.getInteger("samplingInterval"));
            rtuPointDetailList.add(rtuPointDetail);
        }
        modbusRTUSpecModel.setPointDetailList(rtuPointDetailList);
        return modbusRTUSpecModel;
    }
}

2.2.4 Modbus TCP相关类实现

2.2.4.1 ModbusTCPProtocol

@Component
@Slf4j
public class ModbusTCPProtocol extends AbstractProtocol {
    
    private ModbusTCPSpecModel modbusTCPSpecModel;
    @Autowired
    private ICollectDataWorker collectDataWorker;
    
    @Override
    public int getType() {
        return Constant.EntityConstants.LINK_TYPE_MODBUSTCP;
    }

    @Override
    protected void doParseConfig(ProtocolContext protocolContext) {
        JSONObject param = protocolContext.getParam();
        modbusTCPSpecModel = ModbusTCPSpecModel.getFromParam(param);
    }

    @Override
    public void doStart() {
        try {
            ModbusTCPMaster modbusTCPMaster = new ModbusTCPMaster(modbusTCPSpecModel.getIp(), modbusTCPSpecModel.getPort(), 5, true);
            modbusTCPMaster.connect();
            ModbusTCPCollectWorker worker = new ModbusTCPCollectWorker(
                    this.dttaskId, this.dttaskJobId, deviceId, modbusTCPSpecModel, modbusTCPMaster);
            collectDataWorker.addCollectTask(dttaskJobId, worker, 5, 1, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("DttaskId={}采集config={}出现异常", dttaskId, modbusTCPSpecModel, e);
        }
    }
}

2.2.4.2 ModbusTCPCollectWorker

@Slf4j
public class ModbusTCPCollectWorker implements Runnable {

    private Long dttaskId;
    private Long dttaskJobId;
    private Long deviceId;
    private ModbusTCPSpecModel modbusTCPSpecModel;
    private ModbusTCPMaster master;
    private Map<String, Long> lastReadMap = new HashMap<>();

    public ModbusTCPCollectWorker(Long dttaskId, Long dttaskJobId, Long deviceId, ModbusTCPSpecModel modbusTCPSpecModel, ModbusTCPMaster master) {
        this.dttaskId = dttaskId;
        this.dttaskJobId = dttaskJobId;
        this.deviceId = deviceId;
        this.modbusTCPSpecModel = modbusTCPSpecModel;
        this.master = master;
    }

    @Override
    public void run() {
        long current = new Date().getTime();
        for (ModbusTCPSpecModel.PointDetail pointDetail : modbusTCPSpecModel.getPointDetailList()) {
            String key = pointDetail.getKey();
            if (lastReadMap.containsKey(key) &&
                    (current - lastReadMap.get(key)) % pointDetail.getSamplingInterval() >= 1) {
                try {
                    Register[] registers = master.readMultipleRegisters(modbusTCPSpecModel.getSlaveId(),
                            pointDetail.getOffset(),
                            pointDetail.getNumOfRegisters());
                    for (Register register : registers) {
                        log.info("Register value:{}", register.getValue());
                    }
                } catch (Exception e) {
                    log.error("DttaskId={}采集pointDetail={}出现异常", dttaskId, pointDetail, e);
                }
            }
        }
    }
}

2.2.4.3 ModbusTCPSpecModel

@Data
public class ModbusTCPSpecModel {
    private Integer mode;
    private Long deviceId;
    private String ip;
    private Integer port;
    private Integer slaveId;
    private List<PointDetail> pointDetailList;

    @Data
    public static class PointDetail {
        private String key;
        private Integer offset;
        private Integer numOfRegisters;
        private Integer samplingInterval;
    }

    public static ModbusTCPSpecModel getFromParam(JSONObject param) {
        JSONObject linkSpec = param.getJSONObject("linkSpec");
        ModbusTCPSpecModel modbusTCPSpecModel = new ModbusTCPSpecModel();
        modbusTCPSpecModel.setMode(linkSpec.getInteger("mode"));
        modbusTCPSpecModel.setDeviceId(linkSpec.getLong("deviceId"));
        modbusTCPSpecModel.setIp(linkSpec.getString("ip"));
        modbusTCPSpecModel.setPort(linkSpec.getInteger("port"));
        modbusTCPSpecModel.setSlaveId(linkSpec.getInteger("slaveId"));

        JSONArray pointDetailJsonArray = linkSpec.getJSONArray("pointDetailList");
        
        List<PointDetail> tcpPointDetailList = new ArrayList<>();
        for (Object pointDetailObject  : pointDetailJsonArray) {
            JSONObject pointDetail = (JSONObject)pointDetailObject; 
            PointDetail tcpPointDetail = new PointDetail();
            tcpPointDetail.setKey(pointDetail.getString("key"));
            tcpPointDetail.setOffset(pointDetail.getInteger("offset"));
            tcpPointDetail.setNumOfRegisters(pointDetail.getInteger("numOfRegisters"));
            tcpPointDetail.setSamplingInterval(pointDetail.getInteger("samplingInterval"));
            tcpPointDetailList.add(tcpPointDetail);
        }
        modbusTCPSpecModel.setPointDetailList(tcpPointDetailList);
        return modbusTCPSpecModel;
    }
}

2.2.5 Virtual相关类实现

2.2.5.1 VirtualProtocol

@Data
@Slf4j
@Component
public class VirtualProtocol extends AbstractProtocol {

    private VirtualSpecModel virtualSpecModel;
    @Autowired
    private ICollectDataWorker collectDataWorker;

    @Override
    public int getType() {
        return -1;
    }
    
    @Override
    public void doParseConfig(ProtocolContext protocolContext) {
        JSONObject jsonObject = protocolContext.getParam();
        this.virtualSpecModel = VirtualSpecModel.getFromParam(jsonObject);
    }

    @Override
    public void doStart() {
        try {
            VirtualCollectWorker worker = new VirtualCollectWorker(
                    dttaskId, dttaskJobId, deviceId, virtualSpecModel);
            collectDataWorker.addCollectTask(dttaskJobId, worker, 5, 1, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("DttaskId={}采集config={}出现异常", dttaskId, protocolContext, e);
        }
    }
}

2.2.5.2 VirtualCollectWorker

@Slf4j
public class VirtualCollectWorker implements Runnable {

    private Long dttaskId;
    private Long dttaskJobId;
    private Long deviceId;
    private VirtualSpecModel virtualSpecModel;

    public VirtualCollectWorker(Long dttaskId, Long dttaskJobId, Long deviceId, VirtualSpecModel virtualSpecModel) {
        this.dttaskId = dttaskId;
        this.dttaskJobId = dttaskJobId;
        this.deviceId = deviceId;
        this.virtualSpecModel = virtualSpecModel;
    }

    @Override
    public void run() {
        log.info("deviceId={},dttaskId={},dttaskJobId={},virtualSpecModel={}",
                deviceId, dttaskId, dttaskJobId, virtualSpecModel);
    }
}

2.2.5.3 VirtualSpecModel

@Data
@Slf4j
public class VirtualSpecModel {
    
    private VirtualSpecModel() {}

    public static VirtualSpecModel getFromParam(JSONObject jsonObject) {
        log.debug("VirtualSpecModel.getFromParam param={}", jsonObject);
        return new VirtualSpecModel();
    }
    
}

2.3 将Job和Protocol串起来的类设计

2.3.1 ICollectDataWorker

public interface ICollectDataWorker {

    void addCollectTask(long dttaskJobId, Runnable runnable, int delay, int period, TimeUnit timeUnit);

    void removeCollectMonitor(long dttaskJobId);

    ScheduledFuture<Void> getCollectMonitorScheduledFuture(long dttaskJobId);
    
    void doCollect(Set<Long> dttaskJobId);

    void stopCollect(Set<Long> dttaskJobId);
}

2.3.2 CommonCollectDataWorker

@Component
@Slf4j
public class CommonCollectDataWorker implements ICollectDataWorker {

    private ScheduledExecutorService collectDataExecutor
            = new InfiniteScheduledThreadPoolExecutor(10, new CustomThreadFactory("CommonCollectDataWorker-THREAD-"));
    private Map<Long, ScheduledFuture<Void>> monitorDttaskJobStateMap = new ConcurrentHashMap<>();

    public void addCollectTask(long dttaskJobId, Runnable runnable, int delay, int period, TimeUnit timeUnit) {
        ScheduledFuture<Void> scheduledFuture = (ScheduledFuture<Void>) collectDataExecutor.scheduleAtFixedRate(runnable, delay, period, timeUnit);
        monitorDttaskJobStateMap.put(dttaskJobId, scheduledFuture);
    }

    public synchronized void removeCollectMonitor(long dttaskJobId) {
        monitorDttaskJobStateMap.remove(dttaskJobId);
    }

    public ScheduledFuture<Void> getCollectMonitorScheduledFuture(long dttaskJobId) {
        return monitorDttaskJobStateMap.get(dttaskJobId);
    }
    
    @Override
    public void doCollect(Set<Long> dttaskJobIds) {
        log.info("进入 CommonCollectDataWorker.doCollect, dttaskJobIds={}", dttaskJobIds);
        List<DttaskJob> dttaskJobs = BeanUseHelper.entityHelpService().queryDttaskJob(dttaskJobIds);
        for (DttaskJob dttaskJob : dttaskJobs) {
            ProtocolContext protocolContext = new ProtocolContext();
            protocolContext.setDttaskId(dttaskJob.getDttaskId());
            protocolContext.setDeviceId(dttaskJob.getDeviceId());
            protocolContext.setDttaskJobId(dttaskJob.getId());
            protocolContext.setLinkType(dttaskJob.getLinkType());
            JSONObject param = new JSONObject();
            param.put("linkSpec", JSON.parseObject(JSON.toJSONString(dttaskJob.getLinkSpec())));
            param.put("jobSpec", JSON.parseObject(JSON.toJSONString(dttaskJob.getJobSpec())));
            protocolContext.setParam(param);
            IProtocol protocol = ProtocolManager.getProtocol(protocolContext.getLinkType());
            protocol.start(protocolContext);
        }
    }

    @Override
    public void stopCollect(Set<Long> dttaskJobIds) {
        log.info("进入 CommonCollectDataWorker.stopCollect, dttaskJobIds={}", dttaskJobIds);
        for (Long dttaskJobId : dttaskJobIds) {
            ScheduledFuture<?> scheduledFuture = getCollectMonitorScheduledFuture(dttaskJobId);
            scheduledFuture.cancel(true);
            removeCollectMonitor(dttaskJobId);
        }
    }
}

2.3.2 CollectDataService

@Component
@Slf4j
public class CollectDataService {
    
    @Autowired
    private ICollectDataWorker collectDataWorker;
    
    private CollectDataService() {
    }

    /**
     * 开始采集数据
     */
    public void startCollectData(Set<Long> dttaskJobId) {
        BeanUseHelper.entityHelpService().runDttaskJob(dttaskJobId, ServerInfo.getServerId());
        collectDataWorker.doCollect(dttaskJobId);
    }
    
    public void stopCollectData(Set<Long> dttaskJobId) {
        collectDataWorker.stopCollect(dttaskJobId);
        BeanUseHelper.entityHelpService().stopDttaskJob(dttaskJobId, ServerInfo.getServerId());
    }
}

根据业务需要使用CollectDataService,它作为采集数据的业务管理类,承接上层业务目的,然后组织实现逻辑后交给ICollectDataWorker的实现去完成。

ICollectDataWorker的实现对接Protocol的实现,组合Protocol需要的参数,调用具体的Protocol完成特定协议采集数据的功能

3. 验证

代码地址在:GitHub - swsm/dttask: 分布式插件化任务执行框架 ,欢迎Star❤️

3.1 准备数据

数据库脚本 (这里job使用的protocol都是Virtual,方便大家测试)

INSERT INTO `t_job` (`id`, `device_id`, `device_link_id`, `link_type`, `link_spec`, `job_spec`, `remark`, `delete_flag`, `created_at`, `updated_at`) VALUES (1, 1, 1, -1, '{\"mode\": \"0\", \"parity\": \"None\", \"unitId\": 1, \"baudRate\": 9600, \"databits\": 8, \"deviceId\": 1, \"portName\": \"COM4\", \"stopbits\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', '{\"mode\": \"0\", \"parity\": \"None\", \"unitId\": 1, \"baudRate\": 9600, \"databits\": 8, \"deviceId\": 1, \"portName\": \"COM4\", \"stopbits\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', NULL, 0, '2023-12-12 16:35:37', '2023-12-12 16:35:37');
INSERT INTO `t_job` (`id`, `device_id`, `device_link_id`, `link_type`, `link_spec`, `job_spec`, `remark`, `delete_flag`, `created_at`, `updated_at`) VALUES (2, 2, 2, -1, '{\"ip\": \"127.0.0.1\", \"mode\": \"1\", \"port\": 1234, \"slaveId\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', '{\"ip\": \"127.0.0.1\", \"mode\": \"1\", \"port\": 1234, \"slaveId\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', NULL, 0, '2023-12-12 16:35:40', '2023-12-12 16:35:40');
INSERT INTO `t_job` (`id`, `device_id`, `device_link_id`, `link_type`, `link_spec`, `job_spec`, `remark`, `delete_flag`, `created_at`, `updated_at`) VALUES (3, 3, 3, -1, '{\"mode\": \"0\", \"parity\": \"None\", \"unitId\": 1, \"baudRate\": 9600, \"databits\": 8, \"deviceId\": 1, \"portName\": \"COM4\", \"stopbits\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', '{\"mode\": \"0\", \"parity\": \"None\", \"unitId\": 1, \"baudRate\": 9600, \"databits\": 8, \"deviceId\": 1, \"portName\": \"COM4\", \"stopbits\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', NULL, 0, '2023-12-12 16:35:44', '2023-12-12 16:35:44');
INSERT INTO `t_job` (`id`, `device_id`, `device_link_id`, `link_type`, `link_spec`, `job_spec`, `remark`, `delete_flag`, `created_at`, `updated_at`) VALUES (4, 4, 4, -1, '{\"ip\": \"127.0.0.1\", \"mode\": \"1\", \"port\": 1234, \"slaveId\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', '{\"ip\": \"127.0.0.1\", \"mode\": \"1\", \"port\": 1234, \"slaveId\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', NULL, 0, '2023-12-12 16:35:45', '2023-12-12 16:35:45');
INSERT INTO `t_job` (`id`, `device_id`, `device_link_id`, `link_type`, `link_spec`, `job_spec`, `remark`, `delete_flag`, `created_at`, `updated_at`) VALUES (5, 5, 5, -1, '{\"mode\": \"0\", \"parity\": \"None\", \"unitId\": 1, \"baudRate\": 9600, \"databits\": 8, \"deviceId\": 1, \"portName\": \"COM4\", \"stopbits\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', '{\"mode\": \"0\", \"parity\": \"None\", \"unitId\": 1, \"baudRate\": 9600, \"databits\": 8, \"deviceId\": 1, \"portName\": \"COM4\", \"stopbits\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', NULL, 0, '2023-12-12 16:35:45', '2023-12-12 16:35:45');
INSERT INTO `t_job` (`id`, `device_id`, `device_link_id`, `link_type`, `link_spec`, `job_spec`, `remark`, `delete_flag`, `created_at`, `updated_at`) VALUES (6, 6, 6, -1, '{\"ip\": \"127.0.0.1\", \"mode\": \"1\", \"port\": 1234, \"slaveId\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', '{\"ip\": \"127.0.0.1\", \"mode\": \"1\", \"port\": 1234, \"slaveId\": 1, \"pointDetail\": [{\"key\": \"1\", \"offset\": 1, \"numOfRegisters\": 2, \"samplingInterval\": 1000}, {\"key\": \"2\", \"offset\": 2, \"numOfRegisters\": 1, \"samplingInterval\": 2000}, {\"key\": \"3\", \"offset\": 3, \"numOfRegisters\": 3, \"samplingInterval\": 1000}, {\"key\": \"4\", \"offset\": 4, \"numOfRegisters\": 5, \"samplingInterval\": 3000}]}', NULL, 0, '2023-12-12 16:35:47', '2023-12-12 16:35:47');

3.2 启动三个节点

这里会看到3个节点启动完成,1号节点为Controller,2 3号节点为Follower,这是前面的逻辑,不清楚的可以看前面的文章。

然后Controller会将任务按照任务分配策略分配给所有节点(包括自己),然后每个节点执行对应的任务。

  • 数据库t_dttask_job表里有了对应每个节点的任务

  • 各节点每秒执行一次任务

因为CollectManager类已经封装了doCollect 和 stopCollect的方法,大家自行创建Controller,调用方法就可以实现对某个任务的停止和启动了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lswsmj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值