created at 2022-12-30
진행상황
Architecture
현재 구현된 서비스를 docker compose를 통해 실행시키면 다음과 같이 컨테이너들이 생긴다.
Kafka와 Chat Server, Auth Server, DB들 모두 외부접속 가능하도록 노출 port를 도커 내부 포트랑 같게 설정했다.
gyuminhwangbo@Gyuminui-MacBookPro spring-chatting-server % docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d54365955efa spring-chatting-server_nginx "/docker-entrypoint.…" 51 seconds ago Up 47 seconds 0.0.0.0:8080->80/tcp nginx af5c00d411db spring-chatting-server_chatting-server-2 "java -jar app.jar" 51 seconds ago Up 48 seconds 0.0.0.0:8084->8084/tcp chatting-server-2 becb35e0bc1f spring-chatting-server_chatting-server-1 "java -jar app.jar" 51 seconds ago Up 48 seconds 0.0.0.0:8083->8083/tcp chatting-server-1 9bfb2b8d53ca confluentinc/cp-kafka:7.2.1 "/etc/confluent/dock…" 51 seconds ago Up 49 seconds 0.0.0.0:8099->8099/tcp, 9092/tcp kafka3 cc8d6c5944b3 confluentinc/cp-kafka:7.2.1 "/etc/confluent/dock…" 51 seconds ago Up 49 seconds 0.0.0.0:8097->8097/tcp, 9092/tcp kafka1 995e41051907 confluentinc/cp-kafka:7.2.1 "/etc/confluent/dock…" 51 seconds ago Up 49 seconds 0.0.0.0:8098->8098/tcp, 9092/tcp kafka2 7d355211b1ce spring-chatting-server_auth-server "java -jar app.jar" 51 seconds ago Up 49 seconds 0.0.0.0:8085->8085/tcp auth-server 29ab743b9847 confluentinc/cp-zookeeper:7.2.1 "/etc/confluent/dock…" 52 seconds ago Up 50 seconds 2181/tcp, 2888/tcp, 3888/tcp zookeeper 722bf2b27df5 postgres:12-alpine "docker-entrypoint.s…" 52 seconds ago Up 50 seconds 5432/tcp, 0.0.0.0:5434->5434/tcp chatting-db-2 ccc3e138004a postgres:12-alpine "docker-entrypoint.s…" 52 seconds ago Up 50 seconds 5432/tcp, 0.0.0.0:5433->5433/tcp chatting-db-1 3a52283f6f52 postgres:12-alpine "docker-entrypoint.s…" 52 seconds ago Up 50 seconds 5432/tcp, 0.0.0.0:5435->5435/tcp auth-db
구현 완료
- API GATEWAY
- 채팅서버(/chat)에 접속 시, ip_hash로 특정 서버로만 들어가도록 구현
- 인증서버(/user)에 접속할 때 uri 맨 뒤
/
가 붙어야만 접속되었었다. 그래서rewrite {regex}
로 맨뒤에 자동으로/
를 붙여주도록 설정
- Auth Server
- 인증서버를 따로 구현함으로써 다른 곳에서도 편하게 이용할 수 있도록 구현
- Chat Server
- 채팅서버를 수동으로 2대 구축
추후 K8S의 auto-scaling으로 구현할 예정
- 채팅서버를 수동으로 2대 구축
- Kafka Broker
- 토픽은 두 개로 올렸으며, 각각 broker 3대 및 2개의 파티션에 걸쳐 백업되도록 운용
파티션은 200개가 적당하다고 한다. 지금 당장은 2개의 파티션으로 운용.
- 토픽은 두 개로 올렸으며, 각각 broker 3대 및 2개의 파티션에 걸쳐 백업되도록 운용
구현 미완료
- Front Server
- API GATEWAY에 비동기+논 블로킹 webClient로 요청 전송 예정
- Log Server
- DB선정에 있어 고민이 있다. 여러가지 비교해봤을 때, 1차적으로 Elastic Search을 선택하였다. 시계열 데이터에 맞는 DB는?
- Elastic Search = RESTful api기반으로 여러 곳에서 활용 가능하며, 역색인을 지원하므로 비정형 doc 색인/검색에 탁월하다. 필자는 채팅을 관찰하며 특정 단어의 빈도수를 모니터링하려 하는데 이때 쓰기 딱 좋을듯. 또한 데이터 저장 시점에 해당 데이터를 색인하기에 실시간이 아닌점이 마음에 든다. Kafka를 쓰기때문에 실시간이 아닐 뿐더러, Logging에 실시간성이 필요없기때문에 알맞은 엔진이라고 생각된다.
- HDFS = 분산저장 아직 하지 않을것임
- File System = Transaction관리가 어려움
- Timescale = 기존 Postgres를 시계열로 확장시킨 DB. 그래서 익숙한 SQL문을 그대로 사용할 수 있다는 장점이 있다. 하지만 단점은 직접 모든 SQL문을 작성해야한다는 점이다(이유는 JPA에서 Timescale을 지원하지 않기 때문에 모든 Dialect를 작성해야함)
- InfluxDB = 가장 많이 사용하는 시계열 DB이자, JPA가 지원하는 시계열 DB. 참고자료. 필자가 원하는 채팅개수 및 신규가입자 수라는 시계열 데이터들을 대시보드로 별도로 쉽게 확인할 수 있다.
InfluxDB는 real-time querying에 좋다고 하는것 같은데, 우리는 카프카를 사용하기때문에 해당 장점을 활용하지 않는다.
- 만약 InfluxDB를 선택한다면, 시계열데이터는 다음과 같이 두 가지로 각각의 하이퍼테이블을 구성할 예정
- symbol = chatAdd, data = 채팅 입력 수
- symbol = userAdd, data = 신규가입자 수
- DB선정에 있어 고민이 있다. 여러가지 비교해봤을 때, 1차적으로 Elastic Search을 선택하였다. 시계열 데이터에 맞는 DB는?
- Orchestration saga
- 필자는 Kafka를 MSA의 백본망으로 사용한다. 따라서 saga의 패턴 중 중앙집권형인 Orchestration 패턴으로 모니터링을 중앙에서 할 것이다.
- 사용할 모니터링 툴 리스트
- 메세지 모니터링 UI for Kafka
카프카 서버 연결 및 Consumer Group 확인/ 토픽 확인이 용이하다. 또한 실제 메세지 내용도 확인할 수 있어서 편리하다.
- Consumer 트래픽 모니터링 https://s262701-id.tistory.com/126
- Linkedin의 burrow
- lag정보 Elasticsearch 수집 데이터파이프라인
- ElasticSearch에 기록된 lag 정보 기반 Grafana lag 모니터링 대시보드 각각의 Kafka 모니터링 툴 비교
- 메세지 모니터링 UI for Kafka
완료된 설정
상세 코드는 https://github.com/ghkdqhrbals/spring-chatting-server에서 확인하능하다.
main branch에 존재
완료된 API GATEWAY(nginx.config)
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
upstream chat-server {
# IP 해쉬화 하여 특정 서버에 들어가도록 설정
# https://nginx.org/en/docs/http/ngx_http_upstream_module.html#ip_hash
ip_hash;
server chatting-server-1:8083;
server chatting-server-2:8084;
}
upstream auth-server {
server auth-server:8085;
}
server {
listen 80;
# 추후 server_name 변경예정
server_name localhost;
location / {
# 채팅서버 backend
location /chat {
proxy_pass http://chat-server;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; # 클라이언트 요청 ip전송
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 클라이언트 요청 ip전송
}
# 인증서버 backend
location /user {
rewrite ^([^.]*[^/])$ $1/ permanent; # tailing slash with every url
proxy_pass http://auth-server;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
완료된 Docker-compose.yml
Kafka 설정에 있어 https://www.confluent.io/blog/kafka-client-cannot-connect-to-broker-on-aws-on-docker-etc/ 문서가 많은 도움이 되었다.
version : '2'
services:
# -------- API GATEWAY --------
nginx:
restart: always
container_name: nginx
depends_on:
- chatting-server-1
- chatting-server-2
build:
context: ./nginx
dockerfile: Dockerfile
ports:
- "8080:80"
# -------- KAFKA --------
zookeeper:
image: confluentinc/cp-zookeeper:7.2.1
container_name: zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
kafka1:
image: confluentinc/cp-kafka:7.2.1
container_name: kafka1
ports:
- "8097:8097"
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: EXTERNAL://localhost:8097,INTERNAL://kafka1:9092
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
kafka2:
image: confluentinc/cp-kafka:7.2.1
container_name: kafka2
ports:
- "8098:8098"
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 2
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: EXTERNAL://localhost:8098,INTERNAL://kafka2:9092
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
kafka3:
image: confluentinc/cp-kafka:7.2.1
container_name: kafka3
ports:
- "8099:8099"
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 3
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: EXTERNAL://localhost:8099,INTERNAL://kafka3:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
# -------- Chatting Server --------
chatting-db-1:
container_name: chatting-db-1
image: postgres:12-alpine
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_USER=postgres
- POSTGRES_DB=chat
expose:
- "5433" # Publishes 5433 to other containers but NOT to host machine
ports:
- "5433:5433"
volumes:
- ./backups:/home/backups
command: -p 5433
chatting-server-1:
container_name: chatting-server-1
build: ./spring-chatting-backend-server
ports:
- "8083:8083"
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://chatting-db-1:5433/chat
- SPRING_DATASOURCE_USERNAME=postgres
- SPRING_DATASOURCE_PASSWORD=password
- SPRING_JPA_HIBERNATE_DDL_AUTO=update
- SERVER_PORT=8083
- KAFKA_BOOTSTRAP=kafka1:9092,kafka2:9092,kafka3:9092 # 내부포트
depends_on:
- kafka1
- kafka2
- kafka3
- chatting-db-1
restart: always
chatting-db-2:
container_name: chatting-db-2
image: postgres:12-alpine
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_USER=postgres
- POSTGRES_DB=chat
expose:
- "5434" # Publishes 5433 to other containers but NOT to host machine
ports:
- "5434:5434"
volumes:
- ./backups:/home/backups
command: -p 5434
chatting-server-2:
container_name: chatting-server-2
build: ./spring-chatting-backend-server
ports:
- "8084:8084"
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://chatting-db-2:5434/chat
- SPRING_DATASOURCE_USERNAME=postgres
- SPRING_DATASOURCE_PASSWORD=password
- SPRING_JPA_HIBERNATE_DDL_AUTO=update
- SERVER_PORT=8084
- KAFKA_BOOTSTRAP=kafka1:9092,kafka2:9092,kafka3:9092 # 내부포트
depends_on:
- kafka1
- kafka2
- kafka3
- chatting-db-2
restart: always
# -------- Authentication Server --------
auth-db:
container_name: auth-db
image: postgres:12-alpine
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_USER=postgres
- POSTGRES_DB=auth
expose:
- "5435" # Publishes 5433 to other containers but NOT to host machine
ports:
- "5435:5435"
volumes:
- ./backups:/home/backups
command: -p 5435
auth-server:
container_name: auth-server
build: ./spring-auth-backend-server
ports:
- "8085:8085"
environment:
- SERVER_PORT=8085
- SPRING_DATASOURCE_URL=jdbc:postgresql://auth-db:5435/auth
- SPRING_DATASOURCE_USERNAME=postgres
- SPRING_DATASOURCE_PASSWORD=password
- SPRING_JPA_HIBERNATE_DDL_AUTO=update
depends_on:
- auth-db
restart: always