아침을 여는 코드카타 시간..
확실히 파이썬으로 풀다가 자바로 풀게 되니 알아야 할 것이 많다
파이썬은 다 알아서 해줬는데..🥲
오늘은 강의 학습에 앞서 평소에 알아봐야겠다고 생각했던 것들 먼저 정리를 하고 시작하려 한다.
학습해야겠다고 생각한 이유는 두 가지다.
튜터님과의 면담에서 내가 생각보다도 더 Java나 기본 개념에 대해 잘 모른다는 것을 알게 되었고
알고리즘을 Java로 풀면서 사용했던 라이브러리도 그냥 써지는 대로 쓰다 보니 잘 모른다는 것이다.
그냥 난 무지랭이다...
추상화 : 필요한 정보만을 노출하고, 불필요한 내부 구현 세부 사항은 감추는 것. 복잡한 시스템을 간단하고 명확하게 표현할 수 있음
목적
1. 복잡성 감소: 사용자에게 필요한 기능만을 보여주고, 내부 세부 구현 사항은 감춤
2. 코드 재사용성 증가: 공통 기능 정의로 여러 클래스에서 재사용 가능하도록 함
3. 유지보수와 확장성 향상: 변경이 필요한 부분은 구체적 구현 클래스에서 처리, 상위 구조는 그대로 유지할 수 있음
Java에서 추상화를 구현하는 방법
추상 클래스와 인터페이스 사용
1. 추상 클래스
장점
1. 코드 가독성, 유지보수성 향상
2. 다형성 지원
3. 유연한 설계
추상화와 캡슐화의 차이
둘다 추상화를 지원하기 위해 사용되나 그 사용 목적과 기능에 차이가 있음
공통점
추상 클래스
abstract 키워드로 선언extends를 사용인터페이스
interface 키워드로 선언implements 사용| 특징 | 추상 클래스 | 인터페이스 |
|---|---|---|
| 다중 상속 | 지원X | 다중 구현 가능 |
| 메서드 구현 여부 | 추상 메서드와 구현된 메서드 모두 가능 | 기본적으로 추상 메서드. Java8 이후부터 default, static 메서드 가능 |
| 필드 | 인스턴스 변수, static 변수 가능 | public static final 상수만 가능 |
| 생성자 | 생성자 정의 가능 | 생성자 정의 불가능 |
| 사용 목적 | 공통된 상태(필드)와 동작(메서드)를 공유하는 클래스 설계 시 적합 | 동작(메서드)을 공유하고, 다형성을 지원하는 계약(contract) 정의 시 적합 |
결론
추상 클래스는 상속 관계에서 공통된 상태와 동작을 공유하는 데 적합. 상속받는 클래스와의 강한 연관성을 가짐
인터페이스는 클래스 간에 공통된 동작을 규정하고 다형성을 지원하기 위한 계약으로 사용됨
다중 구현이 필요한가? 공통된 필드와 메서드 구현이 필요한가?를 기준으로 선택
+다중 상속: 하나의 클래스가 여러 부모 클래스를 상속받는 것. Java에서는 모호성에 의한 복잡성과 오류 방지를 위해 금지되었다.
+다중 구현: 하나의 클래스가 여러 인터페이스를 구현하는 것. 인터페이스는 구현 코드가 없는 추상 메서드이기에, 동일한 이름의 메서드가 있더라도 충돌을 방지할 수 있음
+메서드의 선언만 있고 내부에 코드가 없는 추상 메서드는 왜 쓰는걸까?
-> 구현 강제성과 구조적 설계의 명확성 때문에
모두 Java에서 에외 처리를 위한 클래스 계층의 일부.
둘의 차이는 체크 예외(Checked Exception)와 언체크 예외(Unchecked Exception)
Exception의 계층 구조
Throwable
├── Error (시스템 레벨 에러, 복구 불가능)
└── Exception (프로그램의 예외 처리)
├── RuntimeException (언체크 예외)
└── 기타 Exception들 (체크 예외)
Exception
RuntimeException
비교
| 특징 | Exception | RuntimeException |
|---|---|---|
| 종류 | 체크 예외 (Checked Exception) | 언체크 예외 (Unchecked Exception) |
| 컴파일러 강제 여부 | 예외 처리가 필수적 | 선택적 |
| 발생 시점 | 주로 외부 환경과 상호작용 중 발생 (파일, DB 등) | 주로 프로그래밍 오류로 인해 발생 |
| 예시 | IOException, SQLException | NullPointerException,ArrayIndexOutOfBoundsException |
| 목적 | 외부 요인으로 인해 발생 가능한 예외를 명시적으로 처리 | 개발자의 실수나 논리 오류를 나타냄 |
| 특징 | 프로그램에서 복구 가능한 예외 상황 | 복구가 필요없는 예외(잘못된 입력 등) |
| 예외 처리가 강제되지 않아 코드가 간결해짐 |
Collection 인터페이스
Collection의 역할
하위 인터페이스로 List, Set, Queue 등이 있음.
| 특징 | Collection |
|---|---|
| 역할 | 데이터의 집합을 관리하기 위한 공통 인터페이스. |
| 하위 인터페이스 | List, Set, Queue. |
| 순서 보장 | List: 순서 유지, Set: 순서 유지 안 함, Queue: FIFO. |
| 중복 허용 | List: 허용, Set: 허용 안 함. |
| 주요 메서드 | 추가(add), 삭제(remove), 포함 확인(contains), 크기 확인(size). |
List
ArrayList, LinkedList, Vector 등이 List를 구현한 클래스임ArrayList
Array
간단한 알고리즘 문제를 풀 때는 ArrayList로 직접 선언하는 게 좋겠다.
StringBuilder
StringBuffer
| 특징 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 불변/가변 | 불변(Immutable) | 가변(Mutable) | 가변(Mutable) |
| Thread-Safe | N/A | 비동기 (Thread-Unsafe) | 동기화 지원 (Thread-Safe) |
| 성능 | 느림 (새 객체 생성) | 빠름 | 동기화로 인해 약간 느림 |
| 사용 환경 | 간단한 문자열 조작 | 단일 스레드 환경 | 멀티스레드 환경 |
StringJoiner
두 문자열을 사전적 순서로 비교하여 정렬하거나 비교하는 작업에 사용됨
Comparable 인터페이스를 구현한 메서드로 문자열 비교의 결과를 정수 값으로 반환함
매개변수: 비교할 대상 문자열
반환값:
동작 원리
대소문자를 무시하고 비교하고 싶을 경우: 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이 작다고 나온다.
특징
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
병렬 처리를 지원하여 대규모 데이터를 효율적으로 처리할 수 있음
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);
}
}
[Closed]
↓ 실패 횟수 초과
[Open]
↓ 일정 시간 후
[Half-Open]
↓ 성공 → [Closed]
↓ 실패 → [Open]
장점
단점
@CircuitBreaker, @Retry같은 어노테이션 활용 가능failureRateThresholdwaitDurationInOpenStateslidingWindowSizemaxAttemptswaitDurationtimeoutDurationlimitForPeriodmaxConcurrentCallsmaxConcurrentCallstimeoutDurationdependencies {
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
Prometheus
resilience4j_circuitbreaker_stateresilience4j_circuitbreaker_calls_totalresilience4j_retry_calls_totalSpring Boot Actuator
/actuator/health : 애플리케이션의 전반적인 상태 확인/actuator/metrics : Resilience4j와 통합된 메트릭을 포함한 애플리케이션 메트릭 확인/actuator/circuitbreakers : 서킷 브레이커 상태 확인작동 흐름
1. Resilience4j: 서킷 브레이커, 재시도 등 탄력성 패턴에 대한 메트릭 데이터 수집
2. Micrometer: Resilience4j의 메트릭 데이터를 Prometheus 형식으로 변환
3. Prometheus: 변환된 메트릭 데이터를 주기적으로 수집
4. Spring Actuator: Resilience4j의 상태 및 메트릭을 HTTP 엔드포인트로 노출
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" : 서킷 브레이커의 이름. 여기에는 productServicestate="<상태>" : 현재 서킷 브레이커 상태(closed, open, half_open 등등)value : 해당 상태인지 여부를 나타내는 값. 1.0-현재상태, 0.0-해당 상태가 아님저 캡처 당시의 서킷 브레이커 상태는 half-open 상태임을 알 수 있다.
오늘은 다른팀에도 놀러다녀왔다

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