package com.its.common.cluster.config; import com.its.common.cluster.vo.ClusterNET; import com.its.common.cluster.vo.ClusterNode; import io.netty.util.AttributeKey; import lombok.Data; import lombok.extern.slf4j.Slf4j; import javax.annotation.PostConstruct; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @Slf4j @Data public abstract class AbstractClusterConfig { public static final AttributeKey CLUSTER_ATTRIBUTE_KEY = AttributeKey.valueOf("clusterInfo"); private boolean enabled = true; // 클러스터 기능 사용 여부 // 어플리케이션 클러스터 정보 private boolean master = true; private int id = -1; // 서버 ID (1부터 시작, 0은 사용하지 않음) private boolean autoFailbackEnabled = false; // client 모드시 작업을 바로 넘겨주는 옵션(기본 false) private int masterId = -1; private int syncSeconds = 5; // 데이터 동기화 주기 (초 단위, 기본 5초, 최소 2초, 최대 60초) private String ip = "127.0.0.1"; // 클러스터 서버의 IP 주소 private int port = 13888; // 데이터 동기화를 위한 포트 private boolean logging = false; // 라이브러리 내 로깅 여부 private boolean packetLogging = false; // 패킷 로깅 여부 private int electionScheduleSeconds = 2; // 마스터 선출 스케줄 실행 주기 (단위: 초, 기본값 2초) private int connectTimeoutSeconds = 5; // 클라이언트(Slave)가 서버(Master)에 연결을 시도할 때의 타임아웃 (단위: 초, 기본값 5초) private boolean witnessNode = false; // 이 서버 인스턴스가 Witness 노드인지 여부 private List nodes; private ClusterNode myCluster; private final HashMap clusterMap = new HashMap<>(); @PostConstruct private void init() throws IOException { if (this.nodes == null) { this.nodes = new ArrayList<>(); } if (this.electionScheduleSeconds < 2) { this.electionScheduleSeconds = 2; } if (this.electionScheduleSeconds > 10) { this.electionScheduleSeconds = 10; } } public boolean isClusterAlive(int clusterId) { ClusterNode clusterNode = this.clusterMap.get(clusterId); if (clusterNode == null) { return true; } return clusterNode.getElectionState().getState() != ClusterNET.CLOSED; } public ClusterNode getClusterNode(int clusterId) { return this.clusterMap.get(clusterId); } public void loggingInfo() { log.info("Cluster config enabled: {}", this.enabled); log.info(" id: {}", this.id); log.info(" master: {}", this.master); log.info(" syncSeconds: {}", this.syncSeconds); log.info(" ip: {}", this.ip); log.info(" port: {}", this.port); log.info(" logging: {}", this.logging); log.info(" packetLogging: {}", this.packetLogging); log.info(" node: {} EA.", this.nodes.size()); for (ClusterNode node : this.nodes) { log.info(" ----------------node id: {}", node.getId()); log.info(" master: {}", node.isMaster()); log.info(" ip: {}", node.getIp()); log.info(" port: {}", node.getPort()); log.info(" logging/packetLogging: {}/{}", node.isLogging(), node.isPacketLogging()); } } private boolean setDefaults() { // 클러스터가 하나라 단일 클러스터 정보로 설정 this.master = true; this.id = 1; this.masterId = 1; this.syncSeconds = 5; this.ip = "127.0.0.1"; this.port = 13888; this.logging = false; this.packetLogging = false; // 단일 노드 정보 설정 this.nodes = new ArrayList<>(); ClusterNode masterNode = new ClusterNode(); masterNode.setMaster(this.master); masterNode.setId(this.id); masterNode.setIp(this.ip); masterNode.setPort(this.port); masterNode.setLogging(this.logging); masterNode.setPacketLogging(this.packetLogging); masterNode.setWitness(false); this.nodes.add(masterNode); this.myCluster = masterNode; this.enabled = false; // 나 자신의 네트워크 상태정보를 항상 CONNECT로 지정 masterNode.getElectionState().connect(null); masterNode.getSyncState().connect(null); return true; } public boolean validateClusterInfo() { if (!this.enabled) { return setDefaults(); } if (this.nodes == null || this.nodes.isEmpty()) { log.error("클러스터 노드 리스트가 비어 있습니다."); return false; } if (this.nodes.size() == 1) { int firstNodeId = this.nodes.get(0).getId(); if (firstNodeId == 1) { return setDefaults(); } log.error("클러스터 노드가 1개 인데 노드 ID 값이 1 이 아닙니다.: {}", firstNodeId); log.error("클러스터 노드가 1개 인 경우 클러스터 사용을 false 로 설정하세요."); return false; } // ID만 추출해서 정렬 List ids = this.nodes.stream() .map(ClusterNode::getId) .sorted() .collect(Collectors.toList()); // 중복 제거 후 크기 비교 Set uniqueIds = new HashSet<>(ids); if (uniqueIds.size() != ids.size()) { log.error("클러스터 노드 ID에 중복이 있습니다: {}", ids); return false; } // 순차성 검증: 1부터 시작해서 1씩 증가해야 함 for (int ii = 0; ii < ids.size(); ii++) { int expected = ii + 1; if (ids.get(ii) != expected) { log.error("클러스터 노드 ID가 순차적이지 않습니다. 예상: {}, 실제: {}", expected, ids.get(ii)); return false; } } // 검증 통과, 클러스터 정보 설정 if (this.syncSeconds < 2) { this.syncSeconds = 2; } if (this.syncSeconds > 30) { this.syncSeconds = 30; } // Master and host information setting int masterId = Integer.MAX_VALUE; for (ClusterNode node : this.nodes) { this.clusterMap.put(node.getId(), node); int nodeId = node.getId(); if (nodeId == this.id) { this.ip = node.getIp(); this.port = node.getPort(); } if (nodeId < masterId) { masterId = nodeId; } } // Master setting ClusterNode masterNode = this.clusterMap.get(masterId); masterNode.setMaster(true); this.master = (this.id == masterId); this.masterId = masterId; // 나 자신의 네트워크 상태정보를 항상 CONNECT로 지정 ClusterNode localNode = this.clusterMap.get(this.id); localNode.getElectionState().connect(null); localNode.getSyncState().connect(null); this.myCluster = localNode; return true; } public ClusterNode get(String ipAddress) { for (Map.Entry entry : this.clusterMap.entrySet()) { ClusterNode cluster = entry.getValue(); if (cluster.getIp().equals(ipAddress)) { return cluster; } } return null; } private int parseConfigData(String data) { try { return Integer.parseInt(data); } catch (NumberFormatException e) { return -1; } } // private String getStringValue(String item, String defValue) throws IOException { // if (!this.enabled) { // return defValue; // } // Pattern stringPattern = Pattern.compile(item + "=([a-zA-Z0-9]+)"); // try (BufferedReader reader = new BufferedReader(new FileReader(this.configFile))) { // String line; // while ((line = reader.readLine()) != null) { // Matcher matcher = stringPattern.matcher(line); // if (matcher.matches()) { // return matcher.group(1); // } // } // } // return defValue; // } // private int getIntValue(String item, int defValue) throws IOException { // if (!this.enabled) { // return defValue; // } // Pattern serverIdPattern = Pattern.compile(item+"=(\\d+)"); // try (BufferedReader reader = new BufferedReader(new FileReader(this.configFile))) { // String line; // while ((line = reader.readLine()) != null) { // Matcher matcher = serverIdPattern.matcher(line); // if (matcher.matches()) { // return parseConfigData(matcher.group(1)); // } // } // } // return defValue; // } // private void setServerId() throws IOException { // Pattern serverIdPattern = Pattern.compile("server\\.id=(\\d+)"); // try (BufferedReader reader = new BufferedReader(new FileReader(this.configFile))) { // String line; // while ((line = reader.readLine()) != null) { // Matcher matcher = serverIdPattern.matcher(line); // if (matcher.matches()) { // this.serverId = parseConfigData(matcher.group(1)); // break; // } // } // } // } // private void loadClusterConfig() throws IOException { // this.serverId = getIntValue("server.id", 1); // this.syncSeconds = getIntValue("syncSeconds", 5); // if (this.syncSeconds < 2) { // this.syncSeconds = 2; // } // if (this.syncSeconds > 60) { // this.syncSeconds = 60; // } // if (!this.enabled) { // return; // } // Pattern serverPattern = Pattern.compile("server\\.(\\d+)=([\\d\\.]+):(\\d+)"); // Pattern pattern = Pattern.compile("server\\.(\\d+)=([\\d\\.]+):(\\d+):(\\d+)"); // int masterId = Integer.MAX_VALUE; // try (BufferedReader reader = new BufferedReader(new FileReader(this.configFile))) { // String line; // while ((line = reader.readLine()) != null) { // if (line.startsWith("server.")) { // Matcher matcher = serverPattern.matcher(line); // if (matcher.matches()) { // int serverId = parseConfigData(matcher.group(1)); // String ipAddress = matcher.group(2); // int syncPort = parseConfigData(matcher.group(3)); // // if (serverId == this.serverId) { // this.ipAddress = ipAddress; // this.syncPort = syncPort; // } // if (serverId < masterId) { // masterId = serverId; // } // ClusterNode haCluster = ClusterNode.builder() // .master(false) // .id(serverId) // .ip(ipAddress) // .syncPort(syncPort) // .logging(this.logging) // .electionState(new ClusterNetState()) // .syncState(new ClusterNetState()) // .build(); // this.clusterMap.put(haCluster.getId(), haCluster); // log.info("{}", haCluster); // } // } // } // } // if (this.serverId == masterId) { // this.master = true; // } // // for (Map.Entry entry : this.clusterMap.entrySet()) { // ClusterNode cluster = entry.getValue(); // if (cluster.getId() == masterId) { // cluster.setMaster(true); // break; // } // } // } }