Hae-Riri / today-alcohol

오늘한주 - 술 레시피 공유 커뮤니티 모바일 앱
0 stars 0 forks source link

2021.08.02 #8

Open Hae-Riri opened 3 years ago

Hae-Riri commented 3 years ago

Pub/Sub 구조

비동기식 메시징 패턴으로, 메시지 기반의 미들웨어 시스템을 말한다. 주로 서버리스/마이크로 서비스 아키텍쳐에서 사용된다.

일반적으로 메시지를 전송할 때는 publisher(sender)가 subscriber(receiver)에게 직접 메시지를 전송한다. 하지만 pub/sub 모델에서는 publisher는 어떤 subscriber가 있는지 모르는 상태에서 메시지를 전송하고, subscriber는 publisher에 대한 정보 없이 자신의 interest에 맞는 메시지만을 전송 받는다.

pub/sub 구조는 비동기식으로 어떻게 작동하는가?

image

  1. Publiser(발신자)는 Subscriber(수신자)에 대한 정보를 몰라도 그냥 일단 메시지를 채널에 보내 놓는다.
  2. 이때 메시지에 맞는 topic으로 보내 놓으면 해당 topic을 구독 중인 subscriber에게만 메시지가 가게 된다.

pub/sub 구조는 낮은 결합도로 인해 안정적이고 확장에 용이하지만 아무래도 브로커(채컬)을 통해 메시지를 전달하기 때문에 부하가 있어서 좀 느리다.

Hae-Riri commented 3 years ago

Spring WebFlux, Armeria로 Reactive+RPC 구현하기

모놀리식 아키텍처는 하나의 커다란 서비스와 저장소로 구성된 아키텍처로, 하나의 모듈이 변경되어도 전체 서버를 배포해야 하고 작은 기능의 문제점도 곧 서버 전체의 문제점이 되었다. 그리고 많은 코드가 엮여 있어서 비즈니스 요구 변화에 유연하게 대처하지 못했다.

MSA 등장

커다란 서비스는 작은 단위의 독립된 모듈로 쪼개지고 추가 기능이나 확장이 필요하면 해당 모듈만 변경하면 된다.

MSA에서의 문제

그래서, 스레드가 응답을 기다리지 않고 다른 일을 처리하다가 응답이 왔을 때 해당 일을 처리하게 해서 불필요하게 응답만을 기다리며 리소스를 점유하는 일이 없도록 하자는, '이벤트루프를 이용한 비동기 프로그래밍'이 등장한다.

이벤트 루프를 활용하면 요청을 보내고 응답이 올 때까지 무작정 기다리는 대신 자신에게 할당된 다른 여러 소켓의 요청을 순차적으로 처리한다. 더 이상 blocking되지 않도록 Spring 5부터 webFlux가 도입되면서 비동기 프로그래밍이 가능해졌다.

Spring WebFlux

Spring MVC

xml 기반의 환경 설정, dispatcher servlet 기반의 코드를 war로 패키징하고 이걸 톰캣과 같은 was의 docRoot에 배포한 뒤 was를 별도로 가동시켜야 애플리케이션이 동작한다.

차이는 진입점 설정 애너테이션 기반 라우팅도 가능하고 함수 기반 라우팅도 가능하다.

// 애너테이션 기반 라우팅
@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로 감싸서 반환해야 한다.

}

- 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를 통해서 마이크로 서비스 간 통신도 쉽게 처리할 수 있다.
Hae-Riri commented 3 years ago

gRPC 원리 + stub 동작에 대해

RPC

별도의 원격 제어를 위한 코딩 없이 다른 주소 공간에서 리모트의 함수나 프로시저(특정 작업을 수행하는 블록)를 실행할 수 있게 하는 프로세스 간 통신 기술

stub과 skeleton

이 과정은 네트워크를 통한 정보 교환이기 때문에 로컬에서의 메소드 콜 보다는 훨씬 많은 시간을 소모한다. 따라서 원거리 호출의 횟수를 줄이는 게 수행 성능 향상을 위해 반드시 고려되야 할 사항이다.

RPC 동작

image RPC는 서버와 클라이언트 구조를 기반으로 작동하며, 각각의 클라이언트와 서버는 리모트 콜을 하기 위한 프로시저에 대한 인터페이스를 통해 서로의 요청에 대해 이해한다.

  1. IDL을 통해 호출에 대한 인터페이스를 정의한다.
  2. IDL에 의해 정의된 인터페이스는 클라이언트의 stub과 skeleton 생성의 기반이 되고, rcpgen(rpc 프로토콜 컴파일러)를 통해 각각의 stub(코드)과 skeleton을 생성한다.
  3. 클라이언트는 리모트의 프로시저를 사용하기 위해 설계된 stub의 프로시저를 호출하고, 프로시저 호출에 필요한 인자와 비즈니스 로직에 필요한 메소드를 호출한다.
  4. stub은 서버가 이해할 수 있는 형태로 데이터 캐스팅을 하고 서버 측 RPC로 호출을 진행한다.
  5. 서버는 수신된 호출에 대한 데이터를 처리한다.
  6. 서버 측 RPC 프로토콜은 처리된 데이터를 캐스팅해서 클라이언트로 응답한다.

요즘에 많은 서비스들이 MSA 구조로 개발되면서 다양한 언어, 프레임워크가 사용되는 환경에서 프로토콜에 맞춰 통신해야 하는 비용이 발생했고, 이 때 RPC를 이용하면 개발환경에 구애받지 않고 각 프로시저를 호출해서 사용할 수 있다.

gRPC 등장

gRPC는 기본적으로 프로토콜버퍼를 IDL(인터페이스 정의 언어)이자 message interchange format(메시지 교환 포맷)으로 사용한다.

gRPC가 MSA에 왜 적합할까?

  1. gRPC를 통해 각 프로시저를 호출해서 사용할 수 있으니 비즈니스 로직에 집중해서 개발할 수 있고 그래서 빠르게 개발할 수 있음.
  2. 다양한 언어와 플랫폼을 지원하기 때문에 폴리글랏을 지향하는 MSA 철학과도 일맥상통한다.
  3. protobuf에 의한 높은 메시지 압축률이 시스템 전체의 네트워크 트래픽을 획기적으로 줄여준다. 즉, 동일한 자원 제약에서 더 많은 서비스 인스턴스를 띄울 수 있음.

HTTP/2

gRPC는 통신 프로토콜로 http/2를 사용한다.

HTTP/1

기본적으로 클라이언트가 서버에 요청을 보내고, 서버가 요청에 대한 응답을 보내는 구조다. 따라서 요청 단위로 클라이언트와 서버를 왕복해야 한다. 또한 쿠키를 포함한 헤더 크키는 불필요하게 크다. 이런 이유로 HTTP/1은 느리다.

Hae-Riri commented 3 years ago

HTTP/2

개선된 HTTP/2

** 이러한 http/2의 특징으로 인해 이걸 기반으로 하는 gRPC는 양방향 스트리밍이 가능하고, 기본적인 통신 속도가 빠르다. on-connection 상태에서 비동기 통신의 구현이 용이하다.

기존에 평문을 사용하고 개행으로 구별되던 HTTP/1과 달리, 2.0에서는 바이너리 포맷으로 인코딩된 Message, Frame으로 구성된다.

HTTP/1.1에 있던 head of line blocking 문제를 HTTP/2에서는 frame, stream으로 해결했다. HOL 블로킹은 아래에서 다루자.

image

그림을 보면 클라이언트가 서버에게 headers frame을 보낸다. 이 그림은 get 요청이라서 header frame만 존재한다.

물론 위의 경우는 클라이언트로부터 온 스트림1에 대한 응답이라서 서버가 스트림1로 [headers frame+data frame = 하나의 스트림] 을 만들어 응답했다.

메타 데이터

http1.1에서 메타데이터는 단순한 text고 크기는 500-800바이트인데 만약 쿠키가 포함된 경우 킬로바이트까지 올라간다. image

근데 여기서 다시 요청을 하면 아마 헤더는 크게 변함이 없을 것이다. 그리고 중복된 필드도 많을텐데, HTTP/1.1에서는 이런 중복된 데이터를 그냥 다시 보낸다. 하지만 http/2.0에서는 중복전송하지 않는 방식으로 header를 압축한다.

HTTP/2.0의 요청

image

HOL 블로킹 (Head-Of-Line blocking)이란?

HTTP 1.1에서 Persistent + Pipelining을 통해 다음과 같은 통신이 가능해졌다.