Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- 클라우드
- 마이크로서비스 통신
- docker
- 서비스 설계
- 클러스터
- 컨테이너오케스트레이션
- 메시징 패턴
- 인메모리데이터베이스
- 프로덕션 운영
- Python
- infrastructureascode
- ApacheBench
- 마이크로서비스
- kubernetes
- CI/CD
- 모노리스 분해
- 모니터링
- 메시지 브로커
- 보안
- 세션저장소
- 마이크로서비스 운영
- 고가용성
- 분산 시스템
- Kafka 클러스터
- 분산 모니터링
- 이벤트 스트리밍
- 서비스 메시
- devops
- rabbitmq
- RabbitMQ Exchange
Archives
- Today
- Total
hobokai 님의 블로그
마이크로서비스 아키텍처 완벽 가이드 1편: 기초와 모노리스 분해 전략 본문
목차
마이크로서비스란?
마이크로서비스 아키텍처는 하나의 큰 애플리케이션을 작고 독립적인 서비스들로 분해하여 구축하는 소프트웨어 개발 접근법입니다. 각 서비스는 특정 비즈니스 기능을 담당하며, 독립적으로 개발, 배포, 확장할 수 있습니다.
핵심 특징
독립성 🔸
- 각 서비스는 독립적인 프로세스로 실행
- 서로 다른 기술 스택 사용 가능
- 독립적인 데이터베이스 보유
분산성 🔸
- 네트워크를 통한 서비스 간 통신
- 여러 서버에 분산 배포
- 장애 격리 및 복구 능력
비즈니스 중심 🔸
- 도메인 기반 서비스 분해
- 팀 단위 서비스 소유
- 빠른 기능 개발 및 배포
왜 마이크로서비스인가?
📈 확장성: 필요한 서비스만 선택적 확장
🚀 개발 속도: 팀별 독립적 개발 및 배포
🛡️ 장애 격리: 한 서비스 장애가 전체에 영향 없음
🔄 기술 다양성: 서비스별 최적 기술 스택 선택
👥 팀 자율성: 작은 팀이 서비스 전체 책임모노리스 vs 마이크로서비스
모노리스 아키텍처
┌─────────────────────────────────────┐
│ 모노리스 애플리케이션 │
├─────────────────────────────────────┤
│ 사용자 인터페이스 │
├─────────────────────────────────────┤
│ 비즈니스 로직 │
│ ├── 주문 관리 │
│ ├── 재고 관리 │
│ ├── 결제 처리 │
│ └── 사용자 관리 │
├─────────────────────────────────────┤
│ 데이터 접근 계층 │
├─────────────────────────────────────┤
│ 단일 데이터베이스 │
└─────────────────────────────────────┘장점:
- 단순한 개발 환경
- 쉬운 테스트 및 디버깅
- 간단한 배포 과정
- 낮은 초기 복잡성
단점:
- 확장성 제한
- 기술 스택 종속성
- 전체 재배포 필요
- 대규모 팀 협업 어려움
마이크로서비스 아키텍처
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 주문 서비스 │ │ 재고 서비스 │ │ 결제 서비스 │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ API Gateway│ │ API Gateway│ │ API Gateway│
├──────────────┤ ├──────────────┤ ├──────────────┤
│ 비즈니스 로직 │ │ 비즈니스 로직 │ │ 비즈니스 로직 │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ 주문 DB │ 재고 DB │ │ 결제 DB │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
└─────────────────┼─────────────────┘
│
┌──────────────────────┐
│ 메시지 브로커 │
│ (Kafka/RabbitMQ) │
└──────────────────────┘장점:
- 독립적 확장 가능
- 기술 다양성 지원
- 빠른 개발 및 배포
- 장애 격리 효과
단점:
- 높은 초기 복잡성
- 네트워크 통신 오버헤드
- 분산 시스템 관리 부담
- 데이터 일관성 문제
마이크로서비스 설계 원칙
1. 단일 책임 원칙 (Single Responsibility)
각 서비스는 하나의 비즈니스 기능만 담당해야 합니다.
# 좋은 예: 주문 서비스
class OrderService:
def create_order(self, order_data):
"""주문 생성"""
pass
def update_order_status(self, order_id, status):
"""주문 상태 업데이트"""
pass
def get_order_history(self, user_id):
"""주문 이력 조회"""
pass
# 나쁜 예: 모든 기능이 섞인 서비스
class MonolithService:
def create_order(self): pass
def manage_inventory(self): pass
def process_payment(self): pass
def send_notification(self): pass # 너무 많은 책임
2. 데이터베이스 분리 원칙
각 서비스는 독립적인 데이터베이스를 가져야 합니다.
# 서비스별 데이터베이스 분리
services:
order-service:
database: order_db
tables: [orders, order_items]
inventory-service:
database: inventory_db
tables: [products, stock]
payment-service:
database: payment_db
tables: [payments, transactions]
3. API 우선 설계 (API-First Design)
서비스 간 통신은 명확한 API 계약을 통해 이루어져야 합니다.
# OpenAPI 스펙 예시
paths:
/orders:
post:
summary: 주문 생성
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
responses:
'201':
description: 주문 생성 성공
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
4. 장애 격리 (Fault Isolation)
한 서비스의 장애가 다른 서비스에 전파되지 않도록 설계해야 합니다.
# Circuit Breaker 패턴 구현
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=60):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.last_failure_time = None
self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
def call(self, func, *args, **kwargs):
if self.state == 'OPEN':
if self._should_attempt_reset():
self.state = 'HALF_OPEN'
else:
raise Exception("Circuit breaker is OPEN")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise e
def _on_success(self):
self.failure_count = 0
self.state = 'CLOSED'
def _on_failure(self):
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = 'OPEN'
# 사용 예시
payment_circuit = CircuitBreaker()
def process_payment_with_circuit_breaker(payment_data):
return payment_circuit.call(payment_service.process, payment_data)
모노리스 분해 전략
1. Domain-Driven Design (DDD) 접근법
Bounded Context 식별
전자상거래 도메인 분석:
┌─────────────────────────────────────────────────────┐
│ 전자상거래 도메인 │
├─────────────────────────────────────────────────────┤
│ 주문 관리 │ 재고 관리 │ 결제 처리 │
│ (Order) │ (Inventory) │ (Payment) │
│ │ │ │
│ • 주문 생성 │ • 재고 조회 │ • 결제 처리 │
│ • 주문 추적 │ • 재고 업데이트 │ • 환불 처리 │
│ • 주문 취소 │ • 재고 예약 │ • 결제 이력 │
├─────────────────────────────────────────────────────┤
│ 사용자 관리 │ 배송 관리 │ 알림 서비스 │
│ (User) │ (Shipping) │ (Notification) │
│ │ │ │
│ • 회원 가입 │ • 배송 주소 │ • 이메일 알림 │
│ • 로그인/로그아웃 │ • 배송 추적 │ • SMS 알림 │
│ • 프로필 관리 │ • 배송업체 관리 │ • 푸시 알림 │
└─────────────────────────────────────────────────────┘2. 데이터 종속성 분석
# 데이터 종속성 매트릭스 분석
class DependencyAnalyzer:
def __init__(self):
self.dependencies = {
'Order': ['User', 'Product', 'Payment'],
'Inventory': ['Product'],
'Payment': ['User', 'Order'],
'Shipping': ['Order', 'User'],
'Notification': ['User', 'Order', 'Payment']
}
def find_service_boundaries(self):
"""서비스 경계 찾기"""
# 강한 결합도를 가진 엔티티들을 같은 서비스로 그룹화
service_groups = {
'order-service': ['Order', 'OrderItem'],
'inventory-service': ['Product', 'Stock', 'Category'],
'payment-service': ['Payment', 'Transaction', 'Refund'],
'user-service': ['User', 'Profile', 'Authentication'],
'shipping-service': ['Shipping', 'Address', 'Carrier'],
'notification-service': ['Notification', 'Template', 'Channel']
}
return service_groups
3. Strangler Fig 패턴
기존 모노리스를 점진적으로 분해하는 전략입니다.
1단계: 새로운 기능을 마이크로서비스로 구현
┌─────────────────┐ ┌─────────────────┐
│ 모노리스 기존 │ │ 새 기능 서비스 │
│ 기능들 │ │ (마이크로서비스) │
└─────────────────┘ └─────────────────┘
2단계: 기존 기능을 하나씩 마이크로서비스로 이전
┌─────────────────┐ ┌─────────────────┐
│ 모노리스 │ │ 주문 서비스 │
│ (일부 기능) │ │ │
└─────────────────┘ └─────────────────┘
┌─────────────────┐
│ 결제 서비스 │
│ │
└─────────────────┘
3단계: 모노리스 완전 분해
┌─────────────────┐ ┌─────────────────┐
│ 사용자 서비스 │ │ 주문 서비스 │
│ │ │ │
└─────────────────┘ └─────────────────┘
┌─────────────────┐ ┌─────────────────┐
│ 재고 서비스 │ │ 결제 서비스 │
│ │ │ │
└─────────────────┘ └─────────────────┘4. 분해 순서 결정
# 분해 우선순위 결정 프레임워크
class DecompositionPrioritizer:
def __init__(self):
self.criteria = {
'business_value': 0.3, # 비즈니스 가치
'technical_debt': 0.25, # 기술 부채
'team_autonomy': 0.2, # 팀 자율성
'scalability_need': 0.15, # 확장성 요구
'complexity': 0.1 # 분해 복잡도
}
def calculate_priority(self, service_metrics):
"""서비스별 분해 우선순위 계산"""
priority_scores = {}
for service, metrics in service_metrics.items():
score = 0
for criterion, weight in self.criteria.items():
score += metrics[criterion] * weight
priority_scores[service] = score
# 우선순위 정렬
return sorted(priority_scores.items(),
key=lambda x: x[1], reverse=True)
# 사용 예시
prioritizer = DecompositionPrioritizer()
service_metrics = {
'order-service': {
'business_value': 9, # 높은 비즈니스 가치
'technical_debt': 7, # 기술 부채 존재
'team_autonomy': 8, # 팀 분리 가능
'scalability_need': 9, # 확장성 필요
'complexity': 6 # 중간 복잡도
},
'payment-service': {
'business_value': 10,
'technical_debt': 8,
'team_autonomy': 7,
'scalability_need': 8,
'complexity': 8
}
}
priorities = prioritizer.calculate_priority(service_metrics)
print("분해 우선순위:", priorities)
실전 분해 사례
온라인 쇼핑몰 모노리스 분해 프로젝트
기존 모노리스 구조:
# 기존 모노리스 애플리케이션
class ECommerceApp:
def __init__(self):
self.user_manager = UserManager()
self.product_catalog = ProductCatalog()
self.order_processor = OrderProcessor()
self.payment_handler = PaymentHandler()
self.inventory_tracker = InventoryTracker()
self.notification_sender = NotificationSender()
def process_order(self, user_id, items):
# 모든 기능이 하나의 애플리케이션에 결합
user = self.user_manager.get_user(user_id)
for item in items:
product = self.product_catalog.get_product(item.product_id)
if not self.inventory_tracker.check_stock(item.product_id, item.quantity):
raise InsufficientStockError()
order = self.order_processor.create_order(user_id, items)
payment = self.payment_handler.process_payment(order.total_amount)
self.inventory_tracker.reserve_items(items)
self.notification_sender.send_order_confirmation(user.email, order)
return order
분해 후 마이크로서비스 구조:
1. 주문 서비스 (Order Service)
# order-service/app.py
from flask import Flask, request, jsonify
import requests
app = Flask(__name__)
class OrderService:
def __init__(self):
self.user_service_url = "http://user-service:8080"
self.inventory_service_url = "http://inventory-service:8080"
self.payment_service_url = "http://payment-service:8080"
self.notification_service_url = "http://notification-service:8080"
@app.route('/orders', methods=['POST'])
def create_order(self):
order_data = request.json
# 1. 사용자 검증 (User Service 호출)
user_response = requests.get(
f"{self.user_service_url}/users/{order_data['user_id']}"
)
if user_response.status_code != 200:
return jsonify({"error": "Invalid user"}), 400
# 2. 재고 확인 (Inventory Service 호출)
inventory_check = requests.post(
f"{self.inventory_service_url}/inventory/check",
json={"items": order_data["items"]}
)
if not inventory_check.json()["available"]:
return jsonify({"error": "Insufficient stock"}), 400
# 3. 주문 생성
order = self._create_order_record(order_data)
# 4. 이벤트 발행 (비동기)
self._publish_order_created_event(order)
return jsonify(order), 201
def _publish_order_created_event(self, order):
"""주문 생성 이벤트 발행"""
event = {
"event_type": "order_created",
"order_id": order["id"],
"user_id": order["user_id"],
"items": order["items"],
"total_amount": order["total_amount"]
}
# Kafka나 RabbitMQ로 이벤트 발행
self.event_publisher.publish("order.created", event)
2. 재고 서비스 (Inventory Service)
# inventory-service/app.py
from flask import Flask, request, jsonify
app = Flask(__name__)
class InventoryService:
def __init__(self):
self.database = InventoryDatabase()
self.event_subscriber = EventSubscriber()
@app.route('/inventory/check', methods=['POST'])
def check_availability(self):
items = request.json["items"]
for item in items:
stock = self.database.get_stock(item["product_id"])
if stock < item["quantity"]:
return jsonify({"available": False}), 200
return jsonify({"available": True}), 200
@app.route('/inventory/reserve', methods=['POST'])
def reserve_items(self):
items = request.json["items"]
try:
with self.database.transaction():
for item in items:
self.database.reserve_stock(
item["product_id"],
item["quantity"]
)
return jsonify({"reserved": True}), 200
except Exception as e:
return jsonify({"error": str(e)}), 400
def handle_order_created_event(self, event):
"""주문 생성 이벤트 처리"""
try:
self.reserve_items_for_order(event["items"])
except Exception as e:
# 보상 트랜잭션 실행
self._publish_inventory_reservation_failed_event(event["order_id"])
3. 결제 서비스 (Payment Service)
# payment-service/app.py
from flask import Flask, request, jsonify
class PaymentService:
def __init__(self):
self.payment_gateway = PaymentGateway()
self.database = PaymentDatabase()
@app.route('/payments', methods=['POST'])
def process_payment(self):
payment_data = request.json
try:
# 외부 결제 게이트웨이 호출
gateway_response = self.payment_gateway.charge(
amount=payment_data["amount"],
payment_method=payment_data["payment_method"]
)
# 결제 기록 저장
payment_record = self.database.save_payment(
order_id=payment_data["order_id"],
amount=payment_data["amount"],
gateway_transaction_id=gateway_response["transaction_id"],
status="completed"
)
# 결제 완료 이벤트 발행
self._publish_payment_completed_event(payment_record)
return jsonify(payment_record), 201
except PaymentFailedException as e:
# 결제 실패 이벤트 발행
self._publish_payment_failed_event(payment_data["order_id"])
return jsonify({"error": "Payment failed"}), 400
4. 이벤트 기반 통신
# 이벤트 기반 비동기 통신
class EventDrivenCommunication:
def __init__(self):
self.kafka_producer = KafkaProducer()
self.kafka_consumer = KafkaConsumer()
def setup_event_handlers(self):
"""이벤트 핸들러 설정"""
# 주문 생성 → 재고 예약
@self.kafka_consumer.subscribe("order.created")
def handle_order_created(event):
inventory_service.reserve_items(event["items"])
# 재고 예약 완료 → 결제 처리
@self.kafka_consumer.subscribe("inventory.reserved")
def handle_inventory_reserved(event):
payment_service.process_payment({
"order_id": event["order_id"],
"amount": event["total_amount"]
})
# 결제 완료 → 알림 발송
@self.kafka_consumer.subscribe("payment.completed")
def handle_payment_completed(event):
notification_service.send_order_confirmation(event["order_id"])
# 실패 시 보상 트랜잭션
@self.kafka_consumer.subscribe("payment.failed")
def handle_payment_failed(event):
inventory_service.release_reservation(event["order_id"])
order_service.cancel_order(event["order_id"])
분해 결과 비교
| 측면 | 모노리스 | 마이크로서비스 |
|---|---|---|
| 배포 속도 | 전체 재배포 (30분) | 개별 서비스 배포 (5분) |
| 팀 생산성 | 하나의 큰 팀 | 4개의 전문 팀 |
| 장애 영향 | 전체 서비스 중단 | 부분 서비스 영향 |
| 확장성 | 전체 스케일링 | 필요한 서비스만 확장 |
| 기술 선택 | 단일 기술 스택 | 서비스별 최적 기술 |
다음 편 미리보기
2편: API Gateway, 서비스 메시, 통신 패턴에서는:
🌐 API Gateway 패턴
- Kong, Netflix Zuul, Spring Cloud Gateway
- 인증/인가, 로드밸런싱, 레이트 리미팅
- API 버저닝 및 문서화 전략
🕸️ 서비스 메시 (Service Mesh)
- Istio, Linkerd 아키텍처
- 사이드카 프록시 패턴
- 트래픽 관리, 보안, 관찰성
🔄 서비스 간 통신 패턴
- 동기 vs 비동기 통신
- 이벤트 기반 아키텍처
- Saga 패턴과 분산 트랜잭션
💻 실전 구현
- Spring Cloud를 이용한 마이크로서비스
- Docker/Kubernetes 배포
- CI/CD 파이프라인 구축
마이크로서비스의 기초를 탄탄히 다졌다면, 이제 실제 서비스 간 통신과 인프라 구성을 다룰 차례입니다. 2편에서는 더욱 실무적이고 구체적인 구현 방법들을 살펴보겠습니다.
성공적인 마이크로서비스 전환은 기술보다 조직 문화와 프로세스 변화가 더 중요합니다. 점진적 접근과 지속적 학습이 핵심입니다.
'System Architecture' 카테고리의 다른 글
| 마이크로서비스 아키텍처 완벽 가이드 3편: 데이터 관리, 모니터링, 배포 전략 (4) | 2025.07.23 |
|---|---|
| 마이크로서비스 아키텍처 완벽 가이드 2편: API Gateway, 서비스 메시, 통신 패턴 (3) | 2025.07.23 |