[TIL] 241121

MONA·2024년 11월 21일

나혼공

목록 보기
32/92
post-thumbnail

아침을 여는 코드카타 시간..
확실히 파이썬으로 풀다가 자바로 풀게 되니 알아야 할 것이 많다
파이썬은 다 알아서 해줬는데..🥲
오늘은 강의 학습에 앞서 평소에 알아봐야겠다고 생각했던 것들 먼저 정리를 하고 시작하려 한다.

학습해야겠다고 생각한 이유는 두 가지다.
튜터님과의 면담에서 내가 생각보다도 더 Java나 기본 개념에 대해 잘 모른다는 것을 알게 되었고
알고리즘을 Java로 풀면서 사용했던 라이브러리도 그냥 써지는 대로 쓰다 보니 잘 모른다는 것이다.
그냥 난 무지랭이다...

궁금했던 것들 정리

Java에서 추상화의 개념

추상화 : 필요한 정보만을 노출하고, 불필요한 내부 구현 세부 사항은 감추는 것. 복잡한 시스템을 간단하고 명확하게 표현할 수 있음

목적
1. 복잡성 감소: 사용자에게 필요한 기능만을 보여주고, 내부 세부 구현 사항은 감춤
2. 코드 재사용성 증가: 공통 기능 정의로 여러 클래스에서 재사용 가능하도록 함
3. 유지보수와 확장성 향상: 변경이 필요한 부분은 구체적 구현 클래스에서 처리, 상위 구조는 그대로 유지할 수 있음

Java에서 추상화를 구현하는 방법
추상 클래스와 인터페이스 사용
1. 추상 클래스

  • 공통된 속성과 동작을 정의
  • 일부 메서드는 구현하지 않고, 하위 클래스에서 반드시 구현하도록 강제함
  • 하위 클래스는 구현된 메서드를 반드시 재정의(override)할 필요가 없음. 그대로 사용할 수도 있고 필요하면 재정의 해서 써도 됨
  1. 인터페이스
  • 클래스가 따라야 하는 행동규약(contract) 정의
  • 구현 클래스는 인터페이스의 모든 메서드를 반드시 구현해야 함
  • 기본적으로 인터페이스의 메서드는 추상 메서드이나, Java8 이후로는 default, static 형태로 구현된 메서드를 포함할 수 있음. 이 두 가지 방법으로 선언되지 않는 메서드는 모두 추상메서드로, 모든 구현 클래스에서 반드시 구현해야 함
    • default 메서드: 구현 클래스에서 필수적으로 재정의하지 않아도 됨. 필요할 경우 재정의 가능
    • static 메서드: 인터페이스에서 정적으로 호출되며, 하위 클래스에서 재정의 할 수 없음

장점
1. 코드 가독성, 유지보수성 향상
2. 다형성 지원
3. 유연한 설계

추상화와 캡슐화의 차이

  • 추상화: 시스템의 복잡한 내부를 감추고, 중요한 부분만을 보여줌. "무엇을 한다"에 초점
  • 캡슐화: 데이터와 메서드를 클래스 내부에 숨기고(private), 외부에는 필요한 것만 노출하는 것. "어떻게 숨길 것인가"에 초점

추상 클래스와 인터페이스의 차이

둘다 추상화를 지원하기 위해 사용되나 그 사용 목적과 기능에 차이가 있음

공통점

  • 직접 인스턴스 생성 불가
  • 상속 또는 구현된 클래스를 통해 사용됨
  • 추상 메서드를 통해 구현 강제성을 제공

추상 클래스

  • abstract 키워드로 선언
  • 일반 클래스처럼 필드와 메서드를 가질 수 있으며, 일부 메서드는 구현될 수 있음
  • 상속받는 클래스는 extends를 사용
  • 단일 상속만 가능
  • 일반 클래스처럼 필드와 생성자를 가질 수 있음

인터페이스

  • interface 키워드로 선언
  • 메서드는 기본적으로 추상 메서드로 간주되며, Java8 부터는 default, static 메서드도 가질 수 있음
  • 구현 클래스는 implements 사용
  • 다중 구현 가능
  • 필드로 public static final 상수만 가질 수 있음
특징추상 클래스인터페이스
다중 상속지원X다중 구현 가능
메서드 구현 여부추상 메서드와 구현된 메서드 모두 가능기본적으로 추상 메서드. Java8 이후부터 default, static 메서드 가능
필드인스턴스 변수, static 변수 가능public static final 상수만 가능
생성자생성자 정의 가능생성자 정의 불가능
사용 목적공통된 상태(필드)와 동작(메서드)를 공유하는 클래스 설계 시 적합동작(메서드)을 공유하고, 다형성을 지원하는 계약(contract) 정의 시 적합

결론

  • 추상 클래스는 상속 관계에서 공통된 상태와 동작을 공유하는 데 적합. 상속받는 클래스와의 강한 연관성을 가짐

  • 인터페이스는 클래스 간에 공통된 동작을 규정하고 다형성을 지원하기 위한 계약으로 사용됨

  • 다중 구현이 필요한가? 공통된 필드와 메서드 구현이 필요한가?를 기준으로 선택

+다중 상속: 하나의 클래스가 여러 부모 클래스를 상속받는 것. Java에서는 모호성에 의한 복잡성과 오류 방지를 위해 금지되었다.
+다중 구현: 하나의 클래스가 여러 인터페이스를 구현하는 것. 인터페이스는 구현 코드가 없는 추상 메서드이기에, 동일한 이름의 메서드가 있더라도 충돌을 방지할 수 있음

+메서드의 선언만 있고 내부에 코드가 없는 추상 메서드는 왜 쓰는걸까?
-> 구현 강제성과 구조적 설계의 명확성 때문에

  • 구현 강제성: 추상 메서드는 하위 클래스에서 반드시 구현하도록 강제함.
  • 공통 인터페이스 제공: 추상 메서드는 다양한 클래스가 같은 방식으로 호출될 수 있는 공통 기준을 제공함. 다형성을 지원하는 중요한 개념
  • 설계 명확화: 추상 메서드는 설계 단계에서 해당 클래스(인터페이스)를 상속받는 클래스는 어떤 메서드를 가져야 하는지를 명확히 정의함
  • 코드 재사용성: 공통 동작을 정의하면서도 하위 클래스에서 커스터마이징 할 여지를 남겨줌

RuntimeException과 Exception의 차이

모두 Java에서 에외 처리를 위한 클래스 계층의 일부.
둘의 차이는 체크 예외(Checked Exception)와 언체크 예외(Unchecked Exception)

Exception의 계층 구조

Throwable
  ├── Error (시스템 레벨 에러, 복구 불가능)
  └── Exception (프로그램의 예외 처리)
       ├── RuntimeException (언체크 예외)
       └── 기타 Exception들 (체크 예외)

Exception

  • Checked Excepion
  • 컴파일 시점에 반드시 처리해야 하는 예외
    • 예외를 처리하지 않으면 컴파일 에러 발생
    • 처리 방식: try-catch 또는 throw 키워드 사용해 호출부로 전달
  • ex)
    IOException: 파일을 읽거나 쓰는 도중 발생하는 예외
    SQLException: 데이터베이스 관련 작업 중 발생하는 예외

RuntimeException

  • Unchecked Exception
  • 컴파일 시점에 예외 처리가 강제되지 않으며 런타임에 발생할 수 있는 예외
  • 대부분의 프로그래밍 오류(논리적 실수, 잘못된 데이터 처리 등)를 나타냄
  • ex)
    NullPointerException: null 참조로 객체의 메서드나 필드에 접근하려 할 때 발생하는 예외
    ArrayIndexOutOfBoundsException: 배열의 인덱스가 범위를 벗어날 때 발생하는 예외
    ArithmeticException: 0으로 나누는 연산을 수행할 때 발생하는 예외

비교

특징ExceptionRuntimeException
종류체크 예외 (Checked Exception)언체크 예외 (Unchecked Exception)
컴파일러 강제 여부예외 처리가 필수적선택적
발생 시점주로 외부 환경과 상호작용 중 발생 (파일, DB 등)주로 프로그래밍 오류로 인해 발생
예시IOException, SQLExceptionNullPointerException,ArrayIndexOutOfBoundsException
목적외부 요인으로 인해 발생 가능한 예외를 명시적으로 처리개발자의 실수나 논리 오류를 나타냄
특징프로그램에서 복구 가능한 예외 상황복구가 필요없는 예외(잘못된 입력 등)
예외 처리가 강제되지 않아 코드가 간결해짐

List, Set의 부모 인터페이스?

Collection 인터페이스

  • Java의 컬렉션 프레임워크에서 가장 기본이 되는 인터페이스. 데이터의 집합을 표현. 데이터를 저장하고 조작하는 데 필요한 공통된 메서드 정의함

Collection의 역할

  • Java의 컬렉션 프레임워크에서 데이터 집합을 처리하기 위한 공통 규약 정의.
  • List, Set, Queue 등은 모두 Collection을 확장한 인터페이스로, 각각의 특화된 동작을 제공함

하위 인터페이스로 List, Set, Queue 등이 있음.

특징Collection
역할데이터의 집합을 관리하기 위한 공통 인터페이스.
하위 인터페이스List, Set, Queue.
순서 보장List: 순서 유지, Set: 순서 유지 안 함, Queue: FIFO.
중복 허용List: 허용, Set: 허용 안 함.
주요 메서드추가(add), 삭제(remove), 포함 확인(contains), 크기 확인(size).

List, ArrayList, Array

List

  • 인터페이스. Java 컬렉션 프레임워크의 일부
  • 요소들의 순서를 유지, 중복 허용. 데이터 타입 제네릭 지원(List\)
  • index를 사용해 요소를 추가, 삭제, 검색 할 수 있음
  • ArrayList, LinkedList, Vector 등이 List를 구현한 클래스임
  • 주요 메서드
    • void add(E e): 요소 추가
    • E get(int index): 특정 인덱스의 요소 반환
    • E remove(int index): 요소 제거
    • int size(): 개수 반환

ArrayList

  • List 인터페이스의 구현 클래스. 배열 기반의 동적 크기 리스트를 제공
  • 요소의 추가, 삭제, 검색이 쉽고 빠름
  • 요소 추가 시 자동으로 크기를 조정
  • 특징
    • 내부적으로 배열을 사용해 데이터 저장
    • 동적 크기
    • 랜덤 엑세스: 인덱스를 통해 빠르게 요소에 접근 가능
    • Thread-safe 안됨: 멀티스레드 환경에서 동기화 처리 필요
    • 요소를 중간에 삽입/삭제할 경우 배열 재구성으로 느림.

Array

  • Java에서 제공하는 기본 자료구조. 고정 크기의 연속된 메모리 공간에 데이터 저장
  • Java의 객체 타입이 아닌 구조적 데이터 타입.
  • 특징
    • 크기 고정: 선언 시 크기 고정. 이후 변경 불가
    • 동일 타입만 저장 가능
    • 효율적인 메모리 사용: 간단, 빠르고 메모리 사용량이 적음
    • 인덱스 사용
  • 장점
    • 빠른 성능: 메모리에서 연속적으로 데이터를 저장해 접근 속도가 빠름
    • 간단함: 컬렉션 클래스보다 사용법이 직관적임
  • 단점
    • 크기 제한
    • 요소 추가, 삭제가 어렵고 중간 삽입/삭제가 비효율적

간단한 알고리즘 문제를 풀 때는 ArrayList로 직접 선언하는 게 좋겠다.

StringBuilder, StringBuffer, StringJoiner

StringBuilder

  • 문자열 조작 시 사용하는 클래스. Java에서 제공하는 문자열 처리 클래스 중 하나
  • String은 불변이지만 StringBuilder는 가변 문자열을 제공
  • 새로운 객체 생성 없이 내부 버퍼를 수정하는 방식으로 추가,수정,삭제를 진행하며 성능이 뛰어남
  • 특징
    • 가변 문자열
    • Thread-safe하지 않음: 단일 스레드 환경에서는 더 빠름. 멀티스레드 환경에서는 StringBuffer 사용.
  • 주요 메서드
    • apeend(String s): 문자열 추가
    • insert(int offset, String s): 특정 위치에 문자열 삽입
    • delete(int start, int end): 특정 범위의 문자열 삭제
    • replace(int start, int end, String s): 특정 범위의 문자열 대체
    • reverse()
    • toString(): StringBuilder 객체를 String으로 변환

StringBuffer

  • Java에서 문자열을 조작하기 위한 클래스 중 하나.
  • 멀티스레드 환경에서 안전하게 동작되도록 설계됨
  • StringBuilder와 유사하지만 동기화를 지원하여 여러 스레드에서 동시에 사용해도 데이터 손상이 없음
  • 특징
    • 가변 문자열
    • Thread-safe 됨: 동기화로 인해 StringBuilder보다 속도가 약간 느림
    • 내부적으로 버퍼(buffer)를 사용하여 문자열을 저장하며, 문자열 크기가 증가하면 자동으로 버퍼 크기를 확장
특징StringStringBuilderStringBuffer
불변/가변불변(Immutable)가변(Mutable)가변(Mutable)
Thread-SafeN/A비동기 (Thread-Unsafe)동기화 지원 (Thread-Safe)
성능느림 (새 객체 생성)빠름동기화로 인해 약간 느림
사용 환경간단한 문자열 조작단일 스레드 환경멀티스레드 환경

StringJoiner

  • 문자열을 구분자로 연결할 때 사용하는 클래스
  • 구분자(Delimiter)를 지정하여 문자열을 결합하며, 선택적으로 시작 문자열(prefix)과 종료 문자열(suffix)도 설정 가능
  • 특징
    • 구분자를 사용한 문자열 조합
    • 가변 길이 문자열: 내부적으로 가변적인 문자열을 사용해 효율적으로 처리함
    • Optional Prefix와 Suffix: 결과 문자열에 시작 문자열과 끝 문자열을 추가할 수 있음
  • 주요 메서드
    • add(String s): 문자열 추가
    • merge(StringJoiner other): 다른 StringJoiner와 병합
    • toString(): 조합된 문자열 반환

String.compareTo

두 문자열을 사전적 순서로 비교하여 정렬하거나 비교하는 작업에 사용됨
Comparable 인터페이스를 구현한 메서드로 문자열 비교의 결과를 정수 값으로 반환함

  • 매개변수: 비교할 대상 문자열

  • 반환값:

    • 0: 두 문자열이 같은 경우
    • 음수: 호출한 문자열이 비교 대상 문자열보다 사전적으로 앞서는 경우
    • 양수: 호출한 문자열이 비교 대상 문자열보다 사전적으로 뒤에 있는 경우
  • 동작 원리

    • 문자열을 각 문자의 unicode 값으로 비교. 왼쪽에서 오른쪽으로 순차적으로 비교하며 서로 다른 문자가 나오면 해당 문자의 unicode값을 비교
    • 한 문자열이 다른 문자열의 접두사일 경우 짧은 문자열이 사전적으로 앞섬
  • 대소문자를 무시하고 비교하고 싶을 경우: compareToIgnoreCase 사용

프로그래머스-크기가 작은 부분 문자열

class Solution {
    public int solution(String t, String p) {
        int pLen = p.length();
        int cnt = 0;

        for(int i=0; i<=t.length()-pLen; i++){
            String part = t.substring(i, i+pLen);

            if (part.compareTo(p) <= 0) {
                cnt++;
            }
        }
        return cnt;
    }
}

이 문제를 풀 때 사용했다. 원래는 다 Double로 바꿔서 비교하려고 했는데, 이렇게도 쓸 수 있었다.
다만 같은 자릿수이기에 쓸 수 있었던 방법이고. 10과 2를 비교하면 10이 작다고 나온다.

Stream API

  • Java8에서 도입된 데이터 처리 기능
  • 컬렉션이나 배열같은 데이터 소스를 연속적으로 처리할 수 있는 기능 제공
  • 선언적, 함수형 프로그래밍 스타일로 데이터 처리.
  • 필터링, 매핑, 정렬, 집계 등의 작업을 효율적으로 수행할 수 있음

특징
1. 선언적 프로그래밍 스타일
2. 데이터 소스를 변경하지 않고 새로운 결과를 생성
3. 중간 연산과 최종 연산: 중간 연산(필터링, 매핑)을 연결한 후 최종 연산(집계, 수집)으로 결과를 생성
4. 지연 연산: 중간 연산은 최종 연산이 호출될 때까지 실행되지 않아 불필요한 계산을 줄임
5. 파이프라인 구성: 데이터 처리 작업을 체인 형태로 연결해 간결하게 표현함
6. 병렬 처리 지원: 멀티코어 CPU를 활용해 대량 데이터를 병렬로 처리

중간 연산
데이터 변환을 정의하지만 즉시 실행되지 않음. 최종 연산 호출 시 실행됨.

메서드설명
filter(Predicate)조건에 맞는 요소만 남김.
map(Function)각 요소를 다른 값으로 변환.
sorted()요소를 정렬. 기본 정렬 또는 Comparator를 사용.
distinct()중복 요소를 제거.
limit(long n)최대 n개의 요소만 포함.
skip(long n)처음 n개의 요소를 건너뜀.

최종 연산
Stream 파이프라인을 종료하고 결과를 생성함.

메서드설명
forEach(Consumer)각 요소에 대해 작업 수행.
collect()결과를 리스트, 맵 등으로 변환.
reduce()모든 요소를 누적하여 단일 결과 생성.
count()요소의 개수 반환.
findFirst()첫 번째 요소 반환 (Optional).
anyMatch(), allMatch(), noneMatch()조건에 맞는 요소가 있는지 검사.

생성

// 리스트에서 생성
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream = list.stream();

// 배열에서 생성
String[] array = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(array);

// Stream builder
Stream<String> stream = Stream.of("A", "B", "C");

// 무한 스트림
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
infiniteStream.limit(5).forEach(System.out::println); // 출력: 0, 1, 2, 3, 4

Parallel Stream
병렬 처리를 지원하여 대규모 데이터를 효율적으로 처리할 수 있음

  • 멀티코어 CPU를 활용하지만 항상 성능이 향상되는 것은 아님
  • 데이터 양, 병렬 처리 오버헤드 등을 고려해야 함
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        IntStream.range(1, 1000)
                 .parallel() // 병렬 처리
                 .filter(n -> n % 2 == 0)
                 .forEach(System.out::println);
    }
}

서킷 브레이커 (Circuit Breaker)

  • 소프트웨어 시스템에서 오류 전파를 방지하고 시스템의 안정성을 유지하기 위한 설계 패턴
  • 주로 마이크로서비스나 분산 시스템에서 사용됨
  • 서비스 간 호출이 실패하거나 장애 발생 시 시스템 전체로 장애가 확산되는 것을 막아줌

개념

  • 실제 전기 회로의 차단기처럼 작동함
  • 서비스 호출 실패가 일정 횟수 이상 발생하면 호출을 중단해 더 큰 문제를 방지함
  • 일정 시간 후에 다시 호출을 시도하여 정상 상태로 복구될 수 있게 함

동작 원리

  1. Closed
  • 정상 상태
  • 서비스 호출이 정상적으로 작동. 호출 실패를 기록
  • 실패 횟수가 임계값을 초과하면 Open 상태로 전환
  1. Open
  • 서비스 호출 중단 상태
  • 일정 시간 동안 모든 호출이 차단(모든 요청을 즉시 실패로 처리)되며 장애가 다른 서비스로 전파되지 않게 함
  • 요청은 실패하지 않고 바로 에러 응답 반환
  • 일정 시간 후 Half-Open 상태로 전환
  1. Half-Open
  • 제한적으로 서비스 호출을 허용
  • 일부 호출을 통해 서비스 상태를 테스트
    • 성공 시 Closed 상태로 전환
    • 실패 시 다시 Open 상태로 전환
[Closed]
    ↓ 실패 횟수 초과
[Open]
    ↓ 일정 시간 후
[Half-Open]
    ↓ 성공 → [Closed]
    ↓ 실패 → [Open]

주요 역할

  1. 장애 전파 방지
  2. 시스템 안정성 유지
  3. 복구 가능성 테스트

장단점

장점

  • 장애 전파 방지로 시스템 안정성 확보
  • 불필요한 호출 차단으로 리소스 낭비 감소
  • 서비스 복구 가능 여부를 점진적으로 확인 가능

단점

  • 추가적인 코드와 설정이 필요
  • 서킷 브레이커 상태 전환 지연으로 인해 정상 서비스도 차단될 가능성
  • 설정이 잘못되면 장애를 적절히 처리하지 못할 수 있음

요약

  • 서킷 브레이커는 분산 시스템과 마이크로 아키텍처에서 필수적인 안정성 패턴이다. 적절한 임계값과 대기 시간을 설정하여 최적화 할 수 있다.

구현

  • 직접 구현하려면 호출 실패 횟수와 대기 시간 등을 관리하면 됨
  • 또는 Resilience4j나 Hystrix같은 라이브러리를 사용함

Resilience4j

  • Java 애플리케이션에서 안정성과 탄력성을 제공하기 위해 설계된 경량 라이브러리
  • 분산 시스템에서 주로 사용되며 서킷브레이커, 재시도, 속도제한, 버퍼링 등 다양한 탄력성 패턴을 지원함

특징

  1. 경량성
  • 가볍고, 각 모듈이 독립적으로 동작하므로 필요에 따라 선택적으로 사용 가능
  • Hystrix와 같은 기존 라이브러리보다 성능이 뛰어나며 프로젝트의 부담이 적음
  1. Java8 및 함수형 프로그래밍 지원
  • 함수형 인터페이스와 람다 표현식으로 설정과 사용이 간단함
  1. Spring Boot와의 통합
  • Spring Boot Starter를 통해 쉽게 통합 가능, Spring의 @CircuitBreaker, @Retry같은 어노테이션 활용 가능
  1. 개별 모듈 설계
  • 서킷 브레이커, 재시도, 속도 제한, 캐싱 등을 각각 독립적으로 사용 가능
  1. 모니터링 및 메트릭
  • Micrometer와 통합되어 Prometheus, Grafana와 같은 도구를 통해 메트릭을 수집하고 모니터링 할 수 있음

주요 탄력성 패턴

  1. Circuit Breaker (서킷 브레이커)
  • 호출 실패가 일정 임계값을 초과하면 호출을 차단하고, 장애 전파를 방지
  • 서킷 브레이커의 3가지 상태를 지원
  • 활용: 외부 API 호출, 불안정한 서비스 호출 시
  • 설정 가능 파라미터
    • 실패율 임계값: failureRateThreshold
    • 열림 상태 유지 시간: waitDurationInOpenState
    • 슬라이딩 윈도우 크기: slidingWindowSize
  1. Retry (재시도)
  • 호출 실패 시 자동으로 재시도 로직을 실행
  • 재시도 횟수, 대기 시간 등을 설정 가능
  • 활용: 일시적인 네트워크 문제, 타임아웃 등의 상황에서 재시도를 통해 안정적인 서비스 제공
  • 설정 가능 파라미터
    • 재시도 횟수: maxAttempts
    • 재시도 간 대기 시간: waitDuration
    • 백오프 전략(점진적 증가)
  1. Rate Limiter (속도제한기)
  • 단위 시간 동안 허용되는 호출 수를 제한하여 시스템의 과부하 방지
  • 특정 서비스에 대한 과도한 호출을 제어하여 안정성 유지
  • 활용: 외부 API 호출에서 속도 제한이 필요한 경우
  • 설정 가능 파라미터
    • 시간 단위: timeoutDuration
    • 허용 호출 수: limitForPeriod
  1. Bulkhead (버퍼링)
  • 시스템의 리소스를 보호하기 위해 동시 실행 가능한 작업 수를 제한
  • 고립된 환경을 제공해 특정 서비스가 전체 시스템에 영향을 주지 않도록 함
  • 활용: 특정 서비스나 리소스에 대한 동시 호출 제한
  • 설정 가능 파라미터
    • 최대 동시 호출 수: maxConcurrentCalls
    • 대기 큐 크기: maxConcurrentCalls
  1. Time Limiter (시간 제한)
  • 서비스 호출에 타임아웃을 설정하여 일정 시간이 지나면 중단
  • 비동기 호출과 함께 사용되어야 함
  • 활용: 시간이 오래 걸리는 작업이나 외부 API 호출 시 타임아웃 설정
  • 설정 가능한 파라미터
    • 타임아웃 시간: timeoutDuration
  1. Fallback(대체 로직)
  • 서킷 브레이커, 재시도 등의 실패 시 대체 로직을 실행하여 기본 응답을 제공
  • 활용: 장애 발생 시 최소한의 기능을 제공하여 사용자 경험을 유지

장점

  1. 모듈화: 필요한 기능만 선택적으로 사용할 수 있어 가볍고 유연
  2. 성능
  • 기존의 Hystrix보다 빠르고 효율적
  • Hystrix는 스레드 풀 기반이지만 Resilience4j는 동기적 방식으로 설계되어 오버헤드가 적음
  1. 확장성
  • 다양한 탄력성 패턴을 제공하며 Spring Boot, Micrometer와 쉽게 통합됨
  1. 함수형 지원

Resilience4j 적용하기

Resilience4j

  1. 의존성 추가
dependencies {
    implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
	runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
}

Micrometer

  • Java 애플리케이션 메트릭(Metrics)을 수집, 처리, 노출하는 경량 라이브러리
  • Spring Boot의 기본 메트릭 수집 라이브러리로, 애플리케이션 성능과 상태를 모니터링함
  • Prometheus, Grafana, Datadog, New Relic 등 다양한 모니터링 시스템과 쉽게 통합할 수 있음

Prometheus

  • 오픈소스 모니터링 및 경보 도구. 주로 애플리케이션의 메트릭 데이터 수집 및 시각화를 위해 사용됨
  • 실시간 모니터링, 장애 대응(경보 설정), 시각화를 위해 사용
  • Resilience4j는 Micrometer를 통해 Prometheus와 통합되며 관련 메트릭 데이터를 Prometheus로 전송 가능
  • 수집 메트릭 예시
    • 서킷 브레이커 상태: resilience4j_circuitbreaker_state
    • 호출 성공/실패 횟수: resilience4j_circuitbreaker_calls_total
    • 재시도 횟수: resilience4j_retry_calls_total

Spring Boot Actuator

  • 애플리케이션의 상태와 진단 정보를 제공하는 라이브러리
  • Resilience4j는 Actuator를 통해 서킷 브레이커와 관련된 상태와 메트릭을 쉽게 확인할 수 있도록 지원함
  • 주요 엔드포인트
    • /actuator/health : 애플리케이션의 전반적인 상태 확인
    • /actuator/metrics : Resilience4j와 통합된 메트릭을 포함한 애플리케이션 메트릭 확인
    • /actuator/circuitbreakers : 서킷 브레이커 상태 확인

작동 흐름
1. Resilience4j: 서킷 브레이커, 재시도 등 탄력성 패턴에 대한 메트릭 데이터 수집
2. Micrometer: Resilience4j의 메트릭 데이터를 Prometheus 형식으로 변환
3. Prometheus: 변환된 메트릭 데이터를 주기적으로 수집
4. Spring Actuator: Resilience4j의 상태 및 메트릭을 HTTP 엔드포인트로 노출

  1. yml 설정
spring:
  application:
    name: sample

server:
  port: 19090

resilience4j:
  circuitbreaker:
    configs:
      default:  # 기본 구성 이름
        registerHealthIndicator: true  # 애플리케이션의 헬스 체크에 서킷 브레이커 상태를 추가하여 모니터링 가능
        # 서킷 브레이커가 동작할 때 사용할 슬라이딩 윈도우의 타입을 설정
        # COUNT_BASED: 마지막 N번의 호출 결과를 기반으로 상태를 결정
        # TIME_BASED: 마지막 N초 동안의 호출 결과를 기반으로 상태를 결정
        slidingWindowType: COUNT_BASED  # 슬라이딩 윈도우의 타입을 호출 수 기반(COUNT_BASED)으로 설정
        # 슬라이딩 윈도우의 크기를 설정
        # COUNT_BASED일 경우: 최근 N번의 호출을 저장
        # TIME_BASED일 경우: 최근 N초 동안의 호출을 저장
        slidingWindowSize: 5  # 슬라이딩 윈도우의 크기를 5번의 호출로 설정
        minimumNumberOfCalls: 5  # 서킷 브레이커가 동작하기 위해 필요한 최소한의 호출 수를 5로 설정
        slowCallRateThreshold: 100  # 느린 호출의 비율이 이 임계값(100%)을 초과하면 서킷 브레이커가 동작
        slowCallDurationThreshold: 60000  # 느린 호출의 기준 시간(밀리초)으로, 60초 이상 걸리면 느린 호출로 간주
        failureRateThreshold: 50  # 실패율이 이 임계값(50%)을 초과하면 서킷 브레이커가 동작
        permittedNumberOfCallsInHalfOpenState: 3  # 서킷 브레이커가 Half-open 상태에서 허용하는 최대 호출 수를 3으로 설정
        # 서킷 브레이커가 Open 상태에서 Half-open 상태로 전환되기 전에 기다리는 시간
        waitDurationInOpenState: 20s  # Open 상태에서 Half-open 상태로 전환되기 전에 대기하는 시간을 20초로 설정

management:
  endpoints:
    web:
      exposure:
        include: prometheus
  prometheus:
    metrics:
      export:
        enabled: true
        

테스트
service에 작성된 메서드에 @CircuitBreaker 어노테이션을 설정해준다.

fallbackMethod : 실패시 실행할 메서드

http://localhost:19090/product/1 호출 시 정상 응답

http://localhost:19090/product/111 호출 시 fallback 응답

몇 번 더 호출해주고
다른 id 호출 시 fallback 상태 반환, 일정 시간 후 다시 요청해보면 정상 응답 반환 확인

로그로 찍어서 확인해보는 코드도 돌려봤다. 재밌당

아래 링크로도 확인할 수 있었다.

http://localhost:19090/actuator/prometheus

# TYPE resilience4j_circuitbreaker_state gauge
resilience4j_circuitbreaker_state{name="productService",state="closed"} 0.0
resilience4j_circuitbreaker_state{name="productService",state="disabled"} 0.0
resilience4j_circuitbreaker_state{name="productService",state="forced_open"} 0.0
resilience4j_circuitbreaker_state{name="productService",state="half_open"} 1.0
resilience4j_circuitbreaker_state{name="productService",state="metrics_only"} 0.0
resilience4j_circuitbreaker_state{name="productService",state="open"} 0.0
# HELP system_cpu_count The number of processors available to the Java virtual machine

서킷 브레이커 상태 설명 표

상태설명동작
closed정상 상태.- 모든 요청을 정상적으로 허용.
- 실패율을 모니터링.
- 실패율이 임계값을 초과하면 open 상태로 전환.
open차단 상태.- 모든 요청을 차단하여 장애가 확산되지 않도록 함.
- 일정 시간이 지나면 half_open 상태로 전환.
half_open테스트 상태.- 일부 요청만 허용하여 서비스가 복구되었는지 확인.
- 성공하면 closed, 실패하면 다시 open 상태로 전환.
forced_open강제 차단 상태.- 수동으로 설정하여 모든 요청을 차단.
- 명시적으로 설정을 변경해야 상태가 전환됨.
disabled비활성화 상태.- 서킷 브레이커 동작이 비활성화되어 모든 요청이 정상적으로 통과.
- 메트릭은 수집되지 않음.
metrics_only메트릭 전용 상태.- 서킷 브레이커의 차단 동작은 수행하지 않고, 메트릭만 수집.
- 장애 상태로 전환되지 않음.
  • resilience4j_circuitbreaker_state{name="productService",state="<상태>"}
    • name="productService" : 서킷 브레이커의 이름. 여기에는 productService
    • state="<상태>" : 현재 서킷 브레이커 상태(closed, open, half_open 등등)
    • value : 해당 상태인지 여부를 나타내는 값. 1.0-현재상태, 0.0-해당 상태가 아님

저 캡처 당시의 서킷 브레이커 상태는 half-open 상태임을 알 수 있다.


오늘은 다른팀에도 놀러다녀왔다

우리 팀에서 잠깐 쫓겨나기도 했지만.. 다함께 춤추는 짤로 엔딩..

profile
고민고민고민

0개의 댓글