알림 서비스를 알아보면 모바일 별 FCM 등 이용방법이 있었고, 각 디바이스 정보를 보관해서 보내는 거 같았다.
예전에는 어떤 방법들이 있었을까?
효율면에서는 시작할 거 같지 않지만, 구현과정은 달랐었고, 3가지 정도 구현해보자 싶었다.
웹 소켓의 양방향성과 다르다고 봤다. 채팅등에 이용되는데, 알람 서비스는 일단 구독 요청에 대해서 추후 알람을 서버에서 클라이언트로 보내주는 점이 다르다.
그리고, 이런 단방향 성 서버 전송 방식이 구현하며 차이를 볼 때 흥미로운 점이 있었다.
학습 하면서 구현 실습 해본 경험이라 잘 못된 부분이 있을 수 있습니다. 알려주시면 감사하겠습니다.
Long Polling 은 서버 측에서 응답을 일정시간 동안 대기 상태에 둬야 한다고 봤다.
어떻게 대기 하지?
앞에서 시간 ➡️ 할 일 순서로 봤는데,
🔙 할 일을 시간 동안 하는 순서로 보면 ?
좀 더 세분화 해서 정리해보면 😢
✅ 주어진 시간 동안 알람 전송 할 데이터를 여러 번 조회 어떻게?
✅ 그 시간 동안 컨트롤러는 응답 지연 중
✅ 데이터 있으면 전송, 없으면 그냥 응답
가장 중요한 걸 미루고 있었다. (모르는데? 모르는데? ...)
주어진 시간 동안 컨트롤러가 대기 후 응답
요청별 스레드 할당 해서 스케줄러로 5초 동안 1초 씩 돈다면
DB 트랜잭션까지 이용하며 조회 해와서 데이터 없으면 그냥 응답인데,
데이터 있으면 그 순간 데이터 담아서 전송해야 한다.
일단 구현하기 ㅎㅎㅎ
요청 받고, 스케줄러 도는데, 계속 돌면서 계속 조회 결과 없고 응답은 없다. 나의 약한 노트북을 이렇게 과거 괴롭힌 적 없는데 ... 😬
하지만, 문제들을 보며, 이 때 부터 로직들을 잘 정리 할 수 있었고, 구현까지 갔다.
여러 옵션 설정이 있다.
fixedRate
: 이전 작업 시작 시간 부터 고정 시간 설정@Scheduled 적용 메서드는 전달할 파라미터나 반환 값이 없어야 한다.
클라이언트가 조회 요청하면, 그 정보를 별도 자료구조에 보관해야 한다.
1명을 5초 동안 반복해서 확인 하는게 처음 생각한 LongPolling 개념 이이었다.
자료구조에는 스케줄러에서 이용할 클라리언트 정보가 담긴다
5초 동안 10명의 요청 이란 ?
비동기 처리 하면 무언가 많은데
LongPolling은 요즘 안 쓰여서 인지 설명이나 구현 예제들이 적어보였지만, DeferredResult 가 기본인 거 같았다.
번역 해서 간력히 써보면
Long polling 은 서버 애플리케이션이 정보를 이용할수 있을 때까지 클라이언트와의 연결을 유지하는 방식으로, 서버가 정보를 얻거나 결과를 기다리기 위해 다운스트림 서비스를 요청해야 할 때 사용된다.
DeferredResult
Long Polling과 DeferredResult
DeferredResult<Response<List<Alarm>>> output = new DeferredResult<>(LONG_POLLING_TIMEOUT);
output.onTimeout(() -> {
output.setErrorResult(String.format("TimeOut: %dms", LONG_POLLING_TIMEOUT));
alarmInMemory.remove();
});
타임아웃 임계치 도달시 컨테이너 스레드에 의한 입력을 Runnable 로 하여,
타입아웃 확인 위한 테스팅 예제
private static void enableTimeout(MvcResult asyncListener) throws IOException {
((MockAsyncContext)asyncListener
.getRequest()
.getAsyncContext())
.getListeners()
.get(0)
.onTimeout(null);
}
output.onCompletion(() -> log.info("The alarm publish is completed."));
output.onTimeout(() -> {
output.setErrorResult(String.format("TimeOut: %dms", LONG_POLLING_TIMEOUT));
alarmInMemory.remove();
});
deferredResult.onError((Throwable t) -> {
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An error occurred."));
});
현재는 많은 기능 없고 댓글 등록시 알람 전송만 하지만, 좋아요나 팔로우 등 추가한다면 생각하고 구현 해봤습니다.
확실히 ... 기능적인 부분보다 복잡 하니 잼잼잼...
설계 과정으로 아주 주관적인 이야기
메시지? : 누가
-의
-에
-을
했습니다. (sender → recipient)
검색 ? : -의
님
이 댓글을 달았습니다.댓글이 달렸습니다
user flow ?
click alarm's message → show 포스트 or 댓글
→ click the part of content → sender 조회
댓글은 제목도 없다.
키워드 ? AlarmType
AlarmType | 메시지 |
---|---|
COMMENT | 댓글을 달았습니다. |
POST_LIKE | 포스트에 좋아요를 눌렀습니다. |
COMMENT_LIKE | 댓글에 좋아요를 눌렀습니다. (댓글 작성자가 recipient) |
FOLLOW | 팔로우/구독? 합니다. |
/api/v2/sns/posts/{postId}/comments
)/api/v2/sns/posts/{postId}/comments
)각 키워드 별(AlarmType) 별 저장 할 데이터들이 달랐다.
처음에는 아래처럼 단순화 할 수 있을까 싶었는데, 요구사항이 위처럼 4가지라면 변경 발생시 적절하지도 못 했다.
💡 그래서 모든 값들을 담아보자 생각해본 방법 4가지
순서대로 보자면
MySQL의 JSON 타입 사용 하기로 했다.
- 의존성 추가
implementation 'com.vladmihalcea:hibernate-types-52:2.20.0\'
- 엔티티에 Json 애노테이션 추가
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Type(type = "json")
- typeClass가 여러가지 이니 추가로 알아보면 좋다.
첫 번째는 쿼리 조회 조건으로 시간만 추가하면 돼서 간단해 보였었다.
2번째로 구현하면 알람이 너무 많은 경우 모아서 전달 해 버린다.
결국은, 알람 목록 확인 페이지 등이 별도로 있다면, 몇 건 미스되더라도 현재 시간 이후의 실시간 알람이 나을 거 같다.
- 서비스 마다 다르겠지만, SNS 피드 형식에서는 알람 끄기 기능이 제공되기도 하고, 너무 많은 알람을 보통 선호하진 않을...거 같은데, 중복은 더 큰 문제인 거 같았다.
- 알람 미전송 보다 중복 알람 더 오류로 느껴질 거 같다고 생각 했습니다.
p.s.주관적 생각입니다.
... 생각과 다르게 구현은 일단 모두 조회해서 전송하고 변경 로직으로 했습니다. 그래서 이곳에 나름 자세히 정리해 봅니다. ㅎㅎㅎ