- Scala 2.11.8 기반 

- Akka 2.4.11 기반 

- Java 8  (akka 2.4 부터는 java 8 요구함) 


설치 ( Deploying )


모든 작업을 마치고 나면 실제 서비스를 위해 설치를 해야합니다.  Scala 기반의 Akka 에서는 어떻게 하는지 알아보겠습니다. 참고로 이렇게 하는방법 말고 다른 방식으로도 할 수 있습니다. 매우 다양한 방식이 도처에 흩날리고 있다는.. OTL.... 하지만 제가 소개하는  Sbt stage 방식이 Akka 나 Play 전개에 가장 좋은거 같네요.


* 우아하게 종료하는 부분에 대해서는 apache 데몬등을 이용해서 만들 수 있을 것 같습니다. 

* 서비스로서 만들기 위해서는 저는 그냥 실행스크립트를 init.d 에 넣어줘서 처리합니다. 다른 방법있으면 소개좀..





0. 기본 프로젝트 생성   


가장 기본적인 프로젝트를 하나 만듭니다. 아래와 같이 간단한 코드를 작성합니다. 


import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, Props}
import scala.concurrent.duration._


class HelloWorld extends Actor with ActorLogging {

def receive = {
case msg: String =>
val hello = "Hello %s".format(msg)
sender() ! hello // 보낸 액터에게 되돌려주기
log.info("Sent response {}",hello)
}
}

class HelloWorldCaller(timer: FiniteDuration, actor: ActorRef)
extends Actor with ActorLogging {

case class TimerTick(msg: String) // 메세지용 케이스클래스

override def preStart(): Unit = {
super.preStart()
implicit val ec = context.dispatcher
context.system.scheduler.schedule(
timer, // 몇초 있다가
timer, // 몇초 간격으로
self, // 자신에게 보내라
new TimerTick("everybody")) // 이 메세지를
}

def receive = {
case msg: String => log.info("received {}",msg) //HelloWolrd 에서 받음.
case tick: TimerTick => actor ! tick.msg // HelloWorld 액터로 전송
}
}


object maintest extends App {

val system = ActorSystem("hellokernel")

val actor = system.actorOf(Props[HelloWorld])
val config = system.settings.config
val timer = config.getInt("helloWorld.timer")
system.actorOf(Props(
                    new HelloWorldCaller(
                    timer millis,
                    actor)))
}

-  일정시간 간격으로 HellowWorld 액터에게 메세지를 보내고 되받는 어플리케이션입니다.

 

1.  스탠드얼론 어플리케이션 만들기

 1-1. project/plugins.sbt 파일에 아래와 같이 추가합니다. (sbt-native-packager plugin 을 이용)

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0")

sbt-native-packager 는 Akka뿐만 아니라 어떤 타입의 어플리케이션에 대한 배포(distribution)를 만들기 위한 툴이다.


1-2. src/universal/ 폴더를 만들고 아래에 application.conf 와 logback.xml 을 추가.

          resources 아래에 있는 기존 파일의 내용을 그대로 복사/붙혀넣기 합니다.


1-3. build.sbt 파일에 아래 내용 추가

enablePlugins(JavaAppPackaging)

scriptClasspath +="../conf"


1-4. 프로젝트 root 폴더에서 sbt stage 실행 

- 위 처럼 universal 폴더가 생겼습니다.

- stage 아래의 bin 에 가보면  윈도우/리눅스에서 각각 실행 할수 있는 파일이 생겼습니다.


1-5. 프로젝트 실행 

윈도우 : HelloWorld.bat

리눅스 : ./HelloWorld 

실행하면 bin 폴더 아래에 logs 폴더가 생기고 로그가 메세지가 찍힐것입니다.  (로그부분은 이전 강좌를 참고) 

 


1-6. 프로젝트 백그라운드로 실행 

저렇게 putty 창에서 실행하면 창 닫으면 종료되기때문에

nohup ./HelloWorld &  식으로 실행한다.



* 먼가 안되면 항상 버전 체크를 해보세요. 자바나 스칼라나 아카나~



부록 : 우분투에 자바 설치 

1. repository 추가

1
$ sudo add-apt-repository ppa:webupd8team/java

2. repository index 업데이트

1
$ sudo apt-get update

3. JDK 설치

1
$ sudo apt-get install oracle-java8-installer


스칼라 설치는 안해도 됨.


참고:

Use-case and Deployment Scenarios



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

- Scala 2.11.8 기반 

- Akka 2.4.11 기반 

- Java 8  (akka 2.4 부터는 java 8 요구함. scala 2.11 은 java 7도 괜찮지만~) 



로깅 (Logging)


실전에서 로깅은 매우 중요합니다. 아카 프레임워크는 내부 로깅 시스템도 있으며 외부 로깅프레임워크 또한 지원 하는데요. 이렇게 혼재하다보니 처음에 아카를 할 때 로깅에 애를 먹을 수 도 있습니다. 다음 예를 따라해보면서 감을 익혀 보겠습니다. 

* self4j 나 logback 그 자체에 대한 정보는 다른글을 참고하세요.
* 모두 테스트하여 제대로 작동되는 것을 확인하였습니다.
* 하지만 이게 최선의 방법이라거나 제가 설명한 내용이 100% 정확하다는 보장은 못해드립니다. OTL.



0. 기본 프로젝트 생성   


가장 기본적인 프로젝트를 하나 만듭니다. 아래와 같이 간단한 코드를 작성합니다. 

class HelloWorld extends Actor {
override def receive: Receive = {
case msg : String => println(s"hi $msg")
}
}


object maintest extends App{
println("application start") <-------- (1)
val system = ActorSystem("helloAkka")
val actor = system.actorOf(Props[HelloWorld])

actor ! "hama"
}

처음엔 편하게  (1)  println 을 이용하여 로그를 출력합니다.  stdout 으로 출력됩니다.


1. 로깅  - 내부로깅 akka.event.Logging 사용

import akka.event.Logging class HelloWorld extends Actor {

val log = Logging(context.system, this)

override def receive: Receive = {
case msg : String => log.info(s"hi $msg")
}
}


- 이번에는 Logging 을 사용하여 출력 합니다.
- 이 경우는 Akka 내부에 가지고 있는 로깅객체를 이용합니다. akka.evnet.Logging 을 임포트 하였습니다.
- 인자로 ActorSystem 이 필요합니다.
- 이것도 역시 stdout 으로 출력됩니다. 


2. 로깅  - 내부 로깅 ActorLogging 사용  

import akka.actor.ActorLogging

class HelloWorld extends Actor with ActorLogging{

override def receive: Receive = {
case msg : String => log.info(s"hi $msg")
}
}

- 이번에는 ActorLogging 을 상속받아서 사용합니다.
-  akka.actor.ActorLogging 을 임포트 하였습니다.
- 이것도 역시 stdout 으로 출력됩니다. 

 resources/application.conf 를 만들고 아래와 같이 넣어주면 

akka {
loggers = ["akka.event.Logging$DefaultLogger"]
loglevel = "error"
}

- error 이상만 출력되도록 했기 때문에 위에 로깅이 출력되지 않습니다.
- loglevel = "debug" 로 하면 잘 출력되겠지요. debug 까지 출력됩니다.( debug - info - warning - error )

아래 처럼 {}  (placeholder) 를 이용하여 여러 정보를 간편히 넣을 수 있습니다.

log.info(s"hi $msg and {} , {} ", "one", "two")


3. 로깅  -  외부 프레임워크 사용해서 file 에도 쓰기

build.sbt 에 외부 로깅프레임워크에 대한  의존성을 추가합니다.

libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % "1.1.7",
"com.typesafe.akka" % "akka-slf4j_2.11" % "2.4.8")

* 읽을거리 : logback 을 사용해야 하는 이유 


resources/application.conf  에는 아래와 같이 넣어주시구요. 


akka {
# event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "DEBUG"
}


 원격 actor 일 경우는 다음과 같습니다. 

Provider {
akka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "DEBUG"
actor.provider = "akka.remote.RemoteActorRefProvider"
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "192.168.1.45"
port = 24321
}
}
}

즉  remote 액터가 설정되있으면 여기도 loggers 설정해줘야 remote 액터들의 로그도 logback.xml 설정대로 작동됩니다.


* 로깅 전략 

resources/logback.xml 을 만들어서 아래와 같이 넣어주십시요.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%X{akkaTimestamp} %-5level[%thread] %logger{0} - %msg%n</pattern>
</encoder>
</appender>

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/akka.log</file>
<append>true</append>
<encoder>
<pattern>%date{yyyy-MM-dd} %X{akkaTimestamp} %-5level[%thread] %logger{1} - %msg%n</pattern>
</encoder>
</appender>

<logger name="akka" level="DEBUG" />

<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>

</configuration>

- logs/akka.log 파일에 로그가 출력됩니다.
- DEBUG 레벨 이상 모두 로깅됩니다. 


위의 FILE Appender 의 경우 하나의 파일에 계속 로깅을 합니다. 따라서 로그파일을 압축시키거나 날짜별로 관리해줄 필요가 생깁니다. 이때는 다른 Appender 를 만들어서 사용하면 됩니다.


* 파일 용량별 구분  Appender 

<!-- 용량이 차면 압축해주는 로깅 --> 
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/hello.log</file>
<encoder>
<pattern>%date %level %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<maxIndex>10</maxIndex>
<FileNamePattern>logs/hello.log.%i.gz</FileNamePattern>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
</appender>

- 위의 경우는 10MB 이내의 10개의 압축파일을 만들며 , 그 이후에는 압축하지 않고 계속 하나의 파일에 쓰게 됩니다.


* 날짜별 구분 파일 Appender


<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{dd-MM-yyyy}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>

<encoder>
<pattern>%date %level %msg%n</pattern>
</encoder>
</appender>

- 날짜별로 하나의 로그파일을 저장해 둡니다.
- 최근 30일치의 로그를 남겨둡니다. 즉 하루가 지나면 가장 옛날 로그파일 하나를 지웁니다.


팁 : 파일이름 뒤에 .zip .gz 처럼 압축형식을 적어주면 자동으로 압축해서 보관합니다.


* 로그 사용 방법 

기존 처럼 아래와 같이 사용하시면 됩니다.

import akka.actor.ActorLogging

class HelloWorld extends Actor with ActorLogging{

override def receive: Receive = {
case msg : String => log.info(s"hi $msg")
}
}


기존에는 stdout 에만 출력되었다면 이제는 file에도 출력됩니다. logs/로그파일명.log 파일을 확인하세요.

2016-12-26  INFO [helloAkka-akka.actor.default-dispatcher-4] a.e.s.Slf4jLogger - Slf4jLogger started
2016-12-26 08:41:11.329UTC DEBUG[helloAkka-akka.actor.default-dispatcher-4] a.e.EventStream - logger log1-Slf4jLogger started
2016-12-26 08:41:11.331UTC DEBUG[helloAkka-akka.actor.default-dispatcher-4] a.e.EventStream - Default Loggers started
2016-12-26 08:41:11.350UTC INFO [helloAkka-akka.actor.default-dispatcher-4] HelloWorld - hi hama


4. ActorLogging 과 scala-logging 의 혼용 

액터 내에서 로거 인스턴스를 제공해야 할 때 ActorLogging 을 사용하는 것은 자연스러운 일입니다. 그러나 때때로 scala-logging(예 : LazyLogging 또는 StrictLogging)으로 로깅하는 것을 볼 수 있습니다. 일반적으로 액터 모델이 Dataflow or Reactive Stream 을 만나는 코드에서 볼 수 있는데요. 이 두 솔루션을 서로 같이 사용할 수 있을까요? 자세한 내용을 살펴 보겠습니다.

ActorLogging은 Akka와 깊이 통합되어 있습니다. 기본적으로 Mapping Diagnostic Context (DiagnosticActorLogging 참조)에는 액터 트리에있는 액터의 전체 경로를 비롯하여 많은 유용한 정보가 있습니다. 또한 로깅 설정 중 일부는 Akka 구성과 공유되기 때문에 application.conf에서 akka.loglevel을 변경하면 액터가 보고하는 로그에 영향을 미칩니다. 또한 ActorLogging에서 제공하는 로그 인스턴스는 액터 내부에서 사용되기 때문에 지연 된 계산 (예 : Futures)에 전달하지 말아야한다는 점도 중요합니다.

반면 scala-logging 은 보다 일반적인 솔루션입니다. Akka와의 연결이 없으므로 액터의 적절한 컨텍스트를 제공 할 수 없습니다. 또한 로깅 구성은 Akka 설정과 완전히 분리되어 있습니다. 로거 인스턴스를 보호 할 필요가 없으므로 필요할 때마다 전달할 수 있습니다.

요약하면, Actor 본문 내부에서 로깅이 발생할 때마다 ActorLogging을 사용하여 로깅 정보의 컨텍스트를 유지합니다. 다른 모든 장소에서는 스칼라 로깅을 사용하십시오.

예)

의존성 추가

libraryDependencies += "com.typesafe.scala-logging" % "scala-logging-slf4j_2.11" % "2.1.2"


 ActorLogging 과 LazyLogging 을 함께 사용하는 모습


import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import com.typesafe.scalalogging.slf4j.LazyLogging

/**
* Created by hama on 2016-12-26.
*/


class HelloWorld extends Actor with ActorLogging with LazyLogging {

override def receive: Receive = {
case msg : String =>
log.info(s"hi $msg")
logger.info("strictLogging...........................")
}
}




참고) 

- Akka in Action

https://sachabarbs.wordpress.com/2016/08/03/akka-logging/


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret