Skip to content

Commit 8916395

Browse files
fru1tworldparkblo
andauthored
Load-Balancer 서버 구현 (#5)
* fix: api 서버와 websocket 분리를 위해 backend 서버 네임 변경 * feature/split api websocket * fix: 분리 후 중복되는 코드 삭제 * fix: 불필요한 파일 삭제 및 하위 subTree connection 연결되어있는 경우 저장되는 오류 해결 * feat: load-balancer server 초기 세팅 nest 생성 redis container 설정 docker-compose 수정 * fix: 하위 subTree 삭제됐는데 note를 생성할 수 있는 로직 수정 * fix: 삭제된 트리에서 노트가 생성되던 문제 해결 및 Validation 계층추가로 결합도 낮추기 * feat: redis 환경 설정 및 연결 여부 확인 * feat: load-balancer server && WebSocket Server 분산 작업 및 docker-compose 작업 * fix: url 주소 변경됨에 따라 urlParse 로직 일부 수정 * fix: room2 연결 안되던 문제 해결 * fix: cron이 작동하지 않는 문제 해결 * feat: lb-server 기능 개발 * fix: lb-server key값 수정 * feat: load-balancer 로직 구현 * feat: lb 요청 로직 추가 * fix: lb 요청 로직 비동기로 작동하도록 수정 * fix: window 상에서 redis 포트 사용 불가로 docker-compose override 수정 * fix: 웹소켓 연결 시 순환참조 수정 * fix: note 생성 요청에 spaceId 추가 * fix: validation 검증 로직 수정 * feat: 노트 에디터 로드밸런싱 로직 적용 --------- Co-authored-by: Byeongju Park <[email protected]>
1 parent 9a6acbc commit 8916395

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1489
-326
lines changed

docker-compose.override.yml

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
version: "3.8"
2-
31
services:
42
mysql:
53
image: mysql:8.0
@@ -23,15 +21,17 @@ services:
2321
retries: 3
2422
command: --bind-address=0.0.0.0
2523

26-
collaborative:
24+
collaborative_room1:
2725
build:
2826
context: .
2927
dockerfile: ./packages/collaborative/Dockerfile
30-
container_name: collaborative
28+
container_name: collaborative-room1
3129
ports:
3230
- "3001:3001"
3331
- "9001:9001"
3432
depends_on:
33+
redis:
34+
condition: service_healthy
3535
mysql:
3636
condition: service_healthy
3737
mongodb:
@@ -47,7 +47,75 @@ services:
4747
- MONGO_USER=honey
4848
- MONGO_PASSWORD=1234
4949
- MONGO_DB=dev_db
50-
- LOG_LEVEL=info
50+
- LOG_LEVEL=debug
51+
- REDIS_HOST=redis-container
52+
- REDIS_USERNAME=honey
53+
- REDIS_PORT=6530
54+
- REDIS_PASSWORD=1234
55+
- SERVER_ID=room1
56+
- WS_PORT=9001
57+
networks:
58+
- app-network
59+
60+
collaborative_room2:
61+
build:
62+
context: .
63+
dockerfile: ./packages/collaborative/Dockerfile
64+
container_name: collaborative-room2
65+
ports:
66+
- "3002:3002"
67+
- "9002:9002"
68+
depends_on:
69+
redis:
70+
condition: service_healthy
71+
mysql:
72+
condition: service_healthy
73+
mongodb:
74+
condition: service_healthy
75+
environment:
76+
- MYSQL_HOST=mysql-container
77+
- MYSQL_PORT=3306
78+
- MYSQL_DATABASE=dev_db
79+
- MYSQL_PASSWORD=1234
80+
- MYSQL_USER=honey
81+
- NODE_ENV=dev
82+
- MONGO_HOST=mongodb-container
83+
- MONGO_USER=honey
84+
- MONGO_PASSWORD=1234
85+
- MONGO_DB=dev_db
86+
- LOG_LEVEL=debug
87+
- REDIS_HOST=redis-container
88+
- REDIS_USERNAME=honey
89+
- REDIS_PORT=6530
90+
- REDIS_PASSWORD=1234
91+
- SERVER_ID=room2
92+
- WS_PORT=9002
93+
networks:
94+
- app-network
95+
api:
96+
build:
97+
context: .
98+
dockerfile: ./packages/api/Dockerfile
99+
container_name: api
100+
ports:
101+
- "3000:3000"
102+
depends_on:
103+
mysql:
104+
condition: service_healthy
105+
mongodb:
106+
condition: service_healthy
107+
environment:
108+
- MYSQL_HOST=mysql-container
109+
- MYSQL_PORT=3306
110+
- MYSQL_DATABASE=dev_db
111+
- MYSQL_PASSWORD=1234
112+
- MYSQL_USER=honey
113+
- NODE_ENV=dev
114+
- MONGO_HOST=mongodb-container
115+
- MONGO_USER=honey
116+
- MONGO_PASSWORD=1234
117+
- MONGO_DB=dev_db
118+
- LOG_LEVEL=debug
51119
networks:
52120
- app-network
53121
api:
@@ -77,20 +145,44 @@ services:
77145
networks:
78146
- app-network
79147

80-
frontend:
148+
load-balancer:
149+
build:
150+
context: .
151+
dockerfile: ./packages/loadbalancer/Dockerfile
152+
container_name: lb-container
153+
ports:
154+
- "4242:4242"
155+
depends_on:
156+
redis:
157+
condition: service_healthy
158+
networks:
159+
- app-network
160+
environment:
161+
- LOG_LEVEL=debug
162+
- REDIS_HOST=redis-container
163+
- REDIS_USERNAME=honey
164+
- REDIS_PORT=6530
165+
- REDIS_PASSWORD=1234
166+
167+
frotend:
81168
build:
82169
context: .
83170
dockerfile: ./packages/frontend/Dockerfile
84171
container_name: frontend
85172
ports:
86173
- "80:80"
87174
depends_on:
88-
collaborative:
175+
load-balancer:
176+
condition: service_started
177+
collaborative_room1:
178+
condition: service_started
179+
collaborative_room2:
89180
condition: service_started
90181
api:
91182
condition: service_started
92183
networks:
93184
- app-network
185+
94186
mongodb:
95187
image: mongo:latest
96188
container_name: mongodb-container
@@ -122,6 +214,30 @@ services:
122214
timeout: 5s
123215
retries: 10
124216

217+
redis:
218+
image: redis:latest
219+
container_name: redis-container
220+
ports:
221+
- "6530:6530"
222+
networks:
223+
- app-network
224+
environment:
225+
REDIS_DISABLE_COMMANDS: "FLUSHDB FLUSHALL"
226+
command:
227+
[
228+
"redis-server",
229+
"--port",
230+
"6530",
231+
"--requirepass",
232+
"1234",
233+
"--bind",
234+
"0.0.0.0",
235+
]
236+
healthcheck:
237+
test: ["CMD", "redis-cli", "-p", "6530", "ping"]
238+
interval: 10s
239+
timeout: 5s
240+
retries: 3
125241
volumes:
126242
mysql_data:
127243
mongo_data:

docker-compose.prod.yml

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,64 @@ services:
88
curl -f telnet://${MYSQL_HOST}:${MYSQL_PORT} || exit 1;
99
curl -f telnet://${MONGO_HOST}:27017 || exit 1;
1010
"
11-
networks:
12-
- app-network
1311
healthcheck:
1412
test: ["CMD", "/bin/sh", "-c", "exit 0"]
1513
interval: 10s
1614
timeout: 5s
1715
retries: 3
1816

17+
loadbalancer:
18+
container_name: loadbalancer
19+
ports:
20+
- "4242:4242"
21+
build:
22+
target: production
23+
environment:
24+
# 배포 환경 세팅
25+
- NODE_ENV=production
26+
27+
# Mongo 세팅
28+
- LOG_LEVEL=${LOG_LEVEL}
29+
30+
# Redis 세팅
31+
- REDIS_USERNAME=${REDIS_USERNAME}
32+
- REDIS_PASSWORD=${REDIS_PASSWORD}
33+
- REDIS_HOST=${REDIS_HOST}
34+
- REDIS_PORT=${REDIS_PORT}
35+
networks:
36+
- app-network
37+
38+
collaborative:
39+
container_name: collaborative
40+
ports:
41+
- "9001:9001"
42+
build:
43+
target: production
44+
environment:
45+
# 배포 환경 세팅
46+
- NODE_ENV=production
47+
48+
# MySQL 세팅
49+
- MYSQL_HOST=${MYSQL_HOST}
50+
- MYSQL_PORT=${MYSQL_PORT}
51+
- MYSQL_USER=${MYSQL_USER}
52+
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
53+
- MYSQL_DATABASE=${MYSQL_DATABASE}
54+
55+
# Mongo 세팅
56+
- MONGO_HOST=${MONGO_HOST}
57+
- MONGO_USER=${MONGO_USER}
58+
- MONGO_PASSWORD=${MONGO_PASSWORD}
59+
- MONGO_DB=${MONGO_DB}
60+
- LOG_LEVEL=${LOG_LEVEL}
61+
62+
# Redis 세팅
63+
- REDIS_USERNAME=${REDIS_USERNAME}
64+
- REDIS_PASSWORD=${REDIS_PASSWORD}
65+
- REDIS_HOST=${REDIS_HOST}
66+
- REDIS_PORT=${REDIS_PORT}
67+
networks:
68+
- app-network
1969
api:
2070
container_name: api
2171
ports:

docker-compose.yml

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,3 @@
1-
services:
2-
api:
3-
build:
4-
context: .
5-
dockerfile: ./packages/api/Dockerfile
6-
restart: unless-stopped
7-
environment:
8-
- NODE_ENV=development
9-
networks:
10-
- app-network
11-
12-
frontend:
13-
build:
14-
context: .
15-
dockerfile: ./packages/frontend/Dockerfile
16-
restart: unless-stopped
17-
environment:
18-
- NODE_ENV=development
19-
networks:
20-
- app-network
21-
221
networks:
232
app-network:
243
driver: bridge

packages/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ MONGO_USER=your-mongo-username
1111
MONGO_PASSWORD=your-mongo-password
1212
MONGO_DB=your-mongo-database
1313

14-
# Redis 설정 (추가된 telnet 기반 체크를 위해 필요)
14+
# Redis 설정
1515
REDIS_HOST=your-redis-host
1616
REDIS_PORT=6379
1717

packages/api/src/app.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getTypeOrmConfig } from './common/config/typeorm.config';
88
import { NoteModule } from './note/note.module';
99
import { SpaceModule } from './space/space.module';
1010
import { TestModule } from './test/test.module';
11+
import { ValidationModule } from './common/validation/validation.module';
1112

1213
@Module({
1314
imports: [
@@ -22,8 +23,9 @@ import { TestModule } from './test/test.module';
2223
inject: [ConfigService],
2324
useFactory: getTypeOrmConfig,
2425
}),
25-
SpaceModule,
26+
ValidationModule,
2627
NoteModule,
28+
SpaceModule,
2729
TestModule,
2830
],
2931
})
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Module } from '@nestjs/common';
2+
import { MongooseModule } from '@nestjs/mongoose';
3+
import { NoteDocument, NoteSchema } from 'src/note/note.schema';
4+
import { SpaceDocument, SpaceSchema } from 'src/space/space.schema';
5+
import { ValidationService } from './validation.service';
6+
7+
@Module({
8+
imports: [
9+
MongooseModule.forFeature([
10+
{ name: NoteDocument.name, schema: NoteSchema },
11+
{ name: SpaceDocument.name, schema: SpaceSchema },
12+
]),
13+
],
14+
providers: [ValidationService],
15+
exports: [ValidationService],
16+
})
17+
export class ValidationModule {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { ValidationService } from './validation.service';
3+
4+
describe('ValidationService', () => {
5+
let service: ValidationService;
6+
7+
beforeEach(async () => {
8+
const module: TestingModule = await Test.createTestingModule({
9+
providers: [ValidationService],
10+
}).compile();
11+
12+
service = module.get<ValidationService>(ValidationService);
13+
});
14+
15+
it('should be defined', () => {
16+
expect(service).toBeDefined();
17+
});
18+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Injectable, Logger } from '@nestjs/common';
2+
import { InjectModel } from '@nestjs/mongoose';
3+
import { Model } from 'mongoose';
4+
import { SpaceDocument } from 'src/space/space.schema';
5+
import { MAX_SPACES } from '../constants/space.constants';
6+
import { ERROR_MESSAGES } from '../constants/error.message.constants';
7+
import { NoteDocument } from 'src/note/note.schema';
8+
9+
@Injectable()
10+
export class ValidationService {
11+
private readonly logger = new Logger(ValidationService.name);
12+
constructor(
13+
@InjectModel(NoteDocument.name)
14+
private readonly noteModel: Model<SpaceDocument>,
15+
@InjectModel(SpaceDocument.name)
16+
private readonly spaceModel: Model<SpaceDocument>,
17+
) {}
18+
async validateNoteExsts(urlPath: string) {
19+
const note = await this.noteModel.findOne({ urlPath });
20+
21+
if (!note) {
22+
throw new Error(ERROR_MESSAGES.SPACE.NOT_FOUND);
23+
}
24+
25+
return note;
26+
}
27+
async validateSpaceLimit(userId: string) {
28+
const spaceCount = await this.spaceModel.countDocuments({ userId });
29+
30+
if (spaceCount >= MAX_SPACES) {
31+
throw new Error(ERROR_MESSAGES.SPACE.LIMIT_EXCEEDED);
32+
}
33+
}
34+
35+
async validateParentNodeExists(parentContextNodeId: string | null) {
36+
if (parentContextNodeId) {
37+
const space = await this.spaceModel.findOne({
38+
id: parentContextNodeId,
39+
});
40+
41+
if (!space) {
42+
throw new Error(ERROR_MESSAGES.SPACE.PARENT_NOT_FOUND);
43+
}
44+
}
45+
}
46+
47+
async validateSpaceExists(id: string) {
48+
const space = await this.spaceModel.findOne({ id });
49+
50+
this.logger.debug(`validation result : ${space}`);
51+
52+
if (!space) {
53+
throw new Error(ERROR_MESSAGES.SPACE.NOT_FOUND);
54+
}
55+
56+
return space;
57+
}
58+
}

0 commit comments

Comments
 (0)