Spring 비동기 호출 에서 문맥 을 전달 하 는 방법

19597 단어 spring비동기문맥
비동기 호출 이란 무엇 입 니까?
비동기 호출 은 동기 호출 에 비해 동기 호출 이란 프로그램 이 예 정 된 순서에 따라 한 걸음 씩 실행 되 고 모든 단 계 는 이전 단계 가 실 행 된 후에 야 실 행 될 수 있 으 며 비동기 호출 은 이전 프로그램 이 실 행 될 때 까지 기다 리 지 않 아 도 실 행 될 수 있다 는 것 을 말한다.비동기 호출 이란 프로그램 이 실 행 될 때 실 행 된 반환 값 을 기다 리 지 않 고 뒤의 코드 를 계속 실행 할 수 있 는 것 을 말한다.Google 의 응용 서비스 에서 많은 업무 논리 적 인 실행 작업 은 동기 화 되 돌아 오지 않 아 도 됩 니 다(예 를 들 어 메 일 발송,불필요 한 데이터 시트 등).비동기 적 으로 만 실행 하면 됩 니 다.
본 고 는 Spring 응용 에서 어떻게 비동기 호출 을 실현 하 는 지 를 소개 할 것 이다.비동기 호출 과정 에서 스 레 드 컨 텍스트 정보의 손실 이 발생 할 수 있 습 니 다.우 리 는 스 레 드 컨 텍스트 정보의 전달 을 어떻게 해결 해 야 합 니까?
Spring 응용 에서 비동기 실현
Spring 은 작업 스케줄 링 과 비동기 적 인 방법 수행 에 주해 지원 을 제공 합 니 다.방법 이나 클래스 에@Async 주 해 를 설정 하면 다른 방법 으로 호출 할 수 있 습 니 다.호출 자 는 호출 시 즉시 되 돌아 오고 호출 된 방법의 실제 실행 은 Spring 의 TaskExecutor 에 맡 겨 집 니 다.그래서 주 해 된 방법 이 호출 될 때 새로운 스 레 드 에서 실 행 됩 니 다.이 를 호출 하 는 방법 은 원래 스 레 드 에서 실 행 됩 니 다.그러면 차단 을 피하 고 작업 의 실시 성 을 확보 할 수 있 습 니 다.
도입 의존

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
스프링 관련 의존 도 를 도입 하면 된다.
입구 류

@SpringBootApplication
@EnableAsync
public class AsyncApplication {
  public static void main(String[] args) {
    SpringApplication.run(AsyncApplication.class, args);
  }

``` 
       `@EnableAsync`   ,               `@Async`   。
####      
           :

```java
@RestController
@Slf4j
public class TaskController {

  @Autowired
  private TaskService taskService;

  @GetMapping("/task")
  public String taskExecute() {
    try {
      taskService.doTaskOne();
      taskService.doTaskTwo();
      taskService.doTaskThree();
    } catch (Exception e) {
      log.error("error executing task for {}",e.getMessage());
    }
    return "ok";
  }
}
TaskService 를 호출 하여 세 가지 비동기 방법 을 실행 합 니 다.
서비스 방법

@Component
@Slf4j
//@Async
public class TaskService {

  @Async
  public void doTaskOne() throws Exception {
    log.info("      ");
    long start = System.currentTimeMillis();
    Thread.sleep(1000);
    long end = System.currentTimeMillis();
    log.info("     ,  :" + (end - start) + "  ");
  }

  @Async
  public void doTaskTwo() throws Exception {
    log.info("      ");
    long start = System.currentTimeMillis();
    Thread.sleep(1000);
    long end = System.currentTimeMillis();
    log.info("     ,  :" + (end - start) + "  ");
  }

  @Async
  public void doTaskThree() throws Exception {
    log.info("      ");
    long start = System.currentTimeMillis();
    Thread.sleep(1000);
    long end = System.currentTimeMillis();
    log.info("     ,  :" + (end - start) + "  ");
  }
}
@Async 는 클래스 에 사용 할 수 있 습 니 다.이 종 류 를 표시 하 는 모든 방법 은 비동기 적 인 방법 이 고 특정한 방법 에 단독으로 사용 할 수 있 습 니 다.모든 방법 은 sleep 1000 ms 입 니 다.
결과 전시
실행 결 과 는 다음 과 같 습 니 다.

TaskService 의 세 가지 방법 은 비동기 로 실 행 된 것 을 볼 수 있 습 니 다.인터페이스의 결 과 는 빠르게 되 돌아 오고 로그 정 보 는 비동기 로 출력 됩 니 다.비동기 호출,새로운 스 레 드 호출 방법 을 열 어 주 스 레 드 에 영향 을 주지 않 습 니 다.비동기 방법의 실제 집행 은 Spring 의 TaskExecutor 에 게 맡 겼 다.
Future:비동기 실행 결과 가 져 오기
위의 테스트 에서 우 리 는 주 호출 방법 이 호출 방법 이 실 행 될 때 까지 기다 리 지 않 고 현재 의 임 무 를 끝 낸 것 을 발견 할 수 있다.호출 된 세 가지 방법 이 모두 실행 되 었 는 지 알 고 싶다 면 어떻게 해 야 합 니까?다음은 비동기 리 셋 을 사용 할 수 있 습 니 다.
비동기 리 셋 은 모든 호출 된 방법 을 하나의 Future 형식의 값 으로 되 돌려 주 는 것 입 니 다.Spring 에 서 는 Future 인터페이스의 하위 클래스 를 제공 합 니 다:AsyncResult,그래서 우 리 는 AsyncResult 형식의 값 을 되 돌려 줄 수 있 습 니 다.

public class AsyncResult<V> implements ListenableFuture<V> {

 private final V value;

 private final ExecutionException executionException;
 //...
}
AsyncResult 는 ListenableFuture 인 터 페 이 스 를 실 현 했 습 니 다.이 대상 내부 에는 두 가지 속성 이 있 습 니 다.반환 값 과 이상 정보 입 니 다.

public interface ListenableFuture<T> extends Future<T> {
  void addCallback(ListenableFutureCallback<? super T> var1);

  void addCallback(SuccessCallback<? super T> var1, FailureCallback var2);
}
ListenableFuture 인 터 페 이 스 는 Future 에서 계승 되 었 고 이 를 바탕 으로 리 셋 방법의 정 의 를 추가 했다.Future 인터페이스 정 의 는 다음 과 같 습 니 다.

public interface Future<V> {
 //                
  boolean cancel(boolean mayInterruptIfRunning);
  
  //        
  boolean isCancelled();
 
 //                   
 V get() throws InterruptedException, ExecutionException;
 //                ,      ,    true,       ,   false
  boolean isDone();
 //   get()   ,               
  V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException;
}
\#get()방법 은 실행 할 때 리 셋 결 과 를 기다 리 고 기 다 려 야 합 니 다.시간 초과 시간 을 설정 하지 않 으 면 작업 이 완 료 될 때 까지 차단 합 니 다.우 리 는 시간 초과 시간 을 설정 하면 현재 작업 이 너무 오래 실 행 된 상황 에서 현재 작업 을 중단 하고 스 레 드 를 방출 할 수 있 습 니 다.그러면 자원 을 계속 차지 하지 않 습 니 다.
\#cancel(boolean)방법,인 자 는 boolean 형식의 값 입 니 다.현재 실행 중인 작업 을 중단 할 수 있 는 지 여 부 를 입력 하 는 데 사 용 됩 니 다.인자 가 true 이 고 현재 작업 이 완료 되 지 않 았 다 면 현재 작업 을 중단 할 수 있 음 을 설명 합 니 다.그러면 true 로 돌아 갑 니 다.현재 작업 이 실행 되 지 않 았 다 면 인자 가 true 든 false 든 반환 값 은 true 입 니 다.현재 작업 이 완료 되 었 다 면 인자 가 true 든 false 든 반환 값 은 false 입 니 다.현재 작업 이 완료 되 지 않 았 고 인자 가 false 라면 반환 값 도 false 입 니 다.즉:
4.567917.퀘 스 트 가 아직 실행 되 지 않 았 다 면 퀘 스 트 를 취소 하려 면 반드시 true 로 돌아 가 야 합 니 다.매개 변수 와 무관 합 니 다4.567917.만약 에 작업 이 완성 되 었 다 면 임 무 는 취소 할 수 없 기 때문에 이때 반환 값 은 모두 false 이 고 매개 변수 와 무관 합 니 다4.567917.작업 이 실행 중이 라면 작업 을 취소 할 지 여 부 는 매개 변수 가 중단 을 허용 하 는 지 여 부 를 봅 니 다(true/false)비동기 방법 반환 값 의 실현 가 져 오기

public Future<String> doTaskOne() throws Exception {
  log.info("      ");
  long start = System.currentTimeMillis();
  Thread.sleep(1000);
  long end = System.currentTimeMillis();
  log.info("     ,  :" + (end - start) + "  ");
  return new AsyncResult<>("     ,  " + (end - start) + "  ");
}
//...        ,  
task 방법의 반환 값 을 Future으로 변경 하고 실행 시간 을 문자열 로 연결 합 니 다.

@GetMapping("/task")
public String taskExecute() {
  try {
    Future<String> r1 = taskService.doTaskOne();
    Future<String> r2 = taskService.doTaskTwo();
    Future<String> r3 = taskService.doTaskThree();
    while (true) {
      if (r1.isDone() && r2.isDone() && r3.isDone()) {
        log.info("execute all tasks");
        break;
      }
      Thread.sleep(200);
    }
    log.info("
" + r1.get() + "
" + r2.get() + "
" + r3.get()); } catch (Exception e) { log.error("error executing task for {}",e.getMessage()); } return "ok"; }

비동기 방법 을 호출 한 후 순환 을 통 해 비동기 방법 이 실행 되 었 는 지 여 부 를 판단 할 수 있다.결 과 는 우리 가 예상 한 바 와 같이 future 에서 얻 은 것 은 AsyncResult 에서 돌아 온 문자열 입 니 다.
스 레 드 탱크 설정
앞 은 가장 간단 한 사용 방법 입 니 다.기본 TaskExecutor 를 사용 합 니 다.사용자 정의 Executor 를 사용 하려 면@Configuration 주석 과 결합 하여 설정 할 수 있 습 니 다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class TaskPoolConfig {

  @Bean("taskExecutor") // bean    ,            
  public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10); //      (     )
    executor.setMaxPoolSize(20); //      
    executor.setQueueCapacity(200); //      
    executor.setKeepAliveSeconds(60); //         (  :    )
    executor.setThreadNamePrefix("taskExecutor-"); //       
    //              
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    return executor;
  }
}
스 레 드 탱크 의 설정 이 매우 유연 하여 핵심 스 레 드 수,최대 스 레 드 수 등 속성 을 설정 합 니 다.그 중에서 rejection-policy 는 스 레 드 탱크 가 최대 스 레 드 수 에 이 르 렀 을 때 새로운 작업 을 어떻게 처리 합 니까?선택 가능 한 정책 은 Caller Blocks Policy,Caller Runs Policy 등 이 있 습 니 다.CALLER_RUNS:새 스 레 드 에서 작업 을 수행 하지 않 고 호출 자가 있 는 스 레 드 에서 실 행 됩 니 다.스 레 드 탱크 의 설정 이 적용 되 는 지 확인 합 니 다.TaskService 에서 현재 스 레 드 이름 을 인쇄 합 니 다.

public Future<String> doTaskOne() throws Exception {
  log.info("      ");
  long start = System.currentTimeMillis();
  Thread.sleep(1000);
  long end = System.currentTimeMillis();
  log.info("     ,  :" + (end - start) + "  ");
  log.info("      {}", Thread.currentThread().getName());
  return new AsyncResult<>("     ,  " + (end - start) + "  ");
}

결 과 를 통 해 스 레 드 탱크 에 설 치 된 스 레 드 이름 접두사 가 유효 합 니 다.Spring@Async 비동기 스 레 드 사용 과정 에서 주의해 야 할 것 은 다음 과 같은 용법 이@Async 를 무효 화 할 수 있 습 니 다.
  • 비동기 방법 은 static 수식 을 사용 합 니 다
  • 비동기 류 는@Component 주해(또는 기타 주해)를 사용 하지 않 아 Spring 에서 비동기 류 를 검색 할 수 없습니다
  • 4.567917.비동기 방법 은 호출 된 비동기 방법 과 같은 유형 에 있 을 수 없다
  • 클래스 에서@Autowired 또는@Resource 등 주 해 를 사용 하여 자동 으로 주입 해 야 하 며 new 대상 을 수 동 으로 주입 할 수 없습니다
  • Spring Boot 프레임 워 크 를 사용 하려 면 시작 클래스 에@EnableAsync 주 해 를 추가 해 야 합 니 다
  • 스 레 드 문맥 정보 전달
    마이크로 서비스 구조 에서 의 요청 은 여러 개의 마이크로 서비스 와 관련 될 때 가 많다.또는 한 서비스 에 여러 가지 처리 방법 이 있 는데 이런 방법 은 비동기 적 인 방법 일 수 있다.요청 한 경로,사용자 의 유일한 userId 와 같은 일부 스 레 드 컨 텍스트 정 보 는 요청 에서 계속 전 달 됩 니 다.만약 어떤 처리 도 하지 않 는 다 면,우 리 는 이 정 보 를 정상적으로 얻 을 수 있 는 지 를 봅 시다.
    
    @GetMapping("/task")
      public String taskExecute() {
        try {
          Future<String> r1 = taskService.doTaskOne();
          Future<String> r2 = taskService.doTaskTwo();
          Future<String> r3 = taskService.doTaskThree();
    
          ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
          HttpServletRequest request = requestAttributes.getRequest();
          log.info("      {},      {},     :{}", Thread.currentThread().getName(), request.getMethod(), request.getRequestURL().toString());
          while (true) {
            if (r1.isDone() && r2.isDone() && r3.isDone()) {
              log.info("execute all tasks");
              break;
            }
            Thread.sleep(200);
          }
          log.info("
    " + r1.get() + "
    " + r2.get() + "
    " + r3.get()); } catch (Exception e) { log.error("error executing task for {}", e.getMessage()); } return "ok"; }
    Spring Boot Web 에서 Request ContextHolder 를 통 해 request 를 쉽게 얻 을 수 있 습 니 다.인터페이스 방법 에서 요청 한 방법 과 요청 한 경 로 를 출력 합 니 다.
    
    public Future<String> doTaskOne() throws Exception {
      log.info("      ");
      long start = System.currentTimeMillis();
      Thread.sleep(1000);
      long end = System.currentTimeMillis();
      log.info("     ,  :" + (end - start) + "  ");
      ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      HttpServletRequest request = requestAttributes.getRequest();
      log.info("      {},      {},     :{}", Thread.currentThread().getName(), request.getMethod(), request.getRequestURL().toString());
      return new AsyncResult<>("     ,  " + (end - start) + "  ");
    }
    동시에 TaskService 에서 요청 한 정 보 를 출력 할 수 있 는 지 검증 합 니 다.프로그램 을 실행 합 니 다.결 과 는 다음 과 같 습 니 다.

    TaskService 에서 모든 비동기 스 레 드 방법 으로 RequestContextHolder 의 요청 정 보 를 가 져 올 때 빈 포인터 이상 을 알 렸 습 니 다.이것 은 요청 한 상하 문 정보 가 비동기 방법의 라인 에 전달 되 지 않 았 음 을 설명 한다.RequestContextHolder 의 실현 은 현재 스 레 드 의 request 를 저장 하 는 ThreadLocal 두 개가 있 습 니 다.
    
    //       request
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
        new NamedThreadLocal<RequestAttributes>("Request attributes");
    //        request
    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
        new NamedInheritableThreadLocal<RequestAttributes>("Request context");
    다시 보기\#getRequestAttributes()방법 은 ThreadLocal 의 값 을 직접 가 져 오 는 것 과 같 습 니 다.그러면 매번 가 져 오 는 Request 는 이 요청 의 request 입 니 다.어떻게 문맥 정 보 를 비동기 스 레 드 로 전달 합 니까?Spring 의 Thread PoolTaskExecutor 는 속성 TaskDecorator 를 설정 합 니 다.TaskDecorator 는 리 셋 인터페이스 로 장식 기 모드 를 사용 합 니 다.장식 모델 은 동태 적 으로 한 대상 에 게 추가 적 인 기능 을 추가 하 는 것 으로 기능 을 증가 하 는 데 있어 장식 모델 은 서브 클래스 를 생 성 하 는 것 보다 더욱 유연 하 다.따라서 Task Decorator 는 주로 작업 호출 시 실행 컨 텍스트 를 설정 하거나 작업 수행 에 감시/통 계 를 제공 합 니 다.
    
    public interface TaskDecorator {
    
     Runnable decorate(Runnable runnable);
    }
    \#decorate 방법,주어진 Runnable 을 장식 하고 포 장 된 Runnable 을 되 돌려 실제 실행 할 수 있 도록 합 니 다.
    다음은 스 레 드 컨 텍스트 를 복사 한 Task Decorator 를 정의 합 니 다.
    
    import org.springframework.core.task.TaskDecorator;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    
    public class ContextDecorator implements TaskDecorator {
      @Override
      public Runnable decorate(Runnable runnable) {
        RequestAttributes context = RequestContextHolder.currentRequestAttributes();
        return () -> {
          try {
            RequestContextHolder.setRequestAttributes(context);
            runnable.run();
          } finally {
            RequestContextHolder.resetRequestAttributes();
          }
        };
      }
    }
    현재 스 레 드 의 context 를 지정 한 Runnable 로 장식 하고 마지막 으로 현재 스 레 드 컨 텍스트 를 초기 화 합 니 다.
    온라인 탱크 설정 에서 리 셋 된 Task Decorator 속성 을 추가 하 는 설정:
    
    @Bean("taskExecutor")
    public Executor taskExecutor() {
      ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
      executor.setCorePoolSize(10);
      executor.setMaxPoolSize(20);
      executor.setQueueCapacity(200);
      executor.setKeepAliveSeconds(60);
      executor.setThreadNamePrefix("taskExecutor-");
      executor.setWaitForTasksToCompleteOnShutdown(true);
      executor.setAwaitTerminationSeconds(60);
      //    TaskDecorator      
      executor.setTaskDecorator(new ContextDecorator());
      executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
      executor.initialize();
      return executor;
    }
    위 설정 을 통 해 저 희 는 서 비 스 를 다시 실행 하고 인 터 페 이 스 를 방문 합 니 다.콘 솔 로그 정 보 는 다음 과 같 습 니 다.

    결 과 를 통 해 알 수 있 듯 이 스 레 드 의 문맥 정 보 는 성공 적 으로 전달 되 었 다.
    작은 매듭
    본 고 는 예제 와 결합 하여 Spring 에서 비동기 방법 을 실현 하고 비동기 방법의 반환 치 를 얻 었 다.스프링 스 레 드 탱크 를 배치 하 는 방식 도 소개 했다.마지막 으로 비동기 다 중 스 레 드 에서 스 레 드 컨 텍스트 정 보 를 전달 하 는 방법 을 소개 합 니 다.스 레 드 상하 문 전달 은 분포 식 환경 에서 자주 사용 된다.예 를 들 어 분포 식 체인 추적 에서 요청 한 TraceId,SpanId 가 필요 하 다.쉽게 말 하면 전달 해 야 할 정 보 는 서로 다른 스 레 드 에 있 을 수 있다.비동기 방법 은 우리 가 일상적인 개발 에서 다 중 스 레 드 로 업무 논 리 를 처리 하 는 것 으로 이런 업무 논 리 는 엄격 한 집행 순 서 를 필요 로 하지 않 는 다.비동기 로 문 제 를 해결 하 는 동시에 비동기 다 중 스 레 드 방식 도 사용 해 야 한다.
    총결산
    이상 은 이 글 의 모든 내용 입 니 다.본 고의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가 치 를 가지 기 를 바 랍 니 다.여러분 의 저희 에 대한 지지 에 감 사 드 립 니 다.

    좋은 웹페이지 즐겨찾기