AbstractClusterConfig.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. package com.its.common.cluster.config;
  2. import com.its.common.cluster.vo.ClusterNET;
  3. import com.its.common.cluster.vo.ClusterNode;
  4. import io.netty.util.AttributeKey;
  5. import lombok.Data;
  6. import lombok.extern.slf4j.Slf4j;
  7. import javax.annotation.PostConstruct;
  8. import java.io.IOException;
  9. import java.util.*;
  10. import java.util.stream.Collectors;
  11. @Slf4j
  12. @Data
  13. public abstract class AbstractClusterConfig {
  14. public static final AttributeKey<ClusterNode> CLUSTER_ATTRIBUTE_KEY = AttributeKey.valueOf("clusterInfo");
  15. private boolean enabled = true; // 클러스터 기능 사용 여부
  16. // 어플리케이션 클러스터 정보
  17. private boolean master = true;
  18. private int id = -1; // 서버 ID (1부터 시작, 0은 사용하지 않음)
  19. private boolean autoFailbackEnabled = false; // client 모드시 작업을 바로 넘겨주는 옵션(기본 false)
  20. private int masterId = -1;
  21. private int syncSeconds = 5; // 데이터 동기화 주기 (초 단위, 기본 5초, 최소 2초, 최대 60초)
  22. private String ip = "127.0.0.1"; // 클러스터 서버의 IP 주소
  23. private int port = 13888; // 데이터 동기화를 위한 포트
  24. private boolean logging = false; // 라이브러리 내 로깅 여부
  25. private boolean packetLogging = false; // 패킷 로깅 여부
  26. private int electionScheduleSeconds = 2; // 마스터 선출 스케줄 실행 주기 (단위: 초, 기본값 2초)
  27. private int connectTimeoutSeconds = 5; // 클라이언트(Slave)가 서버(Master)에 연결을 시도할 때의 타임아웃 (단위: 초, 기본값 5초)
  28. private boolean witnessNode = false; // 이 서버 인스턴스가 Witness 노드인지 여부
  29. private List<ClusterNode> nodes;
  30. private ClusterNode myCluster;
  31. private final HashMap<Integer, ClusterNode> clusterMap = new HashMap<>();
  32. @PostConstruct
  33. private void init() throws IOException {
  34. if (this.nodes == null) {
  35. this.nodes = new ArrayList<>();
  36. }
  37. if (this.electionScheduleSeconds < 2) {
  38. this.electionScheduleSeconds = 2;
  39. }
  40. if (this.electionScheduleSeconds > 10) {
  41. this.electionScheduleSeconds = 10;
  42. }
  43. }
  44. public boolean isClusterAlive(int clusterId) {
  45. ClusterNode clusterNode = this.clusterMap.get(clusterId);
  46. if (clusterNode == null) {
  47. return true;
  48. }
  49. return clusterNode.getElectionState().getState() != ClusterNET.CLOSED;
  50. }
  51. public ClusterNode getClusterNode(int clusterId) {
  52. return this.clusterMap.get(clusterId);
  53. }
  54. public void loggingInfo() {
  55. log.info("Cluster config enabled: {}", this.enabled);
  56. log.info(" id: {}", this.id);
  57. log.info(" master: {}", this.master);
  58. log.info(" syncSeconds: {}", this.syncSeconds);
  59. log.info(" ip: {}", this.ip);
  60. log.info(" port: {}", this.port);
  61. log.info(" logging: {}", this.logging);
  62. log.info(" packetLogging: {}", this.packetLogging);
  63. log.info(" node: {} EA.", this.nodes.size());
  64. for (ClusterNode node : this.nodes) {
  65. log.info(" ----------------node id: {}", node.getId());
  66. log.info(" master: {}", node.isMaster());
  67. log.info(" ip: {}", node.getIp());
  68. log.info(" port: {}", node.getPort());
  69. log.info(" logging/packetLogging: {}/{}", node.isLogging(), node.isPacketLogging());
  70. }
  71. }
  72. private boolean setDefaults() {
  73. // 클러스터가 하나라 단일 클러스터 정보로 설정
  74. this.master = true;
  75. this.id = 1;
  76. this.masterId = 1;
  77. this.syncSeconds = 5;
  78. this.ip = "127.0.0.1";
  79. this.port = 13888;
  80. this.logging = false;
  81. this.packetLogging = false;
  82. // 단일 노드 정보 설정
  83. this.nodes = new ArrayList<>();
  84. ClusterNode masterNode = new ClusterNode();
  85. masterNode.setMaster(this.master);
  86. masterNode.setId(this.id);
  87. masterNode.setIp(this.ip);
  88. masterNode.setPort(this.port);
  89. masterNode.setLogging(this.logging);
  90. masterNode.setPacketLogging(this.packetLogging);
  91. masterNode.setWitness(false);
  92. this.nodes.add(masterNode);
  93. this.myCluster = masterNode;
  94. this.enabled = false;
  95. // 나 자신의 네트워크 상태정보를 항상 CONNECT로 지정
  96. masterNode.getElectionState().connect(null);
  97. masterNode.getSyncState().connect(null);
  98. return true;
  99. }
  100. public boolean validateClusterInfo() {
  101. if (!this.enabled) {
  102. return setDefaults();
  103. }
  104. if (this.nodes == null || this.nodes.isEmpty()) {
  105. log.error("클러스터 노드 리스트가 비어 있습니다.");
  106. return false;
  107. }
  108. if (this.nodes.size() == 1) {
  109. int firstNodeId = this.nodes.get(0).getId();
  110. if (firstNodeId == 1) {
  111. return setDefaults();
  112. }
  113. log.error("클러스터 노드가 1개 인데 노드 ID 값이 1 이 아닙니다.: {}", firstNodeId);
  114. log.error("클러스터 노드가 1개 인 경우 클러스터 사용을 false 로 설정하세요.");
  115. return false;
  116. }
  117. // ID만 추출해서 정렬
  118. List<Integer> ids = this.nodes.stream()
  119. .map(ClusterNode::getId)
  120. .sorted()
  121. .collect(Collectors.toList());
  122. // 중복 제거 후 크기 비교
  123. Set<Integer> uniqueIds = new HashSet<>(ids);
  124. if (uniqueIds.size() != ids.size()) {
  125. log.error("클러스터 노드 ID에 중복이 있습니다: {}", ids);
  126. return false;
  127. }
  128. // 순차성 검증: 1부터 시작해서 1씩 증가해야 함
  129. for (int ii = 0; ii < ids.size(); ii++) {
  130. int expected = ii + 1;
  131. if (ids.get(ii) != expected) {
  132. log.error("클러스터 노드 ID가 순차적이지 않습니다. 예상: {}, 실제: {}", expected, ids.get(ii));
  133. return false;
  134. }
  135. }
  136. // 검증 통과, 클러스터 정보 설정
  137. if (this.syncSeconds < 2) {
  138. this.syncSeconds = 2;
  139. }
  140. if (this.syncSeconds > 30) {
  141. this.syncSeconds = 30;
  142. }
  143. // Master and host information setting
  144. int masterId = Integer.MAX_VALUE;
  145. for (ClusterNode node : this.nodes) {
  146. this.clusterMap.put(node.getId(), node);
  147. int nodeId = node.getId();
  148. if (nodeId == this.id) {
  149. this.ip = node.getIp();
  150. this.port = node.getPort();
  151. }
  152. if (nodeId < masterId) {
  153. masterId = nodeId;
  154. }
  155. }
  156. // Master setting
  157. ClusterNode masterNode = this.clusterMap.get(masterId);
  158. masterNode.setMaster(true);
  159. this.master = (this.id == masterId);
  160. this.masterId = masterId;
  161. // 나 자신의 네트워크 상태정보를 항상 CONNECT로 지정
  162. ClusterNode localNode = this.clusterMap.get(this.id);
  163. localNode.getElectionState().connect(null);
  164. localNode.getSyncState().connect(null);
  165. this.myCluster = localNode;
  166. return true;
  167. }
  168. public ClusterNode get(String ipAddress) {
  169. for (Map.Entry<Integer, ClusterNode> entry : this.clusterMap.entrySet()) {
  170. ClusterNode cluster = entry.getValue();
  171. if (cluster.getIp().equals(ipAddress)) {
  172. return cluster;
  173. }
  174. }
  175. return null;
  176. }
  177. private int parseConfigData(String data) {
  178. try {
  179. return Integer.parseInt(data);
  180. }
  181. catch (NumberFormatException e) {
  182. return -1;
  183. }
  184. }
  185. // private String getStringValue(String item, String defValue) throws IOException {
  186. // if (!this.enabled) {
  187. // return defValue;
  188. // }
  189. // Pattern stringPattern = Pattern.compile(item + "=([a-zA-Z0-9]+)");
  190. // try (BufferedReader reader = new BufferedReader(new FileReader(this.configFile))) {
  191. // String line;
  192. // while ((line = reader.readLine()) != null) {
  193. // Matcher matcher = stringPattern.matcher(line);
  194. // if (matcher.matches()) {
  195. // return matcher.group(1);
  196. // }
  197. // }
  198. // }
  199. // return defValue;
  200. // }
  201. // private int getIntValue(String item, int defValue) throws IOException {
  202. // if (!this.enabled) {
  203. // return defValue;
  204. // }
  205. // Pattern serverIdPattern = Pattern.compile(item+"=(\\d+)");
  206. // try (BufferedReader reader = new BufferedReader(new FileReader(this.configFile))) {
  207. // String line;
  208. // while ((line = reader.readLine()) != null) {
  209. // Matcher matcher = serverIdPattern.matcher(line);
  210. // if (matcher.matches()) {
  211. // return parseConfigData(matcher.group(1));
  212. // }
  213. // }
  214. // }
  215. // return defValue;
  216. // }
  217. // private void setServerId() throws IOException {
  218. // Pattern serverIdPattern = Pattern.compile("server\\.id=(\\d+)");
  219. // try (BufferedReader reader = new BufferedReader(new FileReader(this.configFile))) {
  220. // String line;
  221. // while ((line = reader.readLine()) != null) {
  222. // Matcher matcher = serverIdPattern.matcher(line);
  223. // if (matcher.matches()) {
  224. // this.serverId = parseConfigData(matcher.group(1));
  225. // break;
  226. // }
  227. // }
  228. // }
  229. // }
  230. // private void loadClusterConfig() throws IOException {
  231. // this.serverId = getIntValue("server.id", 1);
  232. // this.syncSeconds = getIntValue("syncSeconds", 5);
  233. // if (this.syncSeconds < 2) {
  234. // this.syncSeconds = 2;
  235. // }
  236. // if (this.syncSeconds > 60) {
  237. // this.syncSeconds = 60;
  238. // }
  239. // if (!this.enabled) {
  240. // return;
  241. // }
  242. // Pattern serverPattern = Pattern.compile("server\\.(\\d+)=([\\d\\.]+):(\\d+)");
  243. // Pattern pattern = Pattern.compile("server\\.(\\d+)=([\\d\\.]+):(\\d+):(\\d+)");
  244. // int masterId = Integer.MAX_VALUE;
  245. // try (BufferedReader reader = new BufferedReader(new FileReader(this.configFile))) {
  246. // String line;
  247. // while ((line = reader.readLine()) != null) {
  248. // if (line.startsWith("server.")) {
  249. // Matcher matcher = serverPattern.matcher(line);
  250. // if (matcher.matches()) {
  251. // int serverId = parseConfigData(matcher.group(1));
  252. // String ipAddress = matcher.group(2);
  253. // int syncPort = parseConfigData(matcher.group(3));
  254. //
  255. // if (serverId == this.serverId) {
  256. // this.ipAddress = ipAddress;
  257. // this.syncPort = syncPort;
  258. // }
  259. // if (serverId < masterId) {
  260. // masterId = serverId;
  261. // }
  262. // ClusterNode haCluster = ClusterNode.builder()
  263. // .master(false)
  264. // .id(serverId)
  265. // .ip(ipAddress)
  266. // .syncPort(syncPort)
  267. // .logging(this.logging)
  268. // .electionState(new ClusterNetState())
  269. // .syncState(new ClusterNetState())
  270. // .build();
  271. // this.clusterMap.put(haCluster.getId(), haCluster);
  272. // log.info("{}", haCluster);
  273. // }
  274. // }
  275. // }
  276. // }
  277. // if (this.serverId == masterId) {
  278. // this.master = true;
  279. // }
  280. //
  281. // for (Map.Entry<Integer, ClusterNode> entry : this.clusterMap.entrySet()) {
  282. // ClusterNode cluster = entry.getValue();
  283. // if (cluster.getId() == masterId) {
  284. // cluster.setMaster(true);
  285. // break;
  286. // }
  287. // }
  288. // }
  289. }