BlockHound: Java 비동기 애플리케이션에서 블로킹 호출을 감지하는 도구

2024. 10. 8. 16:39·프레임워크/자바 스프링

BlockHound로 비동기 코드에서 블로킹 호출 감지하기

Java에서 비동기 프로그래밍은 높은 성능과 확장성을 제공하는 강력한 방법입니다. 특히, Spring WebFlux와 Project Reactor 같은 비동기 프레임워크들은 적은 리소스로도 수많은 동시 요청을 처리할 수 있습니다. 하지만 비동기 코드에서 실수로 블로킹 호출을 사용하면, 이러한 비동기 프로그래밍의 장점을 잃어버릴 수 있으며 심각한 성능 저하가 발생할 수 있습니다.

이 문제를 예방하고, 비동기 코드에서 블로킹 호출을 감지하는 데 도움을 주는 도구가 바로 BlockHound입니다.

BlockHound란?

BlockHound는 Java 애플리케이션에서 블로킹 호출을 탐지하는 Java Agent입니다. 특히 비동기 프로그래밍 환경에서 실수로 블로킹 메서드(예: Thread.sleep(), 파일 읽기/쓰기, 블로킹 I/O)를 사용하는 경우를 찾아내고, 이를 실시간으로 감지하여 예외를 던지거나 로그로 남깁니다. 이를 통해 개발자는 문제를 빠르게 인지하고 수정할 수 있습니다.

비동기 환경에서 블로킹 호출이 실행되면, 스레드가 대기 상태에 들어가며 이는 성능 저하를 일으킵니다. BlockHound는 이러한 상황을 방지하여 비동기 애플리케이션이 고성능을 유지할 수 있도록 도와줍니다.

BlockHound 주요 기능

  1. 비동기 코드에서 블로킹 호출 탐지: BlockHound는 비동기 논블로킹 코드에서 실수로 사용된 블로킹 호출을 감지합니다. 예를 들어, I/O 작업이나 Thread.sleep() 같은 메서드 호출을 감지하고, 예외를 발생시킵니다.
  2. 커스터마이징 가능: BlockHound는 특정 메서드나 라이브러리에서 발생하는 블로킹 호출을 허용하거나 무시하도록 설정할 수 있습니다. 이를 통해 개발자는 애플리케이션에 맞는 최적의 설정을 적용할 수 있습니다.
  3. Reactor, RxJava와의 통합: BlockHound는 Project Reactor, RxJava 같은 비동기 프레임워크와 쉽게 통합되어 사용할 수 있습니다. 비동기 논블로킹 환경을 지향하는 대부분의 비동기 프레임워크와 호환됩니다.
  4. 실시간 블로킹 호출 감지: 애플리케이션 실행 중 블로킹 호출이 발생하면 BlockHound는 실시간으로 이를 감지하고 로그를 남기거나 예외를 발생시켜 개발자가 즉각적인 조치를 취할 수 있게 합니다.

BlockHound 설치 방법

BlockHound는 간단한 설정으로 애플리케이션에 적용할 수 있습니다. Gradle 프로젝트를 기준으로 BlockHound를 설정하는 방법을 살펴보겠습니다.

1. Gradle 의존성 추가

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'io.projectreactor.tools:blockhound:1.0.9.RELEASE'
    testImplementation 'io.projectreactor.tools:blockhound:1.0.9.RELEASE'
}

2. BlockHound 설치

애플리케이션 실행 시 BlockHound를 설치하는 코드를 추가해야 합니다. 일반적으로 애플리케이션 시작 지점인 main 메서드에서 설치합니다.

import reactor.blockhound.BlockHound;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebfluxApplication {

    public static void main(String[] args) {
        // BlockHound 설치
        BlockHound.install();
        SpringApplication.run(WebfluxApplication.class, args);
    }
}

BlockHound.install()을 호출하면 애플리케이션이 실행되는 동안 비동기 환경에서 블로킹 호출이 감지됩니다.

3. JDK 13+에서의 추가 설정

JDK 13 이상을 사용하는 경우 -XX:+AllowRedefinitionToAddDeleteMethods JVM 옵션을 추가해야 합니다. 예외가 발생하면 이 플래그를 추가해 문제를 해결할 수 있습니다.

Exception in thread "main" java.lang.IllegalStateException: The instrumentation have failed.
It looks like you're running on JDK 13+.
You need to add '-XX:+AllowRedefinitionToAddDeleteMethods' JVM flag.

IntelliJ 또는 다른 IDE에서 해당 JVM 플래그를 추가해 문제를 해결할 수 있습니다.

간단한 BlockHound 테스트

이제 BlockHound를 사용해 실제로 블로킹 호출을 탐지하는 코드를 작성해보겠습니다. Spring Boot 애플리케이션의 ApplicationRunner 인터페이스를 구현하여 테스트해볼 수 있습니다.

테스트 코드

package com.example.blockhound;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import reactor.blockhound.BlockHound;
import reactor.core.publisher.Mono;

import java.time.Duration;

@SpringBootApplication
public class BlockhoundApplication implements ApplicationRunner {

    public static void main(String[] args) {
        // BlockHound 설치
        BlockHound.install();
        SpringApplication.run(BlockhoundApplication.class, args);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Mono.delay(Duration.ofSeconds(1))
                .doOnNext(it -> {
                    try {
                        // 블로킹 호출: Thread.sleep() 사용
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                })
                .subscribe();
    }
}

실행 결과

서버를 실행하면 BlockHound가 Thread.sleep() 호출을 감지하고 다음과 같은 예외 로그를 출력합니다.

reactor.core.Exceptions$ErrorCallbackNotImplemented: reactor.blockhound.BlockingOperationError: Blocking call! java.lang.Thread.sleep
Caused by: reactor.blockhound.BlockingOperationError: Blocking call! java.lang.Thread.sleep
    at java.base/java.lang.Thread.sleep(Thread.java) ~[na:na]
    at com.example.blockhound.BlockhoundApplication.lambda$run$0(BlockhoundApplication.java:25) ~[main/:na]

이 로그는 비동기 코드에서 Thread.sleep() 같은 블로킹 호출이 사용되었음을 알리고, 이를 수정할 필요가 있음을 보여줍니다.

BlockHound의 커스터마이징

BlockHound는 모든 블로킹 호출을 무조건 감지하지만, 특정 블로킹 호출을 허용해야 하는 상황이 있을 수 있습니다. 이럴 때는 BlockHound의 allowBlockingCallsInside() 메서드를 사용해 특정 메서드나 클래스에서의 블로킹 호출을 허용할 수 있습니다.

특정 메서드에서 블로킹 호출 허용

BlockHound.builder()
    .allowBlockingCallsInside("com.example.MyClass", "myMethod")
    .install();

위 코드는 com.example.MyClass의 myMethod()에서 발생하는 블로킹 호출을 허용하도록 설정합니다.

특정 라이브러리에서 블로킹 호출 허용

BlockHound.builder()
    .allowBlockingCallsInside("org.springframework.util.ReflectionUtils", "doWithFields")
    .install();

이 설정은 Spring Framework의 ReflectionUtils.doWithFields 메서드에서 발생하는 블로킹 호출을 허용합니다.

BlockHound와 Reactor 통합

BlockHound는 Project Reactor와 같은 비동기 프레임워크와 자연스럽게 통합되어 동작합니다. 예를 들어, Reactor에서 Mono 또는 Flux를 사용하는 코드에서 블로킹 호출이 발생하면 BlockHound는 이를 감지하고 예외를 던집니다.

Mono.just("Hello")
    .map(value -> {
        Thread.sleep(1000); // 블로킹 호출
        return value;
    })
    .block();

이 코드는 Thread.sleep() 블로킹 호출을 감지하여 예외를 발생시킵니다.

BlockHound 테스트 코드 작성

BlockHound는 테스트 환경에서 블로킹 호출이 발생하지 않도록 보장할 수 있는 유용한 도구입니다. JUnit을 사용한 간단한 테스트 코드를 작성하여 BlockHound의 동작을 확인해볼 수 있습니다.

package com.example.blockhound;

import org.junit.jupiter.api.Test;
import reactor.blockhound.BlockHound;
import reactor.blockhound.BlockingOperationError;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.time.Duration;

public class BlockHoundTest {
    static{
        BlockHound.install(); // Activate BlockHound
    }

    @Test
    void blockHoundTest(){


        StepVerifier.create(Mono.delay(Duration.ofSeconds(1))
                        .doOnNext( it -> {
                            try{
                                Thread.sleep(100); // This should trigger BlockHound
                            }catch(InterruptedException e){
                                throw new RuntimeException(e);
                            }
                        })
                ).expectErrorMatches(throwable -> throwable instanceof BlockingOperationError)
                .verify();
    }
    @Test
     void testBlockHoundDetectsBlockingCall() {

        // 블로킹 호출을 비동기적으로 실행
        Mono<String> mono = Mono.delay(Duration.ofSeconds(1))
                .map(it -> {
                    try {
                        Thread.sleep(10); // 블로킹 호출
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return "Hello, World!";
                });

        // 블로킹 호출을 감지하고 예외를 던지는지 테스트
        StepVerifier.create(mono)
                .expectErrorMatches(throwable -> throwable instanceof reactor.blockhound.BlockingOperationError)
                .verify();
    }
}

위 테스트 코드는 Thread.sleep()과 같은 블로킹 호출

을 포함한 Mono를 실행하고, BlockHound가 이를 감지해 예외를 던지는지 확인합니다. StepVerifier는 Reactor에서 비동기

코드 테스트를 지원하는 도구로, 비동기 흐름에서 발생하는 예외를 검증하는 데 유용합니다.

 

결론

BlockHound는 비동기 프로그래밍에서 흔히 발생하는 블로킹 호출 문제를 실시간으로 탐지하고 예외를 발생시켜 개발자가 빠르게 인지하고 수정할 수 있도록 도와주는 도구입니다. 이를 통해 비동기 애플리케이션의 성능을 최적화하고, 블로킹 호출로 인한 성능 저하를 방지할 수 있습니다.

비동기 프로그래밍을 할 때, 특히 Spring WebFlux, Project Reactor 같은 프레임워크를 사용할 때, BlockHound를 적극 활용하여 코드 내에서 불필요한 블로킹 호출을 탐지하고 최적의 성능을 유지하도록 하세요.

저작자표시 (새창열림)

'프레임워크 > 자바 스프링' 카테고리의 다른 글

접속자 대기열 시스템 #3- 셋업  (2) 2024.10.10
접속자 대기열 시스템 #4- 대기열 등록 API 개발  (6) 2024.10.09
Spring MVC와 Spring Webflux 성능비교  (0) 2024.10.08
Reactive Redis  (1) 2024.10.07
webflux - R2DBC 실습  (1) 2024.09.26
'프레임워크/자바 스프링' 카테고리의 다른 글
  • 접속자 대기열 시스템 #3- 셋업
  • 접속자 대기열 시스템 #4- 대기열 등록 API 개발
  • Spring MVC와 Spring Webflux 성능비교
  • Reactive Redis
hyeseong-dev
hyeseong-dev
안녕하세요. 백엔드 개발자 이혜성입니다.
  • hyeseong-dev
    어제 오늘 그리고 내일
    hyeseong-dev
  • 전체
    오늘
    어제
    • 분류 전체보기 (286)
      • 여러가지 (107)
        • 알고리즘 & 자료구조 (72)
        • 오류 (4)
        • 이것저것 (29)
        • 일기 (1)
      • 프레임워크 (39)
        • 자바 스프링 (39)
        • React Native (0)
      • 프로그래밍 언어 (38)
        • 파이썬 (30)
        • 자바 (3)
        • 스프링부트 (5)
      • 컴퓨터 구조와 운영체제 (3)
      • DB (17)
        • SQL (0)
        • Redis (17)
      • 클라우드 컴퓨팅 (2)
        • 도커 (2)
        • AWS (0)
      • 스케쥴 (65)
        • 세미나 (0)
        • 수료 (0)
        • 스터디 (24)
        • 시험 (41)
      • 트러블슈팅 (1)
      • 자격증 (0)
        • 정보처리기사 (0)
      • 재태크 (5)
        • 암호화폐 (5)
        • 기타 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    #개발자포트폴리오 #개발자이력서 #개발자취업 #개발자취준 #코딩테스트 #항해99 #취리코 #취업리부트코스
    FastAPI
    mybatis
    프로그래머스
    WebFlux
    시험
    파이썬
    SAA
    그리디
    AWS
    자바
    celery
    OOP
    RDS
    취업리부트
    항해99
    Redis
    ecs
    Python
    Spring WebFlux
    reactor
    spring
    백준
    EC2
    완전탐색
    Spring Boot
    docker
    Docker-compose
    java
    DP
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
hyeseong-dev
BlockHound: Java 비동기 애플리케이션에서 블로킹 호출을 감지하는 도구
상단으로

티스토리툴바