-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(app-load-balancing): add logic to balance game load
- Loading branch information
1 parent
0f38224
commit 2fcc81c
Showing
11 changed files
with
265 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
packages/game-app/src/loadBalancer/apis/callUpdateOnlineStatus.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import firebase from 'firebase'; | ||
import Database = firebase.database.Database; | ||
import { RTDBPaths, Status } from '@pipeline/common'; | ||
|
||
export async function startListenToOnlineStatus( | ||
rtdb: Database, | ||
uid: string, | ||
gameId: string, | ||
onConnect: () => void, | ||
onDisconnect: () => void, | ||
) { | ||
const userStatusDatabaseRef = rtdb.ref(`/${RTDBPaths.Statuses}/${uid}`); | ||
|
||
const isOfflineForDatabase = { | ||
state: 'offline' as const, | ||
updatedAt: firebase.database.ServerValue.TIMESTAMP, | ||
gameId: null, | ||
} as Status; | ||
|
||
const isOnlineForDatabase = { | ||
state: 'online' as const, | ||
updatedAt: firebase.database.ServerValue.TIMESTAMP, | ||
gameId, | ||
} as Status; | ||
|
||
rtdb.ref('.info/connected').on('value', async snapshot => { | ||
if (snapshot.val() === false) return; | ||
await userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase, () => { | ||
onDisconnect(); | ||
}); | ||
await userStatusDatabaseRef.set(isOnlineForDatabase, () => { | ||
onConnect(); | ||
}); | ||
}); | ||
} | ||
|
||
export async function stopListenToOnlineStatus(rtdb: Database) { | ||
rtdb.ref('.info/connected').off('value'); | ||
} | ||
|
||
export async function callUpdateOnlineStatus(rtdb: Database, uid: string, gameId: string, state: 'online' | 'offline') { | ||
const userStatusDatabaseRef = firebase.database().ref(`/${RTDBPaths.Statuses}/${uid}`); | ||
|
||
const statusForDatabase = { | ||
state, | ||
updatedAt: firebase.database.ServerValue.TIMESTAMP, | ||
gameId, | ||
} as Status; | ||
|
||
await userStatusDatabaseRef.set(statusForDatabase); | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/game-app/src/loadBalancer/apis/selectBestRTDBInstance.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import axios from 'axios'; | ||
|
||
export default async function selectBestRTDBInstance(gameId: string): Promise<string> { | ||
const res = await axios.get(`url?gameId=${gameId}`); | ||
return res.data.bestRTDBInstanceId; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { | ||
initializeRTDB, | ||
pollUpdateStatusSagaWatcher, | ||
startListenToOnlineStatusSaga, | ||
stopListenToOnlineStatusSaga, | ||
updateOnlineStatusSaga, | ||
watchStatusChannel, | ||
} from './updateOnlineStatus'; | ||
import { all } from 'redux-saga/effects'; | ||
|
||
export default function* loadBalancerSaga() { | ||
yield all([ | ||
initializeRTDB(), | ||
updateOnlineStatusSaga(), | ||
startListenToOnlineStatusSaga(), | ||
stopListenToOnlineStatusSaga(), | ||
pollUpdateStatusSagaWatcher(), | ||
watchStatusChannel(), | ||
]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { call, put, select, takeEvery, delay, take, race } from 'redux-saga/effects'; | ||
import { channel } from 'redux-saga'; | ||
import { actions, selectors as loadBalancerSelectors } from '../slice'; | ||
import { actions as loadGameActions, selectors as gameSelectors } from '../../gameView/slice'; | ||
import { addRequestStatusManagement } from '@pipeline/requests-status'; | ||
import { | ||
callUpdateOnlineStatus, | ||
startListenToOnlineStatus, | ||
stopListenToOnlineStatus, | ||
} from '../apis/callUpdateOnlineStatus'; | ||
import firebase from 'firebase'; | ||
import { AuthUser, selectors as authSelectors } from '@pipeline/auth'; | ||
import Database = firebase.database.Database; | ||
|
||
const statusChannel = channel(); | ||
|
||
function* executeUpdateOnlineStatus(action: ReturnType<typeof actions.updateOnlineStatus>) { | ||
const rtdb: Database = yield select(loadBalancerSelectors.getRTDB); | ||
const user: AuthUser = yield select(authSelectors.getCurrentUser); | ||
const gameId: string = yield select(gameSelectors.getSelectedGameId); | ||
yield call(callUpdateOnlineStatus, rtdb, user.id, gameId, action.payload); | ||
yield put(actions.updateOnlineStatusSuccess(action.payload)); | ||
} | ||
|
||
export function* updateOnlineStatusSaga() { | ||
yield takeEvery( | ||
actions.updateOnlineStatus, | ||
addRequestStatusManagement(executeUpdateOnlineStatus, 'loadBalancer.status'), | ||
); | ||
} | ||
|
||
function* executeStartListenToOnlineStatus(action: ReturnType<typeof actions.startListenToOnlineStatus>) { | ||
const rtdb: Database = yield select(loadBalancerSelectors.getRTDB); | ||
const user: AuthUser = yield select(authSelectors.getCurrentUser); | ||
const gameId: string = yield select(gameSelectors.getSelectedGameId); | ||
yield call( | ||
startListenToOnlineStatus, | ||
rtdb, | ||
user.id, | ||
gameId, | ||
() => { | ||
statusChannel.put(actions.updateOnlineStatusSuccess('online')); | ||
}, | ||
() => { | ||
statusChannel.put(actions.updateOnlineStatusSuccess('offline')); | ||
}, | ||
); | ||
} | ||
|
||
export function* startListenToOnlineStatusSaga() { | ||
yield takeEvery(actions.startListenToOnlineStatus, executeStartListenToOnlineStatus); | ||
} | ||
|
||
function* executeStopListenToOnlineStatus(action: ReturnType<typeof actions.stopListenToOnlineStatus>) { | ||
const rtdb: Database = yield select(loadBalancerSelectors.getRTDB); | ||
yield call(stopListenToOnlineStatus, rtdb); | ||
} | ||
|
||
export function* stopListenToOnlineStatusSaga() { | ||
yield takeEvery(actions.stopListenToOnlineStatus, executeStopListenToOnlineStatus); | ||
} | ||
|
||
function* pollUpdateStatusSagaWorker() { | ||
while (true) { | ||
const rtdb: Database = yield select(loadBalancerSelectors.getRTDB); | ||
const user: AuthUser = yield select(authSelectors.getCurrentUser); | ||
const gameId: string = yield select(gameSelectors.getSelectedGameId); | ||
yield call(callUpdateOnlineStatus, rtdb, user.id, gameId, 'online'); | ||
yield put(actions.updateOnlineStatusSuccess('online')); | ||
yield call(delay, 5000); | ||
} | ||
} | ||
|
||
export function* pollUpdateStatusSagaWatcher() { | ||
while (true) { | ||
yield take(actions.startPollingOnlineStatus); | ||
yield race([call(pollUpdateStatusSagaWorker), take(actions.stopPollingOnlineStatus)]); | ||
} | ||
} | ||
|
||
export function* watchStatusChannel() { | ||
while (true) { | ||
const action = yield take(statusChannel); | ||
yield put(action); | ||
} | ||
} | ||
|
||
function* executeInitializeRTDB(action: ReturnType<typeof loadGameActions.saveGame>) { | ||
const app = firebase.initializeApp({ | ||
databaseURL: `https://${action.payload.rtdbInstance}.firebaseio.com`, | ||
}); | ||
const rtdb = app.database(); | ||
yield put(actions.updateRTDB(rtdb)); | ||
} | ||
|
||
export function* initializeRTDB() { | ||
yield takeEvery(loadGameActions.saveGame, executeInitializeRTDB); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { createAction, createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit'; | ||
|
||
import firebase from 'firebase/app'; | ||
import { Status } from '@pipeline/common'; | ||
import Timestamp = firebase.firestore.Timestamp; | ||
import Database = firebase.database.Database; | ||
|
||
export interface State { | ||
status: Status | null; | ||
rtdb: Database; | ||
} | ||
|
||
const initialState = { | ||
status: null, | ||
} as State; | ||
|
||
const slice = createSlice({ | ||
name: 'loadBalancer', | ||
initialState: initialState, | ||
reducers: { | ||
updateOnlineStatusSuccess(state, action: PayloadAction<'online' | 'offline'>) { | ||
return { | ||
...state, | ||
status: { | ||
state: action.payload, | ||
updatedAt: Timestamp.now(), | ||
}, | ||
}; | ||
}, | ||
updateRTDB(state, action: PayloadAction<Database>) { | ||
return { | ||
...state, | ||
rtdb: action.payload, | ||
}; | ||
}, | ||
}, | ||
}); | ||
|
||
const getSlice = createSelector( | ||
(state: { [name]: State }) => state, | ||
state => state[name], | ||
); | ||
|
||
const getRTDB = createSelector(getSlice, state => state.rtdb); | ||
|
||
export const reducer = slice.reducer; | ||
export const name = slice.name; | ||
|
||
export const actions = { | ||
...slice.actions, | ||
updateOnlineStatus: createAction<'online' | 'offline'>(`${name}/startListenToOnlineStatus`), | ||
startListenToOnlineStatus: createAction(`${name}/startListenToOnlineStatus`), | ||
stopListenToOnlineStatus: createAction(`${name}/stopListenToOnlineStatus`), | ||
startPollingOnlineStatus: createAction(`${name}/startPollingToOnlineStatus`), | ||
stopPollingOnlineStatus: createAction(`${name}/stopPollingToOnlineStatus`), | ||
}; | ||
|
||
export const selectors = { | ||
getRTDBInstanceName, | ||
getRTDBInstanceURL, | ||
getRTDB, | ||
}; |