개요

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가 소잡는 칼이라 닭을 더 쉽게 잡을 수 있다.

문제

Spring Batch를 사용하기 위해 Maven 의존성을 추가하고, BatchConfig.java를 설정을 완료했다.
그리고 배치를 시작하는 순간 transactionManager bean이 중복선언 되었다는 에러를 마주했다.
배치에서 새로 생성한 transactionManager 때문에, 기존 프로젝트에서 사용하던 기존의 transacrionManager bean을 등록할 수 없다는 이유였다.

@Configuration
@EnableBatchProcessing
public class BatchJobConfig {

@Autowired
JobBuilderFactory jobBuilderFactory;
@Autowired
StepBuilderFactory stepBuilderFactory;

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

@Bean
public Step somethingStep1() {
return stepBuilderFactory.get("updateSomethingJobStep1")
.tasklet((contribution, chunkContext) -> {
log.info("Running UpdateSomethingJobStep1...");
return RepeatStatus.FINISHED;
})
.build();
}
}

APPLICATION FAILED TO START *************************** Description:

The bean 'transactionManager', defined in class path resource [...], could not be registered. A bean with that name has already been defined in class path resource [SimpleBatchConfiguration.class] and overriding is disabled. Action: Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

해결 시도

  1. "아 왜 배치용 transactionManager Bean 이름을 'transactionManager'로 한거야... batchTransactionManager 이런걸로 하면 안되나" 하여 불평.
  2. DefaultBatchConfigur, SimpleBatchConfiguration Bean을 상속받아서 열심히 getTransactionManager()를 열심히 오버라이딩 시도.
  3. 오버라이딩 하며 한창 삽질하다, Error description 에서 BatchConfiguration을 내가 만든게 아니라, SimpleBatchConfiguration.class 만 보던것을 발견. 
  4. 왜 그러지? 왜 왜 bean overriding이 안되지 고민 끝에 Error 내용 중에 Action 문구를 발견함. 

Action: Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

   5. 구글링해보니 Spring Boot 2.1 부터 사고를 방지하기 위하여 Bean Overriding이 기본 비활성화 됨. - Spring Boot 2.1 Release Notes

Bean Overriding

Bean overriding has been disabled by default to prevent a bean being accidentally overridden. If you are relying on overriding, you will need to set spring.main.allow-bean-definition-overriding to true.

  6. Bean Overriding을 활성화 하기 위해 application.yml에 spring.main.allow-bean-definition-overriding: true 옵션 추가.
  7. 파-워 해결.

결론

Spring boot 2.1 부터는 bean definition overriding이 false인것을 기억하자.
분명 이전에도 비슷한 문제를 겪었었는데.... 


Maven vs Gradle

스프링 기반의 프로젝트를 시작하면서 Maven을 처음 접했다.

Ant를 사용한적도 없었고 의존성 관리와 빌드 스크립트에 대한 지식도 없었기에 이런게 있나보다 하고 사용했었다.

Maven 책을 한권 보고나서야 프로젝트 구성, 빌드툴이 무었인지 이해할 수 있었고, 편리한 의존성 관리에 감사하며 부족함을 느끼지 못했다.

하지만 프로젝트의 단위가 커지면서 빌드와 테스트에 소요되는 시간이 길어졌고,

여러 모듈에서 설정을 상속받기 시작하면서 Gradle이라는 녀석이 계속 눈에 뛰었다.

Gradle이 Maven의 단점을 보완해주고 사용해본 사람들이 좋다고들 하니 Maven이랑 비교해서 얼마나 좋은지 알아보려 한다.

Maven

Apache의 이름 아래 2004년 출시

Ant를 사용하던 개발자들의 불편함을 해소 + 부가기능 추가

Maven은 무엇인가?

  • 빌드를 쉽게 (Making the build process easy)
  • pom.xml을 이용한 정형화된 빌드 시스템 (Providing a uniform build system)
  • 뛰어난 프로젝트 정보 제공 (Providing quality project information_
    • Change log document created directly from source control
    • Cross referenced sources
    • Mailing lists
    • Dependency list
    • Unit test reports including coverage
  • 개발 가이드 라인 제공 (Providing guidelines for best practices development)
    • Keeping your test source code in a separate, but parallel source tree
    • Using test case naming conventions to locate and execute tests
    • Have test cases setup their environment and don’t rely on customizing the build for test preparation.
  • 새로운 기능을 쉽게 설치할 수 있고 업데이트할 수 있음 (Allowing transparent migration to new features)

Gradle

Ant와 Maven의 장점을 모아모아 2012년 출시

Android OS의 빌드 도구로 채택 됨

Gradle이란 무엇인가?

  • Ant처럼 유연한 범용 빌드 도구 (A very flexible general purpose build tool like Ant.)
  • Maven을 사용할 수 있는 변환 가능 컨벤션 프레임 워크 (Switchable, build-by-convention frameworks a la Maven. But we never lock you in!)
  • 멀티 프로젝트에 사용하기 좋음 (Very powerful support for multi-project builds.)
  • Apache Ivy에 기반한 강력한 의존성 관리 (Very powerful dependency management (based on Apache Ivy))
  • Maven과 Ivy 레파지토리 완전 지원 (Full support for your existing Maven or Ivy repository infrastructure.)
  • 원격 저장소나, pom, ivy 파일 없이 연결되는 의존성 관리 지원
    (Support for transitive dependency management without the need for remote repositories or pom.xml and ivy.xml files.)
  • 그루비 문법 사용 (Groovy build scripts.)
  • 빌드를 설명하는 풍부한 도메인 모델 (A rich domain model for describing your build.)

Maven VS Gradle

Maven에는 gradle과 비교 문서가 없지만, gradle에는 비교문서가 존재. (비교에 자신있는 모습 ..ㅋ)

Gradle이 시기적으로 늦게 나온만큼 사용성, 성능 등 비교적 뛰어난 스펙을 가지고있다.

Gradle이 Maven보다 좋은점

  • Build라는 동적인 요소를 XML로 정의하기에는 어려운 부분이 많다.
    • 설정 내용이 길어지고 가독성 떨어짐
    • 의존관계가 복잡한 프로젝트 설정하기에 부적절
    • 상속구조를 이용한 멀티 모듈 구현
    • 특정 설정을 소수의 모듈에서 공유하기 위해서는 부모 프로젝트를 생성하여 상속하게 해야 함 (상속의 단점 생김)
  • Gradle은 Groovy를 사용하기 때문에, 동적인 빌드는 Groovy 스크립트로 플러그인을 호출하거나 직접 코드를 짜면 된다.
    • Configuration Injection 방식을 사용해서 공통 모듈을 상속해서 사용하는 단점을 커버했다.
    • 설정 주입 시 프로젝트의 조건을 체크할 수 있어서 프로젝트별로 주입되는 설정을 다르게 할 수 있다.

 

Gradle vs Maven: Performance Comparison

  • 600명의 엔지니어가 1년동안 1분걸리는 빌드를 매주 42번 빌드를 진행할 때 들어가는 비용은
    600 engineers * $1.42/minutes * 42 builds/week * 44 work weeks/year = $1,600,000/year
    이때 빌드 속도가 90% 빨라진다면?
  • Gradle은 메이븐 보다 최대 100배 빠르다.
  • 어떻게?
    • the Gradle Daemon is a long-lived process that keeps build information “hot” in memory
    • incremental task inputs and outputs for various types of tasks makes it unnecessary to run clean ever again
    • incremental compilation analyzes the dependencies between your sources and classes and recompiles only those which are affected by changes
    • the build cache fetches results from a cache when switching branches or running a clean build and the same output has already been produced somewhere else in the organization.
    • Gradle’s smart classpath analyzer avoids unnecessary compilation when the binary interface of a library hasn’t changed
    • better modelling of dependencies using the Java Library plugin reduces the size of the compile classpath which has a large, positive impact on performance

예제

스프링부트를 이용하여 같은 기능과 라이브러리 의존성을 가지는 Maven, Gradle 프로젝트를 생성해 보았다. (Java 1.8, Spring Boot 1.5.4)

  1. 스크립트 길이와 가독성 면에서 Gradle(groovy)이  앞선다.
  2. 빌드와 테스트 실행 결과 Gradle이 더 빠르다. (Gradle이 캐시를 사용하기 때문에 테스트 반복 시 차이가 더 커진다.)
  3. 의존성이 늘어날 수록 성능과 스크립트 품질의 차이가 심해질 것이다.
Maven
<?
xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>demo-maven</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>demo-maven</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>


</project>
Gradle
buildscript {
ext {
springBootVersion = '1.5.4.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
mavenCentral()
}


dependencies {
compile('org.springframework.boot:spring-boot-starter')
testCompile('org.springframework.boot:spring-boot-starter-test')
}

결론

지금 시점에서 Gradle을 사용하지 않을 이유는 '익숙함' 뿐인 것 같다.

Gradle이 출시되었을 때에는 Maven이 지원하는 Scope를 지원하지 않았고 성능면에서도 앞설것이 없었다.

Ant의 유연한 구조적 장점과 Maven의 편리한 의존성 관리 기능을 합쳐놓은 것만으로도 많은 인기를 얻었던 Gradle은 버전이 올라가며 성능이라는 장점까지 더해지면서 대세가 되었다.

 

물론 그동안 사용해왔던 Maven과 이제는 익숙해진 XML을 버리고 Gradle과 Groovy문법을 배우는 것은 적지않은 비용이 든다.

특히 협업을 하는 경우, 프로젝트 구성과 빌드만을 위해 모든 팀원이 Groovy 문법을 익여야 한다는 사실은 Gradle를 사용하는데 큰 걸림돌이 된다.

실제로 여전히 Maven의 사용률은 Gradle을 앞서고 있으며 구글 트랜드 지수도 Maven이 Gradle을 앞선다.

 

협업과 러닝커브를 고려하여 여전이 Maven를 사용하는 팀이 많고 부족함 없이 잘 사용하고 있지만,

앞서 요약했듯 프로젝트의 빌드타임이 비용문제로 이어질 경우 Gradle을 사용해야 할 것 같다.

리드미컬?하게 테스트를 진행하고 민첩한? 지속적 배포를 생각하고 있다면 새로움 배움이 필요하더라도 Gradle을 사용해보자. 아자.

 

출처

1. 개요


서비스에 대용량의 트래픽이 몰리면서, 특정 API 호출에 대한 응답이 지연되면 전체적 로직에 영향을 미치게 된다.

이러한 상황을 재현하기 위해 응답을 늦게 보내는 서버를 구현 하고자 했다.

단순하게 요청을 받고 몇초간의 sleep 후 응답을 돌려주면 간단하게 구현할 수 있지만, 

제한된 자원(톰캣 1대)에서 다수(초당 15000명)의 request 를 처리하기 위해서 일반적인 sleep 방법으로는 구현 할 수 없었다.

(성능 이슈가 없다면 이곳 을 참조하면 간단하게 응답 지연 api를 얻을 수 있다.)


2. 문제점


사실, response time은 늦으면서, 다수의 request를 처리한다는 건 역설적인 말이다. 

request 가 많을 수록, connection 의 수는 늘어 날 것이고, sleep 을 통해 connection을 놓아주지 않으면  더 이상의 request 를 처리할 수 없기 때문이다. 

request 를 위한 connection 비용이 적지 않을 뿐더러, Spring 에서 request 를 처리하기 위한 servlet container thread 자원은 application의 로직을

수행하기 위한 application thread 보다 적고 한정적이다. 


3. 해결 방법


Long polling 방식으로 request 에 대하여 늦은 response를 전달하는 방법으로 서버 성능 문제를 해결하고자 했다.

단순히 클라이언트 쪽에서 long polling 방식은 connection을 그대로 유지하고 있기 때문에 문제를 해결 할 수 없는 방식이었고,

서버쪽에서 long polling 을 적용하여 connection 위한 thread 관리가 필요했다.


사실 long polling 방식도 request - response 모델이어서, 서버에서 클라이언트에게 응답을 주기 전까지 connection을

유지하기 때문에 이 방식도 성능 이슈를 해결하기 어려울 것 처럼 보였다. 

그러던 중 이러한 long polling의 문제점을 보완하기 위해 Spring 3.2에서 추가된 Async Servlet 기능을 보게 되었다. 

Async Servlet는 request 처리를 위한 servlet container thread의 일을 application thread에 위임시키고 servlet container thread를 반납해서

새로운 request 를 처리하는 방식으로 작동한다.


Spring 의 Async Servlet 을 이용하면, 서버의 thread pool 과 acceptCount 값을 조정하여, 

제한된 자원에서 다수의 request 를 처리할 수 있을것이라 생각했다.


4. 적용


소스 코드는 Spring-mvc-showcase 를 참조하였다. 


(1) 소스

@Controller @RequestMapping("/async/callable") public class CallableController { @RequestMapping("/response-body-2") public @ResponseBody Callable<String> callable() { return new Callable<String>() { @Override public String call() throws Exception { Thread.sleep(2000); return "Callable result"; } }; } }



(2) 결과

응답이 2초 후 온 것을 알 수 있다.


위 로직을 수행하는 동안의 로그는 다음과 같다.

15:23:20 [http-apr-80-exec-9] DispatcherServlet - DispatcherServlet with name 'appServlet' processing GET request for [/async/callable/response-body-2]

15:23:20 [http-apr-80-exec-9] RequestMappingHandlerMapping - Looking up handler method for path /async/callable/response-body-2

15:23:20 [http-apr-80-exec-9] RequestMappingHandlerMapping - Returning handler method [public java.util.concurrent.Callable<java.lang.String> org.springframework.samples.mvc.async.CallableController.callable()]

15:23:20 [http-apr-80-exec-9] DispatcherServlet - Last-Modified value for [/async/callable/response-body-2] is: -1

여기 까지 기존 로직을 따른다

15:23:20 [http-apr-80-exec-9] WebAsyncManager - Concurrent handling starting for GET [/async/callable/response-body-2]

WebAsyncManager(application thread) 에서 병행처리를 시작하고

15:23:20 [http-apr-80-exec-9] DispatcherServlet - Leaving response open for concurrent processing

Dispatcher(servlet container thred) 에서 작업을 끝낸다.

이렇게 작업 쓰레드간 switching 이 이루어지고

15:23:22 [MvcAsync5] WebAsyncManager - Concurrent result value [Callable result] - dispatching request to resume processing

작업을 넘겨받은 MvcAsyc 는 작업을 수행(2초간의 sleep)후 dispatching 을 요청한다.

15:23:22 [http-apr-80-exec-4] DispatcherServlet - DispatcherServlet with name 'appServlet' resumed processing GET request for [/async/callable/response-body-2]

15:23:22 [http-apr-80-exec-4] RequestMappingHandlerMapping - Looking up handler method for path /async/callable/response-body-2

15:23:22 [http-apr-80-exec-4] RequestMappingHandlerMapping - Returning handler method [public java.util.concurrent.Callable<java.lang.String> org.springframework.samples.mvc.async.CallableController.callable()]

15:23:22 [http-apr-80-exec-4] DispatcherServlet - Last-Modified value for [/async/callable/response-body-2] is: -1

15:23:22 [http-apr-80-exec-4] RequestMappingHandlerAdapter - Found concurrent result value [Callable result]

15:23:22 [http-apr-80-exec-4] RequestResponseBodyMethodProcessor - Written [Callable result] as "text/html" using [org.springframework.http.converter.StringHttpMessageConverter@7967f612]

Spring mvc가 callable 을 보고 병행작업임을 알고 나머지 로직을 수행한다.

15:23:22 [http-apr-80-exec-4] DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'appServlet': assuming HandlerAdapter completed request handling

15:23:22 [http-apr-80-exec-4] DispatcherServlet - Successfully completed request



5. Servlet container thread 와 application specific thread 의 차이


여기서 servlet container thread 와 application thread 의 차이점을 살펴보자.

servlet thread의 일을 application thread 로 넘겨줌으로 해서 long polling 의 단점을 보완한다고 하는데, 결국 application thread 역시

서버의 자원을 사용하지 않는가? 라는 의문을 가질 수 있다.


Servlet container 란 HTTP Request 와 response 객체를 서블릿에 넘겨주고, 서블릿의 생명주기를 관리한다.

따라서 servlet container thread 는 사용자의 request 를 처리하는 작업을 수행한다. 

반면 application는 서비스 자체를 의미하고, application thread는 Spring framework 에서 발생하는 작업들을 수행한다.


이러한 관점에서 보았을때,  serblet thread 의 일을 application thread 에 위임하는 것이 

많은 request 를 수신하기 위한 효율 적인 방법임을 알 수 있다.

하지만, 결국 한정된 서버의 자원을 공유하여 사용한다는것은 변함이 없다.

spring mvc 내에서 servlet container에 할당 할 thread 수를 조정할 수 있는지 알아보고 최적화가 필요할 것 같다.


6. 결론


서버의 한정된 자원에서 다수의 request 를 처리하는데 한계가 있지만, 

서버는 무엇으로 구성할지 (톰캣, 네티, 직접구현)에 따라서, 그리고 서버 환경 설정을 어떻게 하는냐에 따라 동시에 수용할 수 있는 request의 수를 증가 시킬 수 있다. 

서버의 설정 이후에는, 어플리케이션의 구현 부분에서 최적화가 필요할 것이다.

고민의 시작은 전자에서 시작했지만, 작업은 후자에서 진행되었다.

다시 서버단으로 돌아가서 고민을 해봐야겠지만, 넉넉한 자원이 주어졌을때, 해당 자원을 제대로 활용하지 못하는 경우

application의 구현부분에서의 최적화 역시 필요할것으로 생각한다.


결과적으로  Async Servlet를 이용한 long polling 방식으로 서버에 요청할 수 있는 최대 request 를 늘리는 것은 실패하였다.

추가적으로 서버의 최적화 또는, tomcat 에 comet 을 적용, 심플한 구조의 서버 구축과 같은 작업이 병행되어야 할 것으로 보인다.

 


+ Recent posts