λκ·λͺ¨ νΈλν½ μν©μμλ μμ μ μΌλ‘ μ μ°©μ μΏ ν°μ λ°κΈνκΈ° μν λ°±μλ μμ€ν μ λλ€. **MSA(Microservices Architecture)**λ₯Ό κΈ°λ°μΌλ‘ νλ©°, νΈλν½ νμ£Όλ‘ μΈν μλ² μ₯μ λ₯Ό λ°©μ§νκΈ° μν΄ **λκΈ°μ΄ μμ€ν (Traffic Shaping)**μ΄ μ μ©λμ΄ μμ΅λλ€.
μ΄ νλ‘μ νΈλ λκ·λͺ¨ νΈλν½μ μμ μ μΌλ‘ μ²λ¦¬νλ κ²μ μμμΌλ‘, ν₯ν νμ₯ κ°λ₯ν μΏ ν° νλ«νΌμΌλ‘ λ°μ μν€λ κ²μ λͺ©νλ‘ ν©λλ€. κ° λͺ¨λμ MSA(Microservices Architecture) μμΉμ λ°λΌ μ² μ ν μν μ΄ λΆλ¦¬λμ΄ μμ΅λλ€.
| λͺ¨λλͺ | μν λ° μ± μ (Role & Responsibility) | κΈ°μ μ€ν |
|---|---|---|
| gate-app | [Traffic Shaping & Flow Control] - λκ·λͺ¨ μ§μ νΈλν½μ λ²νΌλ§ λ° μ λ μ μ΄ - Redis ZSet κΈ°λ°μ λκΈ°μ΄ μμ€ν ꡬν - Downstream μλΉμ€ 보νΈλ₯Ό μν Throttling |
Redis, Kafka Producer Spring Boot |
| issuer-app | [Coupon Issuance Processor] - λκΈ°μ΄μ ν΅κ³Όν μμ²μ λν μ€μ§μ λ°κΈ νΈλμμ μ²λ¦¬ - λμμ± μ μ΄ λ° λ°μ΄ν° λ¬΄κ²°μ± λ³΄μ₯ - (ν₯ν) μ΄μ’ μλΉμ€ κ° λΆμ° νΈλμμ μ£Όκ΄ |
Kafka Consumer, MySQL (JPA) Spring Boot |
| core | [Domain Core & Shared Kernel] - μΏ ν° λλ©μΈμ ν΅μ¬ λΉμ¦λμ€ λ‘μ§ λ° μ μ± μ μ - λͺ¨λ κ° κ³΅μ λλ DTO, Event κ°μ²΄, κ³΅ν΅ μ νΈλ¦¬ν° - νλ«νΌ νμ₯μ μν κ³΅ν΅ κ·μ½ κ΄λ¦¬ |
Java 17 (Library) |
νμ¬λ κ³ νΈλν½ μμ μ± ν보μ μ§μ€νκ³ μμΌλ©°, μ μ§μ μΌλ‘ μλ κΈ°μ μ κ³Όμ λ€μ μννμ¬ νλ«νΌμ μμ±λλ₯Ό λμΌ κ³νμ λλ€.
-
Phase 1: μμ μ± ν보 (Current)
- λκΈ°μ΄ μμ€ν (Gate)μ ν΅ν νΈλν½ μ μ΄ λ° μλ² λ³΄νΈ
- Kafkaλ₯Ό νμ©ν λΉλκΈ° μ²λ¦¬ λ° λ°μ΄ν° μ μ€ λ°©μ§
-
Phase 2: νλ«νΌ κ³ λν
- λ€μν μΏ ν° μ μ± (ν μΈμ¨, μ ν¨κΈ°κ°, μ€λ³΅ λ°κΈ λ±)μ Core λͺ¨λμ μ§μ½
- λ©ν° ν λνΈ(Multi-tenant) ꡬ쑰 κ³ λ €
-
Phase 3: MSA μ¬ν (Distributed System)
- λΆμ° νΈλμμ μ²λ¦¬: Saga Pattern λ±μ λμ νμ¬ κ²°μ /ν¬μΈνΈ λ± ν λ§μ΄ν¬λ‘μλΉμ€μμ μ ν©μ± 보μ₯
- λ°μ΄ν° μΌκ΄μ±: CDC(Change Data Capture) λ° Outbox Pattern λμ κ²ν
- μ μ μ§μ
:
POST /enqueueβ Gate (Redis λκΈ°μ΄ μ μ¬) - λκΈ° λ° ν΄λ§: μ μ λ
GET /rankλ₯Ό ν΅ν΄ λ³ΈμΈμ μλ² νμΈ (Waiting) - μ€μΌμ€λ§: Gate λ΄λΆ μ€μΌμ€λ¬κ° μ€μ λ μλ(TPS)μ λ§μΆ° μ μ λ₯Ό Processing μνλ‘ λ³κ²½ λ° Kafkaλ‘ λ©μμ§ λ°ν
- λ°κΈ μ²λ¦¬: Issuerκ° Kafka λ©μμ§λ₯Ό μλΉ(Consume)νμ¬ MySQLμ μΏ ν° λ°κΈ κΈ°λ‘ μ μ₯
보μμ μν΄ application.yml νμΌμ Gitμ ν¬ν¨λμ΄ μμ§ μμ΅λλ€.
νλ‘μ νΈ μ€νμ μν΄ κ° λͺ¨λμ src/main/resources κ²½λ‘μ application.ymlμ μ§μ μμ±ν΄μΌ ν©λλ€.
spring:
data:
redis:
host: localhost # K8s λ°°ν¬ μ μλΉμ€λͺ
(μ: redis.rediclaim.svc.cluster.local)
port: 6379
kafka:
bootstrap-servers: localhost:9092 # K8s λ°°ν¬ μ μλΉμ€λͺ
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
# Gate μ μ© μ€μ
gate:
event-ids: 1001, 1002
dispatch-mode: kafka
dispatch-per-second: 100 # μ΄λΉ μ²λ¦¬λ μ ν
spring:
datasource:
url: jdbc:mysql://localhost:3306/rediclaim_db # K8s λ°°ν¬ μ μλΉμ€λͺ
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: coupon-group
CI/CD(GitHub Actions) μμ΄ λ‘컬 νκ²½μμ μ§μ λΉλνμ¬ Kubernetes(Kind)μ λ°°ν¬νλ μ μ°¨μ λλ€.
- Docker Desktop μ€ν μ€
- Docker Hub λ‘κ·ΈμΈ μλ£ (
docker login) - Kubernetes ν΄λ¬μ€ν°(Kind) μ€ν μ€
Gradleμ bootBuildImageλ₯Ό μ¬μ©νμ¬ κ° λͺ¨λλ³λ‘ μ΄λ―Έμ§λ₯Ό λΉλν©λλ€. (Dockerfile λΆνμ)
# Gate λͺ¨λ λΉλ (νκ·Έλͺ
μ λ³ΈμΈ νκ²½μ λ§κ² μμ )
./gradlew :gate-app:bootBuildImage --imageName=seongjunnoh/gate-app:latest
# Issuer λͺ¨λ λΉλ
./gradlew :issuer-app:bootBuildImage --imageName=seongjunnoh/issuer-app:latest
Kind ν΄λ¬μ€ν°κ° μ΄λ―Έμ§λ₯Ό λ΄λ €λ°μ μ μλλ‘ λ컀 νλΈμ μ λ‘λν©λλ€.
docker push seongjunnoh/gate-app:latest
docker push seongjunnoh/issuer-app:latest
μ΅μ μ΄λ―Έμ§λ₯Ό λ°μνκΈ° μν΄ νλλ₯Ό μ¬μμν©λλ€. (imagePullPolicy: Always μ€μ νμ)
# YAML μ€μ μ μ© (μ΅μ΄ 1ν νΉμ λ³κ²½ μ)
kubectl apply -f k8s/gate-app-deployment.yml
kubectl apply -f k8s/issuer-app-deployment.yml
# λ‘€μμ μ¬μμ (μ΄λ―Έμ§ κ°±μ )
kubectl rollout restart deployment/gate-app -n rediclaim
kubectl rollout restart deployment/issuer-app -n rediclaim
# λ°°ν¬ μν λͺ¨λν°λ§
kubectl rollout status deployment/gate-app -n rediclaim
# μ€μκ° λ‘κ·Έ νμΈ
kubectl logs -n rediclaim -l app=gate-app -f
λ‘컬 Kind ν΄λ¬μ€ν°μ νμν λ―Έλ€μ¨μ΄λ₯Ό μ€μΉν©λλ€.
# 1. λ€μμ€νμ΄μ€ μμ±
kubectl create namespace rediclaim
# 2. μΈνλΌ λ°°ν¬ (Redis, Kafka, MySQL)
kubectl apply -f k8s/redis.yml
kubectl apply -f k8s/kafka.yml
kubectl apply -f k8s/mysql.yml
Note: Redisλ μ±λ₯ ν μ€νΈλ₯Ό μν΄
appendonly no,save ""μ΅μ μΌλ‘ μ€μ λ μ μμ΅λλ€. (μ΄μ νκ²½μμλ Persistence μ€μ νμ)
K6λ₯Ό μ¬μ©νμ¬ λκΈ°μ΄ μμ€ν μ μ±λ₯κ³Ό μμ μ±μ κ²μ¦ν©λλ€.
μ μ κ° λκΈ°μ΄ μ§μ
-> μλ² λκΈ°(Polling) -> μ²λ¦¬μ΄ μ΄λ(Processing) νλ μ 체 κ³Όμ μ κ²μ¦ν©λλ€.
# λμ보λμ ν¨κ» μ€ν
K6_WEB_DASHBOARD=true k6 run scripts/k6-gate-polling.js
μ£Όμ κ²μ¦ νλͺ©:
- κΈ°λ₯ μ ν©μ±: λͺ¨λ μ μ κ° μλ¬ μμ΄ Processing μνλ‘ μ νλλκ°?
- λκΈ° μκ°(UX): μ μ κ° μ€μ μ
μ₯νκΈ°κΉμ§ 걸리λ μκ° (
waiting_timeμ§ν) - μμ μ±: μ€νμ΄ν¬ νΈλν½ λ°μ μ μλ²κ° λ€μ΄λμ§ μλκ°?
- Language: Java 17
- Framework: Spring Boot 3.4.4
- Database: MySQL 8.0, Redis 7.x
- Message Queue: Apache Kafka 3.8
- Build Tool: Gradle
- Deployment: Docker, Kubernetes (Kind)
- Testing: K6 (Load Testing), JUnit 5