신뢰성 없는 네트워크, asynchronous / 시계 문제
synchronous / asynchronous / blocking / non-blocking 을 어떻게 해석해야 될까? 학계에서도 명확한 정의가 없다. 나는 아래 3가지 관점으로 나눠지는 것을 경험했다. 어떤것도 명확히 정답이라고 볼 순 없지만 개인적으로 1번을 선호한다.
1. synchronous/asynchronous 와 blocking/non-blocking 는 별개 카테고리로 봐야한다.
synchronous/asynchronous
blocking/non-blocking
2. synchronous 는 blocking 이고 asynchronous 는 non-blocking 이다.
synchronous -> blocking
asynchronous -> non-blocking
3. asynchronous 안에 blocking/non-blocking 이 있다.
sync
async
- blocking
- non-blocking
1. synchronous/asynchronous 와 blocking/non-blocking 는 별개 카테고리로 봐야한다.
출처 : https://www.youtube.com/watch?v=HKlUvCv9hvA
동기(synchronous) : syn 은 함께 라는 뜻. chrono 는 시간 이라는 뜻. 즉 함께 시간을 맞춘다.
비동기(asynchronous) : 시간을 맞추지 않는다 는 뜻
그러므로 동기 비동기에 대한 이야기를 할 때는 반드시 두 가지 이상을 언급해야한다. 무엇과 무엇이? 그리고 시간 개념이 있어야 한다. 어떤 시간을 맞춘다(동기) 안맞춘다(비동기).
동기라는 것은 예를 들어서 A, B 가 함께 간다고 할 때, 두 가지가 동시에 시작하거나 동시에 종료하거나 동시에 같이 진행을 하면 동기다.
동기 예1) A, B 스레드가 동시에 작업을 시작하게 하는 CyclicBarrier
동기 예2) 메서드 리턴 시간(A)과 결과를 전달받는 시간(B)가 일치하면 동기
동기 예3) A가 작업을 시작하고 끝나면 끝나는 시간에 맞춰서 B가 시작하는 개념 (Synchronized 블록, BlockingQueue)
블록킹, 논블로킹
동기, 비동기와는 관점이 다르다. 동기, 비동기에서 항상 블로킹, 논블로킹을 얘기할 수 있는게 아니다. 같이 언급할 수 있는 경우는 '내가 직접 제어할 수 없는 대상을 어떻게 상대할것인냐' 이 부분을 설명할 때 이다. 이때는 블로킹과 논블로킹 얘기를 같이 해도 된다. 그래서 대상이 제한적이다. 여기서 말하는 '내가'의 나는 스레드다. 즉 스레드 관점에서 봐야한다.
ExecutorService es = Executors.newCachedThreadPool();
String res = es.submit(() -> "Hello Async").get();
위 코드를 동기/비동기, 블로킹/논블로킹 관점에서 설명해보면 다음과 같다. es.submit 메서드는 비동기다. 메서드 리턴 시간과 Callable 의 실행 결과를 받는 시간이 일치하지 않는다.
@Async
public String asyncCall() {
restTemplate.exchange();
}
2. synchronous 는 blocking 이고 asynchronous 는 non-blocking 이다.
자바8인액션 책에서는 다음과 같이 설명한다.
전통적인 동기 API 에서는 메서드를 호출한 다음에 메서드가 계산을 완료할 때까지 기다렸다가 메서드가 반환되면 호출자는 반환된 값으로 계속 다른 동작을 수행한다. 호출자와 피호출자가 각각 다른 스레드에서 실행되는 상황이었더라도 호출자는 피호출자의 동작 완료를 기다렸을 것이다. 이처럼 동기 API 를 사용하는 상황을 블록 호출(blocking call)이라고 한다.
반면 비동기 API 에서는 메서드가 즉시 반환되며 끝내지 못한 나머지 작업을 호출자 스레드와 동기적으로 실행될 수 있도록 다른 스레드에 할당한다. 이와 같은 비동기 API 를 사용하는 상황을 비블록 호출(non-blocking call)이라고 한다.
3. asynchronous 안에 blocking/non-blocking 이 있다.
Three Communication Modes
1. synchronous message passing : send 가 보내지고 recevier 가 돌고 있어야 시작하고 receiver 결과를 보낼 때 까지 기다림
2. blocking send/receive : receiver 가 돌든 말든 시작하고 send 에서 kernel copy 까지는 기다림. 하지만 receiver 에서 결과 보낼 때까지는 기다리지 않음.
3. non-blocking send/receive : receiver 가 돌든 말든 시작하고 send 에서 kernel copy 까지도 기다리지 않음.
신뢰성 없는 네트워크 문제를 다루는 흔한 방법은 타임아웃이다. 얼마 간의 시간이 지나면 응답 대기를 멈추고 응답이 도착하지 않는다고 가정한다. 그러나 타임아웃이 발생했을 때 원격 노드가 응답을 받았는지 아닌지는 여전히 알 수 없다(그리고 요청이 아직 어딘가의 큐에 들어있다면 전송 측은 요청을 포기했더라도 메세지가 수신 측에 도착할 수도 있다).
이 문장을 엄밀히 receive 와 deliver 로 구분지어 설명하면, receive 는 상대방 노드까지 간걸 의미하고 deliver 는 app 단까지 전달된걸 의미한다. 아래 그림에서 hold-back 큐는 middleware 나 os level 의 큐를 의미한다.
신뢰성 없는 시계문제(p287) 에 대한 램포트 알고리즘
개별 장비는 자신의 시계를 갖고 있다. 하지만 이 장치는 완벽하지 않다. 드리프트(drift) 현상이 생긴다(더 빠르거나 느리게 실행). 시계 드리프트는 장비 온도에 따라 변한다.
clock drift rate 가 달라서 happend before relation 이 안맞게 된다. 예를 들어 m3 구간을 보면 60 에서 보낸 응답을 56이 받게 된다. 60보다 커야한다. 다시말해 cosal relation 을 유지키시기 위해 clock 을 바꿔야 한다. 이 말이 이해 안갈 수 있다. 9장 일관성과 합의 p343 에서도 램포트 타임스탬프에 대해 설명할 때 제대로 정리해서 설명하겠다.