스파르타 Java 단기 심화 과정


코드카타


프로그래머스 81301 숫자 문자열과 영단어

https://school.programmers.co.kr/learn/courses/30/lessons/81301

— 문제 설명

네오와 프로도가 숫자놀이를 하고 있습니다. 네오가 프로도에게 숫자를 건넬 때 일부 자릿수를 영단어로 바꾼 카드를 건네주면 프로도는 원래 숫자를 찾는 게임입니다.

다음은 숫자의 일부 자릿수를 영단어로 바꾸는 예시입니다.

  • 1478 → "one4seveneight"
  • 234567 → "23four5six7"
  • 10203 → "1zerotwozero3"

이렇게 숫자의 일부 자릿수가 영단어로 바뀌어졌거나, 혹은 바뀌지 않고 그대로인 문자열 s가 매개변수로 주어집니다. s가 의미하는 원래 숫자를 return 하도록 solution 함수를 완성해주세요.

참고로 각 숫자에 대응되는 영단어는 다음 표와 같습니다.

숫자영단어
0zero
1one
2two
3three
4four
5five
6six
7seven
8eight
9nine

— 제한 조건

  • 1 ≤ s의 길이 ≤ 50
  • s가 "zero" 또는 "0"으로 시작하는 경우는 주어지지 않습니다.
  • return 값이 1 이상 2,000,000,000 이하의 정수가 되는 올바른 입력만 s로 주어집니다.

— 입출력 예

sresult
"one4seveneight"1478
"23four5six7"234567
"2three45sixseven"234567
"123"123

입출력 예 #1

  • 문제 예시와 같습니다.

입출력 예 #2

  • 문제 예시와 같습니다.

입출력 예 #3

  • "three"는 3, "six"는 6, "seven"은 7에 대응되기 때문에 정답은 입출력 예 #2와 같은 234567이 됩니다.
  • 입출력 예 #2와 #3과 같이 같은 정답을 가리키는 문자열이 여러 가지가 나올 수 있습니다.

입출력 예 #4

  • s에는 영단어로 바뀐 부분이 없습니다.

— 제한 시간
• 정확성 테스트 : 10초

— 문제 풀이

class Solution {
    public int solution(String s) {
        StringBuilder sb = new StringBuilder();
        /*
        0 z+4
        1 o+3
        2 t+3(2 뒤가 o면 2)
        3 t+5(2 뒤가 o아니면 3)
        4 f+4(3 뒤가 r이면 4)
        5 f+4(3 뒤가 r아니면 5)
        6 s+3(2 뒤가 x면 6)
        7 s+5(2 뒤가 x아니면 7)
        8 e+5
        9 n+4
        */
        int i=0;
        while(i<s.length()){
            if(s.charAt(i)>='0'&&s.charAt(i)<='9') {
                sb.append(s.charAt(i));
                i++;
                continue;
            }
            switch(s.charAt(i)){
                case 'z':
                    sb.append("0");
                    i+=4;
                    break;
                case 'o':
                    sb.append("1");
                    i+=3;
                    break;
                case 't':
                    if(s.charAt(i+2)=='o'){
                        sb.append("2");
                        i+=3;
                    }else {
                        sb.append("3");
                        i+=5;
                    }
                    break;
                case 'f':
                    if(s.charAt(i+3)=='r'){
                        sb.append("4");
                        i+=4;
                    }else {
                        sb.append("5");
                        i+=4;
                    }
                    break;
                case 's':
                    if(s.charAt(i+2)=='x'){
                        sb.append("6");
                        i+=3;
                    }else {
                        sb.append("7");
                        i+=5;
                    }
                    break;
                case 'e':
                    sb.append("8");
                    i+=5;
                    break;
                case 'n':
                    sb.append("9");
                    i+=4;
                    break;
            }
        }
        
        int answer = Integer.parseInt(sb.toString());
        return answer;
    }
}

프로그래머스 12915 문자열 내 마음대로 정렬하기

https://school.programmers.co.kr/learn/courses/30/lessons/12915

— 문제 설명

문자열로 구성된 리스트 strings와, 정수 n이 주어졌을 때, 각 문자열의 인덱스 n번째 글자를 기준으로 오름차순 정렬하려 합니다. 예를 들어 strings가 ["sun", "bed", "car"]이고 n이 1이면 각 단어의 인덱스 1의 문자 "u", "e", "a"로 strings를 정렬합니다.

— 제한 조건

  • strings는 길이 1 이상, 50이하인 배열입니다.
  • strings의 원소는 소문자 알파벳으로 이루어져 있습니다.
  • strings의 원소는 길이 1 이상, 100이하인 문자열입니다.
  • 모든 strings의 원소의 길이는 n보다 큽니다.
  • 인덱스 1의 문자가 같은 문자열이 여럿 일 경우, 사전순으로 앞선 문자열이 앞쪽에 위치합니다.

— 입출력 예

stringsnreturn
["sun", "bed", "car"]1["car", "bed", "sun"]
["abce", "abcd", "cdx"]2["abcd", "abce", "cdx"]

입출력 예 1

"sun", "bed", "car"의 1번째 인덱스 값은 각각 "u", "e", "a" 입니다. 이를 기준으로 strings를 정렬하면 ["car", "bed", "sun"] 입니다.

입출력 예 2

"abce"와 "abcd", "cdx"의 2번째 인덱스 값은 "c", "c", "x"입니다. 따라서 정렬 후에는 "cdx"가 가장 뒤에 위치합니다. "abce"와 "abcd"는 사전순으로 정렬하면 "abcd"가 우선하므로, 답은 ["abcd", "abce", "cdx"] 입니다.

— 문제 풀이

import java.util.*;

class Solution {
    class NewString implements Comparable<NewString>{
        String str;
        int n;
        public NewString(String str, int n){
            this.str = str;
            this.n = n;
        }
        @Override
        public int compareTo(NewString o){
            if(this.str.charAt(n)==o.str.charAt(o.n)){
                return this.str.compareTo(o.str);
            }else {
                return this.str.charAt(n) - o.str.charAt(o.n);
            }
        }
    }
    public String[] solution(String[] strings, int n) {
        ArrayList<NewString> list = new ArrayList<>();
        for(int i=0;i<strings.length;i++){
            list.add(new NewString(strings[i], n));
        }
        Collections.sort(list);
        String[] answer = new String[strings.length];
        for(int i=0;i<strings.length;i++){
            answer[i] = list.get(i).str;
        }
        return answer;
    }
}

프로그래머스 42748 K번째수

https://school.programmers.co.kr/learn/courses/30/lessons/42748

— 문제 설명

배열 array의 i번째 숫자부터 j번째 숫자까지 자르고 정렬했을 때, k번째에 있는 수를 구하려 합니다.

예를 들어 array가 [1, 5, 2, 6, 3, 7, 4], i = 2, j = 5, k = 3이라면

  1. array의 2번째부터 5번째까지 자르면 [5, 2, 6, 3]입니다.
  2. 1에서 나온 배열을 정렬하면 [2, 3, 5, 6]입니다.
  3. 2에서 나온 배열의 3번째 숫자는 5입니다.

배열 array, [i, j, k]를 원소로 가진 2차원 배열 commands가 매개변수로 주어질 때, commands의 모든 원소에 대해 앞서 설명한 연산을 적용했을 때 나온 결과를 배열에 담아 return 하도록 solution 함수를 작성해주세요.

— 제한 조건

  • array의 길이는 1 이상 100 이하입니다.
  • array의 각 원소는 1 이상 100 이하입니다.
  • commands의 길이는 1 이상 50 이하입니다.
  • commands의 각 원소는 길이가 3입니다.

— 입출력 예

arraycommandsreturn
[1, 5, 2, 6, 3, 7, 4][[2, 5, 3], [4, 4, 1], [1, 7, 3]][5, 6, 3]

입출력 예 1

[1, 5, 2, 6, 3, 7, 4]를 2번째부터 5번째까지 자른 후 정렬합니다. [2, 3, 5, 6]의 세 번째 숫자는 5입니다.

[1, 5, 2, 6, 3, 7, 4]를 4번째부터 4번째까지 자른 후 정렬합니다. [6]의 첫 번째 숫자는 6입니다.

[1, 5, 2, 6, 3, 7, 4]를 1번째부터 7번째까지 자릅니다. [1, 2, 3, 4, 5, 6, 7]의 세 번째 숫자는 3입니다.

— 문제 풀이

import java.util.*;

class Solution {
    public int[] solution(int[] array, int[][] commands) {
        int[] answer = new int[commands.length];
        for(int t=0;t<commands.length;t++){ // commands의 길이만큼 명령 반복
            int[] tmp = new int[commands[t][1]-commands[t][0]+1]; // ex 2~5 == 5-2+1
            for(int i=0;i<tmp.length;i++){
                tmp[i] = array[commands[t][0]-1+i];
            }
            Arrays.sort(tmp);
            answer[t] = tmp[commands[t][2]-1];
        }
        return answer;
    }
}

프로그래머스 68644 두 개 뽑아서 더하기

https://school.programmers.co.kr/learn/courses/30/lessons/68644

— 문제 설명

정수 배열 numbers가 주어집니다. numbers에서 서로 다른 인덱스에 있는 두 개의 수를 뽑아 더해서 만들 수 있는 모든 수를 배열에 오름차순으로 담아 return 하도록 solution 함수를 완성해주세요.

— 제한 조건

  • numbers의 길이는 2 이상 100 이하입니다.
    • numbers의 모든 수는 0 이상 100 이하입니다.

— 입출력 예

numbersresult
[2,1,3,4,1][2,3,4,5,6,7]
[5,0,2,7][2,5,7,9,12]

입출력 예 1

  • 2 = 1 + 1 입니다. (1이 numbers에 두 개 있습니다.)
  • 3 = 2 + 1 입니다.
  • 4 = 1 + 3 입니다.
  • 5 = 1 + 4 = 2 + 3 입니다.
  • 6 = 2 + 4 입니다.
  • 7 = 3 + 4 입니다.
  • 따라서 [2,3,4,5,6,7] 을 return 해야 합니다.

입출력 예 2

  • 2 = 0 + 2 입니다.
  • 5 = 5 + 0 입니다.
  • 7 = 0 + 7 = 5 + 2 입니다.
  • 9 = 2 + 7 입니다.
  • 12 = 5 + 7 입니다.
  • 따라서 [2,5,7,9,12] 를 return 해야 합니다.

— 문제 풀이

import java.util.*;

class Solution {
    public int[] solution(int[] numbers) {
        ArrayList<Integer> list = new ArrayList<>();
        for(int i=0;i<numbers.length-1;i++){
            for(int j=i+1;j<numbers.length;j++){
                if(!list.contains(numbers[i]+numbers[j]))list.add(numbers[i]+numbers[j]);
            }
        }
        Collections.sort(list);
        int[] answer = new int[list.size()];
        for(int i=0;i<answer.length;i++){
            answer[i] = list.get(i);
        }
        return answer;
    }
}

컨피그 서버 (Spring Cloud Config)

Spring Cloud Config란?

  • 분산 시스템 환경에서 중앙 집중식 구성 관리를 제공하는 프레임워크
  • 어플리케이션의 설정을 중앙에서 관리하고, 변경 사항을 실시간으로 반영할 수 있음
  • Git, 파일 시스템, JDBC 등 다양한 저장소 지원

Spring Cloud Config의 주요 기능

  • 중앙 집중식 구성 관리 : 모든 마이크로서비스의 설정을 중앙에서 관리
  • 환경별 구성 : 개발, 테스트, 운영 등 환경별로 구성을 분리하여 관리할 수 있음
  • 실시간 구성 변경 : 설정 변경 시 어플리케이션을 재시작하지 않고도 실시간으로 반영할 수 있음

Spring Cloud Config 서버 구성

  • 설정 파일을 저장하고 제공하는 역할
  • 의존성
    • 서버
      • org.springframework.cloud:spring-cloud-config-server
    • 클라이언트
      • org.springframework.cloud:spring-cloud-config
  • application.yml
    • 서버
      # github 예시
      server:
        port: 8888
      
      spring:
        cloud:
          config:
            server:
              git:
                uri: https://github.com/my-config-repo/config-repo
                clone-on-start: true
    • 클라이언트
      spring:
        application:
          name: my-config-client
        cloud:
          config:
            discovery:
              enabled: true
              service-id: config-server
           
      eureka:
        client:
          service-url:
            defaultZone: http://localhost:19090/eureka/
  • SpringBootApplication에 @EnableConfigServer 어노테이션 추가( Config 서버만!! )

환경별 구성 관리

  • 설정 파일
    • 환경별로 application-dev.yml, appication-prod.yml 등 환경별 설정 관리 가능
  • 프로파일
    • Spring Boot Application에서 프로파일을 사용하여 환경을 구분할 수 있음
      spring:
        profiles:
          active: dev

실시간 구성 변경

  • 실시간 구성 변경 반영 방법
    • Spring Cloud Bus
    • 수동 /actuator/refresh 엔드포인트 호출
    • Git 저장소 사용
    • Spring Boot DevTools
  • Spring Cloud Bus
    • 설정 변경 사항을 실시간으로 클라이언트 어플리케이션에 반영할 수 있음. 이를 위해서 메시징 시스템 (RabbitMQ 또는 Kafka 등)을 사용하여 변경 사항을 전파해야함
  • 수동 구성 갱신
    • /actuator/refresh 엔드포인트 사용
      • Spring Cloud Bus를 사용하지 않는 경우, 클라이언트 어플리케이션에서 수동으로 설정을 갱신할 수 있음. 이를 위해 Spring Actuator의 /actuator/refresh 엔드포인트를 사용할 수 있음
    • 설정 갱신 절차
      1. Config 서버에서 설정 파일 변경
      2. 클라이언트 어플리케이션의 /actuator/refresh 엔드포인트를 POST 요청으로 호출하여 변경된 설정을 반영
      • 이 방법은 간단하지만, 각 클라이언트 어플리케이션에서 수동으로 엔드포인트를 호출해야 함
  • Spring Boot DevTools 사용
    • Spring Boot DevTools는 개발 환경에서 파일 변경을 자동으로 감지하고 어플리케이션을 재시작할 수 있음.
  • Git 저장소 사용
    • Spring Cloud Config 서버가 Git 저장소에서 설정 파일을 읽어오도록 설정 가능. 설정 파일의 변경 사항을 쉽게 반영하고, 여러 서비스 간에 일관된 구성을 유지하는 데 유용함

Spring Cloud Config 실습

프로젝트 구성

  • Eureka Server
  • Product Server
  • Config Server (New)
  • 실행 순서
    • Eureka → Config → Product

Config Server 구현

  • 의존성
    • Spring Web
    • Lombok
    • Eureka Discovery Client
    • Config Server
    • Spring Boot Actuator
  • SpringBootApplication에 @EnableConfigServer 어노테이션 추가
  • application.yml
    server:
      port: 18080
    
    spring:
      profiles:
        active: native
      application:
        name: config-server
      cloud:
        config:
          server:
            native:
              search-locations: classpath:/config-repo
    
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:19090/eureka/
  • resources 디렉토리에 config-repo 디렉토리 추가
  • config-repo 디렉토리에 product-service.yml 작성
    server:
      port: 19093
    
    message: "product-service message"
  • config-repo 디렉토리에 product-service-local.yml 작성
    server:
      port: 19083
    
    message: "product-service-local message"
  • Product Project에 config 디펜던시 추가
    • org.springframework.cloud:spring-cloud-starter-config
  • Product Project application.yml 수정
    spring:
      application:
        name: product-service
      profiles:
        active: local
      config:
        import: "configserver:"
      cloud:
        config:
          discovery:
            enabled: true
            service-id: config-server
    server:
      port: 0
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:19090/eureka/
    
    message: "default message"
  • Product Controller 수정
    @RestController
    public class ProductController {
    
        @Value("${server.port}")
        private String serverPort;
    
        @Value("${message}")
        private String message;
    
        @GetMapping("/product")
        public String getProduct() {
    
            return "Product info From Port : " + serverPort + "and message : " + message;
        }
    }
    
  • 실행 후 product api 호출 결과

수동 갱신 실습

  • Product 프로젝트 applicaion.yml 수정
    # 추가
    management:
      endpoints:
        web:
          exposure:
            include: refresh
  • Product Controller에 @RefreshScope 어노테이션 추가
  • Config 프로젝트의 product-servier-local.yml의 message 수정 후 프로젝트 재실행
  • Product Server의 /actuator/refresh POST 요청 결과

  • 업데이트 된 후 product api 요청 결과 ( Product 서버 재실행 X )


분산 추적 (Spring Cloud Sleuth) 및 로깅 (Zipkin)

분산 추적이란?

  • 분산 시스템에서 서비스 간의 요청 흐름을 추적하고 모니터링하는 방법
  • 각 서비스의 호출 관계와 성능을 시각화하여 문제를 진단하고 해결할 수 있도록 도움
  • 주요 개념 : Trace, Span, Context

분산 추적이 필요한 이유

  • MSA에서는 여러 서비스가 협력하여 하나의 요청을 처리함
  • 서비스 간의 복잡한 호출 관계로 인해 문제 발생 시 원인을 파악하기 어려울 수 있음
  • 분산 추적을 통해 각 서비스의 호출 흐름을 명확히 파악하고, 성능 병목이나 오류를 빠르게 진단할 수 있음

Micrometer란?

  • Spring 기반 App에서 메트릭을 수집하고 모니터링하기 위한 라이브러리
  • 각 서비스의 성능 지표를 수집하고, Prometheus, Grafana 등과 연동하여 시각화할 수 있음
  • 분산 추적을 위한 기능도 제공하여 서비스 간의 호출 흐름을 추적할 수 있음

Micrometer의 주요 특징

  • 다양한 메트릭 수집 : App의 다양한 성능 지표를 수집할 수 있음
  • 유연한 연동 : Prometheus, Grafana 등 다양한 모니터링 도구와 연동할 수 있음
  • 추적 기능 : 서비스 간의 호출 흐름을 추적하여 성능 병목을 진단할 수 있음

Zipkin이란?

  • 트레이스 데이터를 수집하고 시각화하는 분산 추적 시스템
  • 각 서비스의 트레이스와 스팬 데이터를 저장하고, 이를 통해 호출 흐름을 시각화

Zipkin 주요 특징

  • 데이터 수집 및 저장 : 각 서비스에서 전송된 트레이스 데이터를 수집하고 저장
  • 시각화 : 트레이스 데이터를 시각화하여 서비스 간의 호출 관계를 명확히 파악할 수 있음
  • 검색 및 필터링 : 특정 트레이스나 스팬을 검색하고 필터링하여 문제를 진단할 수 있음

Zipkin 사용

  • Docker를 사용하여 서버 실행
    docker run -d -p 9411:9411 openzipkin/zipkin
  • 대시보드 사용
    • 대시보드에 접속해서 트레이스 데이터를 시각화
    • URL : http://localhost:9411
    • 트레이스 검색 및 분석

서비스 호출 흐름 추적

  • 에제 서비스 간의 호출 흐름을 추적하고, Zipkin 대시보드에서 시각화
  • 각 서비스 호출 시 트레이스와 스팬이 생성되고, Zipkin 서버로 전송됨

성능 병목 진단

  • Zipkin 대시보드를 통해 성능 병목이 발생하는 부분을 식별
  • 각 스팬의 소요 시간과 호출 관계를 분석하여 성능 문제를 진단하고 해결함

분산 추적 및 로깅 실습

프로젝트 구성 ( 이전 프로젝트 활용 )

  • Eureka Server
  • Order
  • Product
  • Gateway
  • Auth
  • 의존성 추가
    implementation 'io.github.openfeign:feign-micrometer'
    implementation 'io.micrometer:micrometer-tracing-bridge-brave'
    implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
  • application.yml 수정
    management:
      zipkin:
        tracing:
          endpoint: "http://localhost:9411/api/v2/spans"
      tracing:
        sampling:
          probability: 1.0
  • 실행 후 API 호출
  • Zipkin 대시보드 접속 후 확인
  • Run Query 버튼 클릭 결과

  • Show 버튼 클릭 결과

  • Dependencies 확인


이벤트 드리븐 아키텍처와 스트림 처리 (Spring Cloud Stream)

이벤트 드리븐 아키텍처란?

  • 이벤트 드리븐 아키텍처는 시스템에서 발생하는 이벤트(상태 변화나 행동)를 기반으로 동작하는 소프트웨어 설계 스타일
  • 이벤트는 비동기적으로 처리되며, 서비스 간의 느슨한 결합을 통해 독립적으로 동작할 수 있게 함

이벤트 드리븐 아키텍처의 주요 개념

  • 이벤트 : 시스템 내에서 발생하는 상태 변화나 행동을 나타내는 메시지
  • 이벤트 소스 : 이벤트를 생성하여 이벤트 버스에 전달하는 역할을 함
  • 이벤트 핸들러 : 이벤트를 수신하여 처리하는 역할을 함
  • 이벤트 버스 : 이벤트 소스와 이벤트 핸들러 간의 메시지 전달을 중개

이벤트 드리븐 아키텍처의 장점

  • 느슨한 결합
    • 서비스 간의 강한 종속성을 제거하여 독립적인 개발과 배포가 가능
    • 이벤트 기반 통신을 통해 서비스 간의 결합도 낮춤
  • 확장성
    • 수평 확장이 용이하여 대규모 시스템에서 유용
    • 이벤트 프로듀서와 컨슈머를 독립적으로 확장 가능
  • 비동기처리
    • 이벤트를 비동기적으로 처리하여 시스템의 응답성 향상
    • 요청과 응답을 비동기적으로 처리하여 성능 최적화

이벤트 드리븐 아키텍처의 단점

  • 복잡성 증가
    • 이벤트 기반 통신으로 인해 시스템의 복잡성 증가 가능
    • 이벤트 흐름과 상태 관리를 체계적으로 설계 필요
  • 장애 전파
    • 이벤트 실패 시 다른 서비스로 장애가 전파될 수 있음
    • 이벤트 재처리 및 장애 복구 메커니즘 구현 필요

Spring Cloud Stream이란?

  • 이벤트 드리븐 마이크로서비스를 구축하기 위한 프레임워크
  • Kafka, RabbitMQ 등의 메시지 브로커와 통합하여 이벤트 스트리밍을 처리함
  • 프로듀서와 컨슈머 간의 통신을 추상화하여 간편하게 이벤트 기반 App을 개발할 수 있음

Spring Cloud Stream의 주요 특징

  • 바인더 추상화 : 메시지 브로커와의 통합을 위한 추상화 레이어를 제공함
  • 프로듀서/컨슈머 모델 : 이벤트를 생성하고 처리하는 프로듀서와 컨슈머 모델을 지원
  • 유연한 설정 : 다양한 설정 옵션을 통해 손쉽게 커스터마이징할 수 있음

MSA 실습 프로젝트

querydsl 적용

  • build.gradle에 아래 내용 추가 (
    ext {
    	set('querydslVersion', "5.0.0")  // QueryDSL 버전 명시적으로 설정
    }
    
    dependencies {
    	implementation "com.querydsl:querydsl-jpa:${querydslVersion}:jakarta"
    	annotationProcessor "com.querydsl:querydsl-apt:${querydslVersion}:jakarta"
    	annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    	annotationProcessor "jakarta.persistence:jakarta.persistence-api"
    }
    
    def querydslSrcDir = 'src/main/generated'
    clean {
    	delete file(querydslSrcDir)
    }
    tasks.named('test') {	useJUnitPlatform()}
    
  • Config 추가
    • 예시
      @Component
      public class OrderApplicationQueryDslConfig {
          @Bean
          JPAQueryFactory jpaQueryFactory(EntityManager em){
              return new JPAQueryFactory(em);
          }
      
      }
  • CustomRepository interface 생성
  • CustomRepositoryImpl class (구현 클래스) 생성
    • JPAQueryFactory를 사용해서 서비스 작성
  • Repository에서 해당 CustomRepository interface 상속
profile
기록을 남겨보자

0개의 댓글