티스토리 뷰

728x90
반응형

다른 웹 서버로부터 요청 후 응답을 받을 때까지 기다리지 않아도 되는 서비스는 비동기 처리방식으로 구현합니다. 비동기 처리 방식을 이용하면, 사용자 입장에서는 멈춤 현상(Blocking)을 겪지 않고, 자연스럽게 웹 사용성이 증가됩니다. 이번 포스팅에서는 Springboot에서 비동기 처리 서비스를 개발하는 방법에 대해 알아보겠습니다.

Springboot Web 프로젝트 다운로드

https://start.spring.io/ 에서 디펜던시에 Spring Web을 추가하고, 스프링 부트 프로젝트를 다운로드합니다.

Spring Web 프로젝트 다운로드

프로젝트의 build.gradle 파일을 열어보면, Spring Web 디펜던시가 추가된 것을 확인할 수 있습니다.

implementation 'org.springframework.boot:spring-boot-starter-web'

Springboot 비동기 처리 서비스 코드 작성하기

비동기 처리 서비스를 개발하는 예제로 Github API를 활용해 유저 정보를 가져오는 코드를 작성해보겠습니다. 먼저, User정보를 저장할 Entity 클래스를 아래와 같이 작성합니다.

package com.example.demo;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class User {

    private String name;
    private String blog;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBlog() {
        return blog;
    }

    public void setBlog(String blog) {
        this.blog = blog;
    }

    @Override
    public String toString() {
        return "User [name=" + name + ", blog=" + blog + "]";
    }

}

User 클래스는 이름과 블로그 주소를 저장하고 출력합니다. 다음으로, Github API를 호출하는 GithubLookupService 클래스를 작성합니다.

package com.example.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;

@Service
public class GitHubLookupService {

    private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);

    private final RestTemplate restTemplate;

    public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    @Async
    public CompletableFuture<User> findUser(String user) throws InterruptedException {
        logger.info("Looking up " + user);
        String url = String.format("https://api.github.com/users/%s", user);
        User results = restTemplate.getForObject(url, User.class);
        // Artificial delay of 1s for demonstration purposes
        Thread.sleep(1000L);
        return CompletableFuture.completedFuture(results);
    }

}

findUser()에서 RestTemplate를 활용하여 github API를 호출하고, 응답 정보를 User 객체로 수신받습니다. @Async는 붙은 메서드는 별도의 스레드가 생성되어 실행됩니다. 그리고, 반환 타입은 CompletableFuture <>인데, 이는 Async 메서드에서 반환할 때 사용해야 하는 타입입니다. GithubLookupService를 실행할 수 있는 main 함수를 작성해보겠습니다,

package com.example.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

@EnableAsync
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
   private static final Logger logger = LoggerFactory.getLogger(DemoApplication.class);

   @Autowired
   GitHubLookupService gitHubLookupService;

   public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
   }

   @Bean
   public Executor taskExecutor() {
      ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
      executor.setCorePoolSize(3);
      executor.setMaxPoolSize(3);
      executor.setQueueCapacity(500);
      executor.setThreadNamePrefix("GithubLookup-");
      executor.initialize();
      return executor;
   }

   @Override
   public void run(String... args) throws Exception {
      long start = System.currentTimeMillis();

      CompletableFuture<User> page1 = gitHubLookupService.findUser("PivotalSoftware");
      CompletableFuture<User> page2 = gitHubLookupService.findUser("CloudFoundry");
      CompletableFuture<User> page3 = gitHubLookupService.findUser("Spring-Projects");

      CompletableFuture.allOf(page1,page2,page3).join();

      logger.info("Elapsed time: " + (System.currentTimeMillis() - start));
      logger.info("--> " + page1.get());
      logger.info("--> " + page2.get());
      logger.info("--> " + page3.get());
   }
}

@EnableAsync를 추가해야, Async 메서드가 스프링 부트의 스레드 풀을 통하여 백그라운드로 실행이 됩니다. 그리고 Executor를 반환하는 taskExecutor()를 정의하여, 스레드 풀에 대해 설정합니다. 만약 taskExecutor를 정의하지 않았다면, Springboot는 SimpleAsyncTaskExecutor를 생성하고 사용합니다. setCorePoolSize, setMaxPoolSize를 3으로 설정하여 스레드 개수를 3개로 설정합니다. github api를 요청하는 코드를 CommandLineRunner를 상속받아 run메서드에 구현하였습니다. API 요청 전 시간을 측정하고, 3개의 요청이 완료된 후 응답을 3개의 응답을 받는데 까지 걸리는 시간을 측정합니다. join()가 응답을 기다리를 역할을 합니다.

findUser()는 @Async 어노테이션을 사용하여 비동기식으로 처리를 지정하였으므로, PivotalSoftware, CloudFoundry, Spring-Projects 유저 정보 조회 api는 응답이 오지 않아도, 먼저 요청이 이루어집니다. 만약 동기식이었다면, PivotalSoftware에 대한 유저 정보를 요청하고 응답을 받은 뒤, 다음 코드가 진행됩니다. 이러한 결과를 통해 비동기 처리는 웹서버로 요청을 먼저 전송한 이후, 응답 결과를 수신하기 전에 다른 로직을 실행할 수 있으므로 작업의 효율을 높일 수 있습니다.

--> User [name=Pivotal Software, Inc., blog=http://pivotal.io]
--> User [name=Cloud Foundry, blog=https://www.cloudfoundry.org/]
--> User [name=Spring, blog=https://spring.io/projects]

 

 

728x90
반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함