개요
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 을 직접 실행할 경우, 로그 확인이 편리
문제점
Q Spring Batch 를 단순 크롤링 작업으로만 사용하기에는 오버스팩!
A 오버스팩인것은 맞다. 하지만 구현 속도는 가장 빠르다.
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가 소잡는 칼이라 닭을 더 쉽게 잡을 수 있다.
'SPRING' 카테고리의 다른 글
Spring batch의 transaction manager bean 중복 문제 해결하기 (feat Spring boot 2.1) (1358) | 2019.02.12 |
---|---|
Maven vs Gradle (507) | 2017.07.04 |
Spring long polling(Async Servlet) 이용하여 응답지연 서버 만들기 (451) | 2015.11.22 |