보라코딩
WW31 본문
240729
* MSA에서 DB 정합성 보장 방법!
https://curiousjinan.tistory.com/entry/spring-event-aws-sns-sqs-msa-data-consistency
* zipkin을 사용해서 카프카는 trace Id가 찍히지 않아 고민했는데 아래 글로 해결함..!
https://curiousjinan.tistory.com/entry/spring-msa-zipkin
* msa 깃프로젝트(인프런강의)
https://github.com/joneconsulting/toy-msa/blob/springboot3.2/README.md
240730
- MDC(Mapped Diagnostic Context) : Map 형식으로 이용하여 클라이언트 특징적인 데이터를 저장하기 위한 메커니즘. 현재 실행중인 쓰레드에 메타 정보를 넣고 관리하는 공간
- System.out.println()을 로깅에 사용하면 안좋은 이유?
ㄴ synchronized 키워드 이용해서 구현되어 있기에 다른 스레드 접근하지 못해 성능 낮춤
ㄴ Blocking I/O 이기에 해당 I/O가 발생하는 동안 CPU가 놀게 되기 때문에 성능 저하
- slf4j : logger의 추상체. 다른 로깅 프레임워크의 인터페이스 역할
- logback : Slf4j의 구현체. spring-boot-starter-web 안에 기본적으로 포함
- log4j2 : Slf4j의 구현체. 멀티 쓰레드 환경에서 비동기 로거를 지원하여 멀티 쓰레드 환경에서의 성능이 다른 로거들에 비해 월등히 좋음.
240731
- 스레드풀 좋은 내용
https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests
- 도커 포트 8081:8080와 8082:8080와 8083:8080이 가능한 이유
ㄴ 컨테이너는 자체적으로 독립적인 네트워크 스택을 가지며, 내부적으로 동일한 포트를 사용할 수 있음. 따라서 동일한 서비스가 각기 다른 설정 없이 쉽게 복제되어 실행 가능
- docker replica : docker compose 에서 replica 기능 설정 가능
ㄴ Docker Swarm(도커 클러스터 관리 도구)에서의 로드 밸런서 개념은 요청을 여러 Replica 간에 부하 분산하는 역할
ㄴ 실제로는 각 Replica가 동시에 실행되고 있으며, 로드 밸런서가 이들을 조율하여 클라이언트의 요청을 고르게 분산
ex)
version: '3.7'
services:
web:
image: nginx:latest
deploy:
replicas: 3
resources:
limits:
cpus: "0.5"
memory: 512M
restart_policy:
condition: on-failure
ports:
- "80:80"
- StringBuffer vs StringBuilder
ㄴ StringBuffer : 스레드 안전. 여러 스레드 동시 접근 시, 모든 메서드에 동기화 적용(한 번에 하나의 스레드만 접근 가능해서 멀티스레드 환경에서 안전). 단, 단일 스레드 환경에서 불필요한 성능 저하
ㄴ StringBuilder : 스레드 안전 X. 동기화 지원 안해서 멀티스레드 환경에서 안전하지 않으나 동기화 오버헤드가 없기에 단일 스레드 환경에서 성능은 더 빠름
- MDC : 로그 메세지에 추가적인 컨텍스트 정보를 첨부할 수 있는 기능 제공하여 로그 추적성과 유연성 높임 (SLF4J안에 MDC 기능이 포함됨)
ex)
log4j2.xml 내부에서 아래와 같은 코드가 있을 때
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n - RequestId: %X{requestId}</pattern>
내부적으로 MDC가 MDC.put("requestId", "12345"); 이렇게 값을 설정해서 로그 메세지 생성시 내부적으로 MDC.get("requestId")를 호출하여 현재 스레드의 MDC 컨텍스트에서 "requestId" 값을 가져옴
- 스프링빈의 싱글톤
https://velog.io/@s2moon98/%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-bean%EA%B3%BC-%EC%8B%B1%EA%B8%80%ED%86%A4%EC%9D%98-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84
ㄴ @Configuration 클래스에 정의된 @Bean 메서드는 스프링 컨테이너에 의해 관리되며, 기본적으로 싱글톤으로 동작
ㄴ 즉, 스프링 컨테이너는 @Bean 메서드를 호출하여 생성된 객체의 인스턴스를 단 하나만 유지
ex.
@Component
public static class Config {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean(); // 매번 새로운 객체를 생성
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
}
// Configuration 버전
@Configuration
public static class Config {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean(); // 한번 Bean으로 등록해둔 뒤에 해당 객체 재사용
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
}
- 필드 주입 vs 생성자 주입
@Autowired 필드 주입과 생성자 주입 모두 스프링 컨테이너에서 빈을 주입받는 방법입니다.
필드 주입은 스프링이 빈을 생성한 후 필드에 주입하며, 생성자 주입은 빈을 생성하기 전에 생성자를 호출하여 의존성을 주입합니다.
생성자 주입은 불변성을 보장하고, 명시적인 의존성 정의와 테스트 용이성 등 여러 장점을 제공합니다.
필드 주입은 보다 간편하지만, 의존성의 명시성과 객체의 불변성을 보장하는 데 한계가 있을 수 있습니다.
- 스프링 컨테이너 올라가는 순서
: 알아보기! configuration이랑 component랑 autowired랑 생성자랑!!!!
- virtual thread 동작 원리
https://techblog.woowahan.com/15398/
- 아래 코드는 동기 -> 비동기로 멀티스레드를 사용하고,
스케쥴러에서 싱글스레드로 진행됨 (리팩터링 시, 스프링 배치 사용해도 됨)
private final ExecutorService executorService = Executors.newFixedThreadPool(MAX_CONNECTIONS);
private void scheduleTasks(FTPInfo ftpEnv) {
List<...> list = ....
list.stream()
.filter(....)
.findFirst()
.ifPresent(env -> {
List<String> ...= .....(); // 여기까지 동기
executorService.submit(() -> taskFactory.startScheduler(..)); // 비동기
});
}
--------------------------------
이후 스케쥴러에서 싱글스레드로
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
public void startScheduler(...) {
scheduler.scheduleWithFixedDelay(() -> {
try {
...
} catch (IOException e) {
...
}
}, 0, ...getDelaySecond(), TimeUnit.SECONDS);
}
- 보통 스프링 배치는 대량 데이터 처리 또는 정기적인 작업에 사용되며 카드사 환불이나 재난 문자 등에 사용되기도 함!
- awesome spring 참고
https://github.com/akullpp/awesome-java?tab=readme-ov-file
- 도커엔진 공부하면 좋음!
https://github.com/akullpp/awesome-java?tab=readme-ov-file
240801
- [Java] Callable, Future 및 Executors, Executor, ExecutorService, ScheduledExecutorService에 대한 이해 및 사용법
https://mangkyu.tistory.com/259
*** 빈 생성
스프링 컨테이너는 등록된 빈의 클래스를 기반으로 실제 빈 객체를 생성한다. 스프링을 실행(run) 하자마자 빈 등록과 빈 생성이 바로 이루어진다.
이 때, 필드 주입이나 세터 주입 시에는 실행 시에 빈 생성까지만 진행이 되고 , 런 타임 전까지는 의존성 주입이 되지 않는다.
생성자 주입 시, 빈 생성과 동시에 아래 단계인 의존성이 주입된다.
생성자 주입 시에 의존성 문제 중 하나인 순환 참조가 걸려있으면 실행하자마자 바로 오류가 나는 이유이다.
실행하자마자 생성자 주입 & 의존성 주입이 이루어지니, 의존성 문제인 순환 참조 문제가 바로 들통나버리게 되는 것이다.
그러므로 실행 자체가 되지 않는다. APPLICATION FAILED TO START 가 떠 버리는 것이다.
생성자 주입의 큰 장점이다. 실행하자마자 빈 생성과 동시에 의존성 주입이 된다는 것 !!
- 프레임워크는 라이브러리의 묶음으로, 객체의 생명주기 등을 자동으로 수행해줍니다.
- 라이브러리는 프로그램의 필요한 기능들의 묶음으로, 프레임워크에 속할 수 있습니다.
쉽게 말해, 프레임워크가 집이라면 라이브러리는 집안의 가구라고 할 수 있습니다.
프레임워크와 라이브러리는 코드 작성에 도움이 되는 타인의 코드의 집합입니다!
프레임워크는 프로그램이 필요한 것을 개발자에게 위임받아 제어권을 역전하고, 라이브러리는 개발자가 필요할 때 호출함으로써 개발자가 능동적으로 사용합니다.
- 스프링 배치 POC Start~~~~
240802
- 스프링 배치 poc 중
- Axon 이벤트소싱이랑 CQRS 공부해보기 (Read는 NoSQL에 Mybatis로, 나머지는 Jpa로 많이 쓴다고 함)
ㄴ https://saysimple.tistory.com/197
ㄴ https://www.baeldung.com/axon-cqrs-event-sourcing
- MSA outbox pattern과 saga 패턴도 공부해야 함!!
- 코드 리뷰 받은거 수정 필요
CGLIB
CGLIB는 자바의 바이트코드를 조작하여 클래스의 서브 클래스를 동적으로 생성할 수 있게 해주는 라이브러리입니다. 주로 메서드 호출을 가로채거나, 동적 프록시를 생성하는 데 사용됩니다.
- 클래스 상속을 통한 프록시 생성:
CGLIB는 원본 클래스를 상속받아 새로운 클래스를 생성합니다. 이 새로운 클래스는 원본 클래스의 모든 메서드를 오버라이드하여, 메서드 호출을 가로채는 기능을 추가할 수 있습니다.
- 사용 예시
1. AOP : AOP 중 트랜잭션 관리도 AOP를 통해 구현되며, CGLIB를 사용하여 메서드 호출 전후에 트랜잭션 처리를 삽입
2. JPA : 엔티티 객체의 지연 로딩 기능을 지원. 데이터베이스에서 엔티티를 처음 요청할 때 모든 데이터를 가져오지 않고, 실제로 필요한 시점에만 데이터를 로드합니다. 구현하기 위해 JPA는 CGLIB를 사용하여 엔티티 클래스를 상속받는 프록시 객체를 생성합니다. 이 프록시는 실제 엔티티 객체를 감싸며, 필요한 경우 실제 데이터베이스 조회를 트리거