2023 03 20
2023-03-20¶
Thread-Safe하게 진짜 랜덤한 13자리 숫자를 만드는 방법?¶
- Random vs SecureRandom
Seq vs List¶
- JSON 리스트 -> Seq[]가 유리하다
- 다형성: Seq[] 타입은 List[], Vector[], 그 외에도 잘 어울림. 유연한 코드 작성 가능
- 퍼포먼스: 많은 컬렉션을 다룰때, Seq[]의 성능이 List[]보다 좋음. Seq[]는 lazy evaluation을 지원하기에, 메모리/시간 측면에서 유리
- FP: Seq[] 타입이 List[] 보다 불변, 합성적인 측면에서 유리한가봄
- Seq[] 장점
- 더 유연함
- 큰 컬렉션에서 더 나은 퍼포먼스
- 불변/합성에 더 유리
- Seq[] 단점
- List[] 보다 덜 구체적임. 몇 개 메서드는 없을껄?
- 타입 어노테이션이 필요할 수도
Play는 어떻게 비동기로 동작하지?¶
- Netty는 싱글쓰레드로 동작하나?
- Netty는 하나 이상의 보스 쓰레드를 두고 있음. 보스 쓰레드는 새로운 커넥션을 수립하는 책임을 가짐
- Netty는 하나 이상의 워커 쓰레드를 두고 있음. 워커 쓰레드는 수립된 커넥션이 요구하는 I/O 이벤트 등을 수행함
- 이러한 구조로 Netty는 많은 트래픽 동시성 처리를 가능하게 함
- Play는?
- Netty 위에 지어진 Play Framework의 경우 비슷한 쓰레드 모델을 채택중임
- Play Framework는 고정된 쓰레드 풀을 사용해 들어오는 요청을 처리함
- 쓰레드풀의 사이즈는 conf에서 변경할 수 있음
- 따라서 Play는, 들어오는 요청을 다루는 thread를 thread pool에서 관리하여 여러개를 처리함
- 쓰레드 몇 개로 관리함?
- 기본적으로 몇 개를 쓰레드풀에 쟁여둘지는
ExecutionContext.global에 명시되어 있음 - 기본적으로는 CPU cores 만큼 쓰레드를 만들 것임
- quad-core라면 4개의 쓰레드를 생성할 것임
- 물론 application.conf에서 조정할 수 있음
- 어플리케이션 및 하드웨어의 속성에 따라 몇개의 쓰레드를 쓸지가 결정됨
- 만약 어플리케이션이 CPU-Bound라면... (계산하는데에 많은 시간을 씀)
- 쓰레드 갯수 증가가 멀티코어 환경에서 퍼포먼스 증대에 역할
- 만약 어플리케이션이 IO-Bound라면...
- 이러면 쓰레드 더 많이 생성한다고 퍼포먼스가 좋아질 이유는 없음
- 만약 어플리케이션이 CPU-Bound라면... (계산하는데에 많은 시간을 씀)
- 기본적으로 몇 개를 쓰레드풀에 쟁여둘지는
- 작업 메인 쓰레드한테 끝났다고 어떻게 알려줘?
- 플레이는 Akka를 기반으로 비동기 처리를 함
- 해당 Task가 끝나면, main thread에게 일 끝났다고 알려줘
- 커뮤니케이션은 Actor System을 기반으로 동작함 (Akka의 근본)
- Akka actor는 가볍고, 동시성 처리에 유리함
- 다른 쓰레드에서 Task가 끝나면, Actor에게 메시지를 보냄
- Actor가 메시지 프로세스하고, Future의 결과값을 봐
- Main Thread는 Future가 역할을 끝내줬길 기다리고 있었고, 결과값이 있다는 걸 확인하면, 실행을 마저 뚝딱
- 정리
Action.async를 사용하면 매핑된 쓰레드가 처리하고 Actor 시스템에 메시지를 응답한다- 메시지는 Future의 Result를 담고있다
- Main Thread는 Future에 Result가 있다는 사실로 Job이 끝났다는 것을 알게되며 이후 필요한 일을 처리한다
- Play's Main Thread != Netty's Boss Thread
- Play의 Main thread는 클라이언트의 요청을 핸들링 해주는 쓰레드임
- Flow
- 클라이언트의 request 도착
- Netty의 worker thread pool 중 하나의 쓰레드가 해당 요청을 처리하라고 매핑됨
- 만약 컨트롤러가
Action.async를 달고 있다면, 해당 쓰레드가 Netty로 반환됨 - 비동기 오퍼레이션이 완료되면, Future에 Result를 담은 메시지가 Akka Actor로 돌아감
- Netty worker thread 중 다른 친구들을 핸들링 하고 있던 쓰레드가 그제서야 알림을 받음
- 한 번 더 정리해보는 Play 흐름
- 클라이언트가 TCP Connection 수립하려 한다
- Netty의 Boss Thread가 Connection을 수립해 Worker Thread Pool 중 하나의 Worker Thread에 넘긴다
- Netty의 Worker Thread는 소켓으로 부터 넘어온 데이터를 읽으며, Play Framework의 Netty Server에게 프로세싱을 맡긴다
- Play Framework가 Request를 받으면 (이게 플레이 메인 쓰레드일 듯), Controller로 해당 요청을 매핑시킨다
- Action.async가 달려있는 거라면, 쓰레드풀에서 하나 쓰레드 할당해 비동기적으로 처리하도록 한다
- 비동기로 처리가 완료되고 나면, Akka로 결과값이 넘어가게 된다
- 플레이 메인쓰레드에게 Akka가 끝났다고 알려준다
- 플레이 메인쓰레드가 Netty의 Worker Thread Pool 중에 하나를 가져와 클라이언트에게 소켓으로 응답함
- Play Framework의 Netty Server란건 또 뭐야?
- Play Framework의 Netty Server는 Netty networking 라이브러리를 활용하여 구축되었음
- Play의 상황에 알맞게 커스텀했음
- Netty는 low-level networking library로써, async/event-driven 방식의 프레임워크임
- Play Framework의 Netty Server는 Netty 위에서 higher-level abstraction으로 HTTP request/response를 다루기 위해 사용됨
-
하나의 쓰레드는 여러개의 소켓 커넥션을 들고 있을 수 있다
- Netty 서버가 소켓 커넥션을 다룸
- Client가 요청 보냄 -> Netty Worker Thread가 요청을 받음 -> Play에게 Request 넘겨줌 ->
Play 할일 하고 Netty Worker Thread에게 응답 줌 -> Client에게 응답 줌
- 이 과정에서 Netty Worker Thread는 socket connection을 여전히 들고 있으면서 플레이의 응답을 기다림
- 그러면서 non-blocking/async니까 딴일도 딴일 나름대로 처리 중
- Netty Worker Thread가 요청 받으면, 해당 요청 처리를 Akka와 같은 non-blocking 라이브러리에 넘길 수 있음
- 여러개의 소켓을 하나의 쓰레드에서 관리할 수 있다!
Future 다루는 방법¶
- map
Future클래스의 메서드로, 새로운Future를 리턴함- 사이드 이펙트 없이 현재
Future를 새로운Future로 변환 - 다른 map, flatMap 등으로 체이닝이 가능
- 다만,
Future가 성공적으로 수행되었는지, 실패했는지는 알 길이 없음
- onSuccess
Future가 성공적으로 완수되면, 콜백 함수가Future의 결과값을 가지고 지지고 볶는다- 하지만 사이드 이펙트 있을 수 있음
Future가 성공했을떄, 무슨 콜백으로 어떤 오퍼레이션 뚝딱 할 것인지 제공- 사이드 이펙트 발생가능 : shared state, logging etc
- 새로운
Future를 반환하진 않음
Action¶
- 참고: https://www.playframework.com/documentation/2.8.x/ScalaActions
- 개요
play.api.mvc.Action은play.api.mvc.Request => play.api.mvc.Result함수를 나타낸다- request를 처리해 result를 만든다는 뜻
- 컨트롤러는 그저 Action 생성기임을 기억해주세용
- Action 만들기
- BaseController를 extend한 컨트롤러의 경우, Action이 기본 탑재되어 있음
- 여러가지 Action 빌더들을 통해 Action을 생성할 수 있음
- 예시 1. Request 참조 없이
- 가장 간단한 방법 중에 하나
- 하지만 incoming request에 대한 참조를 얻을 수 없다
-
예시 2. Request => Result
Request => Result를 아규먼트로 받아 반환하는 친구가 있음
- 여기서 request를 implicit으로 받으면, 다른 API에서 받아서 처리할 수 있음
- 예시 3. additional BodyParser
- 다른 BodyParser 기능을 통해 Action 객체를 만들수 있나봄
- 현재로썬 그냥 Any content body parser가 있나보다~
- Simple Results
def index = Action { Result( header = ResponseHeader(200, Map.empty), body = HttpEntity.Strict(ByteString("Hello world!"), Some("text/plain")) ) } // 위에꺼랑 아래꺼랑 똑같아 def index = Action { Ok("Hello World!") } val ok = Ok("Hello world!") val notFound = NotFound val pageNotFound = NotFound(<h1>Page not found</h1>) val badRequest = BadRequest(views.html.form(formWithErrors)) val oops = InternalServerError("Oops") val anyStatus = Status(488)("Strange response type")
- Redirect도 그저 간단한 Result 일 뿐
Action.async¶
- 개요
- 비동기적으로 다른 쓰레드 풀에서 non-blocking 코드를 실행하도록 지원함
- 따라서 해당 코드가 long-run operation에 들어가더라도, 서버는 다른 incoming request 처리가 가능하다
- 비동기적으로 다른 쓰레드 풀에서 non-blocking 코드를 실행하도록 지원함
- 클라이언트에게 응답이 어떻게 가능하지?
Action.async는Future[Result]를 반환한다Action.async가 있다면, 플레이가 Http request를 받는 쓰레드가 아닌, 다른 쓰레드에 해당 function 수행을 매핑시켜 실행시킬 거임Future[Result]가 완료된다면, 플레이는 Http request를 통해 클라이언트에게 해당 응답을 줄 수 있도록 할 것임
- 예시
- Action.async vs Action
- 시간을 많이 잡아먹는 작업이 request를 받는 thread에서 수행된다면 => Action.async가 좋음
- 다만,
Action.async에는 단점이 있어서 필요할 때만 사용하는 것도 좋아보인다.Future를 관리하는 비용- 쓰레드풀 관리 비용
- 벤치마크 확인하고, 코드 프로파일링하고, 병목/최적화 하는 것이 좋아보임