콘텐츠로 이동

Channel 동작 원리

가이드 홈 | 이전: Relocation | 다음: Handler와 메시지 처리

이 장을 읽고 나면

각 구성이 어떤 연결을 열고, 누구를 대상으로 고르고, 언제 거부되는지 알 수 있다. 이 장의 코드는 예제 저장소의 tutorial과 TicTacToe·ZoneWorld 샘플 README에서 가져왔으며, 각 README의 「내려받기와 설치」·「빌드」·「실행」 절을 따르면 연결과 대상 선택을 재현할 수 있다.

Channel 메시징이 등록하고 호출하는 방법을 다뤘다면, 이 장은 왜 그렇게 되는지와 어디까지 되는지를 다룬다. 패턴 사이의 차이, 대상 선택 규칙, 연결과 discovery, 시작 단계 검증, 그리고 실패했을 때 호출한 쪽이 보는 결과를 설명한다.

1. 패턴별 비교

RouteMesh ClientServer Fanout
호출 방향 양쪽이 서로 호출한다 client에서 server로만 publisher에서 subscriber로만
수신자를 정하는 주체 Framework 호출하는 쪽이 연결한 server 집합 정하지 않는다
한 호출이 도달하는 node 담당 node 하나 선택된 server 하나 구독한 전부
사용 가능한 호출 request · send request · send publish
응답 request만 받는다 request만 받는다 없다
연결 mesh peer 연결 하나를 여러 channel이 공유 server가 공개한 endpoint로 client가 연결 publisher PUB endpoint마다 subscriber SUB socket
보내는 node 자신이 Server일 때 후보가 아니다 다른 server와 같은 후보다 해당 없음
후보가 없을 때 즉시 대상 없음으로 실패 잠깐 기다린 뒤 실패 받는 node 없이 성공
손실 없다 없다 느린 구독자 몫을 버린다(NoDrop이면 오류로 끝난다)

호출 코드는 RouteMesh와 ClientServer가 같다. send_to_channel·request_to_channel은 ChannelName 하나로 process-local 송신 경로를 고르며, 그 경로가 RouteMesh인지 ClientServer인지는 등록이 정한다. 두 패턴을 바꾸는 작업은 등록만 바꾸는 작업이다.

2. 물리 배선 — 구성마다 다른 socket을 연다

각 구성은 이름만 다른 것이 아니라 서로 다른 socket을 연다. 한 process가 이들을 모두 사용하면 listener도 셋이 따로 생긴다.

↗ 크게 보기

▶ 둘러보기에서 직접 눌러 보기 — RouteMesh·ClientServer·Fanout 배선

여는 listener 연결 단위 channel을 늘리면
RouteMesh MeshNode마다 ROUTER 하나 node 사이 peer 연결 socket이 늘지 않는다
ClientServer Server channel마다 하나 channel 전용 연결 channel 수만큼 listener가 는다
Fanout publisher마다 PUB 하나 publisher ↔ subscriber SUB publisher 수만큼 는다

2.1 RouteMesh — 연결 하나를 여러 channel이 공유한다

MeshNode는 routing id 하나와 peer가 연결할 ROUTER endpoint 하나를 가진다. 그 위에 등록한 ChannelName이 몇 개든 같은 ROUTER 연결을 함께 사용한다. ChannelName을 추가해도 socket이나 node 사이 연결은 추가되지 않는다.

그래서 mesh에 이미 참여한 node끼리는 channel을 새로 만드는 비용이 이름 등록뿐이다. 반대로 mesh에 참여하지 않은 process는 그 channel을 호출할 수 없다.

수동 연결은 연결할 endpoint를 MeshNode에 등록한다. endpoint만 적을 수도 있고, 그 자리에 있어야 할 node의 routing id를 함께 적을 수도 있다.

mesh.peer_connections ().connect (zlink::routing_id_t::from (sample_names_t::play_a_rid),
                                  topology.play_a_route_endpoint);
mesh.peer_connections ().connect (zlink::routing_id_t::from (sample_names_t::play_b_rid),
                                  topology.play_b_route_endpoint);

2.2 ClientServer — channel마다 자기 listener를 연다

ClientServer Server는 mesh와 무관하게 자기 포트를 열고 주소를 알린다. channel이 둘이면 listener도 둘이고, 두 channel은 연결 대상과 수명을 공유하지 않는다. 같은 process가 mesh에도 참여한다면 ROUTER listener는 그것대로 따로 있다.

client는 그 주소로 직접 연결하므로 mesh에 참여하지 않아도 호출할 수 있다. 외부 팀이 만든 서비스나 별도 배포 단위를 호출할 때 이 구성을 사용하는 이유다.

2.3 Fanout — PUB/SUB 소켓 쌍을 따로 연다

Fanout publisher는 PUB listener를 열고, subscriber는 publisher endpoint마다 전용 SUB socket을 사용한다. MeshNode나 Spot과 소켓을 공유하지 않는다. 그래서 mesh 구성과 상관없이 전달되고, Logical Multicast와 대상 집합도 공유하지 않는다.

RouteMesh·ClientServer·fanout publisher·STREAM server는 모두 별개의 listener이며 process 기본 network 값을 함께 사용한다. 특정 listener에만 다른 bind·advertise 주소가 필요하면 그 listener에 override를 지정한다.

3. 호출 방향

3.1 RouteMesh — role 등록이 방향을 정한다

RouteMesh 자체에는 방향이 없다. 방향은 node가 ChannelName마다 등록한 role이 정한다.

  • Server()를 등록한 node만 그 channel의 호출을 받는다.
  • Client()만 등록한 node는 호출을 시작할 수 있지만 받지는 않는다.
  • Server() role은 송신 기능을 포함한다. 같은 ChannelName에 Client()를 다시 등록하지 않는다.

한 MeshNode 위에 channel을 여러 개 등록할 수 있고 channel마다 role이 다를 수 있다.

mesh.channel (names_t::zone_channel).server ();
mesh.channel (names_t::report_channel).client ();

그래서 A가 api의 Server이고 B가 billing의 Server이면 A→B와 B→A가 모두 성립한다. 두 호출은 같은 peer 연결을 사용하지만 서로 다른 channel이다. channel 하나가 양방향이 되는 것이 아니라, 두 channel이 서로 반대 방향으로 등록된 것이다.

role 목록은 startup 뒤에 바꿀 수 없다. 실행 중에 바꿀 수 있는 것은 Server membership의 weight뿐이다 — 실행 중에 새 요청만 멈추기.

3.2 ClientServer — 방향이 고정된다

ClientServer는 단방향 service 경계다. 다음이 함께 고정된다.

고정되는 것 내용
업무 호출 방향 server는 client를 대상으로 새 호출을 시작하지 않는다
연결 시작 방향 manual·automatic discovery 모두 client만 server로 connection을 시작한다

client가 server에서 받는 것은 자신이 시작한 request에 대응하는 reply뿐이다. client request 없이 server가 먼저 보낸 message는 받지 않는다.

반대 방향 호출이 필요하면 반대쪽 node가 자신의 endpoint를 공개하는 server가 되어야 하며, 그것은 별도 ChannelName을 가진 별도 channel이다.

한 process가 같은 ChannelName에 Client와 Server를 각각 한 번씩 등록할 수는 있다. 모니터링 snapshot의 client_and_server는 그 두 registration이 함께 있다는 표현이며 별도 role이 아니다.

4. ToChannel 호출의 대상 node 선택

send_to_channel·request_to_channel이 어느 node로 갈지 정하는 규칙이다. RouteMesh와 ClientServer 두 구성에만 해당한다. node를 직접 지정하는 호출과 Spot·Actor 호출은 대상이 이미 정해져 있으므로 선택이 없고, publish는 대상을 고르지 않는다.

4.1 대상 선택 — 같은 process의 Server를 RouteMesh에서만 제외한다

한 process가 호출하는 쪽이면서 동시에 그 channel을 처리하는 쪽일 수 있다. 이때 자기 process의 Server를 후보로 볼지는 두 구성이 다르다.

RouteMesh ClientServer
같은 process에 그 channel의 Server 등록이 함께 있을 때 후보가 아니다 다른 server와 같은 후보다
그 process 말고 다른 곳에 Server가 없으면 대상 없음으로 실패한다 자기 process의 server가 선택된다
후보가 아직 하나도 없을 때 즉시 실패한다 잠깐 기다린 뒤 실패한다

ClientServer는 한 process가 같은 ChannelName에 Client와 Server를 각각 한 번씩 등록할 수 있다. 그러면 그 process는 호출도 하고 처리도 한다. 이때 자기 process의 server는 remote server와 같은 조건(준비 상태·weight·종료 절차 여부)으로 후보에 들어가며, local이라고 우선하거나 remote를 빼지 않는다.

선택되더라도 handler를 직접 호출하지 않는다. 정규 송신 경로를 그대로 거치므로 codec, timeout, 취소, correlation이 remote와 똑같이 적용된다. local handler를 곧바로 호출하는 별도 경로는 제공하지 않는다.

RouteMesh가 자기 process를 빼는 이유는 구조에 있다. channel 등록은 새 socket을 만들지 않고 이미 있는 peer 연결을 사용하는데, MeshNode는 자기 자신과 peer 연결을 맺지 않는다. 그래서 그 channel의 Server가 자기 process에만 있으면 보낼 경로가 없다. 같은 process에서 처리되게 하려면 ClientServer를 사용한다.

기다리는 쪽도 이유가 있다. RouteMesh에서 후보가 없다는 것은 그 이름을 게시한 peer가 없다는 뜻이라 기다려도 생기지 않는다. ClientServer에서는 같은 process에 설정이 이미 있고 준비만 안 끝났을 수 있다. 그래서 호출의 timeout과 5초 중 짧은 쪽만큼 기다린다. 이 대기는 준비를 앞당기지 않고 진행 중인 준비가 끝나기를 기다릴 뿐이다.

4.2 후보 사이의 분배 — 기본값은 round-robin, 값을 바꾸면 가중치 분배

먼저 후보에서 빠지는 대상이 있다. 두 구성에서 같다.

제외되는 대상 이유
ready가 아닌 target 초기화와 discovery 기록이 끝나지 않았다
weight가 0인 target membership은 유지하되 새 선택의 후보가 아니다
안전 종료 절차에 들어간 target 남은 요청을 처리하고 내려가는 중이다

위 대상을 뺀 뒤 남는 대상이 하나도 없으면 request와 one-way send는 모두 NotFound로 끝난다. 고를 대상이 없다는 결과는 두 호출에서 같다.

남은 대상 사이의 분배는 weight가 정한다. weight는 0..10000 범위이고 기본값은 100이다.

값을 지정하지 않으면 모두 100이므로 균등 round-robin이 된다. 서버를 몇 대 띄우든 새 요청이 돌아 가며 분배된다는 뜻이다. 대부분의 구성은 여기서 끝난다.

값을 다르게 주면 그 비율로 분배된다. 두 후보가 100과 300이면 길게 보아 약 1:3이다 — 호출 하나하나의 순서를 보장한다는 뜻은 아니다. 사양이 다른 장비를 섞어 사용하거나 한 대에 트래픽을 덜 보내야 할 때 사용한다.

초기값은 등록할 때 정한다. channel role을 등록하는 자리에서 set_weight(...)로 준다. 값을 주지 않으면 100이다. 실행 중 변경은 실행 중에 새 요청만 멈추기가 다룬다.

↗ 크게 보기

4.3 실행 중에 새 요청만 멈추기

유지보수나 순차 재시작을 앞두고, node를 내리지 않은 채 새 요청만 받지 않게 하고 싶을 때가 있다. weight를 0으로 바꾸면 된다. 실행 중에 바꿀 수 있는 값은 이것 하나다. RouteMesh runtime 옵션을 주입받아 ChannelName으로 지정한다.

언어별 실행 코드는 Options의 runtime weight 예제를 따른다.

  • Weight = 0은 serving socket을 닫지 않는다. 이미 들어온 요청은 끝까지 처리하고 응답하며, 다른 node가 이 node를 새 요청 대상에서만 뺀다. Location Store의 등록 정보도 그대로 남는다.
  • 그 channel을 맡은 node가 이 하나뿐이면 새 호출이 Unavailable로 끝난다. 후보가 비는 것이지 경로가 끊긴 것이 아니다 — 후보 사이의 분배.
  • Weight = 100이 정상 복귀다.
  • 전파는 즉시가 아니다. 보장되는 것은 신호를 보냈다는 것까지이고, 다른 node가 실제로 후보에서 뺐는지는 그 node의 상태로 확인한다(모니터링).
  • 운영에서는 이 두 동작을 흔히 drain·restore라고 한다. application의 관리 API가 Weight = 0·= 100에 그 이름을 붙인 것이며, Framework가 그 이름의 API를 제공하지는 않는다.

4.4 같은 ChannelName을 여러 node로 늘리기

처리량을 늘리려면 같은 MeshName과 ChannelName을 맡은 provider를 여러 개 실행한다.

처리하는 쪽은 바꿀 것이 없다. 같은 ChannelName을 Server()로 등록한 process를 한 대 더 띄우면 된다. 등록 코드는 node 수와 무관하게 같다.

호출하는 쪽은 Location Store를 사용하면 역시 바꿀 것이 없다. Store가 새 provider의 등록 정보를 갖고 있으므로 후보가 저절로 늘어난다.

주소를 직접 적는 구성이라면 provider endpoint를 모두 등록한다. 등록하는 코드 형태는 RouteMesh — 연결 하나를 여러 channel이 공유한다에서 본 수동 연결과 같으며, 연결할 endpoint만 여러 개로 늘어난다.

이 경우 provider를 늘릴 때마다 호출하는 쪽 설정을 고치고 다시 시작해야 한다. 그래서 node 수가 변하는 구성에는 Location Store를 사용한다.

등록되지 않은 ChannelName은 다른 곳에서 찾아 주지 않는다. 같은 process에 다른 MeshNode나 ClientServer client가 있어도 그쪽으로 대신 보내지 않는다. 반대로 한 process에서 같은 ChannelName을 RouteMesh와 ClientServer 양쪽에 등록하는 것도 시작할 때 거부된다 — 이름 하나가 송신 경로 하나만 가리켜야 한다.

특정 엔티티(주문 ID·사용자 ID)를 늘 같은 실행 단위가 처리해야 하면 channel이 아니라 Spot이나 actor를 사용한다(Spot).

4.5 Spot handler가 시작하는 channel 호출

Spot handler와 timer는 channel send · request를 시작할 수 있다. 그 Spot을 소유한 MeshNode에 해당 ChannelName이 없어도 된다 — 같은 process에 그 이름의 송신 경로가 하나라도 등록되어 있으면 사용할 수 있다. 다른 RouteMesh의 경로여도, ClientServer client의 경로여도 된다.

같은 process에 없으면 거기서 끝난다. 다른 process나 다른 MeshNode를 중계로 삼아 찾아 주지 않으며 NotFound로 끝난다. 그래서 Spot을 배치할 node를 정할 때 그 Spot이 호출할 channel의 송신 경로가 같은 process에 등록되어 있는지를 함께 본다.

5. pub/sub의 형태

발행에는 대상 선택이 없다. 구독한 쪽이 받을 뿐이다. 그 발행·구독의 형태는 다음과 같이 나뉜다. 이름이 비슷해서 섞이기 쉬운데 소켓·대상 범위·손실 규칙이 모두 다르다.

Logical Multicast Classic fanout
소켓 이미 연결된 mesh 소켓을 그대로 사용한다 독립 PUB/SUB 소켓 쌍을 연다
받는 대상 그 channel에서 같은 topic을 구독한 Spot 연결된 구독자 전원
mesh 구성과의 관계 그 mesh 안으로 한정된다 무관하다
손실 해당 없음 허용한다(NoDrop이면 오류로 끝난다)
filter 실행되지 않는다 실행된다
등록 위치 Spot이 시작할 때 fanout channel builder

5.1 Logical Multicast — mesh 위의 Spot 사이 이벤트

Spot은 id로 찾는 상태 객체이고 자기 앞으로 온 일을 한 줄로 세워 처리한다. RouteMesh channel 위에서 그 Spot끼리 이벤트를 주고받는 것을 Logical Multicast라 한다. 별도 소켓이 없고, 받는 쪽은 그 channel에서 같은 topic을 구독한 Spot으로 한정된다.

▶ 둘러보기에서 직접 눌러 보기 — RouteMesh pub/sub

co_await _context.publish (sample_names_t::player_milestone_topic, milestone_event)
  .async ();

발행 호출은 topic을 받는다. ChannelName까지 함께 받는 언어도 있고, Spot이 등록된 mesh를 그대로 사용하는 언어도 있다 — 위 탭에서 그 언어의 것을 본다.

구독은 그 topic을 받을 handler를 Spot이 시작할 때 등록해 연다. 등록에 topic을 적는 자리가 언어마다 다르다 — 등록 호출의 인자로 넘기거나, handler 타입에 표시한다.

_context.handlers ().add_subscribe<&tictactoe_entry_spot_t::on_player_win_milestone> (
  sample_names_t::player_milestone_topic);

Spot 밖에서 발행해야 하면 spot publisher client를 주입받아 같은 방식으로 보낸다.

5.2 Classic fanout — 손실을 허용하는 전달

fanout channel은 그 자체로 독립된 PUB/SUB 소켓 쌍을 연다. Spot이나 MeshNode와 무관하게 발행자 하나가 연결된 구독자 전원에게 전달한다.

손실 규칙이 다르다. 어느 구독자의 수신이 늦어 발행자의 송신 queue가 상한(SendHighWaterMark)에 닿으면 그 구독자 몫을 버리고 발행은 성공으로 끝난다. 나머지 구독자는 영향을 받지 않고, 발행자는 느린 구독자 하나 때문에 멈추지 않는다.

느린 구독자 몫도 버리지 않으려면 NoDrop을 켠다. 그러면 느린 구독자의 backpressure가 발행을 기다리게 하고, deadline 안에 비워지지 않으면 발행은 오류로 끝난다.

// The publisher keeps no subscriber list. Subscribers may come and go with
// no change here. A concrete bind host is required: a wildcard one leaves
// subscribers with no address to dial and is rejected at startup.
options.add_fanout_channel ("broadcast")
  .enable_publisher ("tcp://127.0.0.1:7412")
  .set_no_drop (true);

NoDrop은 publisher capability가 있는 channel에서만 설정한다. Logical Multicast도 저장·재전송·ack는 제공하지 않는다.

5.3 Topic — 두 형태 모두 수신 대상을 고른다

topic은 두 형태에서 모두 수신 대상을 고른다. Logical Multicast는 Spot을 고르고, Classic fanout은 subscriber socket을 고른다.

Logical Multicast Classic fanout
구독 등록 ChannelName + topic ChannelName + topic
topic이 전달을 거르는가 거른다. 같은 topic을 구독한 Spot만 받는다 거른다. 같은 topic을 구독한 subscriber만 받는다
받은 뒤 해당 Spot의 구독 handler가 처리한다 packet 이름으로 handler를 찾고, 없으면 버린다

Classic fanout subscriber는 받을 topic을 subscribe(topic)으로 등록한다. 같은 topic에 발행한 event만 그 subscriber까지 전달된다.

// A fanout handler is registered through a named group, and the channel
// then maps that group. Without a Location Store the subscriber is told
// the publisher's endpoint outright; connect(...) is what makes this
// subscriber manual, so enable_subscriber() must not be added next to it.
options.handlers ().group ("broadcast").add_publish<maintenance_notice_subscriber_t> ();

options.add_fanout_channel ("broadcast")
  .connect ("tcp://127.0.0.1:7412")
  .subscribe (maintenance_notice_t::packet_name)
  .use_handler_group ("broadcast");

handler가 함께 받는 publish context에는 도착한 topic도 들어 있으므로, 하나의 handler가 여러 topic을 등록해 나누어 처리할 수도 있다.

5.4 발행할 때 topic을 정하는 방법

  • Publish(channelName, message) — topic을 지정하지 않는다. 이때 event의 packet 이름이 topic이 된다.
  • Publish(channelName, topic, message) — topic을 직접 정한다.
  • 예약된 topic을 넘기면 ArgumentException으로 거부한다. Framework가 연결 상태 확인에 사용하는 값이다.

두 형태 모두 subscriber별 acknowledgement나 replay state를 갖지 않는다. 구독자가 연결되지 않은 동안 발행된 event는 나중에 전달되지 않는다.

6. 연결과 discovery

주소를 공개하는 쪽 연결을 시작하는 쪽 discovery
RouteMesh 양쪽 node가 각자 endpoint를 연다 두 node가 동시에 시작하지 않도록 한쪽이 정해진다 MeshNode descriptor
ClientServer server client manual connect 또는 Location Store의 server descriptor
Fanout publisher subscriber Location Store의 publisher descriptor

6.1 Location Store — 누가 어디 있는지 적어 두는 곳

앞의 표에 계속 나오는 Location Store는 Framework 밖에 두는 저장소다. 시스템의 구성 요소 하나이며, 실행 중인 process들이 서로를 찾는 데 사용한다. 튜토리얼과 샘플은 Redis를 사용한다.

↗ 크게 보기

각 process가 시작할 때 자기 주소와 자기가 맡은 이름을 Store에 적고, 살아 있는 동안 그 기록을 갱신한다. 멈추면 기록이 사라진다. 호출하는 쪽은 Store를 읽어 연결할 곳을 찾는다.

그래서 서버를 늘리거나 다른 주소로 다시 시작해도 호출하는 쪽 설정을 고치지 않는다. 이것이 connect(...)로 주소를 직접 적는 방식과 달라지는 지점이다.

Store가 보관하는 것은 주소만이 아니다.

적히는 것 사용하는 곳
MeshNode의 주소와 맡은 ChannelName RouteMesh 자동 연결
ClientServer Server의 주소 client가 연결할 대상 찾기
Fanout publisher의 주소 subscriber가 연결할 대상 찾기
Spot·Actor가 지금 있는 node id로 호출할 때 위치 해석
같은 Spot을 두 곳에서 만들지 않게 하는 생성 권한 Spot 생성

Spot과 Actor를 사용하려면 Store가 반드시 있어야 한다. 그 기능은 "지금 어느 node에 있는가"를 Store에서 읽어 결정하기 때문이다. channel 메시징만 사용하고 주소를 직접 적는다면 Store 없이도 동작한다.

등록 코드는 Spot에, 운영 조회는 운영과 lifecycle에 있다.

Location Store와 Relocation Store는 맡는 기록이 다르다. Location Store는 작은 위치 기록의 원자적 변경을 맡고, Relocation Store는 옮기고 난 뒤에 남는 기록 — Instance Spot을 처음 깨운 기록과 이동 뒤에 완료되는 요청의 종결 기록 — 을 담는다. 옮겨 가는 상태와 queue와 timer 자체는 Store를 거치지 않는다 — Relocation이 그 경로를 다룬다.

Store 언제 필요한가
Location Store object 역할이 client나 server인 MeshNode에 필수. 없으면 socket을 열기 전에 설정 오류로 끝난다
Relocation Store 이동 정책을 가진 factory나 Instance Spot factory가 하나라도 있으면 필수. 전부 이동을 끄고 Instance Spot factory도 없을 때만 없어도 된다

각각 정확히 한 번 등록한다. 둘을 하나의 등록 호출로 묶는 표면은 없고, 필요한데 빠뜨리거나 둘 이상 등록하면 socket을 열기 전에 설정 오류다. Store가 없을 때 Framework가 process 안에 대체 Store를 만들어 주지 않는다 — 단일 node로 조용히 동작하지 않고 실패한다.

두 Store는 같은 Redis 배포를 사용해도 된다. key prefix는 서로 다르게 둔다. Framework는 Store를 가로지르는 transaction에 기대지 않으므로 필요하면 물리 Redis도 분리할 수 있다. Store를 등록한 뒤 application이 provider 동작을 직접 호출하거나 정리하지 않는다 — Framework가 Store의 수명과 호출 순서를 관리한다.

6.2 owner lease — 소유권이 살아 있다는 표시

owner는 자기가 살아 있다는 표시를 주기적으로 갱신하고, 그 표시가 만료되면 다른 node가 그 owner가 된다. 갱신 주기·만료 시간·갱신 한 번의 상한·만료 전에 미리 끊는 여유는 서로 묶여 있다. 관계를 어기면 시작할 때 오류다. 값을 바꿀 때는 이 값들을 함께 본다. 값 이름과 기본값은 언어별 옵션 장이 소유한다.

lease 갱신이 끊긴 host는 계산해 둔 시각을 넘는 순간 새 작업을 받지 않는다 — 상태를 바꾸는 message와 timer 시작, factory와 복원 결과의 확정, 이동 상태 변경과 수용 공간 확보가 모두 막힌다. 이미 queue에 들어온 작업의 마무리와 정리는 계속한다. 새 owner와 옛 owner가 동시에 사용하는 것을 막는 장치다.

6.3 Store가 멎어 있는 동안

Store 장애에 대비한 유예 시간이 있다. 그것은 마지막으로 완전히 읽은 node 목록을 유지해 주는 시간이지, 소유권을 연장해 주는 시간이 아니다.

유예 동안 결과
이미 맺은 연결 상태 판단을 계속한다
새 바깥 연결 만들지 않는다. 유예가 끝나도 node 목록 전체를 같은 시점으로 다시 읽기 전에는 만들지 않는다
owner lease와 이동 기한 연장되지 않는다

이미 받은 요청은 유지되고, 멈추는 것은 대상 목록의 추가·제거 계산이다. store가 복구되면 최신 등록 정보를 기준으로 목록을 다시 맞춘다.

6.4 manual과 automatic

수동 연결은 MeshNode의 peer 목록에 설정한다. 설정하는 코드는 RouteMesh — 연결 하나를 여러 channel이 공유한다에서 본 수동 연결과 같다.

endpoint 인자는 startup 설정이다. host 시작 뒤 실행 중인 socket을 직접 제어하는 handle이 아니다. 실행 중에 바꿀 수 있는 값은 실행 중에 새 요청만 멈추기의 weight뿐이다.

자동 연결 모드는 peer 목록의 소유권이 Location Store에 있다. server가 새 endpoint로 다시 시작하면 store의 descriptor row가 갱신되고 client 연결도 따라 갱신되므로 별도 조작이 필요 없다. 수동 연결은 설정을 바꾼 뒤 application을 다시 시작해야 적용된다.

Fanout subscriber는 automatic discovery와 manual endpoint를 함께 지정할 수 없다. 둘을 같이 등록하면 시작할 때 거부된다.

6.5 store에서 찾았다고 바로 보내지 않는다

client는 등록 정보에서 endpoint를 얻은 뒤 실제 연결에서 신원과 lifecycle generation을 다시 확인하고 나서야 그 대상을 사용한다. 수동 연결도 같은 확인을 거친다. 그래서 store에 row가 있는데도 호출이 대상 없음으로 끝날 수 있다 — 그때는 store가 아니라 연결이 맺어졌는지를 본다.

server를 재시작하면 lifecycle generation이 바뀐다. 이는 node가 몇 번째 실행 중인지를 나타내는 값이다. endpoint가 같아도 이전 lifecycle generation의 연결은 새 대상으로 사용하지 않고, client가 새 값을 준비한 뒤 이전 연결을 해제한다. lifecycle generation 값은 숫자 크기로 순서를 판단하지 않는다.

늦게 도착한 reply는 원래 요청이 아직 기다리고 있으면 그 결과가 된다 — 이전 세대에서 온 것이어도 그렇다. 반대로 timeout·취소·client 재시작으로 그 요청이 사라졌으면 버리고, 나중에 시작한 다른 요청의 결과로 사용하지 않는다.

7. 호출이 끝났다는 것의 의미

호출 완료 시점
request 대상 handler가 반환한 reply가 도착했다
send source-local queue가 message를 수락했다
publish source-local publish admission이 끝났다

send와 publish의 완료는 전달을 보장하지 않는다. publish는 구독자 수도 수신 완료도 반환하지 않는다. 처리 결과가 필요하면 request를 사용할 수 있는 패턴을 고른다.

handler가 없는 packet으로 보냈을 때는 경로마다 다르다.

호출 결과
request error reply로 실패한다. 호출한 쪽은 예외로 받는다
send 조용히 drop된다

drop은 호출한 쪽에 reply가 없다는 뜻이지 관측 흔적이 없다는 뜻이 아니다. 구성한 logger·telemetry provider에는 dispatch 실패가 no_handler·reply_error·drop structured record로 남는다(모니터링).

8. 관련 문서

  • 등록과 호출 방법 — Channel 메시징
  • 공통 처리를 한곳에 모으기 — Filter
  • 직렬화 codec — Codec
  • 이 장 코드의 실행본 — framework/languages/dotnet/tutorial