개요

2분 마다 시총 상위 100개 코인에 대한 가격정보를 크롤링하려 한다.
크롤링을 위해서는 Batch Job 과 Scheduler가 필요한데, 어떤 환경으로 구성할까 고민하다 Batch Job은 Spring Batch로 Scheduler는 Jekins로 구성했다.

Spring Batch를 쓴 이유

1. Spring boot를 사용중이라 기존에 만들어둔 Service를 재사용할 수 있다.
2. Transaction, Datasouce 관리가 편하다.
3. 익숙한 환경 (Java, RestTemplate, Error handling 등등)

Jekins를 쓴 이유

1. GUI 환경 Batch Job 관리가 편리하다. (상태확인, 실행 주기 관리, 로그 확인 등등)
2. Jenkins에서 Jar 형태의 Batch Job Application 을 직접 실행할 경우, 로그 확인이 편리

문제점

Spring Batch 를 단순 크롤링 작업으로만 사용하기에는 오버스팩!
오버스팩인것은 맞다. 하지만 구현 속도는 가장 빠르다.

Q Spring Batch 는 Database Table로 Job을 관리하는데 Jekins로 실행한다고?
A Database Table를 이용하지 않고, In - Memory 에서 작동하도록 설정했다. 

Q Batch Application를 이중화 하려면? 
A 이중화가 불가능한 문제다. 다만, 배포를 위한 중단시간이 제로에 가깝고, 잡을 분리하는 등 운영방식으로 해결할 수 있다.

구현

1. 스프링 배치 설정

Spring Batch 의존성 추가 (Spring boot 2.1 기준)

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>

Batch Config, Batch Job 생성 
(InMemoryBatchConfigurer는 따로 생성했다.)

@Configuration
@EnableBatchProcessing
public class BatchJobConfig {

@Autowired
JobBuilderFactory jobBuilderFactory;
@Autowired
StepBuilderFactory stepBuilderFactory;

@Bean
public BatchConfigurer batchConfigurer() {
return new InMemoryBatchConfigurer();
}

@Autowired
private UpdateTop100CoinPriceTasklet updateTop100CoinPriceTasklet;

@Bean
public PlatformTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}

@Bean
public Job updateTop100CoinPriceJob() {
return jobBuilderFactory.get("updateTop100CoinPrice")
.start(updateTop100CoinPrice())
.build();
}

@Bean
public Step updateTop100CoinPrice() {
return stepBuilderFactory.get("updateTop100CoinPrice")
.tasklet(updateTop100CoinPriceTasklet)
.build();
}
}

Batch Tasklet 생성
Tasklet과 InitializingBean을 구현하면 된고 RepeatStatus를 통해 Tasklet 반복여부를 선택할 수 있다.
Tasklet에서 실행시킬 로직은 기존 Service Bean 주입을 통해 해결할 수 있어서 편

@Component
public class UpdateTop100CoinPriceTasklet implements Tasklet, InitializingBean {

@Autowired
MarketCapService marketCapService;

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
marketCapService.updateTop100CoinPrice();
return RepeatStatus.FINISHED;
}

@Override
public void afterPropertiesSet() throws Exception {

}
}

In - Memory Batch Configurer 설정
Database Table로 Job을 관리하면 여러모로 신경써야할게 많다. 단순한 목적에 맞게 Memory에서 관리하자. 

public class InMemoryBatchConfigurer implements BatchConfigurer {

private PlatformTransactionManager transactionManager;
private JobRepository jobRepository;
private JobLauncher jobLauncher;
private JobExplorer jobExplorer;

@Override
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}

@Override
public JobRepository getJobRepository() {
return jobRepository;
}

@Override
public JobLauncher getJobLauncher() {
return jobLauncher;
}

@Override
public JobExplorer getJobExplorer() {
return jobExplorer;
}

@PostConstruct
public void initialize() {
if (this.transactionManager == null) {
this.transactionManager = new ResourcelessTransactionManager();
}
try {
MapJobRepositoryFactoryBean jobRepositoryFactoryBean =
new MapJobRepositoryFactoryBean(this.transactionManager);
jobRepositoryFactoryBean.afterPropertiesSet();
this.jobRepository = jobRepositoryFactoryBean.getObject();

MapJobExplorerFactoryBean jobExplorerFactoryBean =
new MapJobExplorerFactoryBean(jobRepositoryFactoryBean);
jobExplorerFactoryBean.afterPropertiesSet();
this.jobExplorer = jobExplorerFactoryBean.getObject();

SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository);
jobLauncher.afterPropertiesSet();
this.jobLauncher = jobLauncher;
} catch (Exception e) {
throw new BatchConfigurationException(e);
}
}
}

2. Application 실행환경 설정

Application Type을 WEB에서 NONE으로 변경했다. 이제 포트를 찾지 않아도 되어서 여러 Job들을 실행할 수 있다. (프로세스 생성비용은 늘어나지만..)
Spring Batch CommandLineJobRunner 를 사용할까 하다가, 어렵고 과한것 같아서 Application 실행 변수로 Batch Job 명을 전달 받아 실행하는 방식으로 세팅했다. 

@Slf4j
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
})
@EnableAsync
@EnableScheduling
@EnableCaching
@EnableTransactionManagement(proxyTargetClass = true)
@EnableJpaRepositories(basePackages = "com.happy.life")
public class BatchApplication {

public static void main(String[] args)
throws JobParametersInvalidException, JobExecutionAlreadyRunningException,
JobRestartException, JobInstanceAlreadyCompleteException {

SpringApplication app = new SpringApplication(BatchApplication.class);
app.setWebApplicationType(WebApplicationType.NONE);
ConfigurableApplicationContext ctx = app.run(args);
JobLauncher jobLauncher = ctx.getBean(JobLauncher.class);

if (args.length > 0) {
Job job = ctx.getBean(args[0], Job.class);
jobLauncher.run(job, new JobParameters());
} else {
log.error("배치 잡 이름을 입력해 주세요. EX) {}", "java -jar app.jar updateTop100CoinPriceJob");
}

System.exit(0);
}
}

3. 젠킨스 설정

젠킨스를 설치하고 깃헙 연동하는 과정은 생략한다.

배포하기

젠킨스에서 Freestyle project를 하나 만들어서 Batch Application을 배포하자.
Github에서 소스를 받아와 maven build하는 기본 빌드 이후에 jar를 복사한다.  yes|cp -rf 옵션을 줘서 항상 덮어쓰기 하도록한다.
이렇게 하면 순단없이 아주 빠르게 배포? 한다고 말할 수 있지않을까? 호호호호

실행하기

배포한 jar를 실행한다. Jenkins의 Build periodically값을 조정해서 2분만다 실행하도록 한다.
그리고 Execute shell 에서 Java -jar 옵션으로 배치를 실행한다.

결과 확인

Job에서 뱉어내는 로그들을 잘 기록하면서 수행된 것을 확인할 수 있다. 오예 끝~!


결론

Spring Batch가 소잡는 칼이라 닭을 더 쉽게 잡을 수 있다.

안드로이드 빌드 서버를 구성하면서 메모한 내용을 정리한다.

빌드환경은  CentOS7, Jenkins, Github, Sdkmanager 로 구성하였다.

안드로이드 25 버전부터는 GLIBC_2.14 를 사용하기 때문에 CentOS 6이 아닌 7을 사용해야한다.

CentOS 6에도 GLIBC_2.14를 설치할 수 있지만, 커널쪽 라이브러리를 수정하는건 부담이다. 피하자.

안드로이드 SDK설치

안드로이드 SDK설치 과정이 바뀌었다. 포스트를 쓰게 된 가장 큰 이유다.

안드로이드 빌드환경 구성 방법을 구글링 해보면 adnroid-sdk r24.4.1 압축파일을 다운받아 SDK를 설정하는 방법이 대부분이다.

여기서는 최신버전 SDK로 빌드하기 위해, 안드로이드 공식 홈에 나와있는 sdkmanager를 사용했다.

sdkmanger 설치 & 경로 설정

android 폴더를 만들어 sdkmanger를 다운 받는다.

sdkmanger의 bin 폴더를 PATH에 추가 후 sdkmanger --list 명령어로 테스트 해본다.

안드로이드 패키지 리스트가 나오면 설치완료.

mkdir android
cd android
wget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
unzip sdk-tools-linux-3859397.zip
rm sdk-tools-linux-3859397.zip
PATH=$PATH:/절대경로/android/tools/bin
sdkmanager --list

sdkmanger로 sdk 다운로드 

아까 생성한 android 폴더에서 platform-tools와 android-25, 26 platform을 설치한다.

설치가 완료되면 몇몇 폴더들이 추가되어있다.

지금의 android 폴더가 ANDROID_HOME이니 경로를 기억해두자.

sdkmanager "platform-tools" "platforms;android-25" "platforms;android-26"

[bkim@server android]$ ls
build-tools licenses output platforms platform-tools tools

깃 설치

깃허브에서 안드로이드 프로젝트 소스를 받아올것이다. 젠킨스 세팅 전에 git을 설치하자.

설치 후 git 명령어를 날려보면 깃 명령어 리스트가 보인다.

yum update
yum install git
git

젠킨스 세팅

젠킨스 설치

젠킨스 설치 과정은 따로 설명하지 않겠다. jar 파일을 다운받아 바로 실행하는 방법도 좋고, tomcat으로 띄우는 방법도 좋다.

젠킨스 공식 사이트를 참고하면 최신버전을 설치 할 수 있다.

환경변수 설정

ANDROID_HOME을 설정하자. 젠킨스는 편리하게 환경변수를 추가할 수 있는 메뉴를 제공한다.

왼쪽 메뉴 중 Jenkins 관리 -> 시스템 설정 -> Global properties에 추가 -> 이름 ANDROID_HOME -> 값 아까 SDK 설치 경로 (/절대경로/android)

플러그인 설치

github 연동을 위하여 git 관련 플러그인들을 설치해주자.

왼쪽 메뉴 중 Jenkins 관리 -> 플러그인 관리 -> 설치가능 -> git plugin, github plugin 설치

(gradle이 설치되어있지않으면 gradle 플러그인도 추가하자)

빌드 테스크설정

빌드 테스크 설정을 위해 새로운 Item을 추가하자.

왼쪽 메뉴 중 새로운 Item -> Item 이름입력 -> Freestyle project 선택 -> OK

아이템이 생성되었으면, 아이템 설정을 해주자. 단순 빌드를 위한 설정은 단순하다.

 

소스 코드 관리 탭 -> git 선택 -> Repository 주소 입력 -> 깃헙 계정 입력 (나는 서버에 깃헙 키를 등록해두어서 따로 사용자 연동을 하진 않았다)

 

빌드 탭 -> Use Gradle Wrapper 선택 -> Make gradlew executable 체크 -> Tasks 에 clean assembleDebug 입력 (gradle 명령어는 각자에 맞게 수정)

 

빌드 후 조치 탭 -> 빌드 후 조치 추가에서 Archive the artifacts 선택 -> Files to archive에 빌드 완료시 apk 결과물이 떨어지는 경로 입력 (따로 세팅 안했으면 app/build/outputs/apk/*.apk 로 입력하면 된다.)

 

빌드 후 조치에서 Jenkins 저장 이외에 메일로 apk를 보내준다던지, 배포서비스로 apk를 전달하는 것 같은 태스크를 추가 할 수 있다.

또한 빌드 유발 탭에서 깃헙 push 할때마다 빌드도록 세팅해두고, 빌드 후 조치 탭에서 빌드 결과를 메일이나 메신저로 전달하면... 좋다.

 

설정 저장 -> Build Now 클릭 -> 빌드 결과로 들어가면 빌드된 이미지를 확인할 수 있다

마치며

젠킨스를 이용해서 안드로이드 빌드 서버를 구성했다.

빌드 서버를 구성하는 이유는 대부분 CI 환경을 만들기 위함일 것이다.

이제 젠킨스에 Findbugs 같은 코드 분석툴을 붙이고, 그래들 스크립트로 테스트 검증도 할 수 있다.

Deploy Gate라는 서비스를 이용하면 apk를 배포하고, 무선으로 테스트폰에 설치까지 해준다고한다.

결국에는 편하자고 하는 일들이니 우리들 모두 화이팅!!!

 

 

  

+ Recent posts