From 1eb4e7a4d9d8bc83ac5da6e3f3af77980b85681c Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Mon, 23 Dec 2024 14:56:36 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20[BE]=20updatePos=20socket=20batch?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../redis/subscribers/player.subscriber.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/BE/src/game/redis/subscribers/player.subscriber.ts b/BE/src/game/redis/subscribers/player.subscriber.ts index 3b2b577..e675a49 100644 --- a/BE/src/game/redis/subscribers/player.subscriber.ts +++ b/BE/src/game/redis/subscribers/player.subscriber.ts @@ -9,6 +9,9 @@ import { SurvivalStatus } from '../../../common/constants/game'; @Injectable() export class PlayerSubscriber extends RedisSubscriber { + private positionUpdates: Map = new Map(); // Map + private positionUpdatesForDead: Map = new Map(); // Map + constructor(@InjectRedis() redis: Redis) { super(redis); } @@ -25,6 +28,15 @@ export class PlayerSubscriber extends RedisSubscriber { const key = `Player:${playerId}`; await this.handlePlayerChanges(key, playerId, server); }); + + setInterval(() => { + this.positionUpdates.forEach((queue, gameId) => { + if (queue.length > 0) { + const batch = queue.splice(0, queue.length); //O(N) + server.to(gameId).emit(SocketEvents.UPDATE_POSITION, batch); + } + }); + }, 100); } private extractPlayerId(channel: string): string | null { @@ -86,7 +98,13 @@ export class PlayerSubscriber extends RedisSubscriber { const isAlivePlayer = await this.redis.hget(REDIS_KEY.PLAYER(playerId), 'isAlive'); if (isAlivePlayer === SurvivalStatus.ALIVE) { - server.to(gameId).emit(SocketEvents.UPDATE_POSITION, updateData); + // 1. Map에 배열을 만들고 set + if (!this.positionUpdates.has(gameId)) { + this.positionUpdates.set(gameId, []); // 빈 배열로 초기화 + } + this.positionUpdates.get(gameId).push(updateData); + + // server.to(gameId).emit(SocketEvents.UPDATE_POSITION, updateData); } else if (isAlivePlayer === SurvivalStatus.DEAD) { const players = await this.redis.smembers(REDIS_KEY.ROOM_PLAYERS(gameId)); const pipeline = this.redis.pipeline(); From bfde31035fed1d6f9eeafa451e0252739dfe57d4 Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Mon, 23 Dec 2024 16:16:13 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20[BE]=20updatePos=20metric=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../redis/subscribers/player.subscriber.ts | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/BE/src/game/redis/subscribers/player.subscriber.ts b/BE/src/game/redis/subscribers/player.subscriber.ts index e675a49..6ec588e 100644 --- a/BE/src/game/redis/subscribers/player.subscriber.ts +++ b/BE/src/game/redis/subscribers/player.subscriber.ts @@ -6,13 +6,28 @@ import { Namespace } from 'socket.io'; import SocketEvents from '../../../common/constants/socket-events'; import { REDIS_KEY } from '../../../common/constants/redis-key.constant'; import { SurvivalStatus } from '../../../common/constants/game'; +import { MetricService } from '../../../metric/metric.service'; @Injectable() export class PlayerSubscriber extends RedisSubscriber { private positionUpdates: Map = new Map(); // Map private positionUpdatesForDead: Map = new Map(); // Map - - constructor(@InjectRedis() redis: Redis) { + private positionUpdatesMetrics: Map = new Map(); // Map + + // 메트릭 코드 + // const startedAt = process.hrtime(); + // + // const endedAt = process.hrtime(startedAt); + // const delta = endedAt[0] * 1e9 + endedAt[1]; + // const executionTime = delta / 1e6; + // + // this.metricService.recordResponse(changes, 'success'); <- 이 친구는 현재 사용 x + // this.metricService.recordLatency(changes, 'response', executionTime); + + constructor( + @InjectRedis() redis: Redis, + private metricService: MetricService + ) { super(redis); } @@ -21,21 +36,45 @@ export class PlayerSubscriber extends RedisSubscriber { await subscriber.psubscribe('__keyspace@0__:Player:*'); subscriber.on('pmessage', async (_pattern, channel, message) => { + const startedAt = process.hrtime(); + const playerId = this.extractPlayerId(channel); if (!playerId || message !== 'hset') { return; } - const key = `Player:${playerId}`; - await this.handlePlayerChanges(key, playerId, server); + const playerKey = REDIS_KEY.PLAYER(playerId); + const gameId = await this.redis.hget(playerKey, 'gameId'); + const changes = await this.redis.get(`${playerKey}:Changes`); + + if (!this.positionUpdatesMetrics.has(gameId)) { + this.positionUpdatesMetrics.set(gameId, []); // 빈 배열로 초기화 + } + this.positionUpdatesMetrics.get(gameId).push(startedAt); + + await this.handlePlayerChanges(changes, playerId, server); }); setInterval(() => { + // 배치 처리 this.positionUpdates.forEach((queue, gameId) => { if (queue.length > 0) { const batch = queue.splice(0, queue.length); //O(N) server.to(gameId).emit(SocketEvents.UPDATE_POSITION, batch); } }); + + // 배치 처리에 관한 메트릭 측정 + this.positionUpdatesMetrics.forEach((metrics) => { + if (metrics.length > 0) { + const batch = metrics.splice(0, metrics.length); + batch.forEach((startedAt) => { + const endedAt = process.hrtime(startedAt); + const delta = endedAt[0] * 1e9 + endedAt[1]; + const executionTime = delta / 1e6; + this.metricService.recordLatency('Position', 'response', executionTime); + }); + } + }); }, 100); } @@ -44,8 +83,7 @@ export class PlayerSubscriber extends RedisSubscriber { return splitKey.length === 2 ? splitKey[1] : null; } - private async handlePlayerChanges(key: string, playerId: string, server: Namespace) { - const changes = await this.redis.get(`${key}:Changes`); + private async handlePlayerChanges(changes: string, playerId: string, server: Namespace) { const playerKey = REDIS_KEY.PLAYER(playerId); const playerData = await this.redis.hgetall(playerKey); From 5f50df143fbc8d2b9fba44f26de48670c63e3caf Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Mon, 23 Dec 2024 16:16:49 +0900 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20[BE]=20=EC=A3=BC=EC=84=9D=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/game/redis/subscribers/player.subscriber.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/BE/src/game/redis/subscribers/player.subscriber.ts b/BE/src/game/redis/subscribers/player.subscriber.ts index 6ec588e..58ab69c 100644 --- a/BE/src/game/redis/subscribers/player.subscriber.ts +++ b/BE/src/game/redis/subscribers/player.subscriber.ts @@ -14,16 +14,6 @@ export class PlayerSubscriber extends RedisSubscriber { private positionUpdatesForDead: Map = new Map(); // Map private positionUpdatesMetrics: Map = new Map(); // Map - // 메트릭 코드 - // const startedAt = process.hrtime(); - // - // const endedAt = process.hrtime(startedAt); - // const delta = endedAt[0] * 1e9 + endedAt[1]; - // const executionTime = delta / 1e6; - // - // this.metricService.recordResponse(changes, 'success'); <- 이 친구는 현재 사용 x - // this.metricService.recordLatency(changes, 'response', executionTime); - constructor( @InjectRedis() redis: Redis, private metricService: MetricService