관리 메뉴

HAMA 블로그

Play 해부 : Hot redeploy 이야기 본문

PlayFramework2

Play 해부 : Hot redeploy 이야기

[하마] 이승현 (wowlsh93@gmail.com) 2017. 4. 18. 20:40



역주: 몇년 지난 이 글에 오류가 있을 수 있으며, 겉핣기 수준의 플레이 지식에 기반한 제 번역에도 오류기 있을 수 있음을 알려드립니다. 수정해야할 부분에 대해서 코멘트 주시면 반영하겠으며, 많은 정보 공유가 필요합니다. 더 정확한 이해를 위해서 직접 소스를 보는 방법이 가장 확실합니다. 저도 나중에 시간만 된다면 전체 소스리딩을 하고 싶네요. 지난 1년간 웹,서버개발보다는 거의 데이터 분석 쪽만 하고 있다는 개인 현황도 알려드리면 서 ㅎㅎ 시작해보죠. 





Play 해부 :  Hot redeploy.

이 연재에서는 플레이 프레임 워크의 내부에 대해 설명하겠습니다. 애플리케이션 시작에서 HTTP 응답 렌더링에 이르기까지 랜더링 작동 방식을 보여 주려고합니다. 오늘은 특히 플레이 프레임 워크가 코드를 컴파일하고 핫로드하는 방법을 보여주고, 플레이와 sbt와의 관계에 대해 주목하겠습니다.

Play 와 SBT

Play의 핵심 기능 중 하나는 대부분의 JVM 기반 프레임 워크와 달리 외부 도구 (JRebel이 필요 없음)에 의존하지 않고 개발 모드에서 모든 코드 수정을 핫로드하는 것입니다. 어떻게 작동하는지 이해하려면 먼저 Play에서 SBT를 사용하여 클래스를 컴파일하는 방법과 어떻게 SBT가 Play에 긴밀하게 통합되어 있는지 확인해 보는 것입니다.

이전 글(play 해부 : 웹서버이야기에서 SBT가 모든 플레이 응용 프로그램의 진입점에 있고 플레이 명령이 기본적으로 sbt의 별칭임을 확인하였는데요. SBT로 제작된 전형적인 스칼라 애플리케이션과 다른 애플리케이션을 구분 짓는 중요한 부분이 바로 play의 sbt 플러그인 (sbt plugin으로 볼 수 있습니다.

보시다시피, 이것은 꽤 큰 SBT 플러그인입니다. 거기에는 당연한 이유가 있습니다.

"매우 많은 것을 하니깐" ^^ 

우선, 플레이는 많은 설정을 추가합니다.

play-assets-directories           play-build-require-assets        play-closure-compiler-options
play-coffeescript-entry-points    play-coffeescript-options        play-common-classloader
play-compile-everything           play-conf                        play-copy-assets
play-default-port                 play-dev-settings                play-dist
play-external-assets              play-javascript-entry-points     play-less-entry-points
play-less-options                 play-monitored-files             play-onStarted
play-onStopped                    play-package-everything          play-plugin
play-reload                       play-require-js                  play-require-js-folder
play-require-js-shim              play-require-native-path         play-routes-imports
play-templates-formats            play-templates-imports           play-version

스크도 추가합니다.

classpath Display the project classpath. clean Clean all generated files. compile Compile the current application. console Launch the interactive Scala console (use :quit to exit). dependencies Display the dependencies summary. dist Construct standalone application package.(* 스탠드얼론 패키지 구축) exit Exit the console. h2-browser Launch the H2 Web browser. license Display licensing informations. package Package your application as a JAR. play-version Display the Play version. publish Publish your application in a remote repository. publish-local Publish your application in the local repository. reload Reload the current application build file. run <port> Run the current application in DEV mode. test Run Junit tests and/or Specs from the command line eclipse generate eclipse project file idea generate Intellij IDEA project file sh <command to run> execute a shell command start <port> Start the current application in another JVM in PROD mode. update Update application dependencies.

PlaySettings.scala 여기에 모든 기본 설정이 정의 되 있구요. 

play run 을 타이핑하면 playRun.scala에 정의 된 playRunSetting을 호출합니다.

무엇을 하는지 좀 더 살펴보면:

  • sbt 클래스로더를 유지한다
  • "common" jar 를 로드 할 수 있는 "common" 클래스로더를 만든다. 이 jar들은 당신의 어플리케이션과 sbt 양쪽에서 이용된다. 그들은 리로드 되지 않을 것이다.
  • 모든 어플리케이션 종속성들을 로드하는 ,공통 클래스 로더의 자식인, 어플리케이션 클래스로더를 만듭니다.

이 특정 클래스 로더는 특히 흥미로운데요 우선, 클래스 리로딩의 "책임자"중 하나입니다. sbtClassloader의 하위 클래스는 아니지만 공유 클래스 로드를 위임합니다.

플레이 어플리케이션 실행하기 (Running)

우리는 멋진 클래스 로더 계층을 만들었으며 이제 애플리케이션을 실행할 준비가 되었습니다. 물론, start 와 마찬가지로, 그것은 어딘가에 있는 메인을 호출하는 것 일 뿐이죠.
이전 글에서 main은 play.core.server.NettyServer 에 있었음을 보았는데요. 흥미롭게도, 플레이는 여기서 SBT설정을 사용하지 않고 있으며, 클래스 이름은 하드코딩
되어 있습니다.

따라서 우리가 할 것은  mainDevHttpMode를 호출하는 것 입니다.!!


이제 거의 끝났네요... ?? 응?  이봐, 우리 아직 클래스 리로딩에 대해 이야기하지 않았잖아?!

메소드에 reloader가 주어집니다. 해당 리로더는 SBTLink 타입입니다. 요청이 서버에 도달 할 때마다 응용 프로그램은 reloader를 호출하여 코드가 변경되었는지 확인합니다. 리로더는 재컴파일 (또는 정적 애셋 복사와 같은 다른 필요한 작업)을 SBT에 위임합니다.


뭐 좋아, 상당히 간단한거 같긴한데, SBT가 기본적으로 파일들의 수정에 대해 알아 볼 수 있는 능력이 있는데, SBTLink를 작성해야 하는 이유는 무엇이지요?


두가지 이유:

  • 첫째, 플레이는 컴파일 오류가 발생하는 시점을 알아야 브라우저에 멋진 오류 페이지가 표시 될 수 있습니다.
  •  핫리로드! 코드를 다시 컴파일하는 것이 좋지만 응용 프로그램은 과거 클래스를 가지고 계속 실행 중입니다. 할 일 많아요.~

어디에서 마법이 시작되나

여기에(Here진짜 속임수가 있습니다. 컴파일이 성공했다면 JVM에서 로드된 클래스를 업데이트해야합니다. 어떻게 해야 할까요? 간단합니다. 이전 어플리케이션 클래스 로더를 제거하고 업데이트된 클래스와 함께 새 클래스로더를 만듭니다.

그런 다음 애플리케이션을 다시 시작하기 만 하면됩니다. JVM을 다시 시작할 필요가 없습니다.
그리고 그게 다에요! 새 코드가 실행됩니다.!! 모든 일이 몇 초 안에 말이죠. 매우 행복합니다 :)

트레이드 오프

클래스 로더를 핫 리로드 코드로 대체하는 것은 간단하고 잘 작동하지만 비용이 따릅니다. 하지만 가능한 이유가 우선 Play는 상태가 없으며 매우 가볍기 때문이죠. (빠른 시작). 그런 다음 빌드 시스템과 프레임 워크 사이에 긴밀한 종속성을 만듭니다. 대부분의 Java 또는 Scala 프레임 워크는 원하는 빌드 시스템과 함께 사용할 수 있습니다. 실제로 SBT를 제거하지는 않습니다. 마지막으로, 때때로 "정확한"클래스 로더를 사용하지 않는 특정 라이브러리를 사용하는 경우가 있습니다.

왜 JEE frameworks 는 불가능 한가? 


잠깐! 그게 단순한 이유라면 왜 누구나 그 기술을 사용하지 않는 것이지 !!! ??? 내 사랑하는 "Java Enterprise Server®"는 매일 AGES를 위해 다시 시작되기를 기다리고. 루비온레일스는 나를 놀리고 있는데요. 왜 내 시간을 낭비하고, 나를 비참하게 느끼게 하는지..

Enterprise Java Architect, having an epiphany



대답은 꽤 간단합니다. 당신의 응용 프로그램은 상태가 있기 때문이죠.

요청간에 객체 인스턴스를 메모리에 보관하고, 코드가 변경되었다고 가정하면, "오래된" 객체 인스턴스로 무엇을 하나요? 클래스 정의가 변경 되었을 수 있습니다. 클래스 로더를 삭제하면 생성 한 모든 인스턴스도 삭제됩니다. 이는 일반적으로 각각의 reload에서 모든 세션 데이터를 잃어 버리는 것을 의미하는데, 전형적인 어플리케이션에서는 매우 성가시게됩니다.

JEE 응용 프로그램에서 재배포 한다는 것은 오랜 시간이 걸리는 응용 프로그램 서버 (JNDI, 데이터베이스 연결 풀, EBJ, JMX, CDI 등)가 제공하는 많은 "서비스"를 다시 시작하는 것을 의미합니다. 핫리로드 만이 유일한 문제는 아닙니다.

플레이는 상태가 없으며, 상당히 가볍기 때문에 모든 것을 버리는 것은 별 문제가 아닙니다.

엔터프라이즈 애플리케이션 서버의 "포괄적인" 철학은 비용이 추가 책정됩니다. 그 포괄적인 것에서 10%도 제대로 사용하지 않는것을 생각하면 뭐..

뭐 ..그냥 JRebel! 사용할래요.

응?!

Jrebel은 오픈 소스가 아니기 때문에 정확히 어떻게 작동하는지 알기가 어렵습니다. 그들의 FAQ 에 많은 정보가 있지 않지만 , 어쨌꺼나 기본 사항을 설명하는 블로그 게시물들은 있는데요.

간단히 설명하면 JVM에 로드된 바이트 코드를 다시 작성하는 Java 에이전트를 추가합니다. 다음을 보시죠.

  • 객체에 추가 된 필드에 대한 참조를 보유하는 각 클래스에 필드를 추가합니다.
  • 실제 메서드 구현으로 익명 클래스에 대한 참조를 보유하는 각 클래스에 필드를 추가합니다.
  • 위의 변경 사항을 마스크 하기 위해 클래스들의 메소드를 수정하라.

물론 클래스가 수정 될 때 변경 영향을 추적하고 이에 따라 클래스를 "다시로드" 해야합니다. 그것은 힘든 일이죠.

3줄요약 

  • Play는 애플리케이션 클래스로더를 폐기하고 ,모든 것을 다시 시작함으로써 간단히 코드를 리로드합니다. 이는 프레임 워크에 깊이 통합된 SBT의 비용으로 발생합니다.
  • JEE 아키텍처는 상태유지타입이며, 어쨌든 그러한 종류의 기능을 허락하기에는 너무 무겁습니다.
  • JRebel은 플레이와 비교하면 대수술입니다.




Comments