μ•Œλ¦Ό κΈ°λŠ₯

μ–΄λ–€ μ΄λ²€νŠΈκ°€ λ°œμƒ ν–ˆμ„ λ•Œ, μ‚¬μš©μžμ—κ²Œ μ•Œλ¦Όμ΄ κ°€λ„λ‘ν•˜λŠ” μ•Œλ¦Ό κΈ°λŠ₯을 μΆ”κ°€ν•˜λ €κ³  ν•œλ‹€.

μ‚¬μš©ν•  수 μžˆλŠ” ν”„λ‘œν† μ½œ

1. Polling

주기적으둜 μ„œλ²„μ— μš”μ²­ν•˜μ—¬ λ³€κ²½ λ‚΄μš©μ„ ν™•μΈν•˜λŠ” 폴링 방식. ν•˜μ§€λ§Œ μ‹€μ‹œκ°„ 톡신이 ν•„μš”ν•  경우 λΉ„νš¨μœ¨μ μ΄λ‹€. μ‹€μ‹œκ°„ μš”μ²­μ΄ μ•„λ‹ˆκ³  μ •ν•΄μ§„ μ‹œκ°„(N)을 κΈ°μ€€μœΌλ‘œ (μš”μ²­ -> 응닡)을 λ°˜λ³΅ν•œλ‹€.

  • Short Polling ν΄λΌμ΄μ–ΈνŠΈκ°€ μ„œλ²„λ‘œλΆ€ν„° 정기적인 정보λ₯Ό λ°›κΈ° μœ„ν•œ ν”„λ‘œν† μ½œμ΄λ‹€. 과정은 λ‹€μŒκ³Ό κ°™λ‹€. 1) ν΄λΌμ΄μ–ΈνŠΈκ°€ μ„œλ²„μ— μƒˆλ‘œμš΄ 정보에 λŒ€ν•œ HTTP μš”μ²­μ„ 보낸닀. 2) μ„œλ²„λŠ” μƒˆλ‘œμš΄ 정보λ₯Ό λ°˜ν™˜ν•œλ‹€. 3) ν΄λΌμ΄μ–ΈνŠΈλŠ” μ„€μ •ν•œ μ£ΌκΈ°(time)둜 μš”μ²­μ„ λ°˜λ³΅ν•œλ‹€. 주기적으둜 HTTP Reqeustλ₯Ό λ³΄λ‚΄λŠ” λ°©μ‹μœΌλ‘œ, μ‹€μ‹œκ°„μ„±μ΄ λ–¨μ–΄μ§€κ³  μ„œλ²„μ— λΆˆν•„μš”ν•œ λΆ€ν•˜λ₯Ό 쀄 수 μžˆλ‹€. TCP의 컀λ„₯μ…˜μ„ λ§Ίκ³  λŠλŠ” 것 μžμ²΄κ°€ 무겁기 λ•Œλ¬Έμ— μ‹€μ‹œκ°„μœΌλ‘œ λ³€ν™˜λ˜λŠ” λΉ λ₯Έ μ •λ³΄μ˜ 응닡을 κΈ°λŒ€ν•˜κΈ° νž˜λ“€λ‹€.

  • Long Polling Short Polling λ³΄λ‹€λŠ” 더 효율적인 방식이닀. μ΄λ²€νŠΈκ°€ λ°œμƒν•  λ•ŒκΉŒμ§€ 연결을 μœ μ§€ν•˜λŠ” λ°©μ‹μœΌλ‘œ μ—¬μ „νžˆ μƒˆλ‘œμš΄ 연결을 자주 μ„€μ •ν•΄μ•Ό ν•œλ‹€. 1) ν΄λΌμ΄μ–ΈνŠΈκ°€ μ„œλ²„μ— μš”μ²­μ„ 보낸닀. μ„œλ²„μ— μƒˆλ‘œμš΄ μ΄λ²€νŠΈκ°€ λ°œμƒν•  λ•ŒκΉŒμ§€ κΈ°λ‹€λ¦°λ‹€. 2) μ„œλ²„λŠ” 이벀트 정보λ₯Ό λ°˜ν™˜ν•œλ‹€. 3) ν΄λΌμ΄μ–ΈνŠΈλŠ” 응닡을 λ°›κ³  μš”μ²­ν•œλ‹€. Shor Polling에 λΉ„ν•΄ ν•„μš”ν•œ μš”μ²­ 수λ₯Ό 쀄일 수 μžˆλ‹€. ν•˜μ§€λ§Œ μ„œλ²„λ‘œλΆ€ν„° 응닡을 λ°›κ³ λ‚˜λ©΄ λ‹€μ‹œ μ—°κ²° μš”μ²­μ„ ν•΄μ•Όν•˜κΈ° λ•Œλ¬Έμ— λΉˆλ²ˆν•˜κ²Œ μ—°κ²° μš”μ²­μ„ ν•˜κ²Œλ˜λ©΄ μ„œλ²„μ— 뢀담이 κ°€λŠ”κ²ƒμ€ λ™μΌν•˜λ‹€. λ”°λΌμ„œ 이 방식은 μ„œλ²„μ˜ μƒνƒœκ°€ 주기적으둜 λ³€ν•˜μ§€ μ•ŠλŠ” κ²½μš°μ— μ ν•©ν•˜λ‹€.

2. Web Socket

μ›Ή μ†ŒμΌ“μ€ OSI 4계측 ν”„λ‘œν† μ½œμΈ TCP에 κΈ°λ°˜ν•œ μ–‘λ°©ν–₯ λ©”μ‹œμ§€ 전달 ν”„λ‘œν† μ½œμ΄λ‹€. 기쑴의 HTTP μš”μ²­-응닡 ꡬ쑰와 달리, μ§€μ†μ μœΌλ‘œ 연결을 μœ μ§€ν•˜λ©° μ„œλ²„μ™€ ν΄λΌμ΄λ„ˆνŠΈ κ°„ 즉각적인 데이터 κ΅ν™˜μ„ κ°€λŠ₯ν•˜κ²Œ ν•œλ‹€. μ΄λ‘œμΈν•΄ HTTP보닀 μ˜€λ²„ν—€λ“œκ°€ 적고 데이터 전솑 속도가 λΉ λ₯΄λ‹€.

μ›Ήμ†ŒμΌ“μ€ μ‹€μ‹œκ°„ μ–‘λ°©ν–₯ 톡신이 ν•„μš”ν•œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μ ν•©ν•˜λ©°, 특히 λ©€ν‹°ν”Œλ ˆμ΄μ–΄ κ²Œμž„, μ‹€μ‹œκ°„ ν˜‘μ—… 도ꡬ, 라이브 μ±„νŒ… λ“±κ³Ό 같이 지속적이고 λΉ λ₯Έ 데이터 μ—…λ°μ΄νŠΈκ°€ μš”κ΅¬λ˜λŠ” κ²½μš°μ— μœ μš©ν•˜λ‹€. Pollingμ΄λ‚˜ Server-Sent Events(SSE)κ°€ μΆ©λΆ„ν•˜μ§€ μ•Šμ€ μƒν™©μ—μ„œ μ›Ήμ†ŒμΌ“ μ‚¬μš©μ„ κ³ λ €ν•  수 μžˆλ‹€. ν•˜μ§€λ§Œ κ΅¬ν˜„μ΄ μƒλŒ€μ μœΌλ‘œ λ³΅μž‘ν•  수 μžˆλ‹€.

3. SSE (Server Sent Events)

SSEλŠ” μ„œλ²„μ—μ„œ ν΄λΌμ΄μ–ΈνŠΈλ‘œμ˜ 단방ν–₯ μ‹€μ‹œκ°„ 톡신을 μœ„ν•œ 기술둜 HTTP ν”„λ‘œν† μ½œμ„ μ‚¬μš©ν•˜μ—¬ κ΅¬ν˜„ν•œλ‹€. 좔가적인 λΌμ΄λΈŒλŸ¬λ¦¬λ‚˜ ν”„ν† μ½œμ΄ ν•„μš”ν•˜μ§€λŠ” μ•Šλ‹€. 연결이 λŠμ–΄μ§€λ©΄ μžλ™ μž¬μ—°κ²° κΈ°λŠ₯을 μ œκ³΅ν•˜λ©°, κ΅¬ν˜„μ΄ κ°„λ‹¨ν•˜κ³  λŒ€λΆ€λΆ„μ˜ λΈŒλΌμš°μ €μ—μ„œλ„ μ§€μ›ν•œλ‹€. λ˜ν•œ HTTP의 Persistence Connection을 μ΄μš©ν•œ 기술이기 λ•Œλ¬Έμ— μ—¬λŸ¬ HTTP μš”μ²­κ³Ό 응닡을 ν•œ 번의 TCP μ—°κ²°λ‘œ μ²˜λ¦¬ν•  수 μžˆλ‹€. (μ§€μ†μ μœΌλ‘œ 데이터λ₯Ό 슀트리밍 ν•  수 있음)

과정은 λ‹€μŒκ³Ό κ°™λ‹€. 1) ν΄λΌμ΄μ–ΈνŠΈλŠ” μ„œλ²„λ₯Ό κ΅¬λ…ν•œλ‹€. (SSE Connection을 맺음) 2) μ„œλ²„λŠ” μ΄λ²€νŠΈκ°€ λ°œμƒν•  λ•Œλ§ˆλ‹€ κ΅¬λ…λœ ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 데이터λ₯Ό μ „μ†‘ν•œλ‹€.

SSEλŠ” 단방ν–₯ 톡신(μ„œλ²„ -> ν΄λΌμ΄μ–ΈνŠΈ)이기 λ•Œλ¬Έμ— μ–‘λ°©ν–₯이 ν•„μš”ν•˜λ©΄ WebSocket을 κ³ λ €ν•΄λ³Έλ‹€.

SSE 채택 이유

μ±„νƒν•œ κ°€μž₯ 큰 μ΄μœ λŠ” 단방ν–₯ 톡신이기 λ•Œλ¬Έμ΄λ‹€. κ΅¬ν˜„ν•˜λ €λŠ” μ•Œλ¦Ό κΈ°λŠ₯은 μ„œλ²„μ—μ„œ ν΄λΌμ΄μ–ΈνŠΈλΌλŠ” νŠΉμ„±μ΄ μžˆμ–΄μ„œ, ν΄λΌμ΄μ–ΈνŠΈκ°€ μ„œλ²„μ— μš”μ²­μ„ 보낼 ν•„μš”κ°€ μ—†μ–΄ μ‹€μ‹œκ°„ λ©”μ‹œμ§€ μ „μ†‘μ—λŠ” 맀우 μ ν•©ν•˜λ‹€. μ΄λŸ¬ν•œ νŠΉμ„±μœΌλ‘œ ν˜„ μƒν™©μ—μ„œλŠ” Web Sockect λ³΄λ‹€λŠ” SSEκ°€ 더 μ ν•©ν•˜κ³  νŒλ‹¨ν–ˆλ‹€.

ν•˜μ§€λ§Œ SSE에도 μ—¬λŸ¬ 단점듀은 μ‘΄μž¬ν•œλ‹€. ν΄λΌμ΄μ–ΈνŠΈμ™€ HTTP 연결을 μ§€μ†μ μœΌλ‘œ μœ μ§€ν•΄μ•Ό ν•˜λ―€λ‘œ, λ™μ‹œμ— λ§Žμ€ ㅇ녀결이 λ°œμƒν•  경우 μ„œλ²„μ˜ λΆ€ν•˜ 관리가 λ¬Έμ œκ°€ 될 수 μžˆλ‹€. λ˜ν•œ, λŒ€λŸ‰μ˜ ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ λ™μ‹œμ— 데이터λ₯Ό μ „μ†‘ν•˜λŠ” κ²½μš°μ—λ„ μ„±λŠ₯ λ¬Έμ œλ„ κ³ λ―Όν•΄ λ΄μ•Όν•˜λŠ” μš”μ†Œ 쀑 ν•˜λ‚˜μΌ 것이닀.

SseEmitter 객체

κ΅¬ν˜„μ— μ•žμ„œ Spring Framework 4.2λΆ€ν„° SSE 톡신을 μ§€μ›ν•˜λŠ” SSeEmitter ν΄λž˜μŠ€κ°€ μΆ”κ°€λ˜κ³ , Spring 5λΆ€ν„°λŠ” webfluxλ₯Ό μ΄μš©ν•΄μ„œ SSE 톡신도 ν•  수 μžˆλ‹€.

λ™μž‘ Flow

  1. λ¨Όμ € μœ μ €λ₯Ό subscribe(Long userId) ꡬ독 ν•΄μ•Όν•œλ‹€.

  2. μ»¨νŠΈλ‘€λŸ¬μ—μ„œ header("lastEventId")λ₯Ό λ°›κ³  Service의 subscribe() 인자둜 μ „λ‹¬ν•œλ‹€.

  3. SseEmitter 객체λ₯Ό μƒμ„±ν•΄μ„œ μ €μž₯μ†Œμ— μ €μž₯μ‹œν‚€κ³  ν•„μš”ν•  λ•Œλ§ˆλ‹€ μœ μ €μ™€ λ§€μΉ­λ˜λŠ” SseEmitterλ₯Ό λΆˆλŸ¬μ™€μ„œ μ΄λ²€νŠΈμ— λŒ€ν•œ 응닡을 전솑해쀀닀.

κ΅¬ν˜„

βœ… EmitterRepositoryImpl

μ΄λ²€νŠΈκ°€ λ°œμƒν•˜λ©΄ userId둜 SseEmitter 객체λ₯Ό μƒμ„±ν•œλ‹€. 그것을 μ €μž₯ν•˜κ³  κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ μ €μž₯μ†Œμ΄λ‹€. (μ•Œλ¦Ό 쑰회, μ‚­μ œ..etc)

➑️ ConcurrentHashMap Thread-safeν•œ Map, λ™μ‹œμ— μ—¬λŸ¬ μŠ€λ ˆλ“œκ°€ μ ‘κ·Όν•˜λ”λΌλ„ 데이터λ₯Ό μ‘°μž‘ν•  수 μžˆλ„λ‘ 보μž₯ν•œλ‹€. [μ°Έκ³ ] https://pplenty.tistory.com/17arrow-up-right

➑️ saveEventCache ν˜Ήμ‹œλ‚˜ 연결이 λŠμ–΄μ‘Œμ„ 경우 μ΄λ²€νŠΈκ°€ λ°œμƒν•΄μ„œ ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 전솑이 κ°€μ§€ μ•ŠλŠ”λ‹€λ©΄ 여기에 μ €μž₯ν•˜κ³  λ‹€μ‹œ ꡬ독할 λ•Œ 전솑할 수 μžˆλ„λ‘ ν•œλ‹€. SseEmitter 객체의 μœ μ‹€μ„ 막기 μœ„ν•¨μ΄λ‹€.

βœ… SubscribeService

  • onCompletion() λ©”μ†Œλ“œλŠ” SSE 톡신이 μ„±κ³΅μ μœΌλ‘œ μ™„λ£Œλ˜λ©΄ ν˜ΈμΆœλœλ‹€. 즉, ν΄λΌμ΄μ–ΈνŠΈμ™€μ˜ 연결이 μ •μƒμ μœΌλ‘œ μ’…λ£Œλ¨μ„ μ˜λ―Έν•œλ‹€. 톡신이 μ’…λ£Œλœ ν›„ 좔가적인 μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ±°λ‚˜ λ¦¬μ†ŒμŠ€λ₯Ό 정리할 수 μžˆλ‹€.

  • onTimeout()은 μœ„μ—μ„œ μ„€μ •ν•œ μ‹œκ°„, SSE 톡신이 νƒ€μž„μ•„μ›ƒ λ˜μ—ˆμ„ λ•Œ ν˜ΈμΆœλœλ‹€. νƒ€μž„μ•„μ›ƒμ€ ν΄λΌμ΄μ–ΈνŠΈκ°€ 일정 μ‹œκ°„ λ™μ•ˆ 응닡을 λ°›μ§€ λͺ»ν•œ 경우 연결이 μ’…λ£Œλœ μƒνƒœλ₯Ό μ–˜κΈ°ν•œλ‹€. 즉, μ—°κ²° μ‹œκ°„μ΄ 만료되면 μΆ”κ°€ μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ±°λ‚˜ μ˜ˆμ™Έ 처리λ₯Ό ν•  수 μžˆλ‹€.

  • onCompletion(), onError()λ©”μ†Œλ“œλŠ” μ’…λ£Œ μƒνƒœμ˜ νŠΈλ¦¬κ±°μ΄λ‹€. λ‘˜ 쀑 ν•˜λ‚˜κ°€ 트리거 된 ν›„μ—λŠ” μ•Œλ¦Όμ΄ μ „μ†‘λ˜μ§€ μ•ŠλŠ”λ‹€.

➑️ Map에 μ €μž₯ν•  Key(String) 값은 μ™œ κ΅¬λΆ„μžμ™€ μ‹œκ°„μœΌλ‘œ μ‘°ν•©ν• κΉŒ? Last-Event-IDλŠ” 헀더에 담겨져 μ˜€λŠ” κ°’μœΌλ‘œ 이전에 λ°›μ§€ λͺ»ν•œ μ΄λ²€νŠΈκ°€ μ‘΄μž¬ν•˜λŠ” 경우 (SSE 연결에 λŒ€ν•œ μ‹œκ°„ 만료 ν˜Ήμ€ μ’…λ£Œ)λ‚˜ 받은 λ§ˆμ§€λ§‰ 이벀트 ID 값을 λ„˜κ²¨ κ·Έ μ΄ν›„μ˜ 데이터(λ°›μ§€ λͺ»ν•œ 데이터) λΆ€ν„° 받을 수 있게 ν•  λ•Œ ν•„μš”ν•œ 값이닀.

➑️ connectNotification() SseEmitter의 유효 μ‹œκ°„(클라-μ„œλ²„ μ—°κ²° μ‹œκ°„) λ™μ•ˆ μ–΄λŠ 데이터도 μ „μ†‘λ˜μ§€ μ•ŠλŠ”λ‹€λ©΄ 503 μƒνƒœ μ½”λ“œλ₯Ό λ°˜ν™˜ν•œλ‹€. 이에 λŒ€ν•œ λ°©μ•ˆμœΌλ‘œ 맨 처음 μ—°κ²°ν•˜κ²Œ 되면 Connection Message(더미 데이터)λ₯Ό λ³΄λ‚΄μ„œ 이λ₯Ό λ°©μ§€ν•œλ‹€.

βœ… SendNotificationService

➑️ sendLostData() λ―Έμˆ˜μ‹ ν•œ 데이터λ₯Ό μ „μ†‘ν•˜λŠ” λ©”μ†Œλ“œ. ν΄λΌμ΄μ–ΈνŠΈκ°€ λ―Έμˆ˜μ‹ ν•œ 이벀트 λͺ©λ‘μ΄ μ‘΄μž¬ν•  경우, subScribe ν•  λ•Œ ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ μ „μ†‘μ‹œμΌœ μœ μ‹€μ„ λ°©μ§€ν•œλ‹€.

이 외에도 μ•Œλ¦Όμ„ μ½λŠ” μ„œλΉ„μŠ€, μ‚­μ œ, 전체 μ‘°νšŒν•˜λŠ” μ„œλΉ„μŠ€λ“€μ„ μΆ”κ°€λ‘œ κ΅¬ν˜„ν–ˆλ‹€.

βœ… Controller

생각해야 ν•  λΆ€λΆ„

  1. Repositoryμ—μ„œ Map으둜 SseEmitter 객체λ₯Ό μ €μž₯ν•  λ•Œ scale-out된 ν™˜κ²½μ—μ„œ 잘 λ™μž‘ν• κΉŒ?

  2. Map에 μ €μž₯된 자료λ₯Ό entrySet()으둜 λŒλ©΄μ„œ λΉˆλ²ˆν•˜κ²Œ μ΄λ²€νŠΈκ°€ λ°œμƒν•œλ‹€λ©΄ 맀번 O(N)으둜 탐색을 μ§„ν–‰ν•΄μ•Ό ν•˜λŠ”λ° μ„±λŠ₯적인 λ¬Έμ œκ°€ λ°œμƒν•˜μ§€ μ•Šμ„κΉŒ?

  3. HTTP 2.0을 적용 ν•˜λŠ”κ²ƒμ€ μ–΄λ–¨κΉŒ?

  4. Nginxμ—μ„œ λ°œμƒν•˜λŠ” λ¬Έμ œμ λ“€

Last updated