스파르타 Java 단기 심화 과정


코드카타


프로그래머스 140108 문자열 나누기

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

— 문제 설명

문자열 s가 입력되었을 때 다음 규칙을 따라서 이 문자열을 여러 문자열로 분해하려고 합니다.

  • 먼저 첫 글자를 읽습니다. 이 글자를 x라고 합시다.
  • 이제 이 문자열을 왼쪽에서 오른쪽으로 읽어나가면서, x와 x가 아닌 다른 글자들이 나온 횟수를 각각 셉니다. 처음으로 두 횟수가 같아지는 순간 멈추고, 지금까지 읽은 문자열을 분리합니다.
  • s에서 분리한 문자열을 빼고 남은 부분에 대해서 이 과정을 반복합니다. 남은 부분이 없다면 종료합니다.
  • 만약 두 횟수가 다른 상태에서 더 이상 읽을 글자가 없다면, 역시 지금까지 읽은 문자열을 분리하고, 종료합니다.

문자열 s가 매개변수로 주어질 때, 위 과정과 같이 문자열들로 분해하고, 분해한 문자열의 개수를 return 하는 함수 solution을 완성하세요.

— 제한 조건

  • 1 ≤ s의 길이 ≤ 10,000
  • s는 영어 소문자로만 이루어져 있습니다.

— 입출력 예

sresult
"banana"3
"abracadabra"6
"aaabbaccccabba"3

입출력 예 #1

s="banana"인 경우 ba - na - na와 같이 분해됩니다.

입출력 예 #2

s="abracadabra"인 경우 ab - ra - ca - da - br - a와 같이 분해됩니다.

입출력 예 #3

s="aaabbaccccabba"인 경우 aaabbacc - ccab - ba와 같이 분해됩니다.

— 문제 풀이

class Solution {
    public int solution(String s) {
        int xCnt = 0;
        int notXCnt = 0;
        int answer = 0;
        char x = '0';
        for(int i=0;i<s.length();i++){
            if(x=='0'){
                x = s.charAt(i);
                xCnt++;
                continue;
            }
            
            if(x==s.charAt(i)){
                xCnt++;
            }else {
                notXCnt++;
            }
            
            if(xCnt==notXCnt){
                xCnt = 0;
                notXCnt = 0;
                x = '0';
                answer++;
            }
        }
        
        if(x != '0') answer++;
        
        return answer;
    }
}

프로그래머스 160586 대충 만든 자판

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

— 문제 설명

휴대폰의 자판은 컴퓨터 키보드 자판과는 다르게 하나의 키에 여러 개의 문자가 할당될 수 있습니다. 키 하나에 여러 문자가 할당된 경우, 동일한 키를 연속해서 빠르게 누르면 할당된 순서대로 문자가 바뀝니다.

예를 들어, 1번 키에 "A", "B", "C" 순서대로 문자가 할당되어 있다면 1번 키를 한 번 누르면 "A", 두 번 누르면 "B", 세 번 누르면 "C"가 되는 식입니다.

같은 규칙을 적용해 아무렇게나 만든 휴대폰 자판이 있습니다. 이 휴대폰 자판은 키의 개수가 1개부터 최대 100개까지 있을 수 있으며, 특정 키를 눌렀을 때 입력되는 문자들도 무작위로 배열되어 있습니다. 또, 같은 문자가 자판 전체에 여러 번 할당된 경우도 있고, 키 하나에 같은 문자가 여러 번 할당된 경우도 있습니다. 심지어 아예 할당되지 않은 경우도 있습니다. 따라서 몇몇 문자열은 작성할 수 없을 수도 있습니다.

이 휴대폰 자판을 이용해 특정 문자열을 작성할 때, 키를 최소 몇 번 눌러야 그 문자열을 작성할 수 있는지 알아보고자 합니다.

1번 키부터 차례대로 할당된 문자들이 순서대로 담긴 문자열배열 keymap과 입력하려는 문자열들이 담긴 문자열 배열 targets가 주어질 때, 각 문자열을 작성하기 위해 키를 최소 몇 번씩 눌러야 하는지 순서대로 배열에 담아 return 하는 solution 함수를 완성해 주세요.

단, 목표 문자열을 작성할 수 없을 때는 -1을 저장합니다.

— 제한 조건

  • 1 ≤ keymap의 길이 ≤ 100
    • 1 ≤ keymap의 원소의 길이 ≤ 100
    • keymap[i]는 i + 1번 키를 눌렀을 때 순서대로 바뀌는 문자를 의미합니다.
      • 예를 들어 keymap[0] = "ABACD" 인 경우 1번 키를 한 번 누르면 A, 두 번 누르면 B, 세 번 누르면 A 가 됩니다.
    • keymap의 원소의 길이는 서로 다를 수 있습니다.
    • keymap의 원소는 알파벳 대문자로만 이루어져 있습니다.
  • 1 ≤ targets의 길이 ≤ 100
    • 1 ≤ targets의 원소의 길이 ≤ 100
    • targets의 원소는 알파벳 대문자로만 이루어져 있습니다.

— 입출력 예

keymaptargetsresult
["ABACD", "BCEFD"]["ABCD","AABB"][9, 4]
["AA"]["B"][-1]
["AGZ", "BSSS"]["ASA","BGZ"][4, 6]

입출력 예 #1

  • "ABCD"의 경우,
  • 1번 키 한 번 → A
  • 2번 키 한 번 → B
  • 2번 키 두 번 → C
  • 1번 키 다섯 번 → D
  • 따라서 총합인 9를 첫 번째 인덱스에 저장합니다.
  • "AABB"의 경우,
  • 1번 키 한 번 → A
  • 1번 키 한 번 → A
  • 2번 키 한 번 → B
  • 2번 키 한 번 → B
  • 따라서 총합인 4를 두 번째 인덱스에 저장합니다.
  • 결과적으로 [9,4]를 return 합니다.

입출력 예 #2

  • "B"의 경우, 'B'가 어디에도 존재하지 않기 때문에 -1을 첫 번째 인덱스에 저장합니다.
  • 결과적으로 [-1]을 return 합니다.

입출력 예 #3

  • "ASA"의 경우,
  • 1번 키 한 번 → A
  • 2번 키 두 번 → S
  • 1번 키 한 번 → A
  • 따라서 총합인 4를 첫 번째 인덱스에 저장합니다.
  • "BGZ"의 경우,
  • 2번 키 한 번 → B
  • 1번 키 두 번 → G
  • 1번 키 세 번 → Z
  • 따라서 총합인 6을 두 번째 인덱스에 저장합니다.
  • 결과적으로 [4, 6]을 return 합니다.

— 문제 풀이

class Solution {
    public int[] solution(String[] keymap, String[] targets) {
        int[] answer = new int[targets.length];
        
        for(int i=0;i<targets.length;i++){
            String target = targets[i];
            int cnt = 0; // 키 누른 횟수 카운트
            for(int j=0;j<target.length();j++){
                String t = target.charAt(j)+"";
                int min = 0; // 키를 누르는 최소 횟수
                for(int k=0;k<keymap.length;k++){
                    if(!keymap[k].contains(t))continue;
                    if(min == 0) min = keymap[k].indexOf(t)+1;
                    else min = Math.min(min, keymap[k].indexOf(t)+1);
                }
                if(min == 0) {
                    cnt = -1;
                    break;
                }
                cnt += min;
            }
            answer[i] = cnt;
        }
        
        return answer;
    }
}

모니터링과 보안의 중요성

모니터링

  • App 개발 후, 안정적이고 효율적인 운영을 위해 필수
  • 시스템의 성능, 가용성, 안정성을 지속적으로 감시하고, 잠재적인 문제를 신속하게 발견하여 대응할 수 있게 해줌
  • App의 성능을 최적화하고, 자원의 효율적인 사용을 보장하는 데 도움이 됨
  • 성능 병목 현상이나 리소스 낭비를 식별하고 해결하여 시스템의 전체적인 효율성을 높일 수 있음
  • 로그 분석을 통해 시스템의 이상 징후를 조기에 발견하고, 문제의 원인을 정확히 파악할 수 있음

보안

  • 해커들이 다양한 기법을 사용하여 시스템의 취약점을 찾고, 이를 악용하려 하기 때문에 보안은 매우 중요한 요소
  • 강력한 인증 및 권한 부여 메커니즘 구현해야 함.
  • 정기적인 보안 점검 및 취약성 테스트는 시스템의 보안 상태를 평가하고, 새로운 위협에 대응하기 위해 필요함

모니터링 시스템

모니터링 이란?

  • 시스템의 성능, 안정성 및 가용성을 실시간으로 관찰하고 측정하는 과정을 의미
  • 모니터링 도구를 사용하여 시스템의 상태를 추적하고, 문제를 감지하고, 성능을 최적화하며, 장애 발생 시 빠르게 대응할 수 있음

모니터링의 장점

  • 문제 예방 : 모니터링을 통해 잠재적인 문제를 사전에 발견하고 해결할 수 있음
  • 빠른 대응 : 문제가 발생했을 때 신속하게 대응할 수 있어 시스템 다운타임을 최소화함
  • 성능 최적화 : App의 성능 병목 지점을 찾아 개선할 수 있음
  • 사용자 경험 향상 : 안정적이고 빠른 서비스를 제공하여 사용자 만족도를 높임
  • 비즈니스 연속성 : 시스템 가용성을 보장하여 비즈니스 운영의 연속성을 유지할 수 있음

모니터링의 범위

  • 서버 모니터링 : CPU, Memory, Disk, Network Traffic 등 서버 자원 사용을 모니터링
  • 어플리케이션 모니터링 : App의 상태, 성능, 로그 등을 모니터링
  • 네트워크 모니터링 : 네트워크 트래픽, 대역폭 사용량, 네트워크 지연 등을 모니터링
  • DB 모니터링 : 쿼리 성능, 연결 수, DB 사용량 등을 모니터링
  • 보안 모니터링 : 보안 이벤트, 침입 시도, 취약점 등을 모니터링

Spring Boot Actuator

Spring Boot Actuator란?

  • Spring Boot App의 상태와 성능을 모니터링하고 관리할 수 있도록 다양한 엔드포인트를 제공하는 기능
  • 헬스 체크, 메트릭스, 환경 정보, 로그 정보 등 여러가지 중요한 정보를 쉽게 확인할 수 있도록 도와줌

Actuator 의존성

  • org.springframework.boot:spring-boot-starter-actuator

Actuator 엔드포인트

  • /actuator 경로 하위에 다양한 정보를 제공하는 엔드포인트들이 존재함
    • /actuator/health : App의 상태를 확인
    • /actuator/metrics : App의 메트릭 정보 제공
    • /actuator/loggers : 로깅 설정을 조회하고 변경 가능
    • /actuator/env : 환경 변수와 설정 정보를 확인
    • /actuator/beans : Application Context에 있는 bean 목록을 확인
    • /actuator/threaddump : 스레드 덤프 확인
    • /actuator/httptrace : 최근 HTTP 요청 및 응답을 추적

Actuator 엔드포인트 설정

  • 기본적으로 모든 엔드포인트가 활성화되어 있지 않을 수 있음.
  • application.properties or application.yml 파일을 사용해서 필요한 엔드포인트를 활성화하거나 비활성화할 수 있음
  • 설정 옵션
    • never : 헬스 체크 상세 정보를 절대 표시하지 않음
    • always : 모든 사용자에게 헬스 체크 상세 정보를 항상 표시
    • when_authorized : 인증된 사용자에게만 헬스 체크 상세 정보를 표시
  • 설정
    management:
    	# 모든 엔드포인트 노출 설정
    	endpoints:
    		web:
    			exposure:
    				include: '*'
    	# 헬스 체크 엔드포인트 상세 정보 표시 설정
    	endpoint:
    		health:
    			show-details: always # 이 설정은 /actuator/health 엔드포인트

Actuator 사용 시 주의 사항 및 권장 사항

  • 보안
    • 모든 엔드포인트를 노출하는 설정은 개발 및 테스트 환경에서는 유용할 수 있지만, 운영 환경에서는 보안 위험을 증가시킬 수 있음. 필요한 엔드포인트만 노출하는 것을 권장
    • 헬스 체크 엔드포인트에서 상세 정보를 항상 노출하는 설정 역시 운영 환경에서는 민감한 정보를 포함할 수 있으므로, show-details설정을 when_authorized 옵션을 사용하는 것이 좋음
  • 엔드포인트 보호
    • actuator 접근 포트만 다른 포트로 설정하여 보호할 수 있음
      server:
      	port: 8080
      # Actuator 엔드포인트를 서버 포트와 별개로 설정 가능
      management:
      	server:
      		port: 19090
    • Spring Security를 사용하여 민감한 엔드포인트에 접근 제어를 설정하는 것이 좋음
      • 예시 ( /actuator 경로에 대한 보안 설정 )
        @Configuration
        @EnableWebSecurity
        public class SecurityConfig extends WebSecurityConfigurerAdapter {
        		@Override
        		protected void configure(HttpSecurity http) throws Exception {
        				http.authorizeRequests()
        						.antMatchers("/actuator/**").authenticated()
        						.and()
        						.httpBasic();
        		}
        }

Actuator 사용 실습

  • 의존성
    • Spring Web
    • Spring Boot Actuator
  • application.yml
    spring:
      application:
        name: sample
    server:
      port: 8080
    
    management:
      endpoints:
        web:
          exposure:
            include: '*'
      endpoint:
        health:
          show-details: always
  • localhost:8080/actuator 접속 후 확인

Prometheus

Prometheus 란?

  • 오픈소스 시스템 모니터링 및 경고 도구
  • SoundCloud에서 시작되어 현재는 CNCF ( Cloud Native Computing Foundation ) 에서 호스팅하고 있음
  • 시계열 DB를 사용하여 메트릭 데이터를 수집하고, 쿼리 및 시각화를 통해 시스템 상태를 모니터링하고 경고를 설정할 수 있음

Prometheus의 구성 요소

  • Prometheus 서버
    • 메트릭 데이터를 수집하고 저장하는 핵심 컴포넌트. 각 타겟으로부터 데이터를 주기적으로 스크랩하여 시계열 DB에 저장함
    • 시계열 DB(Time Series Database, TSDB)는 시간에 따라 변화하는 데이터를 효율적으로 저장하고 조회할 수 있도록 최적화된 DB
  • Exporters
    • Prometheus는 기본적으로 Application에서 메트릭 데이터를 수집
    • Exporter는 특정 어플리케이션이나 시스템의 메트릭 데이터를 Prometheus가 이해할 수 있는 형식으로 변환해주는 도구
    • ex) Node Exporter (서버의 시스템 메트릭 수집), PostgreSQL Exporter (PostgreSQL 메트릭 수집), Spring Boot의 micrometer-registry-prometheus 디펜던시 등
  • Pushgateway
    • 짧은 수명의 작업(job)에서 메트릭을 수집하여 Prometheus 서버에 푸시할 수 있음
    • 일반적으로 지속적으로 실행되지 않는 작업에서 사용됨.
      • ex) 배치 작업, 스크립트 실행, 크론잡 등
  • Alertmanager
    • 서버에서 발생하는 경고(Alert)를 처리하고, 이메일, PagerDuty, Slack 등 다양한 방법으로 알림을 보낼 수 있음
  • Grafana
    • Prometheus 데이터를 시각화하기 위해 자주 사용되는 시각화 도구
    • Prometheus에서 수집한 메트릭 데이터를 대시보드 형태로 시각화할 수 있음

Prometheus 사용 실습

  • Actuator 실습 프로젝트에서 아래의 의존성 추가
      runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
  • application.yml
    spring:
      application:
        name: sample
    server:
      port: 8080
    
    management:
      endpoints:
        web:
          exposure:
            include: '*'
      endpoint:
        health:
          show-details: always
        # 프로메테우스 활성화
        prometheus:
          enabled: true
  • 위의 설정을 통해 /actuator/prometheus 경로에 Prometheus가 사용할 수 있는 메트릭 정보가 활성화 됨
  • prometheus.yml
    global:
      scrape_interval: 15s
    
    scrape_configs:
      - job_name: "spring-boot"
        metrics_path: "/actuator/prometheus"
        static_configs:
          - targets: ["host.docker.internal:8080"]
  • prometheus 실행
    docker run -d --name=prometheus -p 9090:9090 -v /path/to/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus
  • Status - Targets에서 타겟 확인

Grafana

Grafana 란?

  • 오픈소스 데이터 시각화 및 모니터링 도구
  • 다양한 데이터 소스를 지원하여 데이터를 시각화하고 분석할 수 있도록 도움
  • 대시보드를 생성하고, 데이터를 그래프나 차트 형태로 표현하며, 알림 기능을 제공하여 모니터링 강화 가능

Grafana의 장점

  • 대시보드 생성
    • 데이터를 시각화할 수 있는 대시보드 생성 가능
  • 다양한 데이터 소스 지원
    • Prometheus, InfluxDB, Graphite, Elasticsearch, MySQL, PostgreSQL 등 다양한 데이터 소스 지원
  • 알림 기능
    • 조건을 설정하고 조건이 충족되면 이메일, Slack, PagerDuty 등 다양한 채널을 통해 알림을 보낼 수 있음
  • 플러그인 지원
    • 플러그인 아키텍처를 지원하여 다양한 플러그인을 통해 기능 확장 가능
  • 사용자 관리
    • 사용자를 관리하고, 대시보드와 데이터 소스에 대한 접근 권한 설정 가능. 협업을 강화하고 데이터 보안 유지 가능

Grafana 사용 실습

  • 그라파나 설치 및 실행
    docker run -d --name=grafana -p 3000:3000 grafana/grafana
  • localhost:3000 에서 admin/admin 으로 접속
  • datasource에서 Prometheus 추가
  • Import Dashboard - 19004 Load ( Spring Boot 3 DashBoard )
  • 대시보드 확인

Grafana - Slack Alert 실습

  • App 생성
    • Slack 워크 스페이스 생성
    • https://api.slack.com/apps 에 접속해서 Create an App
    • From Scratch에서 App 이름 입력 후 생성한 워크스페이스 선택
    • 생성 후 OAuth & Permissions 탭 이동
    • Scopes - Bot Token Scopes에 “chat:write” 추가
    • OAuth Tokens에서 Install to {Workspace Name} 실행
    • Incoming Webhooks 탭 이동
    • Activate Incoming Webhooks 활성화 후, 하단 Add New Webhook to Workspace 클릭
      • Alert를 게시할 채널 선택
    • Webhook URL Copy 후 생성한 App을 Alert를 게시할 채널에 추가
  • Alert 설정
    • Grafana에서 Alerting - Contact points 접속 후 Add contact point 클릭
    • Name 입력 후 Integration Slack 선택, Webhook URL에 Copy한 URL Paste 후 Save
      • test시 채널에 메시지 오는 것 확인 가능
    • Alerting - Notification policies 에서 default policy의 edit 클릭
    • default contact point를 생성한 contact point로 설정
    • Alerting - Alert rules 이동 후 New alert rule 클릭
    • Name 입력 후 Define query and alert condition에서 Metric에서 up 선택 후 Label filter에서 Job = spring-boot 선택
      • Expressions에서 Threshold A is below 1로 설정
    • Set evaluation behavior 섹션에서 folder와 evaluation group 생성
      • evaluation interval과 pending period은 1m으로 설정
    • Configure labels and notifications 섹션에서 contact point를 이전에 설정한 point로 설정
    • 우측 상단 Save rule and exit 클릭
  • 확인
    • Prometheus 실습 때 실행 중이던 Spring Boot Test App을 중지
    • Alert Firing 상태 확인 및 Firing Alert 날아오는 것 확인
    • Test App 실행 후 Resolved Alert 오는 것 확인

Application Log Monitoring

Loki

  • Grafana Labs에서 개발한 로그 집계 시스템, Prometheus의 메트릭 수집 방식과 유사하게 로그 데이터를 수집하고 쿼리할 수 있도록 설계되어있음
  • 로그 데이터를 저장하고, 이를 Grafana를 통해 시각화하는 데 사용됨
  • 주요 특징 중 하나는 라벨 기반의 메타데이터를 사용하여 로그를 효율적으로 검색할 수 있다는 점

loki-logback-appender

  • Logback을 사용하는 Java Application에서 로그를 Loki로 직접 전송하기 위한 라이브러리
  • 별도의 Promtail 설정 없이 로그를 Loki로 전송할 수 있게 해줌

Loki 사용 실습

  • build.gradle
    dependencies {
    	implementation 'com.github.loki4j:loki-logback-appender:1.5.1' //추가
    
    	implementation 'org.springframework.boot:spring-boot-starter-actuator'
    	implementation 'org.springframework.boot:spring-boot-starter-web'
    	runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
    	testImplementation 'org.springframework.boot:spring-boot-starter-test'
    	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    }
  • SampleController
    @RestController
    public class SampleController {
    
        private static final Logger logger = LoggerFactory.getLogger(SampleController.class);
    
        @GetMapping("/")
        public String hello(HttpServletResponse response) throws IOException {
            logger.info("Attempted access to / endpoint resulted in 403 Forbidden");
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
            return null;
        }
    }
  • resources/logback.xml 파일 생성
    <configuration>
        <appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
            <http>
                <url>http://localhost:3100/loki/api/v1/push</url>
            </http>
            <format>
                <label>
                    <pattern>app=my-app,host=${HOSTNAME}</pattern>
                </label>
                <message class="com.github.loki4j.logback.JsonLayout" />
            </format>
        </appender>
    
        <root level="DEBUG">
        <appender-ref ref="LOKI" />
        </root>
    </configuration>
  • Loki 디렉토리 생성 후 하위에 loki-config.yml 생성
    • https://grafana.com/docs/loki/latest/setup/install/docker/ 문서 참고

      auth_enabled: false
      
      server:
        http_listen_port: 3100
        grpc_listen_port: 9096
      
      common:
        instance_addr: 127.0.0.1
        path_prefix: /tmp/loki
        storage:
          filesystem:
            chunks_directory: /tmp/loki/chunks
            rules_directory: /tmp/loki/rules
        replication_factor: 1
        ring:
          kvstore:
            store: inmemory
      
      query_range:
        results_cache:
          cache:
            embedded_cache:
              enabled: true
              max_size_mb: 100
      
      schema_config:
        configs:
          - from: 2020-10-24
            store: tsdb
            object_store: filesystem
            schema: v13
            index:
              prefix: index_
              period: 24h
      
      ruler:
        alertmanager_url: http://localhost:9093
      
      # By default, Loki will send anonymous, but uniquely-identifiable usage and configuration
      # analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/
      #
      # Statistics help us better understand how Loki is used, and they show us performance
      # levels for most users. This helps us prioritize features and documentation.
      # For more information on what's sent, look at
      # https://github.com/grafana/loki/blob/main/pkg/analytics/stats.go
      # Refer to the buildReport method to see what goes into a report.
      #
      # If you would like to disable reporting, uncomment the following lines:
      #analytics:
      #  reporting_enabled: false
  • 로키 컨테이너 실행
    docker run --name loki -d -v ${loki-config.yml 이 저장된 폴더}:/mnt/config -p 3100:3100 grafana/loki:3.0.0 -config.file=/mnt/config/loki-config.yml
  • Grafana Datasource에 Loki 추가
  • Grafana - Explore 탭에서 Loki 탭으로 전환
  • Label filters
    • app = my-app
  • Line contains INFO
    • Operations 클릭 후 Line contains SampleController
  • 우측 상단 Run query 클릭
  • 결과
profile
기록을 남겨보자

0개의 댓글