728x90

재직중인 회사에서  네이버사 연계중 초당 10회의 제한있어 이를 처리 하는 로직을 수정을 진행 하였고 동기, 비동기, 블로킹, 논블로킹을 다시 리마인드 할겸 정리를 한다. 

 

동기/비동기 와 블로킹 논블로킹 이 두 개념은 표현 형태는 비슷해 봉리지라도, 서로 다른 차원에서 작업의 수행방식을 설명하는 개념이다.

 

작업을 순차적으로 수행할지 아닌지에 대한 관점이고, 블로킹/ 논블로킹은 단어 그대로 현재 작업이 block(차단, 대기) 되느냐 아니냐에 따라 다른 작업을 수행할 수 있는지에 대한 관점이다.

 

 

 

1.동기(sync) 와 비동기 (Async)

 

 

Synchronous의 Syn 그리스어로 '함께'이란 뜻이고 chrono '시간'이라는 뜻이다.

즉, Synchronous는 작업 시간을 함께 맞춰서 실행한다 라는 뜻으로 해석된다. 작업을 맞춰 실행한다는 말은 요청한 작업에 대해 완료 여부를 따져 순차대로 처리하는 것을 말한다. 

Asynchronous는 앞에 A라는 접두사가 붙어 부정하는 형태이다. 그래서 동기와 반대로 요청한 작업에 대해 완료 여부를 따지지 않기 때문에 자신의 다음 작업을 그대로 수행하게 된다.

 

 

비동 기 처리 이점

위와 같이 순차처리 와 다르게 비동기 처리시 Task1이 끝나는 시점에 끝나기 떄문에 2배 이상 빠른 처리 가능

 


 

2. 블로킹(Blocking)과 논블로킹(Non-Blocking)

 

현재 작업을 차단 혹은 대기 하냐 안하냐의 차이를 나타내는 프로세스 실행 방식.

동기/비동기가 전체 작업의 순차 흐름 유무. 블로킹/논블로킹 은 전체 작업의 흐름 자체를 막냐 안막냐 의 차이이다.

 

비동기와 논블로킹의 차이

console.log("시작");

setTimeout(() => {
  console.log("0.5초 비동기/ 논블로킹");
}, 500);

console.log("끝");

 

script   setTimeout 을 예를 들면 

출력 순서와 정의된 코드 라인 순서가 맞지 않은 것이다. 

이는 setTimeout 함수에 대해 타이머 작업 완료 여부를 신경 쓰지 않고 바로 그 다음 콘솔 작업을 수행하였기 때문이다. 그리고 setTimeout 함수의 타이머 작업 완료 알람을 콜백 함수를 통해 값을 받아 출력하였다. 따라서 setTimeout 은 비동기(Asynchronouse)이다.

 

 

setTimeout 함수는 자신의 타이머 작업을 수행하기 위해 메인 함수를 블락하지 않고 백그라운드에서 별도로 처리되었다. 메인 함수를 블락하지 않으니 setTimeout 함수를 호출하고 바로 그 다음 콘솔 함수를 호출한 것이다. 따라서 setTimeout 은 논블로킹(Non-blocking)이다.

 


 

3. 동기/비동기 + 블로킹 논블로킹

 

  • Sync Blocking(동기 + 블로킹)
  • Async Blocking(비동기 + 블로킹)
  • Sync Non-Blocking(동기 + 논블로킹)
  • Async Non-Blocking(비동기 + 논블로킹)

 

 

 

Sync Blocking 

다른 작업이 진행되는 동안 자신의 작업을 처리하지 않고(Blocking) 다른 작업의 완료 여부를 바로 받아 순차적으로 처리하는 방식

 

Async Non-Blocking

 

다른 작업이 진행되는 동안에도 자신의 작업을 처리하고( Non Blocking), 다른 작업의 결과를 바로 처리하지 않아 작업 순서가 지켜지지 않는  Async 방식

 

 

Sync Non-Blocking 

다른 작업이 진행되는 동안에도 자신의 작업을 처리하고( Non Blocking) 

다른 작업의 결과를 바로 처리하여 작업을 수낯대로수행하는  sync 방식

 

async Blocking 

 

다른 작업이 진행되는 동안 자신의 작업을 멈추고 기다리는( Blocking),  다른 작업의 결과를 바로 처리하지 않아 순서대로 작업을 수행하지 않는  Async 방식 (실제 로  sync blocking  과 큰 차이가 없고 작업할때 볼일이 없을 듯 하다).

 

 

 

728x90
728x90

재직 중인 회사에서 작업 후 잘못 작업 한 부분이 있어  이를 확인하고 정리 할려고 한다. 

spring @Transaction, @Async 2가지  어노테이션을 사용법이나 주의 사항을 정리하고자 한다.

 

 

@Transactional, @Async  흔하게 많이 쓰는 어노테이션이다. 

 

 

 

Trasction 이란 데이터베이스의 상태를 변화시키기 위해서 수행하는 작업의 단위를 뜻한다. 

 

Spring에서 트랜잭션 관련된 3가지 핵심 

1. 동기화

2. 추상화

3. AOP이용한 트랜잭션 분리 

 

 

동기화 

 

트랜잭션을 시작하기 위한  connection 객체를 특별한 저장소에 보관해두고 필요할때 꺼내쓸 수 있도록 하는 기술 

 

//트랜잭션 매니저  시작 

TransactionSynchronizeManager.initSynchronization();
Connection c = DataSourceUtils.getConnection(dataSource);



//종료
DataSourceUtils.releaseConnection(c, dataSource);
TransactionSynchronizeManager.unbindResource(dataSource);
TransactionSynchronizeManager.clearSynchronization();

 

트랜잭션 동기화 저장소는 작업 쓰레드마다 Connection 객체를 독립적으로 관리, 멀티쓰레드 환경에서도 충돌이 발생할 여지가 없다. 

*Hibernate 에서는 Connection이 아닌 Session 을씀 이로 인한 JDBC  종속적인 트랜잭션 동기화 코드들을 문제를 유발

이를 해결하기 위해 아래 기술할 추상화 기술 제공.

 


 

추상화

 

Spring은 트랜잭션 기술의 공통점을 담은 트랜잭션 추상화 기술 제공.  이를 통해  JDBC, JPA, Hobernate 등 종속적인 코들르 이용하지 않고 일관되게 트랜잭션을 처리 가능.

 

 

트랜잭션 경계설정을 위한 추상 인터페이스는 Platform TransactionManager 이다.

 


 

AOP 

 

흔히 우리가 쓰는 @Transactional 어노테이션이다. 이 어노테이션을 사용 함으로써 마치 트랜젹션 코드가 존재하지 않는 것 처럼 보이지만 실제로 트랜잭션이 적용이 되는것이다.

 

 


 

트랜잭션 세부 설정

 

Spring의 DefaultTrasactionDefinition이 구현하고 있는 TrasctionDefinition 인터페이스는 트랜잭션의 동작방식에 영향을 줄 수 있는 네 가지 속성을 정의 하고 있다. 아래 4가지 속성은 세부적으로 이용할 수 있게 도와주며, @Transactional 어노테이션에도 공통적으로 적용할 수 있다. 

 

1. 트랜잭션 전파

2. 격리수준

3. 제한시간

4. 일기전용

 


트랜잭션 전파

 

트랜잭션 전파란 트랜잭션의 경계에서 이미 진행중인 트랜잭션이 있거나 없을 때 어떻게 동작할 것인가를 결정하는 방식을 의미.

예를 들어 A 작업에 대한 트랜잭션이 진행중이고 B 작업에 대한 트랜잭션을 어떻게 처리할까에 대한 부분이다.

 

A의 트랜잭션 참여 (PROPAGATION_REQUIRED)

B의 코드는 새로운 트랜잭션을 만들지 않고 A에서 진행중인 트랜잭션에 참여할 수 있다. 이 경우 B의 작업이 마무리 되고 나서, 남은 A의 작업(2)를 처리할 때 예외가 발생하면 A와 B의 작업이 모두 취소된다. 왜냐하면 A와 B의 트랜잭션이 하나로 묶여있기 떄문이다.

 

독립적인 트랜잭션 생성(PROGATION_REQUIRES_NEW)

반대로 B의 트랜잭션은 A의 트랜잭션과 무관하게 만들 수 있다. 이 경우 B의 트랜잭션 경계를 빠져나오는 순간 B의 트랜잭션은 독자적으로 커밋 또는 롤백되고, 이것은 A에 어떠한 영향도 주지 않는다. 즉 이후 A가 (2)번 작ㅇ버을 하면서 예외가 발생해 롤백되어도 B의 작업에는 영향을 주지 못한다.

 

 

 

트랜잭션 없이 동작(PROGATION_NOT_SUPPORTED)

B의 작업에 대해 트랜잭션을 걸지 않을 수 있다. B의 작업이 단순 데이터 조회라면 굳이 트랜잭션이 필요 없을 것이다.

 


 

격리 수준

 

모든 DB 트랜잭션은 격리 수준을 가지고 있어야한다. 서버에는 여러 개의 트랜잭션이 동시에 진행 될 수 있는데, 모든 트랜잭션을 독립적으로 만들고 순차 진행 한다면 안전하지만 성능이 떨어짐. 따라서 적절하게 격리수준을 조정해서 가능한 많은 트랜잭션을 동시에 진행시키면서 문제가 발생하지 않도록 제어해야 한다. 이는 JDBC  드라이버나 DataSource  등에서 재설정하고 트랜잭션 단위 로 격리 수준을 조정할 수 있다.

DefualtTrasactionDefinition에 설정된 격리 수준은 ISOLATION_DEFAULT로 DataSource에 정의된 격리 수준을 따른다는 것이다.

기본적으로는  기본 격리 수준을 따르는 것이 좋지만. 특별한 작업을 수행하는 메소드라면 독자적으로 지정해줄 필요가 있다.

 

 


 

제한시간

 

트랜잭션을 수행하는 제한시간을 설정할 수 있다.  제한시간의 설정은 트랜잭션을 직접 시작하는 PROGATION_REQUIRED나 PROGATION_REQUIRES_NEW의 경우에 사용해야만 의미가 있다.

 


 

읽기전용

읽기전용으로 설정해두면 트랜잭션 내에서 데이터를 조작하는 시도를 막아주며 데이터 엑세스 기술에 따라 성능 향상이 된다.

(재직중인 곳이 아니더라도 보통 SELECT 문 을 처리할떄 slaveDB에 읽기전용으로 붙어서 쓰고 있다.)

 

 

 

 

 


 

@Async

 

@Async는 spring 에서 제공하는 Thread Pool을 활용하는 비동기 메소드 지원 어노테이션이다. 

@Async로 annotationg된 bean 의 메소드는 별도의 스레드에서 실행

기존 JAVA 에서 비동기 방식으로 메서드를 구현할때  java.util.concurrent.ExecutorService을 활용해서 비동기 방식의 method를 정의 할 때마다, Runnable의 run()을 재구현해야 하는 등 동일한 작업들의 반복이 잦았다.

 

그러나 @Async 사용 시  손쉽게 비동기 메서드 작성 가능

 

spring의 Async 지원은 JDK Proxy 또는 CGlib와 같은 객체를 사용하여 Async가 정의된 객체를 생성

그 후 Spring은 메소드의 로직을 별도의 실행 경로로 제출하기 위해 컨텍스트와 연결된 스레드 풀을 찾으려고 합니다. 구체적으로는, 고유한 TaskExcutor 빈이나 taskExcutor로  이름 지정된 빈을 검색

이러한 빈이 없으면 기본적으로 SimpleAsyncTaskExcutor를 사용

 

SimpleAsyncTaskExcutor는 스레드 풀이 아닙니다. 그렇기 때문에 스레드를 관리하고 재사용하는 것이 아니라 계속 만들어 냅니다.

스레드 자원이 많이 들기 때문에 SimpleAsyncTaskExcutor를 쓰지 말아야 합니다.

 

따라서 TaskExecutor를 빈으로 등록하여 사용하는것이 옮은 방법.

 

아래는 async config 중 일부이다.

@Configuration
@EnableAsync
public class AsyncThreadConfiguration {
	@Bean
	public Executor asyncThreadTaskExecutor() {
		ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
		threadPoolTaskExecutor.setCorePoolSize(5);
		threadPoolTaskExecutor.setMaxPoolSize(10);
		threadPoolTaskExecutor.setThreadNamePrefix("Executor-");
		return threadPoolTaskExecutor;
	}
}

 

 

스레드 생성 하는 경우 

Rules for creating Threads internally by SUN:

  • the number of threads 가 corePoolSize보다 작으면 새로운 스레드를 생성
  • the number of threads 가 corePoolSize 같거나(또는 크다면), task를 큐에 넣습니다.
  • 큐가 다 찼을 때 the number of threads가 maxPoolSize보다 작으면 새로운 스레드를 생성
  • 큐가  the number of treads가 maxpoolsize보다 같거나(또는 크다면) 해당 task를 reject 한다.(RejectExcutionException)

 


 

 

주의사항

 

1. private 메서드 적용 불가 proxy로 만들 수 없기 떄문에

 

2. self-invocation 해서는 안됨 proxy로 만ㄷ루 서 없기 떄문에

 

3. return 값은 void 혹은 CompleteableFuture<>

 

 

 

 

간략하게나마 트랜잭션과 async 처리에 대해서 정리해보았는데  앞으로는 좀더 알고 사용 알 수 있을것 같다.

728x90
728x90

async 처리를 할때 로그인된 정보를 가져 올 수 없는 문제를 부딪쳤다.

 

@async 처리가 아닌 경우에는 aspectj 에서 로그인 정보를 정상적으로 가져오지만 async 처리 시에는 해당 정보를 호출시 NPE 에러가 떨어졌다.

 

해당 문제를 해결하기 위해 구글링을 하던중에 아래와 같은 해결 방법을 보게 되었다.

아래는 stackoverflow 

 

https://stackoverflow.com/questions/5246428/spring-security-and-async-authenticated-users-mixed-up

 

Spring Security and @Async (Authenticated Users mixed up)

I asynchronously invoke a method with Spring, using @Async. This method invokes another method annotated with @PreAuthorize, the Spring Security Annotation. To make the authorization work I have to...

stackoverflow.com

 

2가지 방법을 확인하였는데 나는 아래의 방법을 참조하여 해결하였다.

 

첫 번째 해결방법

public void execute(final Runnable r) {
    final Authentication a = SecurityContextHolder.getContext().getAuthentication();

    super.execute(new Runnable() {
        public void run() {
            try {
                SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                ctx.setAuthentication(a);
                SecurityContextHolder.setContext(ctx);
                r.run();
            } finally {
                SecurityContextHolder.clearContext();
            }
        }
    });
}

 

위와 같이 excute 실행 전 authentication을 변수에 담아 두었다가 실행시 재 설정해주는 방법이다 .

 

 

실제 적용 코드

 

두번째 방법

config 설정 시 아래와 같이 설정하는 방법이 있다.

@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(20);
    executor.setMaxPoolSize(1000);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setThreadNamePrefix("Async-");
    executor.initialize(); // this is important, otherwise an error is thrown
    return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}

 

다음에 같은 문제가 발생한다면 config 로 공통적으로 해결이 가능하게 수정하면 좋을것 같다는 생각이 작성중에 들어 전체 리소스 리팩토링을 진행해야 할듯 하다.

728x90

'코드 > dev' 카테고리의 다른 글

java dateTime 타임존  (2) 2023.11.22
Junit test filtering  (2) 2023.11.17
저장소에 올라간 브랜치 되돌리기  (2) 2023.11.15
spring AOP & AspectJ  (1) 2023.11.15
mariaDB Schedule  (3) 2023.11.14

+ Recent posts