0. ํด๊ฒฐํด์ผ ํ๋(๊ตฌํํด์ผ ํ๋) ๋ฌธ์ (์๊ตฌ ์ฌํญ)
- ์ฌ์ดํธ์ ์ค์๊ฐ์ผ๋ก ์ ์ํ ์ฌ๋์ ๋ชฉ๋ก๊ณผ ํด๋น ์ฌ์ฉ์๋ค์ด ํ์ฌ ์ด๋ค ํ์ด์ง, ํญ์ ์ ์ํด์๋์ง๋ฅผ ํ์ธํด๋ณด๊ณ ์ ํ๋ค
- http, rest API๋ฅผ ํตํ ๊ตฌํ๋ ๊ฐ๋ฅํ๊ฒ ์ง๋ง, ์ด๋ ์ง์ ํ "์ค์๊ฐ"์ ์๋ฏธํ๋ ๊ฒ์ด ์๋๋ฉฐ, ์กฐ๊ธ์ด๋ผ๋ ์ฐ๊ฒฐ์ด ๋์ด์ง๊ธฐ ๋๋ฌธ์ ์น์์ผ(WebSocket) ํ๋กํ ์ฝ์ ํตํ ์ ๊ทผ์ ์์ํ๋ค
- ํ์ฌ ํ๋ก์ ํธ ํ๊ฒฝ์์๋ ์คํ๋ง์ ์ฌ์ฉํ๊ณ ์๊ณ , ์คํ๋ง์์ ์น์์ผ ํ๋กํ ์ฝ์ ํ์ฉํ ๋ ์์ ์น์์ผ์ ์ฌ์ฉํด๋ ๋์ง๋ง, Stomp ํ๋กํ ์ฝ์ ์ฌ์ฉํด ๋์ฑ ๊ฐํธํ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ฏ๋ก, ์ด ๊ฐ๋ฐ์ ์งํํ๋ค
-> ์ฌ์ฉํ ๊ธฐ์ฌ ์คํ
1) stomp
2) spring messing
3) event Listener
4) ConcurrentHashMap
1. ๊ตฌํ ๊ฐ trade - off
1) ์์ ์น์์ผ vs stomp
- ์น์์ผ์ ๊ธฐ์ด์ ์ธ ํ๋กํ ์ฝ์ด๊ธฐ ๋๋ฌธ์, ์ค์๊ฐ ํต์ ์ ํ์ํ ๋ฉ์ธ์ง ๋ผ์ฐํ , ๋ฉ์ธ์ง์ ์ธ์ ๊ด๋ฆฌ, ๋ธ๋ก๋ ์บ์คํ ์ ๋ํ ๊ธฐ๋ฅ ๊ตฌํ์ ๋ชจ๋ ํด์ผํ๋ค.
stomp์์๋ ํด๋น ๊ธฐ๋ฅ์ ๋ณด๋ค ๊ฐํธํ๊ฒ ๊ตฌํํ ์ ์๋๋ก ๋ฉ์ธ์ง ๋ธ๋ก์ปค๋ฅผ ์ง์ํ๋ฉฐ,
๋ํ ๋ฉ์ธ์ง์ ๋ํด ํค๋์ ๋ฐ๋๋ฅผ ๋ช ํํ๊ฒ ๊ตฌ๋ถํ๊ณ ์์ด ๋ฉ์ธ์ง ์ก์์ ์ด ๋ณด๋ค ๊ฐํธํ๊ณ ๋ช ํํ๊ฒ ์งํ์ด ๊ฐ๋ฅํ๋ค
2) ๋ฉ์ธ์ง ๋ธ๋ก์ปค : spring messging, rabbitMQ, kafka
- spring messging์ ๋ ๋ฆฝ์ ์ธ ๋ฉ์ธ์ง ๋ธ๋ก์ปค์ ๊ฐ๋ ์ด ์๋๋ผ, ์คํ๋ง์์ ์ ๊ณตํ๋ ๋ฉ์ธ์ง ๋ธ๋ก์ปค ์ถ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ค์ํ ๋ฉ์ธ์ง ๋ธ๋ก์ปค๋ฅผ ์ฝ๋์ ๋ณ๊ฒฝ์ด ๊ฑฐ์ ์์ด ์์ ๋กญ๊ฒ ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ๋ค๋ ์ฅ์ ์ ์ ๊ณตํ๋ค. ๋ฉ์ธ์ง ํ ํ๋ฆฟ์ผ๋ก messagingTemplate ์ธ์คํด์ค๋ฅผ ์ ๊ณตํ๋ฉฐ, ์ด ๋์ spring - rabbit ๋๋ spring - kafka ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ ์ ์๋ค.
+ ํ์ฌ ํ๋ก์ ํธ์์ rabbitMQ์ kafka๋ฅผ ํตํ ๋ฉ์ธ์ง ๊ตฌํ์ ์ค๋ฒ์คํ์ด์ด์,(ํธ๋ํฝ์ด ๋์ง ์๋ค) ํด๋น ๊ธฐ์ ์ ํ์ฉํ ๊ฐ๋ฐ์ ์์ฝ์ง๋ง, ๋ฌ๋์ปค๋ธ๋ฅผ ๊ณ ๋ คํด ๋ค์ ๊ธฐํ๋ก ๋ฏธ๋ค๋ค
3) ๋์์ฑ ๊ด๋ฆฌ๋ฅผ ์ํ concurrentHashMap ์๋ฃ๊ตฌ์กฐ ์ฌ์ฉ
- spring messging ํ ํ๋ฆฟ๋ง์ ์ด์ฉํด ๋ฉ์ธ์ง ๊ด๋ฆฌ๋ฅผ ํ๊ธฐ ๋๋ฌธ์, ์ฌ์ฉ์ ์ถ์ ๋ฐฉ์์
๊ตฌํํ ๋ ์ด๋ค ๋ฐฉ์์ผ๋ก ํด์ผํ๋ ์ง ๊ณ ๋ฏผ์ ๋ง์ด ํ๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉ์๋ฅผ ํน์ ํ ๋ ๋ง์ด ์ฌ์ฉํ๋ ์ธ์ ์ผ๋ก ์ฌ์ฉ์๋ฅผ ํน์ ํ๋ ค๊ณ ํ๋ฉด ๋ฉ์ธ์ง ๋ถ๋ถ์์ ์ธ์ ์ด ๋ฉ์ธ์ง๋ง๋ค ๊ฐ๊ฐ ์๋ก ์์ฑ๋๋ ๊ฒ์ ์๊ฒ ๋์๋ค. ๋ฐ๋ผ์ ๊ฐ์ ๋ธ๋ผ์ฐ์ ๋ด๋ผ๋ ๋ค๋ฅธ ํญ ๋ฑ์ผ๋ก ๋ฉ์ธ์ง๋ฅผ ํด๋ผ์ด์ธํธ์์ ์ก์ ํ๋ ๊ฒฝ์ฐ ๋ค๋ฅธ ์ฌ์ฉ์๋ก ์ธ์ํ๊ธฐ ๋๋ฌธ์ ํด๋น ๋ฌธ์ ํด๊ฒฐ์ด ํ์ํ๋ค
(์ถํ IP๋ฅผ ํตํ ๊ด๋ฆฌ๋ก ๋ฆฌํฉํ ๋ง์ด ๊ฐ๋ฅํ ์ง ๋ถ์ํด์ผํ๋ค)
๊ทธ๋์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ด์ Dto๋ฅผ ์์ฑํ๊ณ , ์ด๋ฅผ concurentHashMap์ผ๋ก ๊ฐ์ผ ์ ์์ ๋ชฉ๋ก, ํด๋น ์ฌ์ฉ์์ ์ธ์ ์ ๊ด๋ฆฌํ๋ ๋ชฉ๋ก์ ๋ง์ฐฌ๊ฐ์ง๋ก concurrentHashMap์ผ๋ก ๊ตฌํํ๋ค. ํด๋น ์ฌ์ฉ์ ๋ชฉ๋ก์ ๋ํด์๋ ์ฌ๋ฌ ์ค๋ ๋์์ ๋์์ ์ ๊ทผํ๊ธฐ ๋๋ฌธ์, ์ค๋ ๋ ์์ ๋ฐ ๋์์ฑ ๋ณด์ฅ์ด ๋งค์ฐ ์ค์ํ๋ค.
concurrentHashMap์, ๋จผ์ ๋ด๋ถ ๋ฐ์ดํฐ์ ๊ฐ๊ฐ ๋ ๋ฆฝ์ ์ธ ๋ฝ์ ์ฌ์ฉํ๋ค. ๋ฐ๋ผ์ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์ ๊ทผํด๋ ๋ฝ ๊ฒฝ์์ด ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ๋ฎ์์ง๋ค. ๋ํ ์ค๋ ๋๊ฐ ์ฆ๊ฐํด๋ ์ฑ๋ฅ ์ ํ์ ์ํฅ์ด ์ต์ํ๋๋๋ก ์ค๊ณ๋ ์๋ฃ๊ตฌ์กฐ๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ์ ์ธ hashMap ๋ณด๋ค ํจ์ฌ ๋์ ์ ์ด ๋ง์๋ค
(์ถํ ํด๋ฌ์คํฐ ํ๊ฒฝ์ด ๋์ ๋๋ค๋ฉด, ์ด ๋ถ๋ถ์ ๋ํด redis ๋ฑ์ ์๋จ์ ํตํด ์ฌ์ฉ์ ๋ชฉ๋ก ๊ด๋ฆฌ๋ฅผ ๊ณ ๋ํํ ์ ์์ ๊ฒ์ด๋ค)
2. ์ฝ๋
1) ์ฐ๊ฒฐ ํด์ ์,
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
String sessionId = accessor.getSessionId(); // ์ฐ๊ฒฐ ํด์ ๋ ์ธ์
ID ์ถ์ถ
if (sessionId != null) {
String email = userSessions.get(sessionId) != null ? userSessions.get(sessionId).get("email") : null;
if (email == null) {
logger.warn("No email found for sessionId: {}", sessionId);
return;
}
removeUserSession(sessionId, email);
sendClientListOnline();
}
}
- ๋ฉ์ธ์ง ํด์ ์ด๋ฒคํธ ๋ฐ์ ํธ๋ค๋ฌ๋ฅผ ํตํด ๋ค์๊ณผ ๊ฐ์ ๋ก์ง ๊ตฌํ
- userSessions๋ ๋์ผ ์ฌ์ฉ์์ ๋ค์ค ๋ฉ์ธ์ง ์ธ์ ๊ด๋ฆฌ๋ฅผ ์ํ map์ผ๋ก ์ฌ์ฉ์๋ฅผ ํน์ ํ๊ธฐ ์ํด ์ฌ๊ธฐ์๋ ์ด๋ฉ์ผ์ ์ฌ์ฉํ๋ค
- ์ด๋ฒคํธ์ ์ธ์ ์์ด๋๋ฅผ ๊ฐ์ง ์ด๋ฉ์ผ(์ฌ์ฉ์)๋ฅผ ์ฐพ๊ณ ๊ทธ ์ฌ์ฉ์๋ฅผ ์ ์์ ๋ชฉ๋ก์์ ์ ๊ฑฐํ ํ, ๋ธ๋ก๋ ์บ์คํ ์ผ๋ก ํด๋น ๋ณ๊ฒฝ ์ฌํญ์ ๋ฐ์ํ๋ค
2) ๋ค์ค ์ธ์ ์์ ์ต์ ์ธ์ ์ ์ ๋ณด๋ก ์ ๋ฐ์ดํธ ํ๊ธฐ ์ํ ์ต์ ์ธ์ ์ฐพ๊ธฐ
private ConcurrentHashMap<String, String> findLatestUserInfo(String targetEmail) {
return userSessions.entrySet().stream()
.filter(entry -> targetEmail.equals(entry.getValue().get("email")))
.max(Comparator.comparing(entry -> {
String timeStamp = entry.getValue().get("timestamp");
return timeStamp != null ? timeStamp : "";
}))
.map(Map.Entry::getValue)
.orElse(null);
}
-๋ค์ค ์ธ์ ์ ๋จ์ํ ์ ์ฅํ๊ธฐ๋ง ํ๋ ๊ฒ์ด ์๋๋ผ, ์ธ์ ์์ ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ธ ์๊ฐ์ timestamp๋ก ๊ธฐ๋กํด ๊ฐ์ฅ ์ต์ ์ ์ธ์ ์ผ๋ก ํ์ฌ ์ฌ์ฉ์๊ฐ ์ด๋ ํ์ด์ง์ ์๋์ง๋ฅผ ํ์ธํ ์ ์๋ ๋ก์ง์ด๋ค
- ๋ํ ์ฌ์ฉ์๊ฐ ์ธ์ ์ ์ข ๋ฃํ ๊ฒฝ์ฐ, ๋์ผ ์ฌ์ฉ์์ ๊ฐ์ฅ ์ต์ ์ธ์ ์ ์ฐพ๊ธฐ์ํด์๋ ํ์ฉ๋๋ค
3) ์ฌ์ฉ์ ์ถ์ ๋ก์ง
public void trackingUserInfo(ConcurrentHashMap<String, String> userInfo) {
String email = userInfo.get("email");
if (email == null) {
logger.warn("Email is null, skipping user tracking");
return;
}
if (!connectedUsers.containsKey(email)) {
logger.debug("Adding user with email: {}", email);
}
makeUserSession(userInfo);
ConcurrentHashMap<String, String> latestUserInfo = findLatestUserInfo(email);
if (latestUserInfo != null) {
connectedUsers.put(email, userInfo); // ์ฌ์ฉ์ ์ ๋ณด ์
๋ฐ์ดํธ
}
sendClientListOnline();
}
- ํด๋ผ์ด์ธํธ์์ ๋ณด๋ด์ค ์ ๋ณด์์ ์ด๋ฉ์ผ์ ์ฐพ์ ํด๋น ์ ๋ณด์ ํ์ฌ ํ์ด์ง ๋ฑ์ ๋ฐํ์ผ๋ก ํ ์ ์ ์ธ์ ๋ฆฌ์คํธ์ ๋ฑ๋กํ๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ธ์ ์ ๋ฐํ์ผ๋ก ์ ์์ ๋ชฉ๋ก์ ์ ๋ฐ์ดํธํ๊ณ ๋ธ๋ก๋ ์บ์คํ ์ ํตํด ํด๋ผ์ด์ธํธ๋ก ๋ฐํํ๋ค
4) ์ธ์ ์ ๊ฑฐ ๋ก์ง
- ์ฐ๊ฒฐ ํด์ ์ด๋ฒคํธ ๋ฐ์ ์, 2)์ ์ต์ ์ ๋ณด ์ฐพ๊ธฐ ๋ก์ง์ ํตํด ๋จผ์ ๋จ์์๋ ์ธ์ ์ด ์๋์ง๋ฅผ ํ์ ํ๋ค. ์๋ ๊ฒฝ์ฐ์ ํด๋น ์ฌ์ฉ์๋ฅผ ์ฌ์ฉ์ ๋ชฉ๋ก์์ ์ ๊ฑฐํ๋ค. ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ์๋ 3)์ ์์์ ์ฐพ์ ์ต์ ์ ์ ์ธ์ ์ ๊ฐ์ง๊ณ ์ฌ ํธ์ถํ์ฌ ํด๋น ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์ ๋ฐ์ดํธํ๋ค
3. ์๊ฐ
- ๋ง๋ค๋ฉด์ ์๋นํ ์ฌ๋ฐ์๋ค. ์ค๊ณ ๋ถ๋ถ์์ ์ค๋ฒ์คํ์ด ๋์ง ์๊ธฐ ์ํด ๊ณ ๋ฏผ์ ํ๊ณ , ํผ์ ๊ตฌํํ๊ธฐ ๋๋ฌธ์ ์ผ์ ์๋ ๋ถ๋ด์ด ์์ด์ผ ํ๋ค. ๊ทธ๋ฐ ์ ์์ ์๋นํ ๊น๋ํ๊ณ ๋ ผ๋ฆฌ์ ์ผ๋ก๋ ์์ฑ๋ ์๋ ๊ธฐ๋ฅ์ ๊ตฌํํ ๋ฏ ํ์ฌ ๋ณด๋์์๋ค
- ์ถํ ์ค๊ณ๋ฅผ ํตํด ์ผ๋ง๋ ์ง ํ์ฅํ ์ ์๋ ํ์ฅ์ฑ์๋ ์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ์ด ์ถฉ๋ถํ ์ฝ๋๋ฅผ ๊ตฌํํ ์ ์ด ๋งค์ฐ ์ข์๊ณ , ๋์์ฑ์ ๊ณ ๋ คํ ์ ๋ ๋งค์ฐ ๋ป์๋ ์ฝ๋์๋ค
'Spring๐' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring Data JPA : Auditing & HATEOAS (0) | 2023.02.13 |
---|---|
T ์์นด๋ฐ๋ฏธ : JPA ํ๋ก๊ทธ๋๋ฐ ๊ธฐ์ด (3) (0) | 2023.02.10 |
SpringData ์ฟผ๋ฆฌ์ QueryDSL (0) | 2023.02.08 |
SpringData JPA ํ์ด์ง๊ณผ ์ ๋ ฌ (0) | 2023.02.03 |
SpringData ๊ตฌ์กฐ ๋ฐ JpaRepository ์๋ฆฌ (0) | 2023.02.03 |