← wiki

Eureka — 서비스들이 서로를 이름으로 찾는 전화번호부

마이크로서비스에서 주문 서비스는 결제 서비스를 호출해야 한다. 그런데 결제 서비스는 지금 어느 IP, 어느 포트에 떠 있을까? 모놀리식이라면 같은 프로세스 안의 함수 호출이라 주소가 필요 없었다. 쪼개고 나니 이게 네트워크 통신이 되면서, "상대가 어디 있는지"를 매번 알아야 하는 문제가 새로 생긴다.

가장 단순한 답은 IP를 설정에 박아두는 것이다. 하지만 클라우드 환경에서 서비스는 수시로 죽고, 다시 뜨고, 부하에 따라 여러 개로 늘어난다. 그때마다 IP가 바뀌니, 박아둔 주소는 금세 거짓말이 된다. Eureka는 이 문제를 전화번호부로 푼다. 서비스들이 부팅하면서 "나 order-service, 여기 있어"라고 자기 위치를 등록하고, 누군가를 부를 땐 이름으로 조회해 실제 주소를 받아간다.

등록여기 있어요
헬스체크살아있나요
발견어디 있죠?
호출실제 통신
용어: 서비스 디스커버리

Eureka가 하는 일을 일반적으로 서비스 디스커버리(service discovery) 라 부른다. Eureka는 그 한 구현체(Spring Cloud의 디스커버리 컴포넌트)이고, 같은 역할을 하는 Consul·Kubernetes의 내장 디스커버리 등도 있다. 이 글은 Eureka를 예로 들지만 발상은 공통이다.


1. IP를 박으면 왜 깨지는가

해법을 보기 전에, "그냥 주소 박으면 안 되나?"에 제대로 답하자. 박힌 주소가 어떻게 거짓말이 되는지 보면 디스커버리의 필요가 분명해진다.

  1. 1
    서비스는 수시로 자리를 옮긴다

    재배포·장애 복구 때마다 새 인스턴스가 새 주소로 뜬다.

    컨테이너 오케스트레이터는 죽은 인스턴스를 버리고 다른 노드에 새로 띄운다. IP가 바뀌므로, 호출하는 쪽에 박아둔 주소는 더 이상 그 서비스를 가리키지 않는다.

  2. 2
    한 서비스가 여러 개로 늘어난다

    부하가 오르면 같은 서비스 인스턴스가 N개로 복제된다.

    이제 주소는 하나가 아니라 여러 개다. 호출하는 쪽은 그 여러 주소 중 하나를 골라 부하를 나눠야 하는데, 박아둔 단일 IP로는 불가능하다.

  3. 3
    이름으로 부르고 싶다

    주소가 아니라 'payment-service'라는 논리적 이름으로.

    호출하는 코드는 "결제 서비스를 부른다"만 알면 되고, 그게 지금 어느 IP에 몇 개 떠 있는지는 신경 쓰고 싶지 않다. 이름 → 실제 주소 변환을 누군가 대신해 주면 된다. 그게 디스커버리다.

요약하면 박힌 IP의 문제는 "주소가 변하는데 코드는 안 변한다"는 어긋남이다. 변하는 주소를 변하지 않는 이름 뒤로 숨기는 것이 디스커버리의 핵심 발상이다.


2. 등록과 발견: 두 동작의 흐름

Eureka 서버는 전화번호부 본체다. 각 서비스는 두 역할을 한다 — 자기를 등록 하는 역할(공급자), 남을 조회해 부르는 역할(소비자). 한 서비스가 둘 다 하는 경우가 많다.

order-serviceEureka 서버
등록: 나 order, 10.0.0.7:8080

부팅하며 자기 위치를 올림

주기적 하트비트

살아있음을 계속 알림

전체 등록부 내려줌

소비자들은 이 목록을 캐시

order-service (소비자)payment-service
payment-service 주소 조회

Eureka 캐시에서 이름으로 찾음

여러 인스턴스 중 하나 선택

클라이언트 측 로드밸런싱

실제 호출

고른 주소로 직접 통신

  1. 1
    등록(register)

    서비스가 부팅하며 자기 이름·주소를 Eureka에 올린다.

    전화번호부에 "이 이름은 이 번호"라고 한 줄 추가하는 것과 같다. 같은 이름으로 인스턴스가 여러 개 등록되면, 그 이름 아래 주소가 여러 개 쌓인다.

  2. 2
    발견(discover) + 로드밸런싱

    이름으로 조회해 받은 주소 목록에서 하나를 골라 호출한다.

    소비자는 등록부를 받아 캐시하고, 부를 때 여러 주소 중 하나를 분산 선택(라운드로빈 등)한다. 매번 Eureka에 묻지 않고 캐시를 쓰므로, Eureka가 잠깐 흔들려도 통신은 유지된다.

클라이언트 측 로드밸런싱

Eureka 모델에선 별도 로드밸런서 장비가 주소를 고르는 게 아니라, 호출하는 서비스 쪽 라이브러리가 목록을 받아 직접 분산한다. 중앙 병목이 줄고, 각 소비자가 자기 캐시로 빠르게 결정한다는 장점이 있다.


3. 죽은 서비스를 어떻게 걸러내나 — 하트비트

전화번호부의 가장 큰 위험은 죽은 번호가 목록에 남아 있는 것이다. 이미 내려간 서비스로 요청을 보내면 그대로 실패한다. 그래서 Eureka는 등록만 받는 게 아니라, 등록된 서비스가 살아 있는지 계속 확인한다.

  1. 1
    하트비트(heartbeat)

    각 서비스가 주기적으로 '나 살아있어'를 보낸다.

    기본적으로 수십 초마다 신호를 보낸다. Eureka는 이 신호로 그 인스턴스를 목록에 살려 둔다. 신호가 일정 시간 끊기면 죽었다고 보고 목록에서 제거(만료)한다.

  2. 2
    자기 보호 모드(self-preservation)

    네트워크가 흔들릴 때 성급히 다 지우지 않는다.

    하트비트가 한꺼번에 많이 끊기면, 서비스들이 진짜 죽은 게 아니라 네트워크 문제일 가능성이 크다. 이때 다 지워버리면 멀쩡한 서비스까지 못 찾게 되므로, Eureka는 보수적으로 등록을 유지한다 — "확실치 않으면 지우지 않는다"는 안전장치다.

목록은 '최신'이 아니라 '거의 최신'이다

하트비트와 캐시 때문에, Eureka의 목록은 실시간으로 완벽하지 않다. 방금 죽은 인스턴스가 잠깐 목록에 남아 있을 수 있다. 그래서 디스커버리만 믿지 말고, 호출이 실패하면 다른 인스턴스로 재시도하는 회복 로직(회로 차단기·재시도)을 함께 둔다. 디스커버리는 "대략 맞는 목록"을 주고, 최종 안전망은 호출하는 쪽이 갖춘다.

정리하면 Eureka는 변하는 주소를 변하지 않는 이름 뒤에 숨기는 전화번호부다. 서비스가 자기를 등록하고, 하트비트로 생사를 알리고, 소비자는 이름으로 조회해 여러 주소 중 하나를 골라 부른다. 목록이 "거의 최신"이라는 한계는, 실패 시 재시도라는 호출 측 안전망으로 메운다.


한 장 요약

  1. 1
    문제

    서비스는 수시로 죽고 늘어나 IP가 변한다 — 박아둔 주소는 곧 거짓말.

  2. 2
    전화번호부

    Eureka에 서비스들이 이름·주소를 등록하고, 이름으로 서로를 조회한다.

  3. 3
    등록·발견

    공급자는 자기를 올리고, 소비자는 받은 목록에서 하나를 골라 호출(클라 측 LB).

  4. 4
    하트비트

    주기 신호로 생사 확인 — 끊기면 목록에서 제거, 단 네트워크 흔들림엔 보수적.

  5. 5
    한계

    목록은 '거의 최신' — 실패 시 재시도/회로 차단기로 호출 측이 보완.

  6. 6
    본질

    변하는 주소를 변하지 않는 이름 뒤에 숨겨, 코드가 위치에 묶이지 않게 한다.