Open Hae-Riri opened 3 years ago
모놀리식 아키텍처는 하나의 커다란 서비스와 저장소로 구성된 아키텍처로, 하나의 모듈이 변경되어도 전체 서버를 배포해야 하고 작은 기능의 문제점도 곧 서버 전체의 문제점이 되었다. 그리고 많은 코드가 엮여 있어서 비즈니스 요구 변화에 유연하게 대처하지 못했다.
커다란 서비스는 작은 단위의 독립된 모듈로 쪼개지고 추가 기능이나 확장이 필요하면 해당 모듈만 변경하면 된다.
그래서, 스레드가 응답을 기다리지 않고 다른 일을 처리하다가 응답이 왔을 때 해당 일을 처리하게 해서 불필요하게 응답만을 기다리며 리소스를 점유하는 일이 없도록 하자는, '이벤트루프를 이용한 비동기 프로그래밍'이 등장한다.
이벤트 루프를 활용하면 요청을 보내고 응답이 올 때까지 무작정 기다리는 대신 자신에게 할당된 다른 여러 소켓의 요청을 순차적으로 처리한다. 더 이상 blocking되지 않도록 Spring 5부터 webFlux가 도입되면서 비동기 프로그래밍이 가능해졌다.
xml 기반의 환경 설정, dispatcher servlet 기반의 코드를 war로 패키징하고 이걸 톰캣과 같은 was의 docRoot에 배포한 뒤 was를 별도로 가동시켜야 애플리케이션이 동작한다.
그럼에도 불구하고 비동기 논블로킹에 대한 지원이 없는 것이 문제가 된다.
Spring WebFlux
Reactive Stream의 구현체. 단순히 JVM 기반에서 비동기 논블로킹 처리를 위한 스펙을 명시한 것으로, 다른 구현체로는 RxJava, Akka Streams 등이 있다. Armeria도 이 스펙을 직접 구현했다.
차이는 진입점 설정 애너테이션 기반 라우팅도 가능하고 함수 기반 라우팅도 가능하다.
// 애너테이션 기반 라우팅
@GetMapping("/hello")
@ResponseBody
public Mono<String> getHello() {
return demoService.getHello();
}
// 함수 기반 라우팅
@Bean
public RouterFunction<ServerResponse> routes(DemoHandler demoHandler) {
return RouterFunctions
.route(RequestPredicates.GET("/hello"), demoHandler::getHello);
}
** webFlux는 mvc와 코드 생김새는 같으나 반환형이 다르다. MVC에서 일반적으로 사용하던 String,List 같은 plain object를 사용할 수 없고 반드시 Publisher Object로 감싸서 반환해야 한다.
DemoHandler.java
@Slf4j
@Component
@RequiredArgsConstructor
public class DemoHandler {
private final Validator validator;
private final DemoService demoService;
public Mono<ServerResponse> post(ServerRequest serverRequest) {
Flux<DemoResponse> ret = demoService.post(
serverRequest.bodyToFlux(DemoModel.class)
.filter(demoModel -> {
Set<ConstraintViolation<DemoModel>> validationResult = validator.validate(demoModel);
if (validationResult.isEmpty()) {
return true;
} else {
return false;
}
})
);
return ServerResponse.ok().body(ret, DemoResponse.class);
}
}
- DemoService.java
@Slf4j @Service public class DemoService {
public Flux<DemoResponse> post(Flux<DemoModel> demoModelFlux) {
return demoModelFlux.
flatMap(demoModel -> {
log.debug("demoModel : {}", demoModel);
return Flux.just(new DemoResponse(demoModel, true));
});
}
}
#### Publiser Object
publisher object는 Mono와 Flux가 있다.
- Mono는 0 또는 1개의 아이템이 나올 수 있음을 의미
- Flux는 0또는 n개의 아이템이 나올 수 있음을 의미
> Mono<List<String>> 과 Flux<String> 비교
>
> 둘 다 여러개의 요소가 포함될 수 있음을 의미하지만 용도가 다름.
> Mono는 0또는 1개 이므로 List<String> 1개가 반환되기 때문에, 여러 개의 요소가 "한번에" 반환된다.
> Flux는 0또는 n개가 반환되므로 여러 개의 String 요소가 Stream 형태로 반환된다.
## Armeria 소개
java8, netty, http/2, thrift, gRPC 기반의 비동기 RPC/REST 라이브러리
애플리케이션의 성능 향상을 위해 http 대신 rpc를 도입하려다 보니 spring 에서는 rpc를 사용하는 게 제한적이다.
#### Armeria와 MSA
armeria를 통해 reactive streams 를 활용하면 많은 양의 데이터와 트래픽을 유연하게 처리할 수 있다. 또한 rpc를 통해서 마이크로 서비스 간 통신도 쉽게 처리할 수 있다.
별도의 원격 제어를 위한 코딩 없이 다른 주소 공간에서 리모트의 함수나 프로시저(특정 작업을 수행하는 블록)를 실행할 수 있게 하는 프로세스 간 통신 기술
이 과정은 네트워크를 통한 정보 교환이기 때문에 로컬에서의 메소드 콜 보다는 훨씬 많은 시간을 소모한다. 따라서 원거리 호출의 횟수를 줄이는 게 수행 성능 향상을 위해 반드시 고려되야 할 사항이다.
RPC는 서버와 클라이언트 구조를 기반으로 작동하며, 각각의 클라이언트와 서버는 리모트 콜을 하기 위한 프로시저에 대한 인터페이스를 통해 서로의 요청에 대해 이해한다.
요즘에 많은 서비스들이 MSA 구조로 개발되면서 다양한 언어, 프레임워크가 사용되는 환경에서 프로토콜에 맞춰 통신해야 하는 비용이 발생했고, 이 때 RPC를 이용하면 개발환경에 구애받지 않고 각 프로시저를 호출해서 사용할 수 있다.
gRPC는 기본적으로 프로토콜버퍼를 IDL(인터페이스 정의 언어)이자 message interchange format(메시지 교환 포맷)으로 사용한다.
gRPC는 통신 프로토콜로 http/2를 사용한다.
기본적으로 클라이언트가 서버에 요청을 보내고, 서버가 요청에 대한 응답을 보내는 구조다. 따라서 요청 단위로 클라이언트와 서버를 왕복해야 한다. 또한 쿠키를 포함한 헤더 크키는 불필요하게 크다. 이런 이유로 HTTP/1은 느리다.
Header Compression
Server Push
head-of-line blocking 문제 해결
Multiplexed Streams 과 Stream Priority
** 이러한 http/2의 특징으로 인해 이걸 기반으로 하는 gRPC는 양방향 스트리밍이 가능하고, 기본적인 통신 속도가 빠르다. on-connection 상태에서 비동기 통신의 구현이 용이하다.
기존에 평문을 사용하고 개행으로 구별되던 HTTP/1과 달리, 2.0에서는 바이너리 포맷으로 인코딩된 Message, Frame으로 구성된다.
HTTP/1.1에 있던 head of line blocking 문제를 HTTP/2에서는 frame, stream으로 해결했다. HOL 블로킹은 아래에서 다루자.
그림을 보면 클라이언트가 서버에게 headers frame을 보낸다. 이 그림은 get 요청이라서 header frame만 존재한다.
물론 위의 경우는 클라이언트로부터 온 스트림1에 대한 응답이라서 서버가 스트림1로 [headers frame+data frame = 하나의 스트림] 을 만들어 응답했다.
http1.1에서 메타데이터는 단순한 text고 크기는 500-800바이트인데 만약 쿠키가 포함된 경우 킬로바이트까지 올라간다.
근데 여기서 다시 요청을 하면 아마 헤더는 크게 변함이 없을 것이다. 그리고 중복된 필드도 많을텐데, HTTP/1.1에서는 이런 중복된 데이터를 그냥 다시 보낸다. 하지만 http/2.0에서는 중복전송하지 않는 방식으로 header를 압축한다.
HTTP 1.1에서 Persistent + Pipelining을 통해 다음과 같은 통신이 가능해졌다.
Pipelining 서버는 반드시 응답을 요청 순서에 맞추어 전달해야 한다.
Pub/Sub 구조
비동기식 메시징 패턴으로, 메시지 기반의 미들웨어 시스템을 말한다. 주로 서버리스/마이크로 서비스 아키텍쳐에서 사용된다.
일반적으로 메시지를 전송할 때는 publisher(sender)가 subscriber(receiver)에게 직접 메시지를 전송한다. 하지만 pub/sub 모델에서는 publisher는 어떤 subscriber가 있는지 모르는 상태에서 메시지를 전송하고, subscriber는 publisher에 대한 정보 없이 자신의 interest에 맞는 메시지만을 전송 받는다.
pub/sub 구조는 비동기식으로 어떻게 작동하는가?
pub/sub 구조는 낮은 결합도로 인해 안정적이고 확장에 용이하지만 아무래도 브로커(채컬)을 통해 메시지를 전달하기 때문에 부하가 있어서 좀 느리다.