๐ ๋น๋๊ธฐ ์ด๋ฒคํธ ํธ์ ๋ฐฉ์ ์ ๋ฆฌ (WebSocket vs SSE + Redis)
์ฌ์ด๋ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ์ค์๊ฐ ์๋ฆผ์ ๋ณด๋ด๋ ๊ธฐ๋ฅ์ ๊ฒ๋ฐํ๊ฒ ๋์๋ค.
๊ฐ๋จํ ์ค๋ช ์ ํ์๋ฉด,,์ผ์ ์ ๋ฑ๋กํ๋ฉด ์ผํธ์คํธ์๊ฒ ๋ฐ๋ก ์๋ฆผ์ด ๊ฐ์ผ ํ๊ณ , ํ์ผ ์ ๋ก๋๋ ์ํ ๋ณ๊ฒฝ๋ ์ฆ์ ๋ฐ์๋์ด์ผ ํ๋ ์ํฉ์ด๋ค.
์ด๋ฒ ํ๋ก์ ํธ์์๋ ๋จ์ SSE ๊ตฌ์กฐ๋ฅผ ๋์ด Redis๋ฅผ ํจ๊ป ์ฌ์ฉํ์ฌ
์๋ฒ ๊ฐ์๋ ์๋ฆผ์ด ์ ๋ฌ๋ ์ ์๋๋ก ๊ตฌ์ฑํ์๋ค.
๋น๋๊ธฐ ์ด๋ฒคํธ ํธ์ ๋ฐฉ์์ผ๋ก ์ ์ฉ์ ํ์๋๋ฐ ์ด์ ๋ํด ์ ๋ฆฌ ํด๋ณด์ ํ๋ค.
๋น๋๊ธฐ ์ด๋ฒคํธ ํธ์?
์ผ๋ฐ์ ์ธ ์น ํต์ ์ ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ์ ๋ณด๋ด๊ณ ์๋ฒ๊ฐ ์๋ต์ ์ฃผ๋ ๊ตฌ์กฐ๋ค.
ํด๋ผ์ด์ธํธ โ ์์ฒญ โ ์๋ฒ โ ์๋ต โ ๋
์ด๋ฌํ ํ๋ฆ์ด ๊ธฐ๋ณธ์ด์ง๋ง, ์ค์๊ฐ ์๋ฆผ ๊ฐ์ ๊ฒฝ์ฐ๋ ์ข ๋ค๋ฅด๋ค.
ํด๋ผ์ด์ธํธ๊ฐ ๊ณ์ ์์ฒญ์ ๋ณด๋ด๋๊ฒ์ด ์๋๋ผ, ์๋ฒ์์ ์ด๋ฒคํธ์ ๋ฐ์ํ์ ๋ ํด๋ผ์ด์ธํธ์๊ฒ ์๋ ค์ค์ผํ๋ค.
์ด๋ฒ ํ๋ก์ ํธ์์๋ ๊ด๋ฆฌ์๊ฐ ์ผ์ ์ ๋ฑ๋กํ์ ๋, ํ์ผ ์
๋ก๋ ์์ (์ด๋ฌํ ์ด๋ฒคํธ ์ํฉ์) ํด๋ผ์ด์ธํธ๊ฐ ๋ฌผ์ด๋ณด์ง ์์๋ ์๋ฒ๊ฐ ๋จผ์ ์๋ ค์ค์ผํ๋ค.
์ด๊ฑธ ๊ฐ๋ฅํ๊ฒ ํด์ฃผ๋๊ฒ ๋น๋๊ธฐ ์ด๋ฒคํธ ํธ์ ๋ฐฉ์์ด๋ค.
ํด๋ผ์ด์ธํธ โ ์ฐ๊ฒฐ ์ ์ง
์๋ฒ โ ์ด๋ฒคํธ ๋ฐ์ โ ํด๋ผ์ด์ธํธ๋ก push
์ด๋ฌํ ๊ตฌ์กฐ์ด๋ฉฐ, ์์ฒญ ๊ธฐ๋ฐ์ด ์๋๋ผ ์ด๋ฒคํธ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๋ ํต์ ๋ฐฉ์์ด๋ค.
์ ํ์ง
Polling : ๊ณ์ ์์ฒญํจ (๋นํจ์จ)
WebSocket : ์๋ฐฉํฅ (๋ฌด๊ฒ๊ณ ๋ณต์ก)
SSE : ์๋ฒ โ ํด๋ผ ๋จ๋ฐฉํฅ (๊ฐ๋ณ๊ณ ์ฌ์)
๋น๋๊ธฐ ์ด๋ฒคํธ ํธ์ ๋ฐฉ์์๋ ์ด๋ ๊ฒ ์ด 3๊ฐ์ง์ ์ ํ์ง๊ฐ ์๋ค.
Polling
Polling์ ์ผ์ ์ฃผ๊ธฐ๋ง๋ค ์๋ฒ์ ๊ณ์ ์์ฒญ์ ๋ณด๋ด๋ ๋ฐฉ์์ด๋ค.
์๋ฆผ ์์ด? โ ์์
์๋ฆผ ์์ด? โ ์์
์๋ฆผ ์์ด? โ ์์
...
์ด๋ฌํ ๊ณผ์ ์ ๊ณ์ ๋ฐ๋ณตํ๋๋ฐ, ์ด ๋ฐฉ์์ ์๋ฒ์ ๋ถํ์ํ ์์ฒญ์ด ๊ณ์ ๋ค์ด๊ฐ๊ณ ๋คํธ์ํฌ ๋ญ๋น๊ฐ ์ฌํด์ ์ ์ธํ๋ค.
WebSocket
WebSocket์ ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ์๋ก ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์๋ ์๋ฐฉํฅ ํต์ ๋ฐฉ์์ด๋ค.
ํ ๋ฒ ์ฐ๊ฒฐ์ ๋งบ์ผ๋ฉด ์ดํ์๋ HTTP ์์ฒญ/์๋ต ๊ณผ์ ์์ด ์ค์๊ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์๋ค.
ํด๋ผ์ด์ธํธ โ ์๋ฒ (์๋ฐฉํฅ)
์ด๋ฌํ ๊ตฌ์กฐ์ด๋ฉฐ ์ฑํ
, ๊ฒ์, ์ค์๊ฐ ํ์
์ฒ๋ผ ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ์๋ก ๋ฐ์ดํฐ๋ฅผ ๊ณ์ ์ฃผ๊ณ ๋ฐ์์ผํ๋ ๊ฒฝ์ฐ์ ์ ํฉํ๋ค.
ํ์ง๋ง ๋ณ๋์ ํ๋กํ ์ฝ(ws://) ์ฌ์ฉ, ์ฐ๊ฒฐ ๊ด๋ฆฌ ํ์, ์ค์ ๊ณผ ๊ตฌํ์ด ๋ณต์กํ๋ค๋ ํน์ง์ผ๋ก ์ธํด ๋จ์ ์๋ฆผ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ์ ๊ณผํ๋ค๊ณ ํ๋จํ์ฌ ์ ์ธํ์๋ค.
SSE
SSE(Server-Sent Events)๋ ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๋ ๋จ๋ฐฉํฅ ํต์ ๋ฐฉ์์ด๋ค.
HTTP ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๋ฉฐ, ํด๋ผ์ด์ธํธ๊ฐ ํ ๋ฒ ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ๋ ์ฐ๊ฒฐ์ ๋์ง ์๊ณ ์ ์งํ๋ฉด์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋๋ง๋ค ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ค.
ํด๋ผ์ด์ธํธ -------- ์ฐ๊ฒฐ ์ ์ง -------- ์๋ฒ
(๋๊ธฐ์ง ์๋ HTTP)
์๋ฒ โ ์ด๋ฒคํธ ๋ฐ์ โ ํด๋ผ์ด์ธํธ๋ก push
WebSocket๊ณผ ๋น๊ตํ์ ๋ ๋ณ๋์ ํ๋กํ ์ฝ์ด ์๋๋ผ HTTP๋ฅผ ์ฌ์ฉํ๊ณ ๊ตฌํ์ด ๊ฐ๋จํ๋ฉฐ, ์๋ฒ์์ ํด๋ผ์ด์ธํธ ์๋ฆผ์ ์ต์ ํ๋๋ค๋ ์ฅ์ ์ด์๋ค.
์ด๋ฒ ํ๋ก์ ํธ์๋ ์ผ์ ์์ฑ, ํ์ผ ์
๋ก๋ ์ ์๋ฆผ์ด๋ผ๋ ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ด๋ผ (ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ๋ก ์ค์๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ํ์๋ ์๋ค) SSE๋ก ์ ํ์ ํ์๋ค.
SSE ๊ตฌ์กฐ์ ํ๊ณ
๊ธฐ๋ณธ์ ์ธ SSE ๊ตฌ์กฐ๋ ์๋ฒ๊ฐ 1๋์ผ ๊ฒฝ์ฐ์๋ ๋ฌธ์ ๊ฐ ์์ง๋ง, ์๋ฒ๊ฐ ์ฌ๋ฌ ๋๋ก ํ์ฅ๋๋ ๊ฒฝ์ฐ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
A ์๋ฒ์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์๋๋ฐ, B ์๋ฒ์ ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ์ด ์กด์ฌํ๋ค๊ณ ์๋ฅผ ๋ค์ด๋ณด์,,
์ด ๊ฒฝ์ฐ A์์ ๋ฐ์ํ ์๋ฆผ์ B์ ์ ๋ฌ๋์ง ์๋๋ค,,์๋ฒ๊ฐ 2๊ฐ ์ด์๋ถํฐ๋ ๊ทธ๋ ๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Redis๋ฅผ ์ถ๊ฐํ์๋ค.
Redis
Redis๋ ์ธ๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ์ ๋ฐ์ดํฐ ์ ์ฅ์๋ก, ๋น ๋ฅธ ์๋๋ฅผ ๋ฐํ์ผ๋ก ์บ์, ์ธ์
์ ์ฅ, ๋ฉ์์ง ๋ธ๋ก์ปค๋ก ๋ง์ด ์ฌ์ฉ๋๋ค.
์ด๋ฒ ํ๋ก์ ํธ์์๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ์ฉ๋๊ฐ ์๋๋ผ ์๋ฒ ๊ฐ ๋ฉ์์ง๋ฅผ ์ ๋ฌํ๊ธฐ ์ํ ์ญํ ๋ก ์ฌ์ฉํ์๋ค.
๊ธฐ๋ณธ์ ์ธ SSE ๊ตฌ์กฐ์์๋ ์๋ฒ๊ฐ 1๋์ผ ๊ฒฝ์ฐ ๋ฌธ์ ๊ฐ ์์ง๋ง, ์๋ฒ๊ฐ ์ฌ๋ฌ ๋๋ก ๋์ด๋๋ ์๊ฐ ์๋ฆผ ์ ๋ฌ์ด ๋๊ธฐ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Redis์ Pub/Sub ๊ตฌ์กฐ๋ฅผ ํ์ฉํ์๋ค.
Publisher โ Redis โ Subscriber
Publisher : ์ด๋ฒคํธ ๋ฐ์ ์ ๋ฉ์์ง ๋ฐํ
Redis : ๋ฉ์์ง๋ฅผ ์ค๊ฐ์์ ์ ๋ฌ
Subscriber : ๋ฉ์์ง๋ฅผ ๋ฐ์ ์ฒ๋ฆฌ
์ ๊ตฌ์กฐ๋ฅผ ํตํด ์ด๋ค ์๋ฒ์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋๋ผ๋ ๋ชจ๋ ์๋ฒ๊ฐ ๋์ผํ ๋ฉ์์ง๋ฅผ ์ ๋ฌ๋ฐ์ผ๋ฉฐ, ๊ฐ ์๋ฒ๋ ์์ ์ด ๊ฐ์ง๊ณ ์๋ SSE ์ฐ๊ฒฐ์ ํตํด ํด๋ผ์ด์ธํธ์๊ฒ ์๋ฆผ์ ๋ณด๋ผ ์ ์๋ค.
์ฆ Redis๋ฅผ ํตํด ์๋ฒ ๊ฐ ์์กด์ฑ ์ ๊ฑฐ, ํ์ฅ ๊ฐ๋ฅํ ๊ตฌ์กฐ, ์์ ์ ์ธ ์๋ฆผ ์ ๋ฌ+๋น ๋ฆ..์ด๋ ๊ฒ 3๊ฐ์ง๋ฅผ ์ป์ ์ ์๋ค.
SSE ๊ตฌํ
SSE๋ฅผ ๊ตฌํํ๊ธฐ ์ํด ํฌ๊ฒ 3๊ฐ์ง ๊ตฌ์ฑ์ผ๋ก ๋๋์ด ์ค๊ณํ์๋ค.
1. Emitter ์ ์ฅ์
Emitter : ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ๊ณ์ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ธฐ ์ํ ์ฐ๊ฒฐ ํต๋ก
SSE๋ ์ฌ์ฉ์๋ง๋ค ์ฐ๊ฒฐ์ด ์ ์ง๋๊ธฐ ๋๋ฌธ์ ๊ฐ ์ฌ์ฉ์๋ณ ์ฐ๊ฒฐ์ ์ ์ฅํ ๊ณต๊ฐ์ด ํ์ํ๋ค.
์ด๋ฅผ ์ํด SseEmitter๋ฅผ Map ํํ๋ก ๊ด๋ฆฌํ์๋ค.
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
key๋ ์ฌ์ฉ์ ID, value๋ ํด๋น ์ฌ์ฉ์์ SSE ์ฐ๊ฒฐ ๊ฐ์ฒด์ด๋ค. ์ด ๊ตฌ์กฐ๋ฅผ ํตํด ํน์ ์ฌ์ฉ์์๊ฒ ์๋ฆผ์ ๋ณด๋ผ ์ ์๋ค.
2. SSE ์ฐ๊ฒฐ ์์ฑ
ํด๋ผ์ด์ธํธ๊ฐ SSE ์ฐ๊ฒฐ์ ์์ํ๋ API์ด๋ค.
@GetMapping("/subscribe")
public SseEmitter subscribe(...) {
return notificationService.connect(memberId);
}
SseEmitter emitter = new SseEmitter(60 * 60 * 1000L);
emitterRepository.save(memberId, emitter);
ํด๋ผ์ด์ธํธ๊ฐ ์ด API๋ฅผ ํ ๋ฒ ํธ์ถํ๋ฉด ์๋ฒ์์ ์ฐ๊ฒฐ์ด ์ ์ง๋๋ฉฐ, ์ดํ์๋ ๋ณ๋์ ์์ฒญ ์์ด๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ์ ์ ์๋ค.
3. ์ด๋ฒคํธ ๋ฐ์
์ผ์ ์์ฑ, ํ์ผ ์ ๋ก๋ ๋ฑ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด ์ฆ์ Redis๋ก ๋ฉ์์ง๋ฅผ ์ ๋ฌํ๋ค.
notificationPublisher.publish(notification);
์ด ์์ ์์ Notification ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ค, Redis๋ก ๋ฉ์์ง๋ฅผ ๋ฐํํ๋ค.
์๋ฒ๋ ์ง์ ํด๋ผ์ด์ธํธ์๊ฒ ์๋ฆผ์ ๋ณด๋ด๋ ๊ฒ์ด ์๋๋ผ, Redis๋ฅผ ํตํด ๋ฉ์์ง๋ฅผ ์ ๋ฌํ๋ ๊ตฌ์กฐ๋ก ๊ตฌ์ฑํ์๋ค.
4. Redis Publisher
redisTemplate.convertAndSend("notification", json);
Notification ๊ฐ์ฒด๋ฅผ JSON์ผ๋ก ๋ณํํ ๋ค โnotificationโ ์ฑ๋๋ก ๋ฉ์์ง๋ฅผ ๋ฐํํ๋ค.
์ด๋ Redis๋ ํด๋น ์ฑ๋์ ๊ตฌ๋
ํ๊ณ ์๋ ๋ชจ๋ ์๋ฒ์๊ฒ ๋ฉ์์ง๋ฅผ ์ ๋ฌํ๊ฒ ๋๋ค.
5. Redis Subscriber
Redis์์ ๋ฉ์์ง๋ฅผ ์์ ํ์ฌ ์ค์ SSE๋ก ์ ๋ฌํ๋ ์ญํ ์ด๋ค.
java
public void onMessage(Message message, byte[] pattern) {
String body = new String(message.getBody());
Notification notification =
objectMapper.readValue(body, Notification.class);
SseEmitter emitter =
emitterRepository.get(notification.getMemberId());
if (emitter != null) {
emitter.send(
SseEmitter.event()
.name("notification")
.data(notification)
);
}
}
Redis์์ ๋ฉ์์ง๋ฅผ ์์ ํ๋ฉด JSON ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ฒด๋ก ๋ณํํ ๋ค, ํด๋น ์ฌ์ฉ์์ ๋ํ SSE ์ฐ๊ฒฐ(emitter)์ ์กฐํํ๋ค.
์ฌ๊ธฐ์ ํด๋น ์ฐ๊ฒฐ์ด ์กด์ฌํ ๊ฒฝ์ฐ ๊ทธ emitter๋ฅผ ํตํด ํด๋ผ์ด์ธํธ์๊ฒ ์๋ฆผ์ ์ ์กํ๋ค.
Redis โ ์๋ฒ โ ์ฌ์ฉ์ emitter โ ํด๋ผ์ด์ธํธ
์ด ํ๋ฆ์ผ๋ก ์ค์ ์๋ฆผ์ด ์ ๋ฌ๋๋ค.
์ ์ฒด ํ๋ฆ
1. ํด๋ผ์ด์ธํธ โ /subscribe ์์ฒญ (SSE ์ฐ๊ฒฐ ์์ฑ)
2. ์๋ฒ โ ์ฌ์ฉ์๋ณ emitter ์ ์ฅ
3. ๊ด๋ฆฌ์ โ ์ผ์ ์์ฑ (์ด๋ฒคํธ ๋ฐ์)
4. ์๋ฒ โ Redis Publisher ํธ์ถ
5. Redis โ ๋ฉ์์ง ์ ๋ฌ
6. Redis Subscriber โ ๋ฉ์์ง ์์
7. ์๋ฒ โ ํน์ ์ฌ์ฉ์ emitter ์กฐํ
8. SSE๋ก ์๋ฆผ push
9. ํด๋ผ์ด์ธํธ โ ์ค์๊ฐ ์๋ฆผ ์์
์ฆ, SSE๋ฅผ ํตํด ํด๋ผ์ด์ธํธ์์ ์ฐ๊ฒฐ์ ์ ์งํ๊ณ , Redis Pub/Sub์ ํตํด ์๋ฒ ๊ฐ ๋ฉ์์ง๋ฅผ ์ ๋ฌํ๋ฉฐ ํ์ฅ ๊ฐ๋ฅํ ์ค์๊ฐ ์๋ฆผ ๊ตฌ์กฐ๋ฅผ ๊ตฌ์ฑํ์๋ค.