docker는 컨테이너화된 애플리케이션을 배포하고 관리하는 데 사용되는 오픈 소스 플랫폼이다. 이러한 컨테이너는 격리된 환경에서 실행되며, 이는 각 컨테이너가 자체 네트워크 인터페이스와 IP 주소를 가질 수 있음을 의미한다.
컨테이너화된 애플리케이션은 여러 개의 컨테이너로 구성될 수 있는데, 이들 컨테이너가 서로 통신하고 데이터를 주고받아야 할 경우 도커 네트워크를 통해 이러한 통신을 쉽게 설정하고 관리할 수 있도록 도와준다. 컨테이너 간의 통신, 호스트와 컨테이너 간의 통신, 외부 네트워크와의 통신을 가능하게 한다.
🐳도커 네트워크의 종류
bridge 네트워크
기본 네트워크 드라이브, 도커가 자동으로 생성하는 기본 네트워크
동일한 호스트 내에서 네트워크 설정
동일 호스트 내의 컨테이너 간 통신 또는 호스트와 컨테이너 간의 통신에 사용된다.
host 네트워크
호스트의 네트워킹을 직접 사용하여 컨테이너 간의 네크워크 격리 제거
호스트에서 제공하는 IP를 직접 할당하여 사용할 수 있다.
컨테이너가 호스트의 네트워크 리소스에 직접 접근할 수 있기 때문에 보안 문제가 발생할 수 있다.
네트워크 격리가 필요하지 않은 경우에 유용하다.
none 네트워크
네트워크 연결이 없는 상태로 컨테이너를 실행한다.
완전히 독립적인 환경을 원할 때 사용한다.
overlay 네트워크
여러 도커 데몬을 연결하고, Docker Swarm 서비스와 컨테이너 간에 노드 간 통신을 가능하게 한다.
Swarm모드에서 작동하며, 여러 호스트에 걸쳐 있는 컨테이너 간에 통신할 수 있도록 한다.
2. IoC vs DI
🌸Spring이란?
자바 언어로 엔터프라이즈급 개발을 편리하게 만들어주는 오픈소스 경량급 애플리케이션 프레임워크로, 애플리케이션 개발에 필요한 기반을 제공해서 개발자가 비즈니스 로직 구현에만 집중할 수 있도록 하는 것
🍔IoC(Inversion of Control) : 제어의 역전
사용할 객체를 직접 사용하지 않고, 객체의 생명주기 관리를 외부(스프링 컨테이너)에 위임하는 것
제어의 역전을 통해 의존성 주입, 관점 지향 프로그래밍이 가능해진다. 이에 따라 개발자는 객체의 제어권을 컨테이너로 넘기고 객체의 생명 주기 관리 등의 복잡한 요소들을 신경 쓰지 않고, 비즈니스 로직에만 집중할 수 있게 된다.
🍟DI(Dependency Injection) : 의존관계 주입
제어 역전의 방법 중 하나로, 사용할 객체를 직접 생성하지 않고 외부 컨테이너가 생성한 객체를 주입받아 사용하는 방식
롬복을 이용한 생성자 주입이 가장 좋은 의존성 주입 방법이다.
롬복의 @RequiredArgsConstructor어노테이션은final이 붙은 필드의 생성자를 자동으로 생성해 준다.
@RestController
@RequiredArgsConstructor
public class DIController {
privite final MyService myService;
//Controller Codes
}
3. Doker vs 가상머신
💻가상머신
가상머신은 하이퍼바이저가 호스트 OS 위에서 여러 게스트 OS를 실행한다.
각 VM은 자체 운영체제를 포함하며, 그 위에 애플리케이션이 실행된다.
VM은 하드웨어 수준에서 가상화되며 각각 독립된 운영체제를 가진다.
🐳Docker
호스트 OS의 커널을 공유하며 컨테이너는 사용자 공간에서 독립된 환경을 제공한다.
컨테이너는 OS 레벨에서 가상화되며 독립된 파일 시스템, 네트워크 인터페이스를 가진다.
각 컨테이너는 애플리케이션과 그 종속성만을 포함한다.
4. Docker File vs Docker Image vs Docker Container
JDBC에서 데이터베이스 드라이버가 어댑터 패턴을 사용하여 구현되는데, 각 데이터베이스 벤더는 JDBC 표준 인터페이스를 구현한 드라이버를 제공하며 이 드라이버가 어댑터 역할을 한다. 이로 인해 자바 어플리케이션은 다양한 데이터베이스와 상호작용 할 수 있다.
JDBC 표준 인터페이스 : Connection, Statement, ResultSet 등
데이터베이스 드라이버 : 드라이버는 어댑터 패턴을 사용하여 JDBC 인터페이스 호출을 해당 데이터베이스에 맞는 네이티브 호출로 변환
드라이버 관리 : DriverManager 클래스는 어플리케이션이 데이터베이스에 연결을 요청하면DriverManager는 url을 기반으로 적절한 드라이버를 찾고 연결을 설정
이로 인해 JDBC는 데이터베이스 독립성을 유지하면서도 다양한 벤더의 데이터베이스와 호환될 수 있는 유연성을 갖추게 된다.
2. 트랜잭션 vs 락
⛔트랜잭션
데이터베이스의 일관성과 무결성 유지
데이터베이스에서 수행되는 일련의 작업 단위
원자성 : 트랜잭션 내의 작업이 모두 성공하거나 모두 실패하도록 보장. 여러 작업 중 하나라도 실패하면 전체 트랜잭션이 취소되고 데이터베이스는 트랜잭션이 시작되기 전 상태로 복구된다.
일관성 : 트랜잭션이 성공적으로 완료된 후 데이터베이스의 상태는 모든 정의된 규칙(무결성 제약 조건)을 만족해야 한다.
격리성 : 동시에 여러 트랜잭션이 실행될 때 각 트랜잭션은 서로의 중간 상태를 확인할 수 없다. 두 트랜잭션이 같은 데이터를 수정하려고 할 때, 하나의 트랜잭션이 완료될 때까지 다른 트랜잭션은 그 데이터를 변경할 수 없다.
지속성 : 트랜잭션이 성공적으로 완료되면 그 결과가 영구적으로 데이터베이스에 반영되어야 한다. 시스템 장애가 발생해도 트랜잭션 결과는 손실되지 않는다.
@Transactional 어노테이션
적용 대상 : 메서드 또는 클래스 레벨에 적용
기본 동작 : 트랜잭션을 시작하고 메서드가 정상적으로 완료되면 커밋, 예외가 발생하면 롤백을 한다.
⛔ 락
동시에 여러 트랜잭션이 동일한 데이터에 접근하는 경우 데이터의 무결성과 일관성을 유지하기 위해 데이터베이스에서 사용되는 메커니즘
공유 락 : 데이터에 대한 읽기 작업을 허용하지만 해당 데이터를 수정할 수 없도록 한다.
배타 락 : 데이터에 대한 읽기 및 쓰기 작업을 모두 허용하지 않는다.
3. Spring AOP에서 프록시 패턴을 어떻게 사용하는지?
❓Spring AOP (Aspect-Oriented Programming)
애플리케이션의 주요 비즈니스 로직과 부가 기능을 분리하여 모듈화할 수 있는 프로그래밍 패러다임.
로깅, 트랜잭션 관리, 보안 등과 같은 공통 관심사를 핵심 비즈니스 로직과 분리하여 코드의 가독성, 유지보수성, 재사용성 향상.
Aspect : 공통 관심사를 모듈화한 것(로깅, 트랜잭션 관리, 보안)
Join Point :Aspect가 적용될 수 있는 지점. 주로 메서드 호출 시점
Pointcut : 특정 Join Point를 선택하는 표현식.Aspect가 적용될 지점을 정의
Advice :Aspect가 Join Point에서 수행하는 동작. 메서드 호출 전후/예외 발생 시점 등에 실행
Weaving :Aspect를 대상 객체에 적용하는 과정. 주로 런타임 Weaving을 사용
✅Spring AOP에서 프록시 패턴 사용
Spring AOP는 런타임에 프록시 객체를 생성하여 Aspect을 메인 비즈니스 로직에 적용한다.
JDK 동적 프록시 : 인터페이스 기반 프록시 생성. 타겟 클래스가 구현한 인터페이스를 통해 프록시 생성
CGLIB 프록시 : 인터페이스가 없는 클래스에도 프록시 생성 가능. 클래스 상속을 통해 프록시 생성
서비스 객체가 빈으로 등록이 되면 스프링이AbstractAutoProxyCreator라는BeanPostProcessor(어떤 Bean이 등록되면, 그 Bean을 가공할 수 있는 life-cycle interface)로 서비스 빈을 감싸는 프록시 객체(빈)를 만들어 그 프록시 객체를 서비스 대신에 등록해서 사용한다.
4. 프록시 서버란 무엇인지?
➡ 클라이언트와 실제 서버 간의 중개자 역할을 하는 서버
클라이언트의 요청을 받아 실제 서버에 전달하고, 실제 서버의 응답을 받아 클라이언트에 전달한다.
✅주요 기능
트래픽 제어 및 관리 : 특정 웹사이트에 대한 접근 제한, 특정 유형의 트래픽 필터링
캐싱 : 자주 요청되는 웹 페이지나 파일을 캐싱하여 서버 부하를 줄이고 응답 시간 단축
보안 및 익명성 제공 : 클라이언트의 IP 주소를 숨기고 실제 서버와의 직접적인 접촉을 막음으로써 보안과 익명성 제공
콘텐츠 필터링 : 특정 콘텐츠에 대한 접근 제한, 악성 웹사이트 차단
로드 밸런싱 : 여러 서버에 걸쳐 트래픽을 분산시켜 서버 부하를 균형있게 분배하여 서버 성능 최적화
📶종류
포워드 프록시: 클라이언트가 인터넷에 접속할 때 사용. 클라이언트의 요청을 받아 인터넷으로 전달하고 인터넷의 응답을 클라이언트에 전달한다. 주로 보안 및 익명성을 위해 사용된다.
리버스 프록시: 인터넷에서 들어오는 요청을 받아 내부 서버로 전달하고 내부 서버의 응답을 받아 클라이언트에 전달. 주로 로드 밸런싱, 보안, 캐싱을 위해 사용된다.
웹 프록시: 웹 트래픽을 중개. 클라이언트가 퉵 사이트에 접속할 때 익명성을 유지할 수 있다.
오픈 프록시: 누구나 사용할 수 있는 공개 프록시 서버. 익명성을 제공하지만 보안상 문제가 있을 수 있다.
public class Singleton {
// 클래스 로드 시 인스턴스 생성
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
🔵지연 로딩
인스턴스가 처음 필요할 때 생성
멀티스레드 환경에서는 인스턴스가 여러 개 생성될 수 있어 문제가 발생할 수 있다.
public class Singleton {
private static Singleton instance;
private Singleton() {}
// 인스턴스 필요 시 생성
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
🔵 synchronized
지연 로딩 방식에서 스레드 안정성을 보장할 수 있지만 메서드 전체에 동기화를 적용하면 블로킹으로 인해 성능이 저하될 수 있다.
public class Singleton {
private static Singleton instance;
private Singleton() {}
// synchronized 키워드를 사용
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
🔵 Double-Checked Locking
synchronized 키워드 사용의 성능 저하 문제를 해결하기 위한 방법
인스턴스가 이미 생성된 경우는 동기화 블록을 통과하지 않도록 한다.
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
// 인스턴스가 이미 생성된 경우는 동기화 블록 통과하지 않음
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
🔵 Lazy Holder
내부 정적 클래스와 클래스 로더의 매커니즘을 이용한 지연 초기화 방식
내부 정적 클래스는 클래스가 로드될 때 인스턴스가 생성되지 않고 외부 클래스가 처음으로 참조될 때 초기화되는 특징
JVM은 클래스 로딩과 초기화를 스레드 안전하게 처리하기 때문에 별도의 동기화가 필요하지 않음
public class Singleton {
private Singleton() {}
// 내부 정적 클래스 : 싱글톤 인스턴스를 가지고 있음
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//외부에서 호출할 수 있는 메서드 : 싱글톤 인스턴스를 반환
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
🔵 Enum
enum 타입은 고정된 인스턴스를 가지기 때문에 싱글톤 패턴과 매우 잘 맞다.
JVM에서 싱글톤 인스턴스가 스래드 안전하게 초기화되도록 보장
//enum 클래스 사용
public enum Singleton {
INSTANCE;
public void someMethod() {
// 비즈니스 로직
}
}
6. 콜백이란?
➡프로그램 내에서 특정 이벤트가 발생했을 때 호출되는 함수나 메서드
다른 함수에 인수로 전달되며 특정 조건이 만족되거나 이벤트가 발생하면 호출된다.
다른 코드의 인수로서 넘겨주는 실행 가능한 코드로 콜백을 넘겨받는 코드는 필요 따라 즉시 실행할 수도 있고 나중에 실행할 수도 있다.
자바에서는 주로 인터페이스와 람다식을 사용하여 콜백 구현
비동기 작업 후에 특정 작업을 수행하기 위해 콜백 사용
한 함수가 다른 함수의 결과에 따라 동작을 달리하기 위해 사용
7. 템플릿 메서드 패턴 vs 템플릿 콜백 패턴
템플릿 콜백 패턴은 템플릿 메서드 패턴과 유사하지만 공통 로직이 아닌 하위 클래스에서 구체화된 코드를 호출하는 부분을 자바8 이전에서는 익명 클래스로 구현하고 자바8 이후에서는 람다식으로 구현한다.
커넥션 유지 비용 증가 : 각 데이터베이스 커넥션은 일정량의 시스템 리소스를 사용하는데 너무 많은 커넥션을 유지하면 CPU와 메모리 사용량이 증가하며, 이는 다른 애플리케이션이나 시스템의 성능 저하를 유발
데이터베이스 과부하 : 너무 많은 커넥션이 동시에 열리면 데이터베이스 서버에 과부하를 줄 수 있다. 이는 데이터베이스 성능 저하와 연결 실패로 이어질 수 있다.
4. 커넥션 풀의 크기를 정의하는 본인만의 공식 정해오기
☝ 공식 가이드 pool size = Tn x (Cm - 1) + 1
Tn : 쓰레드의 최대 개수 Cm : 동시에 사용하는 커넥션의 최대 개수
모니터링 환경 구축(서버 리소스, 서버 스레드 수, DBCP 등) 백엔드 시스템 부하 테스트 : 트래픽을 점점 늘려가면서 테스트 (nGrinder) request per second (단위 초당 몇개의 요청까지 처리할 수 있는지), avg response time (요청에 대한 평균적인 응답 시간) 두가지를 확인 백엔드 서버, DB 서버의 cpu, 메모리 등 리소스 사용률 확인
max-connections를 먼저 확인하고 사용할 백엔드 서버 수를 고려하여 maximunpoolsize 결정
5. JWT의 장단점
장점
토큰검증만을 통해 사용자 정보를 확인가능 하여 추가 검증 로직이 필요 없다.
매번 세션이나 데이터베이스 같은 인증 저장소가필요 없다.
사용자가 늘어나더라도 사용자 인증을 위한 추가 리소스비용이 없다.
다른 서비스에 공통 스펙으로 사용이 가능하여 확장성이 높다.
단점
base64 인코딩 정보를 전달하기에 전달량이 많다.
토큰이 탈취당할 시 만료될 때까지 대처가 불가능하다.
Payload부분은 누구든 디코딩하여 확인할 수 있다.
6. 리프레시 토큰이 등장한 이유
토큰은 탈취될 수도 있다는 단점이 있는데 만료시간을 짧게 설정해두면 탈취가 되더라도 그 토큰을 사용하는 데 제한이 있다. 액세스 토큰의 만료 시간을 짧게 설정해두고 서버에서 관리하는 리프레쉬 토큰의 만료 시간을 길게 설정해두고 사용하면 이러한 단점을 해결할 수 있다.
7. 왜 세션/쿠키 인증 방식 대신에 JWT토큰을 썼나요?
서버에 상태를 저장할 필요가 없기 때문에 별도의 서버를 더 추가하지 않아도 돼서 확장하기에 좋다.
사용자의 인증 정보가 토큰 자체에 있기 때문에 다른 데이터를 서버에 요청할 필요가 없기 때문에 성능이 향상된다.
자체적인 만료 시간을 가지고 있어 만료에 대한 관리가 편리하다.
8. DispatcherServlet이란?
서블릿 컨테이너의 가장 앞단에서 HTTP 프로토콜로 들어오는 모든 요청을 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러
프론트 컨트롤러 : 서블릿 컨테이너의 제일 앞에서 서버로 들어오는 클라이언트의 모든 요청을 받아서 처리해주는 컨트롤러, 공통의 로직에 대한 처리 가능
DispatcherServlet의 흐름
클라이언트에서 요청이 오면 DispatcherServlet이 요청을 받는다.
HandlerMapping을 통해 요청에 맞는 컨트롤러를 찾아낸다.
찾아낸 컨트롤러를 Handler Adapter를 통해 해당 컨트롤러의 메서드를 실행시킨다.
컨트롤러는 요청을 처리한 뒤 해당 요청에 대한 결과와 뷰에 대한 정보를 다시 DispatcherServlet에 전달한다.
받은 정보로 DispatcherServlet 은 View Resolver를 뷰 파일을 찾는다.
쇼규모 프로그램에서는 개발자의 생산성과 코드품질을 향상시킬 수 있으나, 일반적인 대규모 소프트웨어에서는 그렇지 않다.
낮은 수준에서 발생하는 특정유형의CheckedException의 경우 (File I/O, Network, Database …) 일반적인 응용프로그램에서는 알 필요 없거나 알고싶지 않아한다. 만약 Exception을 캐치한다고 하더라도 적절하게 대응하기 힘든경우가 대부분이라RuntimeException으로 rethrow처리하는 경우가 많다.
코드의 확장성에 이슈가 생길수 있다. 단일 CheckedException 사용은 훌륭하게 동작하는것으로 보이나 4~5개의 서로다른CheckedExcpetion을 사용하는 하위 API를 호출하는 경우 Exception 체인이 기하급수적으로 증가할 수 있다.
반드시 예외 처리를 하는 코드를 작성해야 한다. 예외를 던지는 것은 모든 하위 메서드, 호출 트리에 누적되기 때문에 수많은 메서드를 조정해야 할 수도 있다.
2. Java 8 기준으로 GC는 어떻게 실행이 되는가? (heap young/old generation 동작 관점)
parellel GC
멀티 스레드로 GC를 수행하기 때문에 stop-the-world 시간을 줄일 수 있다.
minor GC(young)에서 멀티 스레드를 사용한다.(후에는 major GC(old)에서도 멀티 스레드 사용)