Project & Issu

토이 프로젝트에 적용할 기능, 기술에 대한 기록 - 알림 기능, 요청에 대한 이력 적재 기능, maven -> gradle

gu9gu 2023. 3. 27. 15:03

두서 없이 작성 중. 아직 정리가 안 되어 있는 상태. 

 

작성 배경

프로젝트에 적용할 기능, 기술에 대한 기록용

정리 하면서 작성하는게 아니라 일단 기록하기 위해 작성

기능목록

1. 알림 기능

2. 요청에 대한 이력 적재 기능

3. Maven vs Gradle

4. jpa n+1 문제 해결

프로젝트 주소

jeoningu/Springboot-JPA-Blog (github.com)

 

 

서론

 - 알림 기능을 구현해보려 하는데, 어떻게 구현해야 할지 정리가 필요하고 관련된 내용들도 추가적으로 학습해서 정리해보려 한다.

 

 - Runnable, BlockingQueue로 구현된 기능에 대해서 작업한 적이 있는데, 알림 개발시에 참고할 수 있을 거 같아서 우선 이것들을 학습해보려고 한다.

학습하기 위해 참고할 블로그 목록을 모아둠. 기본적인 사용방법을 학습하고 사용 목적, 사용시 고려해야 할 것들을 추가로 학습하고 개인 블로그 프로젝트에 알림 기능, 이력적재 기능 같은 걸 추가할 때 사용해보려고 한다.

 

- Maven vs Gradle

 Maven과 Gradle은 모두 Java 프로젝트를 빌드하고 관리하기 위한 빌드 도구입니다.

 Maven은 XML 기반의 설정 파일을 사용하며, 이 파일에 필요한 의존성과 빌드 설정을 명시하면 됩니다. 이러한 설정 파일은 Maven이 제공하는 플러그인을 사용하여 쉽게 생성할 수 있습니다. 또한, Maven의 기본 설정은 이미 잘 되어 있어서 대부분의 경우 추가 구성이 필요하지 않습니다. 이렇게 Maven은 초보자가 쉽게 사용할 수 있는 사용자 친화적인 인터페이스를 가지고 있습니다.

반면에, Gradle은 Groovy나 Kotlin 같은 동적 언어를 사용하여 빌드 스크립트를 작성해야 합니다. 또한, Gradle은 task graph라는 특별한 빌드 시스템을 가지고 있어서 Maven에 비해 러닝 커브가 높을 수 있습니다. 이러한 이유로 Gradle은 Maven보다 설정이 복잡하고 초보자가 사용하기 어려울 수 있습니다. 하지만, Gradle은 빌드 속도가 빠르고 효율적이며 유연하고 맞춤형 빌드를 만들 수 있습니다.

 => 토이프로젝트에서는 규모가 작고 maven의 기능과 빌드 성능으로도 충분하기 때문에 쉬운 설정 파일 구조인 maven을 사용한다.

 

 

본론

알람 기능 개발

1. 화면에서 어떻게 알림을 보여줄 것인가?

 - 기본 html, css, javascript로 toast(애플리케이션 위에 잠깐 떠있다 사라지는 뷰) 구현

 

2. 댓글을 DB에 적용한 뒤에 화면으로 어떻게 요청을 해줄 것인가?

- Polling 또는 Server-Side-Event  또는 WebSocket

- 우선 SSE를 사용해서 화면 단에서 요청을 받는 것 까지는 구현해봄. Spring Security를 사용 하고 있었어서 sse에서 사용하는 url에 대한 허가 처리도 필요했음

- session을 확인해서 게시글 작성자에게 알림을 보내줘야 될 것 같다.

  SSE로 클라이언트에서 서버에 구독요청을 할 때 ID에 사용자번호를 포함시켜서 해야 될 것 같고

그러면 로그인이 완료 응답을 받은 즉시 클라이언트에서 다시 서버로 SSE 구독 요청을 하면 될 거 같음.

그런데, 스프링 시큐리티 OAuth로그인을 OAuth2 Client 라이브러를 사용해서 구현 했어서

로그인 요청을 a태그로 구현함. a태그는 응답 처리가 불가능하니 ajax요청으로 바꿔야 될 거 같은데 이게 맞나 싶음.. 

왜냐면 a태그 요청시 OAuth2 Client가 인식할 수 있는 url로 요청하면 요청을 잡아서 파라미터를 추가하고 추가 요청 작업들을 하는 거 같은데.. OAuth2 Client가 요청을 잡아서 어떻게 바꿔주는지 확인해서 ajax로 똑같이 구현해야 될 것 같고.. 이런 작업을 하는게 맞나 싶음.

 

3. queue를 사용할 필요가 있는가?

4.  queue를 어떻게 적용할 것인가?

5. radditMQ를 사용하는 곳이 있던데 검색해보니 여러 서버가 있는 환경에서 사용되는 거라던데 이걸 사용해서 처리 해봐도 되는건가?

 

==> ajax로 안 하고 header에서 로그인 됐는지 체크하고 localStorage에 저장한 sse 구독 여부도 체크 해서 sse구독 요청 하도록 구현

 


요청에 대한 이력 적재 개발

Future
 - 비동기로 수행한 쓰레드가 수행한 결과를 담는다.
  - Future 내부적으로 Thread-Safe 하도록 구현되어있다.

ExecutorService, Runnable를 사용하는 이유
 - java.util.concurrent.Executors와 java.util.concurrent.ExecutorService를 이용하면 간단히 쓰레드풀을 생성하여 병렬처리를 할 수 있습니다.
 - 인자 개수만큼 고정된 스레드를 생성하는 스레드 풀 : ExecutorService executor = Executors.newFixedThreadPool(int n);

BlockingQueue를 사용하는 이유

1. ArrayBlockingQueue는 멀티 쓰레드 환경에서 사용하기 위해 thread-safe하도록 설계되었다. Future와 같이 사용하면 어차피 Future가 Thread-Safe 하도록 구현되어있어서 신경안써도 된다.

- Queue가 꽉찼을때의 삽입 시도 / Queue가 비어있을때의 추출 시도를 막는 다는 것이다.이 자동으로 '막는' 기능이 있어 BlockingQueue 의 구현체는 모두 Thread-safe 하다.
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    
    .
    .
    
   public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        Objects.requireNonNull(e);
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length) {
                if (nanos <= 0L)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }
    
    .
    .
    
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0L)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
    .
    .
}
 

2. 어떠한 작업마다 내부에서 lock()과 unlock()을 동시에 실행하기때문에 동시성을 보장해서 synchronized 구문 없이 사용해도 됩니다.

그러나 대량작업의 addAll(), removeAll()같은 경우는 사용할때 매우 주의해야하는데 따로 명시되어있지 않는 한 addAll()같은 경우의 실행중 예외를 던질 수 있다. 10개의 요소가 담긴 자료구조중에서 7번째 요소를 넣는 도중에 에러가 발생할 수 있다는 것이다.

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    
    .
    .
    
   public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

	.
	.
    public boolean offer(E e) {
        Objects.requireNonNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
    .
    .
 }

 

Runnable 사용 이유

 - 비동기성 처리를 하기 위함.

 - 작업이 실패한 경우 다른 곳에 영향을 주지 않게 하기 위함

    : 에러가 발생할 수도 있는데 예를 들면 db에 적재시 데이터에 문제가 있어서 db 오류가 발생할 수 있음

 

Runnable, BlockingQueue를 예제

1. Runnable 을 구현해서 run 메서드에서 while문으로 돌리는 worker (consumer 역할) 클래스 구현

 - quue에서 poll해서 있으면 처리할 작업 수행.

 - Thread.sleep(1000); 으로 1초 딜레이 줌 => 모든 쓰레드가 반복할 시간.

 

2. 원하는 queue 수만큼 ArrayBlockingQueue를 생성해서 ExecutorService에 담고 Future를 생성해서 원하는 수 만큼  FutureList에 담는다.

이 때, FutureList에 담기는 ExecutorService가 

위 작업을 스프링 부트 시작시에 수행될 수 있게 한다.

 

 

 

 

 

 

 

 

 

 

 

 

추가 참고

Java - ExecutorService를 사용하는 방법 (codechacha.com)

Java - ArrayBlockingQueue 사용 방법 (codechacha.com)

 

Java - Future 사용 방법 (codechacha.com)

@deveely :: BlockingQueue 정리 (tistory.com)

[자바] 자바의 비동기 프로그래밍 (velog.io)

 

 

메시지 큐 Message Queue :: 우당탕탕 히온이네 (tistory.com)

자바 스케쥴링 & 타이머 방법들 (tistory.com)

 

 

 

'Project & Issu' 카테고리의 다른 글

좋아요 기능 동시성 처리  (0) 2023.05.04
게시판 개인 프로젝트 JPA 이슈 해결 - N+1, LazyInitializationException  (0) 2023.05.02
JWT 연습  (0) 2023.02.10
security  (0) 2023.01.08
springboot-blog  (0) 2022.12.15