Skip to content

Commit 82d1731

Browse files
committed
♻️ redisson 적용
1 parent 24e3293 commit 82d1731

File tree

9 files changed

+943
-322
lines changed

9 files changed

+943
-322
lines changed

backend/src/main/java/io/f1/backend/domain/game/app/RoomService.java

Lines changed: 189 additions & 148 deletions
Large diffs are not rendered by default.

backend/src/main/java/io/f1/backend/domain/game/websocket/controller/GameSocketController.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@ public void initializeRoomSocket(@DestinationVariable Long roomId, Message<?> me
3939
public void reconnect(@DestinationVariable Long roomId, Message<?> message) {
4040

4141
UserPrincipal principal = getSessionUser(message);
42-
roomService.changeConnectedStatus(roomId, principal.getUserId(), ConnectionState.CONNECTED);
43-
roomService.reconnectSendResponse(roomId, principal);
42+
roomService.changeConnectedStatusWithLock(roomId, principal.getUserId(), ConnectionState.CONNECTED);
43+
roomService.reconnectSendResponseWithLock(roomId, principal);
4444
}
4545

4646
@MessageMapping("/room/exit/{roomId}")
4747
public void exitRoom(@DestinationVariable Long roomId, Message<?> message) {
4848

4949
UserPrincipal principal = getSessionUser(message);
5050

51-
roomService.exitRoom(roomId, principal);
51+
roomService.exitRoomWithLock(roomId, principal);
5252
}
5353

5454
@MessageMapping("/room/start/{roomId}")

backend/src/main/java/io/f1/backend/domain/game/websocket/eventlistener/WebsocketEventListener.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.f1.backend.domain.game.websocket.eventlistener;
22

3+
import static io.f1.backend.domain.game.app.RoomService.ROOM_LOCK_PREFIX;
4+
import static io.f1.backend.domain.game.app.RoomService.USER_LOCK_PREFIX;
35
import static io.f1.backend.domain.game.websocket.WebSocketUtils.getSessionUser;
46

57
import io.f1.backend.domain.game.app.RoomService;
@@ -8,6 +10,7 @@
810
import io.f1.backend.domain.game.websocket.HeartbeatMonitor;
911
import io.f1.backend.domain.user.dto.UserPrincipal;
1012

13+
import io.f1.backend.global.lock.LockExecutor;
1114
import lombok.RequiredArgsConstructor;
1215
import lombok.extern.slf4j.Slf4j;
1316

@@ -24,6 +27,7 @@ public class WebsocketEventListener {
2427
private final RoomService roomService;
2528
private final DisconnectTaskManager taskManager;
2629
private final HeartbeatMonitor heartbeatMonitor;
30+
private final LockExecutor lockExecutor;
2731

2832
@EventListener
2933
public void handleDisconnectedListener(SessionDisconnectEvent event) {
@@ -41,17 +45,24 @@ public void handleDisconnectedListener(SessionDisconnectEvent event) {
4145
return;
4246
}
4347

44-
Long roomId = roomService.getRoomIdByUserId(userId);
48+
Long roomId = lockExecutor.executeWithLock(USER_LOCK_PREFIX, userId,
49+
() -> roomService.getRoomIdByUserId(userId));
4550

46-
roomService.changeConnectedStatus(roomId, userId, ConnectionState.DISCONNECTED);
51+
lockExecutor.executeWithLock(
52+
ROOM_LOCK_PREFIX, roomId, () -> roomService.changeConnectedStatus(roomId, userId,
53+
ConnectionState.DISCONNECTED));
4754

4855
taskManager.scheduleDisconnectTask(
49-
userId,
50-
() -> {
51-
if (ConnectionState.DISCONNECTED.equals(
56+
userId,
57+
() -> {
58+
lockExecutor.executeWithLock(ROOM_LOCK_PREFIX, roomId,
59+
() -> {
60+
if (ConnectionState.DISCONNECTED.equals(
5261
roomService.getPlayerState(userId, roomId))) {
53-
roomService.disconnectOrExitRoom(roomId, principal);
62+
roomService.disconnectOrExitRoom(roomId, principal);
63+
}
5464
}
55-
});
65+
);
66+
});
5667
}
5768
}

backend/src/main/java/io/f1/backend/global/exception/errorcode/CommonErrorCode.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ public enum CommonErrorCode implements ErrorCode {
1313
INTERNAL_SERVER_ERROR(
1414
"E500001", HttpStatus.INTERNAL_SERVER_ERROR, "서버에러가 발생했습니다. 관리자에게 문의해주세요."),
1515
INVALID_JSON_FORMAT("E400008", HttpStatus.BAD_REQUEST, "요청 형식이 올바르지 않습니다. JSON 문법을 확인해주세요."),
16-
LOCK_ACQUISITION_FAILED("E409003", HttpStatus.CONFLICT, "다른 요청이 작업 중입니다. 잠시 후 다시 시도해주세요.");
16+
LOCK_ACQUISITION_FAILED("E409003", HttpStatus.CONFLICT, "다른 요청이 작업 중입니다. 잠시 후 다시 시도해주세요."),
17+
LOCK_INTERRUPTED("E500004", HttpStatus.INTERNAL_SERVER_ERROR, "작업 중 락 획득이 중단되었습니다. 다시 시도해주세요.");
18+
1719

1820
private final String code;
1921

backend/src/main/java/io/f1/backend/global/lock/DistributedLockAspect.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
@RequiredArgsConstructor
2121
public class DistributedLockAspect {
2222

23-
private static final String LOCK_KEY_FORMAT = "lock:%s:{%s}";
23+
public static final String LOCK_KEY_FORMAT = "lock:%s:{%s}";
2424

2525
private final RedissonClient redissonClient;
2626

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package io.f1.backend.global.lock;
2+
3+
import static io.f1.backend.global.lock.DistributedLockAspect.LOCK_KEY_FORMAT;
4+
5+
import io.f1.backend.global.exception.CustomException;
6+
import io.f1.backend.global.exception.errorcode.CommonErrorCode;
7+
import java.util.concurrent.TimeUnit;
8+
import java.util.function.Supplier;
9+
import lombok.RequiredArgsConstructor;
10+
import lombok.extern.slf4j.Slf4j;
11+
import org.redisson.api.RLock;
12+
import org.redisson.api.RedissonClient;
13+
import org.springframework.stereotype.Component;
14+
15+
@Slf4j
16+
@Component
17+
@RequiredArgsConstructor
18+
public class LockExecutor {
19+
20+
private final RedissonClient redissonClient;
21+
22+
// 시간단위를 초로 변경
23+
private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
24+
25+
// 락 점유를 위한 대기 시간
26+
private static final long DEFAULT_WAIT_TIME = 5L;
27+
28+
// 락 점유 시간
29+
private static final long DEFAULT_LEASE_TIME = 3L;
30+
31+
public <T> T executeWithLock(String prefix, Object key, Supplier<T> supplier) {
32+
String lockKey = formatLockKey(prefix, key);
33+
RLock rlock = redissonClient.getLock(lockKey);
34+
35+
boolean acquired = false;
36+
try {
37+
acquired = rlock.tryLock(DEFAULT_WAIT_TIME, DEFAULT_LEASE_TIME, DEFAULT_TIME_UNIT);
38+
39+
if(!acquired) {
40+
log.warn("[DistributedLock] Lock acquisition failed: {}", key);
41+
throw new CustomException(CommonErrorCode.LOCK_ACQUISITION_FAILED);
42+
}
43+
log.info("[DistributedLock] Lock acquired: {}", key);
44+
45+
return supplier.get();
46+
} catch (InterruptedException e) {
47+
Thread.currentThread().interrupt();
48+
throw new CustomException(CommonErrorCode.LOCK_INTERRUPTED);
49+
} finally {
50+
if (acquired && rlock.isHeldByCurrentThread()) {
51+
rlock.unlock();
52+
log.info("[DistributedLock] Lock released: {}", key);
53+
}
54+
}
55+
}
56+
57+
public void executeWithLock(String prefix, Object key, Runnable runnable) {
58+
executeWithLock(prefix, key, () -> {
59+
runnable.run();
60+
return null;
61+
});
62+
}
63+
64+
65+
private String formatLockKey(String prefix, Object value) {
66+
return String.format(LOCK_KEY_FORMAT, prefix, value);
67+
}
68+
}

backend/src/test/java/io/f1/backend/domain/game/app/GameFlowTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ static class TestRoomService extends RoomService {
222222
private final Map<Long, Room> rooms = new ConcurrentHashMap<>();
223223

224224
public TestRoomService() {
225-
super(null, null, null, null, null, null);
225+
super(null, null, null, null, null, null,null);
226226
}
227227

228228
@Override

0 commit comments

Comments
 (0)