Thiết kế: Matching & Matchmaking Feature
1. Tổng quan kiến trúc
flowchart TB
subgraph Client
WS["WebSocket\nJOIN/LEAVE/QUERY commands"]
end
subgraph WebSocket Module
MC["MatchMatchingCmdConsumer"]
OPC["OpponentPoolCmdHandler"]
IC["InvitationCmdConsumer"]
CRS["CreateRoomService\n(routing + validation)"]
end
subgraph ActiveMQ
Q_PRIZE["queue-prize-allocation-PRIZE"]
Q_PS["queue-prize-allocation-PRIZE_FOR_STREAMER"]
Q_UN["queue-prize-allocation-UNLIMITED"]
Q_INV["queue-invitation"]
Q_PVPM["queue-{settingId}-PVP-MULTI"]
Q_PVPS["queue-{settingId}-PVP"]
end
subgraph Batch Module
PAL_P["JMSPrizeAllocationListener\n(PRIZE)"]
PAL_PS["JMSPrizeForStreamerAllocationListener"]
IL["JMSInvitationListener"]
MM_P["JMSMatchingGameModePvPMessageListener"]
MM_PM["JMSMatchingGameModePvPMultiMatchMessageListener"]
SCHED["MatchMakingForAllGameModeConfig\n(scheduler)"]
end
subgraph Core
UMS["UserMatchingService\n(Redis matching state)"]
OPS["OpponentPoolService\n(Redis pool state)"]
InvSvc["InvitationCacheService\n(Redis invitation)"]
end
WS --> MC & OPC & IC
MC --> CRS
OPC --> CRS
IC --> CRS
CRS -->|"PRIZE"| Q_PRIZE
CRS -->|"PRIZE_FOR_STREAMER"| Q_PS
CRS -->|"UNLIMITED"| Q_UN
CRS -->|"invitation"| Q_INV
Q_PRIZE --> PAL_P --> UMS
Q_PS --> PAL_PS --> UMS
Q_UN --> |"opponent pool"| OPS
Q_INV --> IL --> InvSvc
Q_PVPM --> MM_PM
Q_PVPS --> MM_P
SCHED -->|"periodic matching"| UMS
UMS --> BCAST["BroadcastPubSubService"]
BCAST -->|"MATCH_FOUND"| Client
2. Multi-match vs Single-match routing
flowchart LR
CMD["JOIN_QUEUE_MATCH_MATCHING"] --> CHECK{prize_ids present?}
CHECK -->|"Yes: prizeIds[]"| MULTI["PVP_MULTI_MATCH mode\ncheckAllocationTypesByPrizeIds()"]
CHECK -->|"No: prizeId"| SINGLE["Single prize mode\ndetermineAllocationType(prizeId)"]
MULTI --> STREAM{any streaming prize?}
STREAM -->|Yes| PS_Q["queue-prize-allocation-PRIZE_FOR_STREAMER"]
STREAM -->|No| P_Q["queue-prize-allocation-PRIZE"]
SINGLE --> TYPE{reservedType?}
TYPE -->|STREAMING| PS_Q
TYPE -->|default| P_Q
3. Sequence diagrams
3.1 JOIN_QUEUE_MATCH_MATCHING (multi-match)
sequenceDiagram
participant C as Client
participant MC as MatchMatchingCmdConsumer
participant CRS as CreateRoomService
participant Q as queue-prize-allocation-PRIZE_FOR_STREAMER
participant L as JMSPrizeForStreamerAllocationListener
participant UMS as UserMatchingService
participant R as Redis
C->>MC: JOIN_QUEUE_MATCH_MATCHING\n{playableGameBoothSettingId, prize_ids: [p1, p2]}
MC->>MC: detect prize_ids (multi-match mode)
MC->>CRS: joinAutoMatchMatching(userId, settingId, prizeIds)
CRS->>CRS: validatePrizeIds(prizeIds)
CRS->>CRS: checkAllocationTypesByPrizeIds(prizeIds)
Note over CRS: Any streaming prize → PRIZE_FOR_STREAMER
CRS->>Q: sendMessageWithActionTypeMultiMatch(prizeIds, PRIZE_FOR_STREAMER)
Q->>L: onMessage(messageJson)
L->>L: validate allocationType == PRIZE_FOR_STREAMER
L->>UMS: joinAutoMatchMatchingSwitchMultiMatch(userId, settingId, prizeIds)
UMS->>R: set matching state (userId → matchingRecord)
UMS-->>L: success
L->>BCAST: broadcast JOIN_QUEUE_SUCCESS to userId
BCAST-->>C: {event_type: JOIN_QUEUE_SUCCESS}
3.2 SEND_INVITATION
sequenceDiagram
participant C as Client (Sender)
participant T as Target User
participant IC as InvitationCmdConsumer
participant CRS as CreateRoomService
participant Q as queue-invitation
participant L as JMSInvitationListener
participant R as Redis
C->>IC: SEND_INVITATION {targetUserId, prizeId, settingId}
IC->>CRS: sendInvitation(senderId, targetId, prizeId, settingId)
CRS->>Q: enqueue invitation message
Q->>L: process invitation
L->>R: save invitation record (with TTL)
L->>BCAST: push INVITATION_RECEIVED to target
BCAST-->>T: {event_type: INVITATION_RECEIVED, from: senderId}
3.3 Matchmaking scheduler
sequenceDiagram
participant SCHED as MatchMakingForAllGameModeConfig
participant UMS as UserMatchingService
participant R as Redis
participant CRJ as CreateRoomJMSService
participant BCAST as BroadcastPubSubService
loop Every N seconds
SCHED->>UMS: runMatchmaking(settingId, gameMode)
UMS->>R: get all waiting users per settingId
UMS->>UMS: group users by matching criteria
loop per matchable pair
UMS->>CRJ: createMatchedRoom(userIds, prizeIds)
CRJ->>BCAST: broadcast MATCH_FOUND + room info
BCAST-->>Client: {event_type: MATCH_FOUND, room: {...}}
end
end
4. State machine - Matching state
stateDiagram-v2
[*] --> QUEUED: JOIN_QUEUE_MATCH_MATCHING
QUEUED --> MATCHED: Scheduler finds match
QUEUED --> CANCELLED: LEAVE_QUEUE_MATCH_MATCHING
MATCHED --> [*]: Room created
note right of QUEUED: Redis key: userId → matchingRecord
note right of MATCHED: Broadcast MATCH_FOUND
note right of CANCELLED: Redis key deleted
5. Invitation TTL flow
stateDiagram-v2
[*] --> PENDING: SEND_INVITATION
PENDING --> ACCEPTED: ACCEPT_INVITATION
PENDING --> DENIED: DENY_INVITATION
PENDING --> EXPIRED: TTL elapsed
ACCEPTED --> [*]: Create room triggered
DENIED --> [*]: Notify sender
EXPIRED --> [*]: Batch cleanup
note right of EXPIRED: DeleteExpiredInvitationConfig\ncleans up stale records
6. Error handling
| Tình huống |
Event |
Reason |
| Inconsistent prize_ids allocation types |
VALIDATE_GAME |
INVALID_PRIZE_ALLOCATION_TYPE |
| Invalid prize_id/prize_ids |
VALIDATE_GAME |
INVALID_PRIZE_IDS |
| Leave queue khi không có record |
Log warning, no error |
- |
| Invitation expired khi accept |
VALIDATE_GAME |
INVITATION_EXPIRED |
| Target user busy (in room) |
VALIDATE_GAME |
TARGET_USER_BUSY |
7. Điểm cần chú ý
| # |
Vấn đề |
Chi tiết |
| 1 |
prize_id vs prize_ids |
Cả 2 format đều phải support (backward compat) |
| 2 |
Matchmaking scheduler timing |
Cần tune interval vs matching latency |
| 3 |
Invitation TTL |
Cần config TTL phù hợp UX (không quá ngắn) |
| 4 |
Leave graceful |
Leave khi không có record phải silent (current implementation đúng) |