Thread의 isAlive()
- 개발자가 커스텀스레드 생성하면 반드시 run()을 오버라이드해야한다.
- run() 메서드 안에서 실행 중이면 true를 반환한다. run() 메서드가 끝나거나 실행 전이면 false를 반환한다.
Thread API 전체적인 흐름
- 회사에서 스레드를 컨트롤할 수 있는 API를 만들어서 이 내용을 되짚어보려고 한다.
- Listener 엔티티
- 카프카와 관련된 데이터(server IP 주소, port 번호, topic name, groupId 등등)가 들어가는 객체이다.
- 카프카 서버로부터 데이터를 받는 서버이면서 엔티티이기 때문에 Listener라는 이름이 붙은 걸로 예상
- Kafka -> Listner server -> Executor server 이렇게 데이터를 전송한다.
- ListenerHistory 테이블에는 Listener와 관련한 이력을 저장한다.
- 따라서, 다대일 연관 관계 매핑이 필요하다.
- successYn: Executor 서버로 보낸 OkHttp API 요청에 대해서 정상적인 HttpStatus 응답을 받았는지 여부
- 에러메시지는 위에서 보낸 OkHttp API 요청에 대해서 실패했을 경우 받는 에러메시지를 뜻한다.
- (어떤 회원에 대한 로그인 이력을 저장하듯 리스너 이력을 저장하는 엔티티)
- ThreadService
- ThreadService는 내가 만든 커스텀 스레드인 MyThread에 관련한 메서드가 들어있다.
- addThread(ListenerId): 테이블에 존재하는 리스너에 대해서 스레드를 만들어 실행한다.
- terminateThread(ListenerId): 실행 중인 스레드를 종료시킨다.
- Map<String, MyThread>형태의 멤버변수 listenerThreadMap을 선언한다.
- (map은 listenerId를 가지고 해당 스레드 정보를 찾기 위해 만든 필드이다. 뒷 부분에서 url 뒤 편에 {listenerId}를 넣어서 해당 스레드를 종료하거나 실행시키는 API를 만들기 위해 꼭 필요하다.)
- EventListener 클래스
- 인터페이스 ApplicationListener<ApplicationStartedEvent>를 implements 하면 onApplicationEvent()를 오버라이드한다.
- 애플리케이션이 뜨자마자 항상 어떤 메서드(@Override onApplicationEvent())를 실행시킨다.
- 메서드 안 에서 몇 가지 조건을 만족하는 리스너를 조회한다. 조건을 만족하는 인스턴스를 모아서 availableListenerList에 담는다.
- 리스트를 조회해서 리스너를 스레드에 담아서 하나씩 스레드를 실행시킨다. 그리고 ThreadService 안의 listenerThreadMap<listenerId, MyThread>에 해당 (리스너와 리스너를 매개변수로 받아서 실행 중인) 스레드 정보를 함께 담아준다.
- 그러면 EventListener 클래스에 ThreadService 를 이용해서 listenerThreadMap에다가 스레드 정보, 리스너 id를 담아주도록 한다.
- onApplicationEvent(): 애플리케이션이 구동하자마자 반드시 실행되는 메서드이다.
- EventListener에 ThreadService라는 변수를 선언해서 Map<> 형태로 스레드 정보를 저장한다.
- 그런데 ThreadController에서 EventListener를 직접 호출하지는 않지만 Service를 변수로 선언하면 이전에 onApplicationEvent()를 실행하면서 저장해준 Map 정보를 읽어올 수 있다.
- 아마도 스프링 컨테이너에 빈으로 등록할 때 싱글톤이 기본값이라서 어느 클래스에서 Service 클래스를 호출해도 같은 Map 정보를 불러올 수 있다. (싱글톤은 static과 성질이 유사하다.)
- Listener를 필드로 가지는 MyThread<T extends Thread> 클래스를 만든다.
- MyThread: Thread를 상속하는 제네릭 클래스를 만든다 .
- 기본적으로 run()을 반드시 오버라이드해야한다.
- run() 안에는 KafkaConsumer에 들어있는 메시지를 while문으로 poll()하면서 Kafka에 저장된 메시지를 consume한다. (즉, 어떤 스레드를 인위적으로 멈추기 전까지는 애플리케이션이 계속 돌아가면서 멀티스레드가 연속으로 실행된다.)
- MyThread의 flag 변수는 해당 스레드를 계속 실행할지 말지 여부를 나타낸다. false로 바뀌는 경우 스레드가 정지되고 run() 에서 빠져간다.
- run() 안에 있는 while문은 flag가 true인 경우만 계속 돌아간다.
- === Apache Kafka ===
- KafkaConsumer를 이용해서 Map 형태의 config를 담아준다. (회사에서 Kafka를 띄운 서버 주소, topic name 등등 카프카 관련 설정 정보)
- 무한 반복문을 돌려서 메시지 큐에 담긴 모든 메시지를 poll() 해준다.
- === MyThread ===
- MyThread 안에는 runThreadStop() 메서드가 있다.
- runThreadStop()는 리스너 id 일치(매개변수와 실제 실행중인 스레드 일치 여부) 를 확인한 후, 해당 스레드의 flag를 false로 만든다. runThreadStop()가 실행되자마자 while문 멈추도록 한다. 실행 중인 스레드에 대한 정지 요청을 보낸다. (스레드의 특성상 요청 보내자마자 바로 정지되는 게 아니다. 따라서, 정지 요청의 시작 여부라고 볼 수 있음)
- === ThreadService ===
- ThreadService에서 스레드를 정지시키려면(terminateThread() 메서드) MyThread에 선언한 runThreadStop()를 가장 먼저 호출한다.
- while문 안에서는 해당 클래스의 Listener가 몇 가지 조건을 만족할 경우, ListenerHistory 안에 해당 리스너를 저장한다.
- (OkHttp: 반복문 마지막에서 MyThread를 이용해서 리스너 ID를 URL 맨 뒤에 매개변수로 넣어서 다른 서버에 Request를 보내고 Response(정상, 실패)까지 받을 예정인데 이 부분은 잠시 보류하고 운영 직전에 테스트 해보려고 한다.)
커스텀 Thread 만들어서 멀티스레드를 이용한 이유
- 하나의 인스턴스를 조회할 때마다 새로운 싱글스레드를 생성하여 실행시킬 것이다. MyThread안에 리스너 객체를 하나씩 넣어서 run()을 돌리기 위해 필요하다.
- run()에는 해당 리스너에 대해 공통적으로 실행하고 싶은 로직을 넣을 수 있다.
- 멀티스레드의 효율성과 run() 안에 내용을 새롭게 구현하기 위해서 멀티스레드를 사용한다고 이해했다.
- 그리고 url 주소 뒷 부분에 {listenerId} 를 매개변수로 넣어서 해당 스레드를 정지, 실행시키기 위해서 멀티스레드 구조로 되어 있어야한다.
에러 해결
- 오류
- class file has wrong version 61.0, should be 52.0
Please remove or make sure it appears in the correct subdirectory of the classpath.
- class file has wrong version 61.0, should be 52.0
- 원인: Java 버전 <=> Spring Boot 버전 호환이 안되서 발생
- 해결: Spring boot 버전을 2점대로 낮췄다
yml 설정 오류
<hide/>
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
- 오류: 데이터베이스에 대한 properties 설정이 없어서 발생한 오류
- 원인: properties 설정
- 해결
- yml 파일에다가 값을 넣어준다.
- 주의) yml 파일은 들여쓰기에 주의하지 않으면 같은 오류가 연달아 발생한다.
<hide/>
spring:
datasource:
url: jdbc:postgresql:// 데이터베이스 주소
username: 아이디
password: 비번
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 4
jpa:
properties:
hibernate:
show_aql: true
format_sql: true
use_sql_comments: true
default_schema: "스키마 이름"
order_inserts: true
order_updates: true
jdbc:
batch_size: 500
ddl-auto: update
HttpMessageNotReadableException
HttpMessageNotReadableException
<hide/>
{
"timestamp": "2023-04-01T16:13:54.541+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<?>
- 오류: RequstBody를 정상적으로 넣었는데 이 부분이 잘못됐다는 오류가 난다.
- 원인
- RequstBody를 형식에 맞춰 넣었지만 오류가 난다.
- 해결
- postman 으로 요청을 보낼 때, Header에 content-length를 꼭 넣어줘야한다.
- cf) host를 안 넣어주면 JSON 형식으로 반환되지 않고 다음과 같이 html 형태로 에러 정보가 반환된다. 따라서, host, content-length, content-type 을 모두 넣어줘야한다.
Request Header 종류
- content-type: 컨텐츠 타입과 문자열 인코딩 (UTF-8 등)을 명시할 수 있다.
- content-length: 요청과 응답 메시지의 본문 크기를 바이트 단위로 표시한다. 메시지 크기에 따라 자동으로 만들어진다.
- host: 서버의 도메인 네임이 나타나는 부분이다. host 헤더는 반드시 하나 있어야한다.
Note
- 스레드에서 run()을 구현한 다음 start()만 호출하면 run()은 자동으로 실행된다.
PostgreSQL의 자료형 Numeric이란?
- 정수나 소수를 저장 가능하다.
- 함수와 같이 precision, scale 변수를 통해 설정하려는 타입 값을 전달 가능하다.
- ex) numeric(20, 5): 소수점 앞에 20개의 숫자 표시 가능하고 소수점 뒤에는 5개의 숫자를 표현 가능하다.
- 고정 소수점을 저장하는 데이터 타입 중 하나이다. 정확한 십진수를 표현 가능하다. (부동 소수점과 대비된다.)
Java의 자료형 BigDecimal이란?
- Java에서 숫자를 정밀하게 저장하고 표현할 수 있는 방법이다.
- Java에서 소수점을 저장할 수 있는 가장 큰 타입은 double이다.
- 숫자에 민감한 금융 거래 시, BigDecimal은 필수이다.
- BigDecimal은 double에 비해서 표현 가능한 길이가 2배 이상 길기 때문에 정교하게 연산 가능하다.
- 장점: 10진수로 표현하므로 정확성이 높다.
- double, float의 경우에는 2진수로 표현하므로 10진수로 표현하는 BigDecimal의 정확도가 높다.
- 부동 소수점 회피: double, float의 경우에는 부동 소수점 문제가 발생할 수 있다. (double, float에서는 0.1 + 0.2 를 하면 0.3이 정확하게 나오지 않을 수도 있다.) BigDecimal에서는 이런 문제가 발생하지 않는다.
- 큰 숫자를 다룰 수 있다.
- 단점
- 계산 비용이 많이 발생해서 속도가 느리다. (double은 연산 비용이 낮아서 속도가 빠름)
- 메모리 사용량이 크다. (double은 대용량으로 처리할 때 효과적이다. )
PostgreSQL의 Numeric <-> Java BigDecimal
- PostgreSQL의 Numeric 필드를 만들기위해서 자바 엔티티에서는 어느클래스를 활용해야될까?
- Numeric 은 정수만 저장하는 Integer와 다르게 정수와 소수까지 저장할 수 있다.
- Java의 BigDecimal이라는 클래스를 활용한다.
- MySQL의 Decimal과 유사하다.
고정 소수점과 부동 소수점의 공통점과 차이점
- 부동 소수점(floating point)이란?
- 2진수를 정규화해서 지수부와 가수부로 나눠 표현한다.
- 컴퓨토 공학에서의 정규화는 1.xxx * 2^n 형태로 바꾸는 것을 의미한다.
- 실수를 표현할 때 소수점의 위치를 고정하지 않는 것을 말한다.
- C, C++, Java 같은 언어에서는 수를 표현하기 위해 정수 타입, 부동 소수점 타입을 제공한다.
- 부동 소수점은 고정 소수점보다 훨씬 넓은 범위의 수를 표현할 수 있다. (유효 숫자: 12345, 소수점 위치: 3)
- 하지만, 항상 오차가 존재한다. 10진수를 정확하게 표현할 수는 없다.
- 고정 소수점(fixed point)이란?
- 10진수를 2진수로 바꿔서 저장한다. 정수부와 소수부로 나뉜다.
- 자릿수가 제한되므로 표현 범위가 한정된다.
- ex) 123.456: 정수 123, 소수 456을 나눠서 표현해야한다. 따라서 한정된 비스에서 정수와 소수를 분할해서 배치해야되는 경우 고정 소수점이 나타낼 수 있는 범위가 매우 한정된다.
- 정수 연산 처럼 연산을 처리하고 부동 소수점보다 빠르다.
참고)
고정 소수점 사진: https://gsmesie692.tistory.com/94
고정 소수점 사진: https://gguguk.github.io/posts/fixed_point_and_floating_point/
부동 소수점 https://dataonair.or.kr/db-tech-reference/d-lounge/expert-column/?mod=document&uid=52381
찾아보기
- content
'개발 일지 > 주간 개발 일지' 카테고리의 다른 글
[04월 3주차] Vue.js 3 과 Spring (0) | 2023.04.22 |
---|---|
[04월 2주차] JPA, Vue.js 3 기본 명령어 (0) | 2023.04.16 |
[04월 1주차] 페이징(Paging), QueryDSL, LocalDate와 String 변환 방법 (1) | 2023.04.08 |
[03월 4주차] OkHttp 예제, Thread와 ExecutorService (1) | 2023.03.26 |
[03월 3주차] JPA 연관 관계 매핑, 복합 키, 제네릭 클래스에 의존성 주입 (0) | 2023.03.18 |