Backend

서버 2대중 1대만 특정 태스크를 (알람) 하도록 하는 법

!쪼렙조햄 2022. 9. 14. 14:48
반응형

1차 시도

master_server db 를 만들어서
마스터가 될 ip 정보를 넣어두었다

그리고 서버들은 1분마다
아래 체크를 해서 
본인이 마스터인지 확인을 했다

    @Transactional(isolation = Isolation.SERIALIZABLE)
    fun checkIfMasterServer(): Boolean {

        val masterServerEntity = masterServerRepository.findByIdOrNull(1) ?: MasterServerEntity(1)

        val expireAt = masterServerEntity.expireAt
        if (expireAt?.isBefore(LocalDateTime.now()) != false) {
            masterServerEntity.ip = InetAddress.getLocalHost().hostAddress
            masterServerEntity.expireAt = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES).plusMinutes(2)

            masterServerRepository.save(masterServerEntity)
            println("오 마스터 자리 있당 : ${InetAddress.getLocalHost().hostAddress}")
        } else {
            //todo
            println("이미 자리를 먹혔군")
        }

        val nowMasterServer = masterServerRepository.findByIdOrNull(1)

        return nowMasterServer?.ip == InetAddress.getLocalHost().hostAddress
    }

그러나 2대의 서버가 자꾸
본인을 마스터라고 우기는 현상 발생

원인은
두 서버가 master_server 의 첫번째 줄이 비어있는걸 동시에 보고
본인들의 ip 를 넣은뒤
본인들이 마스터라고 자축한것;

두 서버가 동시에 첫번째 줄이 비어있다고 보는 현상을 막기 위해
isolation 레벨을 가장 높은 Serializable 로 바꿨으나
이 현상이 계속 되었다

2차 시도

이번에는 서버들이 본인 ip 와
본인이 살아있음을 알리기 위해
health_check_at 컬럼 값을 당시 시간으로 1분마다 업데이트 하도록 했다
(master_server_health 테이블)

그리고 기존 master_server 테이블에는
health_check_at 봤을때 살아있는 애들 중에
(최근 2분안의 값인 경우)

ip 로 정렬해서 하나 뽑아서 값을 넣도록 했다
이러면 여러 서버들이 ip를 넣어도 다 같은걸 넣겠찌?!

    fun checkIfMasterServer(): Boolean {
        return masterServerRepository.findByIdOrNull(1)?.ip == InetAddress.getLocalHost().hostAddress
    }
    
    
    @Scheduled(cron = "0 * * * * *")
    fun serverHealthCheck() {
        val ip = InetAddress.getLocalHost().hostAddress
        val entity = masterServerHealthCheckRepository.findByIdOrNull(ip)
            ?: MasterServerHealthCheckEntity(ip)
        entity.healthCheckAt = LocalDateTime.now()
        masterServerHealthCheckRepository.save(entity)

        val masterIp =
            masterServerHealthCheckRepository.findAllByHealthCheckAtAfterOrderByIp(LocalDateTime.now().minusMinutes(3))
                .first()
                .ip

        //todo
        println("제가 찾아낸 masterIp 는 바로 : $masterIp")
        val masterEntity = masterServerRepository.findByIdOrNull(1)
            ?: MasterServerEntity(1)
        masterEntity.ip = masterIp
        masterServerRepository.save(masterEntity)

        val expiredIps = masterServerHealthCheckRepository.findAllByHealthCheckAtBeforeOrderByIp(
            LocalDateTime.now().minusMinutes(10)
        ).map { it.ip }
        try {
            masterServerHealthCheckRepository.deleteAllById(expiredIps)
        } catch (_: Exception) {
        }
    }

드디어 모두가...
입을 모아 동일한 마스터를 찾기 시작...
문제 해결

반응형