瀏覽代碼

add system health check

hante 1 周之前
父節點
當前提交
380518ca6b
共有 19 個文件被更改,包括 832 次插入123 次删除
  1. 5 0
      .idea/inspectionProfiles/Project_Default.xml
  2. 1 1
      ggits-comm-server/src/main/java/com/sig/ggits/comm/server/cluster/ClusterMasterService.java
  3. 1 1
      ggits-comm-server/src/main/java/com/sig/ggits/comm/server/dto/RegionCenter.java
  4. 1 1
      ggits-etlp-server/src/main/java/com/sig/ggits/etlp/server/cluster/ClusterMasterService.java
  5. 4 0
      readme2.txt
  6. 1 1
      sig-comm-server/src/main/java/com/sig/comm/server/cluster/ClusterMasterService.java
  7. 58 11
      sig-todp-server/src/main/java/com/sig/todp/server/cluster/ClusterMasterService.java
  8. 15 5
      sig-todp-server/src/main/java/com/sig/todp/server/cluster/ClusterSlaveService.java
  9. 3 0
      sig-todp-server/src/main/java/com/sig/todp/server/cluster/dto/ClusterTodpDto.java
  10. 28 0
      sig-todp-server/src/main/java/com/sig/todp/server/cluster/dto/RegionCenterInfo.java
  11. 20 0
      sig-todp-server/src/main/java/com/sig/todp/server/dto/RegionCenter.java
  12. 5 4
      sig-todp-server/src/main/java/com/sig/todp/server/process/dbms/DbmsDataProcess.java
  13. 13 13
      sig-todp-server/src/main/java/com/sig/todp/server/scheduler/ApplicationScheduler.java
  14. 84 74
      sig-todp-server/src/main/java/com/sig/todp/server/service/SigTodpService.java
  15. 275 0
      sig-todp-server/src/main/java/com/sig/todp/server/service/SigTodpServiceOld.java
  16. 15 12
      sig-todp-server/src/main/java/com/sig/todp/server/service/SigTodpWorker.java
  17. 246 0
      sig-todp-server/src/main/java/com/sig/todp/server/service/SigTodpWorkerCallable.java
  18. 55 0
      sig-todp-server/src/main/java/com/sig/todp/server/service/SkippableScheduledTask.java
  19. 2 0
      sig-todp-server/src/main/resources/logback-spring.xml

+ 5 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -1,6 +1,11 @@
 <component name="InspectionProjectProfileManager">
   <profile version="1.0">
     <option name="myName" value="Project Default" />
+    <inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+      <Languages>
+        <language minSize="890" name="Java" />
+      </Languages>
+    </inspection_tool>
     <inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
   </profile>
 </component>

+ 1 - 1
ggits-comm-server/src/main/java/com/sig/ggits/comm/server/cluster/ClusterMasterService.java

@@ -85,7 +85,7 @@ public class ClusterMasterService extends AbstractClusterMasterService {
         log.info("ClusterNodeId: {}, ClusterMasterService.onClusterMessage: fromClusterNodeId: {}, {} Centers.",
                 this.clusterConfig.getId(), message.getClusterId(), infos.get(0).getCenters().size());
 
-        for (ClusterGgitsDto clusterTodpDto : infos){
+        for (ClusterGgitsDto clusterTodpDto : infos) {
             if (this.clusterConfig.getId() == clusterTodpDto.getClusterId()) {
                 log.warn("ClusterNodeId: {}, ClusterMasterService.onClusterMessage: clusterId: {}, master: {}, time: {}: clusterId error, this: {}, receive: {}",
                         this.clusterConfig.getId(), message.getClusterId(), message.isMaster(), ClusterUtils.timeToString(message.getCurrentTimeMillis()), this.clusterConfig.getId(), clusterTodpDto.getClusterId());

+ 1 - 1
ggits-comm-server/src/main/java/com/sig/ggits/comm/server/dto/RegionCenter.java

@@ -66,7 +66,7 @@ public class RegionCenter implements Serializable {
     }
 
     public void copyInfo(RegionCenterInfo info) {
-        this.clusterId = info.getClusterId();              // 서버 ID
+        //this.clusterId = info.getClusterId();              // 서버 ID
         this.realClusterId = info.getRealClusterId();          // 실제 운영중인 서버 ID
         this.isCommOnline = info.isCommOnline();
         this.lastCommMilliSeconds = info.getLastCommMilliSeconds();

+ 1 - 1
ggits-etlp-server/src/main/java/com/sig/ggits/etlp/server/cluster/ClusterMasterService.java

@@ -56,7 +56,7 @@ public class ClusterMasterService extends AbstractClusterMasterService {
         log.info("ClusterNodeId: {}, ClusterMasterService.onClusterMessage: fromClusterNodeId: {}, {} Centers.",
                 this.clusterConfig.getId(), message.getClusterId(), infos.get(0).getCenters().size());
 
-        for (ClusterEtlpDto clusterTodpDto : infos){
+        for (ClusterEtlpDto clusterTodpDto : infos) {
             if (this.clusterConfig.getId() == clusterTodpDto.getClusterId()) {
                 log.warn("ClusterNodeId: {}, ClusterMasterService.onClusterMessage: clusterId: {}, master: {}, time: {}: clusterId error, this: {}, receive: {}",
                         this.clusterConfig.getId(), message.getClusterId(), message.isMaster(), ClusterUtils.timeToString(message.getCurrentTimeMillis()), this.clusterConfig.getId(), clusterTodpDto.getClusterId());

+ 4 - 0
readme2.txt

@@ -9,4 +9,8 @@ tns
 SIGDB
 
 
+EXEC DBMS_STATS.GATHER_TABLE_STATS('SIGUSER', 'TB_INT_SIGNALMAP');
+EXEC DBMS_STATS.GATHER_TABLE_STATS('SIGUSER', 'TB_INT_PHASE');
+EXEC DBMS_STATS.GATHER_TABLE_STATS('SIGUSER', 'TB_INT_STATUS');
+
 

+ 1 - 1
sig-comm-server/src/main/java/com/sig/comm/server/cluster/ClusterMasterService.java

@@ -61,7 +61,7 @@ public class ClusterMasterService extends AbstractClusterMasterService {
         log.info("ClusterNodeId: {}, ClusterMasterService.onClusterMessage: fromClusterNodeId: {}, {} Centers.",
                 this.clusterConfig.getId(), message.getClusterId(), infos.get(0).getCenters().size());
 
-        for (ClusterCommDto clusterTodpDto : infos){
+        for (ClusterCommDto clusterTodpDto : infos) {
             if (this.clusterConfig.getId() == clusterTodpDto.getClusterId()) {
                 log.warn("ClusterNodeId: {}, ClusterMasterService.onClusterMessage: clusterId: {}, master: {}, time: {}: clusterId error, this: {}, receive: {}",
                         this.clusterConfig.getId(), message.getClusterId(), message.isMaster(), ClusterUtils.timeToString(message.getCurrentTimeMillis()), this.clusterConfig.getId(), clusterTodpDto.getClusterId());

+ 58 - 11
sig-todp-server/src/main/java/com/sig/todp/server/cluster/ClusterMasterService.java

@@ -4,8 +4,12 @@ import com.its.common.cluster.service.AbstractClusterMasterService;
 import com.its.common.cluster.utils.ClusterUtils;
 import com.its.common.cluster.vo.ClusterMessage;
 import com.its.common.cluster.vo.ClusterMessageData;
+import com.its.common.cluster.vo.ClusterNET;
 import com.its.common.cluster.vo.ClusterNode;
 import com.sig.todp.server.cluster.dto.ClusterTodpDto;
+import com.sig.todp.server.cluster.dto.RegionCenterInfo;
+import com.sig.todp.server.dto.RegionCenter;
+import com.sig.todp.server.repository.ApplicationRepository;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
@@ -16,11 +20,11 @@ import java.util.List;
 @Service
 public class ClusterMasterService extends AbstractClusterMasterService {
 
-    private final int clusterId;
+    private final ApplicationRepository appRepo;
 
-    public ClusterMasterService(ClusterConfig clusterConfig) {
+    public ClusterMasterService(ClusterConfig clusterConfig, ApplicationRepository appRepo) {
         super(clusterConfig);
-        this.clusterId = clusterConfig.getId();
+        this.appRepo = appRepo;
     }
 
     @Override
@@ -28,14 +32,42 @@ public class ClusterMasterService extends AbstractClusterMasterService {
         if (isMasterChanged) {
             log.info("ClusterMasterService:election: master state changed: clusterId: {}, master: {}", clusterId, isMaster);
         }
+        rebalanceRunClusterId();
+    }
+
+    /**
+     * 센터를 운영할 클러스터 정보를 재 분배한다.
+     */
+    private void rebalanceRunClusterId() {
+        this.appRepo.getCenterMap().forEach((regionId, center) -> {
+            if (this.clusterConfig.getId() == center.getClusterId()) {
+                center.setRealClusterId(this.clusterConfig.getId());
+                // 클러스터가 동일하면 추가
+                return;
+            }
+            if (!this.clusterConfig.isMaster()) {
+                // 마스터가 아니면 리턴
+                if (this.clusterConfig.getId() == center.getRealClusterId()) {
+                    // 원복(내가 마스터일때 처리하던것)
+                    center.setRealClusterId(center.getClusterId());
+                }
+                return;
+            }
+            int clusterId = center.getClusterId();
+            ClusterNode clusterNode = this.clusterConfig.getClusterMap().get(clusterId);
+            if (clusterNode != null && clusterNode.getElectionState().getState() == ClusterNET.CLOSED) {
+                // 통신이 이상이면 추가
+                center.setRealClusterId(this.clusterConfig.getId());
+            }
+        });
     }
 
     @Override
     public void onClusterMessage(ClusterMessage message) {
         // 슬래이브로 부터 수신되는 메시지 처리
         if (message.getInfos() == null) {
-            log.warn("onClusterMessage: clusterId: {}, master: {}, time: {}: message infos not found, receive clusterNodeId: {}",
-                    message.getClusterId(), message.isMaster(), ClusterUtils.timeToString(message.getCurrentTimeMillis()), message.getClusterId());
+            log.warn("ClusterNodeId: {}, ClusterMasterService.onClusterMessage: clusterId: {}, master: {}, time: {}: message infos not found, receive clusterNodeId: {}",
+                    this.clusterConfig.getId(), message.getClusterId(), message.isMaster(), ClusterUtils.timeToString(message.getCurrentTimeMillis()), message.getClusterId());
             return;
         }
         List<ClusterTodpDto> infos = new ArrayList<>();
@@ -44,18 +76,33 @@ public class ClusterMasterService extends AbstractClusterMasterService {
                 infos.add((ClusterTodpDto)info);
             }
         }
+        if (infos.isEmpty()) {
+            log.warn("ClusterNodeId: {}, ClusterMasterService.onClusterMessage: fromClusterNodeId: {}, Data Empty.",
+                    this.clusterConfig.getId(), message.getClusterId());
+            return;
+        }
 
-        for(ClusterTodpDto clusterTodpDto : infos){
-            if (this.clusterId == clusterTodpDto.getClusterId()) {
-                log.warn("onClusterMessage: clusterId: {}, master: {}, time: {}: clusterId error, this: {}, receive: {}",
-                        message.getClusterId(), message.isMaster(), ClusterUtils.timeToString(message.getCurrentTimeMillis()), this.clusterId, clusterTodpDto.getClusterId());
+        log.info("ClusterNodeId: {}, ClusterMasterService.onClusterMessage: fromClusterNodeId: {}, {} Centers.",
+                this.clusterConfig.getId(), message.getClusterId(), infos.get(0).getCenters().size());
+
+        for (ClusterTodpDto clusterTodpDto : infos) {
+            if (this.clusterConfig.getId() == clusterTodpDto.getClusterId()) {
+                log.warn("ClusterNodeId: {}, ClusterMasterService.onClusterMessage: clusterId: {}, master: {}, time: {}: clusterId error, this: {}, receive: {}",
+                        this.clusterConfig.getId(), message.getClusterId(), message.isMaster(), ClusterUtils.timeToString(message.getCurrentTimeMillis()), this.clusterConfig.getId(), clusterTodpDto.getClusterId());
+            }
+
+            for (RegionCenterInfo centerInfo: clusterTodpDto.getCenters()) {
+                RegionCenter center = this.appRepo.getCenterMap().get(centerInfo.getRegionCd());
+                if (center != null) {
+                    center.copyInfo(centerInfo);
+                }
             }
         }
     }
 
     @Override
     public void onClusterChannelActive(ClusterNode clusterNode) {
-
+        rebalanceRunClusterId();
     }
 
     /**
@@ -64,7 +111,7 @@ public class ClusterMasterService extends AbstractClusterMasterService {
      */
     @Override
     public void onClusterChannelInactive(ClusterNode clusterNode) {
-
+        rebalanceRunClusterId();
     }
 
 }

+ 15 - 5
sig-todp-server/src/main/java/com/sig/todp/server/cluster/ClusterSlaveService.java

@@ -3,6 +3,7 @@ package com.sig.todp.server.cluster;
 import com.its.common.cluster.service.AbstractClusterSlaveService;
 import com.its.common.cluster.vo.ClusterMessageData;
 import com.sig.todp.server.cluster.dto.ClusterTodpDto;
+import com.sig.todp.server.repository.ApplicationRepository;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
@@ -13,11 +14,11 @@ import java.util.List;
 @Service
 public class ClusterSlaveService extends AbstractClusterSlaveService {
 
-    private final int clusterId;
+    private final ApplicationRepository appRepo;
 
-    public ClusterSlaveService(ClusterConfig clusterConfig, ClusterMasterService masterService) {
+    public ClusterSlaveService(ClusterConfig clusterConfig, ClusterMasterService masterService, ApplicationRepository appRepo) {
         super(clusterConfig, masterService);
-        this.clusterId = clusterConfig.getId();
+        this.appRepo = appRepo;
     }
 
     @Override
@@ -28,12 +29,21 @@ public class ClusterSlaveService extends AbstractClusterSlaveService {
 
         // 일반 핑처럼 클러스터 정보 확인
         ClusterTodpDto clusterInfo = ClusterTodpDto.builder()
-                .clusterId(this.clusterId)
-                .realClusterId(this.clusterId)
+                .clusterId(this.clusterConfig.getId())
+                .realClusterId(this.clusterConfig.getId())
                 .currentTimeMillis(System.currentTimeMillis())
+                .centers(new ArrayList<>())
                 .build();
         result.add(clusterInfo);
 
+        this.appRepo.getCenterMap().forEach((key, center) -> {
+            if (this.clusterConfig.getId() == center.getRealClusterId()) {
+                clusterInfo.getCenters().add(center.cloneCopy());
+            }
+        });
+        log.info("ClusterNodeId: {}, ClusterSlaveService.getClusterMessageData: {} Centers.",
+                this.clusterConfig.getId(), clusterInfo.getCenters().size());
+
         return result;
     }
 

+ 3 - 0
sig-todp-server/src/main/java/com/sig/todp/server/cluster/dto/ClusterTodpDto.java

@@ -4,6 +4,8 @@ import com.its.common.cluster.vo.ClusterMessageData;
 import lombok.Builder;
 import lombok.Data;
 
+import java.util.List;
+
 @Data
 @Builder
 public class ClusterTodpDto implements ClusterMessageData {
@@ -18,4 +20,5 @@ public class ClusterTodpDto implements ClusterMessageData {
     private int realClusterId;          // 실제 운영중인 서버 ID
     private long currentTimeMillis;     // 현재시간(밀리세컨드)
 
+    List<RegionCenterInfo> centers;
 }

+ 28 - 0
sig-todp-server/src/main/java/com/sig/todp/server/cluster/dto/RegionCenterInfo.java

@@ -0,0 +1,28 @@
+package com.sig.todp.server.cluster.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@Builder
+@NoArgsConstructor//(access = AccessLevel.PROTECTED)
+@AllArgsConstructor
+public class RegionCenterInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /*
+    *  Region Center properties
+     */
+    private int clusterId;              // 서버 ID
+    private int realClusterId;          // 실제 운영중인 서버 ID
+
+    private String regionCd;        /* 지역 코드 */
+    private Integer regionId;       /* 지역 ID */
+
+    private long lastCommMilliSeconds;
+    private long lastTodRunTime;
+}

+ 20 - 0
sig-todp-server/src/main/java/com/sig/todp/server/dto/RegionCenter.java

@@ -1,5 +1,6 @@
 package com.sig.todp.server.dto;
 
+import com.sig.todp.server.cluster.dto.RegionCenterInfo;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -81,4 +82,23 @@ public class RegionCenter implements Serializable {
         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         return dateFormat.format(date);
     }
+
+    public void copyInfo(RegionCenterInfo info) {
+        //this.clusterId = info.getClusterId();              // 서버 ID
+        this.realClusterId = info.getRealClusterId();          // 실제 운영중인 서버 ID
+        this.lastTodRunTime = info.getLastTodRunTime();
+        this.lastCommMilliSeconds = info.getLastCommMilliSeconds();
+    }
+
+    public RegionCenterInfo cloneCopy() {
+        return RegionCenterInfo.builder()
+                .clusterId(this.clusterId)
+                .realClusterId(this.realClusterId)
+                .regionCd(this.regionCd)
+                .regionId(this.regionId)
+                .lastTodRunTime(this.lastTodRunTime)
+                .lastCommMilliSeconds(this.lastCommMilliSeconds)
+                .build();
+    }
+
 }

+ 5 - 4
sig-todp-server/src/main/java/com/sig/todp/server/process/dbms/DbmsDataProcess.java

@@ -62,7 +62,7 @@ public class DbmsDataProcess {
     }
 
     public void process(DbmsData data) {
-        int result1 = 0;
+        int relsult = 0;
         long elapsedTime1 = 0;
 //        int result2 = -1;
 //        long elapsedTime2 = 0;
@@ -81,7 +81,7 @@ public class DbmsDataProcess {
                     if (data.getCenter().isSimulateFlag()) {
                         // 상태정보 업데이트
                         try {
-                            result1 = this.sigTodpServerDao.updateIntSimulationSend(intTypeLists);
+                            relsult = this.sigTodpServerDao.updateIntSimulationSend(intTypeLists);
                         }
                         catch (Exception e) {
                             log.error("DbmsJobProcess.process: [{}]. {}, updateIntSimulationSend, Exception: {}", center.getLogKey(), type, e.getMessage());
@@ -105,10 +105,11 @@ public class DbmsDataProcess {
             }
 //            log.info("DbmsDataProcess.run: [{}]. Request: {}, updateIntSimulationSend({} EA. {} ms.), updateIntSimulationSendTrans({} EA. {} ms.)",
 //                    center.getLogKey(), count, result1, elapsedTime1, result2, elapsedTime2);
-            log.info("DbmsDataProcess.run: [{}]. Request: {}, updateIntSimulationSend({} EA. {} ms.),", center.getLogKey(), count, result1, elapsedTime1);
+            log.info("DbmsDataProcess.run: [{}]. updateIntSimulationSend({}/{} EA. {} ms.),",
+                    center.getLogKey(), count, relsult, elapsedTime1);
         }
         finally {
-            MDC.remove(center.getLogKey());
+            MDC.remove("id");
             MDC.clear();
         }
     }

+ 13 - 13
sig-todp-server/src/main/java/com/sig/todp/server/scheduler/ApplicationScheduler.java

@@ -62,19 +62,19 @@ public class ApplicationScheduler {
         }
     }
 
-    @Scheduled(cron = "30 * * * * *")
-    public void checkTodpWorkerThread() {
-        if (!this.config.isStartSchedule()) {
-            log.info("ApplicationScheduler.checkTodpWorkerThread: Scheduling is not started.");
-            return;
-        }
-        try {
-            this.sigTodpService.checkTodpWorkerThread();
-        }
-        catch(Exception e) {
-            log.error("ApplicationScheduler.checkTodpWorkerThread: Exception {}", e.getMessage());
-        }
-    }
+//    @Scheduled(cron = "30 * * * * *")
+//    public void checkTodpWorkerThread() {
+//        if (!this.config.isStartSchedule()) {
+//            log.info("ApplicationScheduler.checkTodpWorkerThread: Scheduling is not started.");
+//            return;
+//        }
+//        try {
+//            this.sigTodpService.checkTodpWorkerThread();
+//        }
+//        catch(Exception e) {
+//            log.error("ApplicationScheduler.checkTodpWorkerThread: Exception {}", e.getMessage());
+//        }
+//    }
 
     @Scheduled(cron = "${application.scheduler.tod-database:0 30 2 * * *}")
     public void loadTodDatabase() {

+ 84 - 74
sig-todp-server/src/main/java/com/sig/todp/server/service/SigTodpService.java

@@ -15,35 +15,28 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.slf4j.MDC;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
 
 @Slf4j
 @Getter
 @Service
 @RequiredArgsConstructor
-@Transactional(rollbackFor = {Exception.class})
 public class SigTodpService {
 
     private final ClusterConfig clusterConfig;
     private final ApplicationRepository repo;
     private final DbmsDataProcess dbmsDataProcess;
     private final IntMapper mapper;
-    private final ThreadGroup workerGroup = new ThreadGroup("SigTodp");
-    private final List<Thread> threadList = new ArrayList<>();
-    private final List<SigTodpWorker> workerList = new ArrayList<>();
-
-    @PostConstruct
-    private void init() {
-        log.info("SigTodpService.init: start.");
-        log.info("SigTodpService.init: ..end.");
-    }
+
+    private ScheduledExecutorService scheduler = null;
+    private final Map<String, ScheduledFuture<?>> futureMap = new ConcurrentHashMap<>();
+    private final Map<String, SigTodpWorkerCallable> workerMap = new ConcurrentHashMap<>();
 
     public void loadTodDatabase() {
         Elapsed elapsed = new Elapsed();
@@ -57,9 +50,9 @@ public class SigTodpService {
                 log.error("SigTodpService.loadTodDatabase: {}, NOT FOUND.", key);
                 continue;
             }
-            try {
-                MDC.put("id", center.getLogKey());
 
+            MDC.put("id", center.getLogKey());
+            try {
                 loadCurrentTrans(center);
                 loadCurrentOpTod(center);
             }
@@ -67,7 +60,7 @@ public class SigTodpService {
                 log.error("SigTodpService..loadTodDatabase: Exception, {}, {}", center.getRegionCd(), e.getMessage());
             }
             finally {
-                MDC.remove(center.getLogKey());
+                MDC.remove("id");
                 MDC.clear();
             }
         }
@@ -75,8 +68,10 @@ public class SigTodpService {
     }
 
     private void loadCurrentTrans(RegionCenter center) {
-        Elapsed elapsed = new Elapsed();
         log.info("SigTodpService.loadCurrentTrans: START. {}.", center.getRegionCd());
+
+        Elapsed elapsed = new Elapsed();
+
         center.getIntMap().forEach((key, dto) -> {
             dto.useTrans = false;
         });
@@ -102,18 +97,7 @@ public class SigTodpService {
             try {
                 if (entity.getIntNo() != oldIntNo) {
                     // 조회과가 순차적으로 들어있음. 교차로번호가 변경되었음.....
-                    if (todInt != null) {
-                        dual = 0;
-                        for (int phase = 0; phase < TTodUtils.MAX_PHASES; phase++) {
-                            if (todInt.trans.minG[0][phase] > 0 && todInt.trans.maxG[0][phase] > 0) {
-                                dual |= 0x01 << phase;
-                            }
-                        }
-                        todInt.trans.phaseCnt = Math.max(aRings, bRings);
-                        todInt.trans.dual = (byte) (dual & 0xFF);
-                        todInt.useTrans = true;
-                        //log.info("{}, {}", todInt.getIntNo(), todInt.trans);
-                    }
+                    updateTodTrans(aRings, bRings, todInt);
 
                     oldIntNo = entity.getIntNo();   // 새로운 교차로를 할당.
                     aRings = bRings = 0;
@@ -156,6 +140,12 @@ public class SigTodpService {
                 log.info("SigTodpService.loadCurrentTrans: Exception. {}. {}. {}", center.getRegionCd(), entity.getIntNo(), e.getMessage());
             }
         }
+        updateTodTrans(aRings, bRings, todInt);
+        log.info("SigTodpService.loadCurrentTrans: ..END. {}. {} EA. {}", center.getRegionCd(), list.size(), Elapsed.elapsedTimeStr(elapsed.nanoSeconds()));
+    }
+
+    private void updateTodTrans(int aRings, int bRings, TTodInt todInt) {
+        int dual;
         if (todInt != null) {
             // 마지막 교차로 처리
             dual = 0;
@@ -169,16 +159,18 @@ public class SigTodpService {
             todInt.useTrans = true;
 //            log.info("{}, {}", todInt.getIntNo(), todInt.trans);
         }
-        log.info("SigTodpService.loadCurrentTrans: ..END. {}. {} EA. {}", center.getRegionCd(), list.size(), Elapsed.elapsedTimeStr(elapsed.nanoSeconds()));
     }
+
     private void loadCurrentOpTod(RegionCenter center) {
-        Elapsed elapsed = new Elapsed();
         log.info("SigTodpService.loadCurrentOpTod: START. {}.", center.getRegionCd());
+
+        Elapsed elapsed = new Elapsed();
+
         center.getIntMap().forEach((key, dto) -> {
             dto.useFlag = false;
         });
 
-        int idx = 0;
+        int idx;
         List<TbCurrTod> list = this.mapper.selectCurrTodAll(center.getRegionCd());
         for (TbCurrTod entity : list) {
             TTodInt dto = center.getIntMap().get(entity.getIntNo());
@@ -221,58 +213,76 @@ public class SigTodpService {
         log.info("SigTodpService.loadCurrentOpTod: ..END. {}. {} EA. {}", center.getRegionCd(), list.size(), Elapsed.elapsedTimeStr(elapsed.nanoSeconds()));
     }
 
-    public boolean start() {
-        List<String> keySet = new ArrayList<>(this.repo.getCenterMap().keySet());
-        Collections.sort(keySet);
-        for (String key : keySet) {
-            RegionCenter center = this.repo.getCenterMap().get(key);
-            if (center == null) {
-                continue;
-            }
-            try {
-                SigTodpWorker todpWorker = new SigTodpWorker(center, this.dbmsDataProcess, this.clusterConfig);
-                this.workerList.add(todpWorker);
-                Thread worker = new Thread(this.workerGroup, todpWorker);
-                worker.setName(String.format("SigTodp-%s", center.getRegionCd()));
-                worker.setDaemon(true);
-                todpWorker.setThread(worker);
-                this.threadList.add(worker);
-            }
-            catch (IOException e) {
-                log.error("SigTodpService.start: Exception {}, {}", center.getRegionCd(), e.getMessage());
-            }
+    public void start() {
+        log.info("SigTodpService.start: START.");
+        List<RegionCenter> centers = new ArrayList<>(repo.getCenterMap().values());
+        int poolSize = centers.size() * 2;
+        this.scheduler = Executors.newScheduledThreadPool(poolSize);
+
+        for (RegionCenter center : centers) {
+            addRegionCenter(center);
         }
-        for (Thread worker : this.threadList) {
-            worker.start();
+
+        log.info("SigTodpService.start: started {} workers.", centers.size());
+    }
+
+    public void addRegionCenter(RegionCenter center) {
+        String regionCd = center.getRegionCd();
+        if (this.futureMap.containsKey(regionCd)) {
+            log.warn("SigTodpService.addRegionCenter: center [{}] already exists", regionCd);
+            return;
         }
-        log.info("SigTodpService.start: START.");
-        return true;
+
+        SigTodpWorkerCallable worker = new SigTodpWorkerCallable(center, this.dbmsDataProcess, this.clusterConfig);
+        SkippableScheduledTask task = new SkippableScheduledTask(worker, center, this.clusterConfig);
+
+        ScheduledFuture<?> future = this.scheduler.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);
+
+        this.workerMap.put(regionCd, worker);
+        this.futureMap.put(regionCd, future);
+
+        log.info("SigTodpService.addRegionCenter: added center [{}]", regionCd);
     }
 
-    public void checkTodpWorkerThread() {
-        for(SigTodpWorker todpWorker : this.workerList) {
-            if (!todpWorker.isRunning()) {
-                Thread worker = new Thread(this.workerGroup, todpWorker);
-                worker.setName(String.format("SigTodp-%s", todpWorker.getCenter().getRegionCd()));
-                worker.setDaemon(true);
-                todpWorker.setThread(worker);
-                worker.start();
-                log.warn("SigTodpService.checkTodpWorkerThread: {}, Thread Restarting.", todpWorker.getCenter().getRegionCd());
-            }
+    public void removeRegionCenter(String regionCd) {
+        ScheduledFuture<?> future = this.futureMap.remove(regionCd);
+        SigTodpWorkerCallable worker = this.workerMap.remove(regionCd);
+
+        if (future != null) {
+            future.cancel(true);
+        }
+
+        if (worker != null) {
+            worker.close();
         }
+
+        log.info("SigTodpService.removeRegionCenter: removed center [{}]", regionCd);
     }
 
     @PreDestroy
-    public void onDestroy() throws Exception {
-        log.error("SigTodpService.onDestroy.");
-        for(SigTodpWorker todpWorker : this.workerList) {
-            todpWorker.close();
+    public void onDestroy() {
+        log.info("SigTodpService.onDestroy: shutdown scheduler");
+
+        for (ScheduledFuture<?> future : this.futureMap.values()) {
+            future.cancel(true);
         }
-        for(SigTodpWorker todpWorker : this.workerList) {
-            Thread thread = todpWorker.getThread();
-            if (thread != null && thread.isAlive()) {
-                thread.join(1000);
+
+        for (SigTodpWorkerCallable worker : this.workerMap.values()) {
+            worker.close();
+        }
+
+        if (this.scheduler != null) {
+            this.scheduler.shutdown();
+            try {
+                if (!this.scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
+                    this.scheduler.shutdownNow();
+                }
+            } catch (InterruptedException e) {
+                this.scheduler.shutdownNow();
             }
         }
+        this.scheduler = null;
+
+        log.info("SigTodpService.onDestroy: shutdown complete");
     }
 }

+ 275 - 0
sig-todp-server/src/main/java/com/sig/todp/server/service/SigTodpServiceOld.java

@@ -0,0 +1,275 @@
+package com.sig.todp.server.service;
+
+import com.its.common.utils.Elapsed;
+import com.sig.todp.server.cluster.ClusterConfig;
+import com.sig.todp.server.dao.mapper.IntMapper;
+import com.sig.todp.server.dto.RegionCenter;
+import com.sig.todp.server.dto.TTodInt;
+import com.sig.todp.server.dto.TTodUtils;
+import com.sig.todp.server.entity.TbCurrTod;
+import com.sig.todp.server.entity.TbCurrTrans;
+import com.sig.todp.server.process.dbms.DbmsDataProcess;
+import com.sig.todp.server.repository.ApplicationRepository;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Slf4j
+@Getter
+//@Service
+@RequiredArgsConstructor
+public class SigTodpServiceOld {
+
+    private final ClusterConfig clusterConfig;
+    private final ApplicationRepository repo;
+    private final DbmsDataProcess dbmsDataProcess;
+    private final IntMapper mapper;
+    private final ThreadGroup workerGroup = new ThreadGroup("SigTodp");
+    private final List<Thread> threadList = new ArrayList<>();
+    private final List<SigTodpWorker> workerList = new ArrayList<>();
+
+    @PostConstruct
+    private void init() {
+        log.info("SigTodpService.init: start.");
+        log.info("SigTodpService.init: ..end.");
+    }
+
+    public void loadTodDatabase() {
+        Elapsed elapsed = new Elapsed();
+        log.info("SigTodpService..loadTodDatabase: START.");
+
+        List<String> keySet = new ArrayList<>(this.repo.getCenterMap().keySet());
+        Collections.sort(keySet);
+        for (String key : keySet) {
+            RegionCenter center = this.repo.getCenterMap().get(key);
+            if (center == null) {
+                log.error("SigTodpService.loadTodDatabase: {}, NOT FOUND.", key);
+                continue;
+            }
+
+            MDC.put("id", center.getLogKey());
+            try {
+                loadCurrentTrans(center);
+                loadCurrentOpTod(center);
+            }
+            catch (Exception e) {
+                log.error("SigTodpService..loadTodDatabase: Exception, {}, {}", center.getRegionCd(), e.getMessage());
+            }
+            finally {
+                MDC.remove("id");
+                MDC.clear();
+            }
+        }
+        log.info("SigTodpService..loadTodDatabase: ..END.. {}",  Elapsed.elapsedTimeStr(elapsed.nanoSeconds()));
+    }
+
+    private void loadCurrentTrans(RegionCenter center) {
+        Elapsed elapsed = new Elapsed();
+        log.info("SigTodpService.loadCurrentTrans: START. {}.", center.getRegionCd());
+        center.getIntMap().forEach((key, dto) -> {
+            dto.useTrans = false;
+        });
+
+        int dual;
+        int aRings = 0;
+        int bRings = 0;
+        int ring;
+        int oldIntNo = -1;
+        TTodInt todInt = null;
+
+        List<TbCurrTrans> list = this.mapper.selectCurrTransAll(center.getRegionCd());
+        for (TbCurrTrans entity : list) {
+            TTodInt dto = center.getIntMap().get(entity.getIntNo());
+            if (dto == null) {
+                return;
+            }
+            ring = entity.getRingNo();
+            if (ring != 0 && ring != 1) {
+                return;
+            }
+
+            try {
+                if (entity.getIntNo() != oldIntNo) {
+                    // 조회과가 순차적으로 들어있음. 교차로번호가 변경되었음.....
+                    updateTodTrans(aRings, bRings, todInt);
+
+                    oldIntNo = entity.getIntNo();   // 새로운 교차로를 할당.
+                    aRings = bRings = 0;
+
+                    todInt = dto;
+
+                    todInt.trans.mainPhaseInx = entity.getIntMainPhase();   /* 주현시 인덱스(0..7) */
+                    todInt.trans.progressionRing = 0;                          /* 연동방향, 0=ARING, 1=BRING */
+                    todInt.trans.transDir = 1;                          /* 전이방향(1=POSITIVE,2=NEGATIVE) */
+                    todInt.trans.acceptableDelta = 10;                         /* 전이 없이 주기단순보정 허용 델타값(가급적 큰 것이 좋음. 10초정도까지도) */
+                    todInt.trans.maxTransition = 5;                          /* 허용 가능한 최대 전이주기 수 ==> 최대 5개로 고정 */
+                    todInt.trans.keepPreInterval = 1;                          /* 전이주기 분할 시 선행현시길이고정(주현시 선행현시를 길이 고정) */
+                    todInt.trans.minimumOverlap = 3;                          /* 최소 동시신호 유지시간 */
+                    todInt.trans.dual = 0;                          /* 듀얼 현시운영지정(LSB:1=1현시듀얼) */
+                }
+                if (todInt == null) {
+                    continue;
+                }
+
+                if (ring == 0) {
+                    todInt.trans.minG[ring][aRings] += entity.getMinTm();  /* 최소청시간 */
+                    todInt.trans.maxG[ring][aRings] += entity.getMaxTm();  /* 최대청시간 */
+                    if (entity.getEop() == 1) {
+                        /* EOP 가 1이면 현시가 바뀌는 것이다. */
+                        aRings++;   /* 현시 수 */
+                    }
+                }
+                else {
+                    /* EOP 가 1이면 현시가 바뀌는 것이다. */
+                    todInt.trans.minG[ring][bRings] += entity.getMinTm();  /* 최소청시간 */
+                    todInt.trans.maxG[ring][bRings] += entity.getMaxTm();  /* 최대청시간 */
+
+                    if (entity.getEop() == 1) {
+                        /* EOP 가 1이면 현시가 바뀌는 것이다. */
+                        bRings++;   /* 현시 수 */
+                    }
+                }
+            }
+            catch (Exception e) {
+                log.info("SigTodpService.loadCurrentTrans: Exception. {}. {}. {}", center.getRegionCd(), entity.getIntNo(), e.getMessage());
+            }
+        }
+        updateTodTrans(aRings, bRings, todInt);
+        log.info("SigTodpService.loadCurrentTrans: ..END. {}. {} EA. {}", center.getRegionCd(), list.size(), Elapsed.elapsedTimeStr(elapsed.nanoSeconds()));
+    }
+
+    private void updateTodTrans(int aRings, int bRings, TTodInt todInt) {
+        int dual;
+        if (todInt != null) {
+            // 마지막 교차로 처리
+            dual = 0;
+            for (int phase = 0; phase < TTodUtils.MAX_PHASES; phase++) {
+                if (todInt.trans.minG[0][phase] > 0 && todInt.trans.maxG[0][phase] > 0) {
+                    dual |= 0x01 << phase;
+                }
+            }
+            todInt.trans.phaseCnt = Math.max(aRings, bRings);
+            todInt.trans.dual = (byte)(dual & 0xFF);
+            todInt.useTrans = true;
+//            log.info("{}, {}", todInt.getIntNo(), todInt.trans);
+        }
+    }
+
+    private void loadCurrentOpTod(RegionCenter center) {
+        Elapsed elapsed = new Elapsed();
+        log.info("SigTodpService.loadCurrentOpTod: START. {}.", center.getRegionCd());
+        center.getIntMap().forEach((key, dto) -> {
+            dto.useFlag = false;
+        });
+
+        int idx = 0;
+        List<TbCurrTod> list = this.mapper.selectCurrTodAll(center.getRegionCd());
+        for (TbCurrTod entity : list) {
+            TTodInt dto = center.getIntMap().get(entity.getIntNo());
+            if (dto == null) {
+                return;
+            }
+            if (entity.getIdxNo() <= 0 || entity.getIdxNo() >= TTodUtils.MAX_TOD_PLANS) {
+                continue;
+            }
+            try {
+                idx = entity.getIdxNo() - 1;
+                dto.plan.itTod[idx].hour = entity.getHour();
+                dto.plan.itTod[idx].minute = entity.getMinute();
+                dto.plan.itTod[idx].cycle = entity.getCycle();
+                dto.plan.itTod[idx].offset = entity.getOffset();
+
+                dto.plan.itTod[idx].split[0][TTodUtils.ARING] = entity.getSplitA1();
+                dto.plan.itTod[idx].split[1][TTodUtils.ARING] = entity.getSplitA2();
+                dto.plan.itTod[idx].split[2][TTodUtils.ARING] = entity.getSplitA3();
+                dto.plan.itTod[idx].split[3][TTodUtils.ARING] = entity.getSplitA4();
+                dto.plan.itTod[idx].split[4][TTodUtils.ARING] = entity.getSplitA5();
+                dto.plan.itTod[idx].split[5][TTodUtils.ARING] = entity.getSplitA6();
+                dto.plan.itTod[idx].split[6][TTodUtils.ARING] = entity.getSplitA7();
+                dto.plan.itTod[idx].split[7][TTodUtils.ARING] = entity.getSplitA8();
+
+                dto.plan.itTod[idx].split[0][TTodUtils.BRING] = entity.getSplitB1();
+                dto.plan.itTod[idx].split[1][TTodUtils.BRING] = entity.getSplitB2();
+                dto.plan.itTod[idx].split[2][TTodUtils.BRING] = entity.getSplitB3();
+                dto.plan.itTod[idx].split[3][TTodUtils.BRING] = entity.getSplitB4();
+                dto.plan.itTod[idx].split[4][TTodUtils.BRING] = entity.getSplitB5();
+                dto.plan.itTod[idx].split[5][TTodUtils.BRING] = entity.getSplitB6();
+                dto.plan.itTod[idx].split[6][TTodUtils.BRING] = entity.getSplitB7();
+                dto.plan.itTod[idx].split[7][TTodUtils.BRING] = entity.getSplitB8();
+                dto.useFlag = true;
+            }
+            catch (Exception e) {
+                log.error("SigTodpService.loadCurrentOpTod: Exception. {}. {}. {}", center.getRegionCd(), entity.getIntNo(), e.getMessage());
+            }
+        }
+        log.info("SigTodpService.loadCurrentOpTod: ..END. {}. {} EA. {}", center.getRegionCd(), list.size(), Elapsed.elapsedTimeStr(elapsed.nanoSeconds()));
+    }
+
+    public void start() {
+        log.info("SigTodpService.start: START.");
+        List<String> keySet = new ArrayList<>(this.repo.getCenterMap().keySet());
+        Collections.sort(keySet);
+        for (String key : keySet) {
+            RegionCenter center = this.repo.getCenterMap().get(key);
+            if (center == null) {
+                continue;
+            }
+            MDC.put("id", center.getLogKey());
+            try {
+                SigTodpWorker todpWorker = new SigTodpWorker(center, this.dbmsDataProcess, this.clusterConfig);
+                this.workerList.add(todpWorker);
+                Thread worker = new Thread(this.workerGroup, todpWorker);
+                worker.setName(String.format("SigTodp-%s", center.getRegionCd()));
+                worker.setDaemon(true);
+                todpWorker.setThread(worker);
+                this.threadList.add(worker);
+            }
+            catch (IOException e) {
+                log.error("SigTodpService.start: Exception {}, {}", center.getRegionCd(), e.getMessage());
+            }
+            finally {
+                MDC.remove("id");
+                MDC.clear();
+            }
+        }
+        for (Thread worker : this.threadList) {
+            worker.start();
+        }
+        log.info("SigTodpService.start: ..END.");
+    }
+
+    public void checkTodpWorkerThread() {
+        for(SigTodpWorker todpWorker : this.workerList) {
+            if (!todpWorker.isRunning()) {
+                Thread worker = new Thread(this.workerGroup, todpWorker);
+                worker.setName(String.format("SigTodp-%s", todpWorker.getCenter().getRegionCd()));
+                worker.setDaemon(true);
+                todpWorker.setThread(worker);
+                worker.start();
+                log.warn("SigTodpService.checkTodpWorkerThread: {}, Thread Restarting.", todpWorker.getCenter().getRegionCd());
+            }
+        }
+    }
+
+    @PreDestroy
+    public void onDestroy() throws Exception {
+        log.error("SigTodpService.onDestroy.");
+        for(SigTodpWorker todpWorker : this.workerList) {
+            todpWorker.close();
+        }
+        for(SigTodpWorker todpWorker : this.workerList) {
+            Thread thread = todpWorker.getThread();
+            if (thread != null && thread.isAlive()) {
+                thread.join(1000);
+            }
+        }
+    }
+}

+ 15 - 12
sig-todp-server/src/main/java/com/sig/todp/server/service/SigTodpWorker.java

@@ -8,6 +8,7 @@ import com.sig.todp.server.process.dbms.DbmsDataProcess;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
 
 import java.io.IOException;
 import java.time.LocalDateTime;
@@ -51,10 +52,18 @@ public class SigTodpWorker implements Runnable, AutoCloseable {
             output[ii] = new TTod();
         }
 
+        MDC.put("id", center.getLogKey());
+
         List<HashMap<String, Object>> lists = new ArrayList<>();
 
         if (this.running.compareAndSet(false, true)) {
             while (this.running.get()) {
+                if (this.center.getRealClusterId() != this.clusterConfig.getId()) {
+                    // 서버클러스터에 운영이 할당된 경우에만 처리하도록 함(대기시간을 늘림)
+                    SleepUtils.safeSleep(5000);
+                    continue;
+                }
+
                 long start = System.currentTimeMillis();
                 LocalDateTime now = LocalDateTime.now();
                 curTime = now.getHour() * 3600 + now.getMinute() * 60 + now.getSecond();
@@ -201,26 +210,18 @@ public class SigTodpWorker implements Runnable, AutoCloseable {
                         param.put("B_RING_8_PHASE_VAL", isTrans);   // split.split[7][1]);
 
                         lists.add(param);
-//                        if ("L30".equals(this.center.getRegionCd()) && dto.getIntNo() == 1664) {
-//                            log.error("A_RING_PHASE_VAL: {}, B_RING_PHASE_VAL: {}, INT_SIG_CYCLE_CNT: {}, INT_SIG_CYCLE_LEN: {}",
-//                                    curPhase[0], curPhase[1], cycleCount, cycle);
-//                        }
 
                         if (lists.size() >= TTodUtils.SIG_SIMUL_SEND_CNT) {
-                            if (this.clusterConfig.isMaster()) {
-                                // 마스터로 운영중일 때에만 데이터베이스 저장
-                                this.dbmsDataProcess.add((new DbmsData(DbmsData.DBMS_DATA_INT_TOD_SIM_SEND, this.center, false, lists)));
-                            }
+                            // 마스터로 운영중일 때에만 데이터베이스 저장
+                            this.dbmsDataProcess.add((new DbmsData(DbmsData.DBMS_DATA_INT_TOD_SIM_SEND, this.center, false, lists)));
                             lists = new ArrayList<>();
                         }
                     } /* 전체 교차로에 대하여 */
                 }
 
                 if (!lists.isEmpty()) {
-                    if (this.clusterConfig.isMaster()) {
-                        // 마스터로 운영중일 때에만 데이터베이스 저장
-                        this.dbmsDataProcess.add((new DbmsData(DbmsData.DBMS_DATA_INT_TOD_SIM_SEND, this.center, false, lists)));
-                    }
+                    // 마스터로 운영중일 때에만 데이터베이스 저장
+                    this.dbmsDataProcess.add((new DbmsData(DbmsData.DBMS_DATA_INT_TOD_SIM_SEND, this.center, false, lists)));
                 }
 
                 // 1 초 간격 으로 작업을 처리 하기 위함...
@@ -231,6 +232,8 @@ public class SigTodpWorker implements Runnable, AutoCloseable {
             }
             this.running.set(false);
         }
+        MDC.remove("id");
+        MDC.clear();
     }
 
     @Override

+ 246 - 0
sig-todp-server/src/main/java/com/sig/todp/server/service/SigTodpWorkerCallable.java

@@ -0,0 +1,246 @@
+package com.sig.todp.server.service;
+
+import com.sig.common.utils.LogUtils;
+import com.sig.todp.server.cluster.ClusterConfig;
+import com.sig.todp.server.dto.*;
+import com.sig.todp.server.process.dbms.DbmsData;
+import com.sig.todp.server.process.dbms.DbmsDataProcess;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+@Slf4j
+@Getter
+@Setter
+public class SigTodpWorkerCallable implements Callable<Boolean>, AutoCloseable {
+
+    private final RegionCenter center;
+    private final DbmsDataProcess dbmsDataProcess;
+    private final ClusterConfig clusterConfig;
+
+    public SigTodpWorkerCallable(RegionCenter center, DbmsDataProcess dbmsDataProcess, ClusterConfig clusterConfig) {
+        this.center = center;
+        this.dbmsDataProcess = dbmsDataProcess;
+        this.clusterConfig = clusterConfig;
+    }
+
+    @Override
+    public Boolean call() {
+
+        MDC.put("id", this.center.getLogKey());
+
+        boolean didWork = false;
+        try {
+            if (this.center.getRealClusterId() != this.clusterConfig.getId()) {
+                // 서버클러스터에 운영이 할당된 경우에만 처리하도록 함
+                return false;
+            }
+            didWork = processTod();
+        }
+        catch (Exception e) {
+            log.error("SigTodpWorkerCallable.call: error in region [{}], {}", this.center.getRegionCd(), e.getMessage());
+        }
+        finally {
+            MDC.remove("id");
+            MDC.clear();
+        }
+        return didWork;
+    }
+
+    public boolean processTod() {
+        TTodIdSplit split = new TTodIdSplit();
+        int  ii, kk;
+        int  curTime, curPoint, todTime;
+        int  cycle, offset, cycleCount;
+        int[] sum = new int[TTodUtils.MAX_RINGS];
+        int[] curPhase = new int[TTodUtils.MAX_RINGS];
+        int phase, tClock, trCnt, dbPhases, isTrans;
+        TTod[] output = new TTod[TTodUtils.MAX_TOD_OUTPUT];
+        for (ii = 0; ii < TTodUtils.MAX_TOD_OUTPUT; ii++) {
+            output[ii] = new TTod();
+        }
+
+        long start = System.currentTimeMillis();
+        this.center.setLastTodRunTime(start);
+
+        LocalDateTime now = LocalDateTime.now();
+        curTime = now.getHour() * 3600 + now.getMinute() * 60 + now.getSecond();
+
+        List<HashMap<String, Object>> lists = new ArrayList<>();
+
+        int intTotal = this.center.getIntMap().size();
+        int workTotal = 0;
+        for (TTodInt dto: this.center.getIntMap().values()) {
+            if (!dto.useFlag) {
+                continue;
+            }
+            isTrans = 0;
+            curPoint = 0;
+            for (kk = 0; kk < TTodUtils.MAX_TOD_PLANS; kk++) {
+                if (dto.plan.itTod[kk].cycle == 0) {
+                    break;
+                }
+
+                todTime = dto.plan.itTod[kk].hour*3600 + dto.plan.itTod[kk].minute*60;
+                if (todTime < curTime) {
+                    /* 현재시각보다 작을때까지 TOD 계획을 찾는다 */
+                    curPoint = kk+1;
+                }
+                else {
+                    /* 현재시각보다 TOD 계획 시작시각이 크거나 같으면 루프 종료 */
+                    break;
+                }
+            }
+            if (curPoint == 0) {
+                /* TOD 계획이 존재하지 않는 경우임 */
+                continue;
+            }
+            curPoint--; /* 계획 인덱스를 하나 줄임 */
+            cycle  = dto.plan.itTod[curPoint].cycle;      /* 주기 */
+            offset = dto.plan.itTod[curPoint].offset;
+
+            /* 실재 현시주기(DB)를 계산될 저장소에 저장 */
+            dbPhases = 0;
+            for (phase = 0; phase < TTodUtils.MAX_PHASES; phase++) {
+                split.split[phase][0] = dto.plan.itTod[curPoint].split[phase][0];
+                split.split[phase][1] = dto.plan.itTod[curPoint].split[phase][1];
+                if (split.split[phase][0] > 0 || split.split[phase][1] > 0) {
+                    dbPhases++;
+                }
+            }
+
+            /*
+             *  TRANS Value setting
+             */
+            if (dto.useTrans && dbPhases > 0) {
+                /* SIGNAL MAP 에 현시 주기가 0으로 들어간것들이 있음(dbPhases > 0). */
+                for (ii = 0; ii < TTodUtils.MAX_TOD_OUTPUT; ii++) {
+                    output[ii].init();
+                }
+
+                for (phase = 0; phase < TTodUtils.MAX_PHASES; phase++) {
+                    /* Split (seconds), 듀얼링 */
+                    dto.trans.tPlan.S[0][phase] = split.split[phase][0];   /* A Ring */
+                    dto.trans.tPlan.S[1][phase] = split.split[phase][1];   /* B Ring */
+                }
+                dto.trans.tPlan.O    = offset;                          /* Offset(seconds) */
+                dto.trans.tPlan.hour = dto.plan.itTod[curPoint].hour;   /*g_tmTod.tm_hour*/;   /* 신호(주기) 시작 시각 */
+                dto.trans.tPlan.min  = dto.plan.itTod[curPoint].minute; /*g_tmTod.tm_min*/;
+                dto.trans.tPlan.sec  = 0;                               /*g_tmTod.tm_sec*/;
+
+                dto.trans.phaseCnt = Math.min(dto.trans.phaseCnt, dbPhases); /* 현시 수 */
+                if (dto.trans.mainPhaseInx >= dto.trans.phaseCnt) {
+                    dto.trans.mainPhaseInx = 0;     /* 주현시 인덱스(0..7) */
+                }
+
+                tClock = curTime;   /*pTrans->tPlan.hour*3600 + pTrans->tPlan.min*60 + pTrans->tPlan.sec*/;
+
+                // 전이계획 작성 후 전이주기 수를 trCnt 에 저장, aClock 가 0이면 현재 시각 사용
+                if (dto.trans.phaseCnt <= 0) {
+                    // phaseCnt 가 0이면 makeTransition 오류 발생
+                    dto.trans.phaseCnt = dbPhases;
+                    log.error("SigTodpWorkerCallable.processTod: [{}] regionId: {}, int_no: {}, phase-count-zero.",
+                            this.center.getRegionCd(), this.center.getRegionId(), dto.getIntNo());
+                    trCnt = 0;
+                }
+                else {
+                    trCnt = TTodUtils.makeTransition(dto.trans, output, tClock);
+                }
+
+                if (trCnt > 0) {
+                    for (phase = 0; phase < TTodUtils.MAX_PHASES; phase++) {
+                        /* 첫번째 데이터가 현재 Trans 로 변경 되어야 할 주기 */
+                        split.split[phase][0] = output[0].S[0][phase];
+                        split.split[phase][1] = output[0].S[1][phase];
+                    }
+                    isTrans = 1;
+                }
+            } /* TRANS Value setting */
+
+            sum[0] = sum[1] = 0;
+            cycleCount = (curTime - offset) % cycle;
+            for (kk = 0; kk < TTodUtils.MAX_PHASES; kk++) {
+                sum[0] += split.split[kk][0];
+                if (cycleCount < sum[0]) {
+                    curPhase[0] = kk;
+                    break;
+                }
+            }
+            for (kk = 0; kk < TTodUtils.MAX_PHASES; kk++) {
+                sum[1] += split.split[kk][1];
+                if (cycleCount < sum[1]) {
+                    curPhase[1] = kk;
+                    break;
+                }
+            }
+
+            if ((dto.sim.phase[0] != curPhase[0]) || (dto.sim.phase[1] != curPhase[1])) {
+                /* 시뮬레이션 완료 된 정보를 설정 */
+                dto.sim.phase[0] = curPhase[0];
+                dto.sim.phase[1] = curPhase[1];
+
+                /* 센터의 시뮬레이션 플래그가 설정된 경우 데이터 전송 */
+                /* TRANS 적용후 데이터 확인을 위해 모든 데이터를 dbmp 로 전송하고 dbmp 에서 시뮬레이션 모드인 경우에 데이터 업데이트 */
+                /* TRANS 적용된 것은 _STATUS_TOD 테이블에 업데이트 */
+
+                HashMap<String, Object> param = new HashMap<>();
+
+                param.put("REGION_CD",          this.center.getRegionCd());                  /*지역센터코드 */
+                param.put("INT_NO",             dto.getIntNo());                     /*교차로 번호 */
+
+                param.put("A_RING_PHASE_VAL",   curPhase[0]);   /*차량등 1 (2004-LSU1) */
+                param.put("B_RING_PHASE_VAL",   curPhase[1]);   /*보행등 1 (2004-LSU2) */
+                param.put("INT_SIG_CYCLE_CNT",  cycleCount);
+                param.put("INT_SIG_CYCLE_LEN",  cycle);
+                param.put("A_RING_1_PHASE_VAL", split.split[0][0]);
+                param.put("A_RING_2_PHASE_VAL", split.split[1][0]);
+                param.put("A_RING_3_PHASE_VAL", split.split[2][0]);
+                param.put("A_RING_4_PHASE_VAL", split.split[3][0]);
+                param.put("A_RING_5_PHASE_VAL", split.split[4][0]);
+                param.put("A_RING_6_PHASE_VAL", split.split[5][0]);
+                param.put("A_RING_7_PHASE_VAL", split.split[6][0]);
+                param.put("A_RING_8_PHASE_VAL", split.split[7][0]);
+                param.put("B_RING_1_PHASE_VAL", split.split[0][1]);
+                param.put("B_RING_2_PHASE_VAL", split.split[1][1]);
+                param.put("B_RING_3_PHASE_VAL", split.split[2][1]);
+                param.put("B_RING_4_PHASE_VAL", split.split[3][1]);
+                param.put("B_RING_5_PHASE_VAL", split.split[4][1]);
+                param.put("B_RING_6_PHASE_VAL", split.split[5][1]);
+                param.put("B_RING_7_PHASE_VAL", split.split[6][1]);
+                param.put("B_RING_8_PHASE_VAL", isTrans);   // split.split[7][1]);
+
+                lists.add(param);
+
+                if (lists.size() >= TTodUtils.SIG_SIMUL_SEND_CNT && this.center.isSimulateFlag()) {
+                    // 마스터로 운영중일 때에만 데이터베이스 저장
+                    this.dbmsDataProcess.add((new DbmsData(DbmsData.DBMS_DATA_INT_TOD_SIM_SEND, this.center, false, lists)));
+                    lists = new ArrayList<>();
+                }
+                workTotal++;
+            } /* 전체 교차로에 대하여 */
+        }
+
+        if (!lists.isEmpty() && this.center.isSimulateFlag()) {
+            // 마스터로 운영중일 때에만 데이터베이스 저장
+            this.dbmsDataProcess.add((new DbmsData(DbmsData.DBMS_DATA_INT_TOD_SIM_SEND, this.center, false, lists)));
+        }
+
+        long elapsed = System.currentTimeMillis() - start;
+        if (elapsed > 900) {
+            log.warn("SigTodpWorkerCallable.processTod: [{}], {} work too long elapsed {} ms. {}/{} EA.",
+                    this.center.getLogKey(), LogUtils.getTimeStr(start), elapsed, intTotal, workTotal);
+        }
+        return true;
+    }
+
+    @Override
+    public void close() {
+    }
+}

+ 55 - 0
sig-todp-server/src/main/java/com/sig/todp/server/service/SkippableScheduledTask.java

@@ -0,0 +1,55 @@
+package com.sig.todp.server.service;
+
+import com.sig.todp.server.cluster.ClusterConfig;
+import com.sig.todp.server.dto.RegionCenter;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@Slf4j
+public class SkippableScheduledTask implements Runnable {
+
+    private final Callable<Boolean> task;
+    private final AtomicBoolean busy = new AtomicBoolean(false);
+    private final RegionCenter center;
+    private final ClusterConfig clusterConfig;
+
+    public SkippableScheduledTask(Callable<Boolean> task, RegionCenter center, ClusterConfig clusterConfig) {
+        this.task = task;
+        this.center = center;
+        this.clusterConfig = clusterConfig;
+    }
+
+    @Override
+    public void run() {
+        if (this.clusterConfig.getId() != this.center.getRealClusterId()) {
+            // 현재 서버에서 실행하지 않는다면 그냥 리턴
+            return;
+        }
+
+        MDC.put("id", this.center.getLogKey());
+        boolean acquired = true;
+        try {
+            acquired = this.busy.compareAndSet(false, true);
+            if (!acquired) {
+                // 이전 작업이 아직 실행 중이면 스킵
+                log.warn("SkippableScheduledTask: skipping cycle [{}]", this.center.getLogKey());
+                return;
+            }
+
+            this.task.call();
+        }
+        catch (Exception e) {
+            log.error("SkippableScheduledTask: error in [{}]: {}", this.center.getLogKey(), e.getMessage());
+        }
+        finally {
+            if (acquired) {
+                this.busy.set(false);
+            }
+            MDC.remove("id");
+            MDC.clear();
+        }
+    }
+}

+ 2 - 0
sig-todp-server/src/main/resources/logback-spring.xml

@@ -65,11 +65,13 @@
         </logger>
 
         <logger name="${APP_CLASS_PATH}.process" level="INFO" additivity="false">
+            <appender-ref ref="CONSOLE"/>
             <appender-ref ref="FILE_CENTER"/>
             <appender-ref ref="FILE_ERROR"/>
         </logger>
 
         <logger name="${APP_CLASS_PATH}.service" level="INFO" additivity="false">
+            <appender-ref ref="CONSOLE"/>
             <appender-ref ref="FILE_CENTER"/>
             <appender-ref ref="FILE_ERROR"/>
         </logger>