微服务架构:3 个服务 + API 网关
使用 Python 构建 3 个微服务,Docker Compose 编排,Nginx 作为 API 网关
你将学到
- 设计微服务架构
- 实现服务间通信
- 使用 Nginx 作为 API 网关
- 使用 Docker Compose 编排多服务
前置知识
架构设计
┌─────────┐
│ Nginx │
│ (网关) │
└────┬────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ User │ │ Order │ │ Product │
│ Service │ │ Service │ │ Service │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│User DB │ │Order DB │ │ProductDB│
└─────────┘ └─────────┘ └─────────┘
实现步骤
第一步:服务结构
microservices/
├── services/
│ ├── user/
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ └── app.py
│ ├── order/
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ └── app.py
│ └── product/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── app.py
├── nginx/
│ └── nginx.conf
└── docker-compose.yml
第二步:User 服务
# services/user/app.py
from flask import Flask, jsonify, request
import psycopg2
import os
app = Flask(__name__)
def get_db():
return psycopg2.connect(
host="user-db",
database=os.getenv("DB_NAME", "users"),
user=os.getenv("DB_USER", "user"),
password=os.getenv("DB_PASSWORD", "password")
)
@app.route("/users", methods=["GET"])
def list_users():
conn = get_db()
cur = conn.cursor()
cur.execute("SELECT id, name, email FROM users")
users = [{"id": r[0], "name": r[1], "email": r[2]} for r in cur.fetchall()]
cur.close()
conn.close()
return jsonify(users)
@app.route("/users", methods=["POST"])
def create_user():
data = request.json
conn = get_db()
cur = conn.cursor()
cur.execute(
"INSERT INTO users (name, email) VALUES (%s, %s) RETURNING id",
(data["name"], data["email"])
)
user_id = cur.fetchone()[0]
conn.commit()
cur.close()
conn.close()
return jsonify({"id": user_id, **data}), 201
@app.route("/health")
def health():
return jsonify({"status": "healthy"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
第三步:Order 服务
# services/order/app.py
from flask import Flask, jsonify, request
import psycopg2
import requests
import os
app = Flask(__name__)
def get_db():
return psycopg2.connect(
host="order-db",
database=os.getenv("DB_NAME", "orders"),
user=os.getenv("DB_USER", "user"),
password=os.getenv("DB_PASSWORD", "password")
)
@app.route("/orders", methods=["GET"])
def list_orders():
conn = get_db()
cur = conn.cursor()
cur.execute("SELECT id, user_id, product_id, quantity FROM orders")
orders = [{"id": r[0], "user_id": r[1], "product_id": r[2], "quantity": r[3]} for r in cur.fetchall()]
cur.close()
conn.close()
return jsonify(orders)
@app.route("/orders", methods=["POST"])
def create_order():
data = request.json
# 验证用户存在
user_resp = requests.get(f"http://user-service/users/{data['user_id']}")
if user_resp.status_code != 200:
return jsonify({"error": "User not found"}), 400
# 验证产品存在
product_resp = requests.get(f"http://product-service/products/{data['product_id']}")
if product_resp.status_code != 200:
return jsonify({"error": "Product not found"}), 400
conn = get_db()
cur = conn.cursor()
cur.execute(
"INSERT INTO orders (user_id, product_id, quantity) VALUES (%s, %s, %s) RETURNING id",
(data["user_id"], data["product_id"], data["quantity"])
)
order_id = cur.fetchone()[0]
conn.commit()
cur.close()
conn.close()
return jsonify({"id": order_id, **data}), 201
@app.route("/health")
def health():
return jsonify({"status": "healthy"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
第四步:Product 服务
# services/product/app.py
from flask import Flask, jsonify, request
import psycopg2
import os
app = Flask(__name__)
def get_db():
return psycopg2.connect(
host="product-db",
database=os.getenv("DB_NAME", "products"),
user=os.getenv("DB_USER", "user"),
password=os.getenv("DB_PASSWORD", "password")
)
@app.route("/products", methods=["GET"])
def list_products():
conn = get_db()
cur = conn.cursor()
cur.execute("SELECT id, name, price FROM products")
products = [{"id": r[0], "name": r[1], "price": float(r[2])} for r in cur.fetchall()]
cur.close()
conn.close()
return jsonify(products)
@app.route("/products/<int:product_id>", methods=["GET"])
def get_product(product_id):
conn = get_db()
cur = conn.cursor()
cur.execute("SELECT id, name, price FROM products WHERE id = %s", (product_id,))
row = cur.fetchone()
cur.close()
conn.close()
if row:
return jsonify({"id": row[0], "name": row[1], "price": float(row[2])})
return jsonify({"error": "Not found"}), 404
@app.route("/health")
def health():
return jsonify({"status": "healthy"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
第五步:Docker Compose
# docker-compose.yml
version: "3.8"
services:
# API Gateway
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- user-service
- order-service
- product-service
# User Service
user-service:
build: ./services/user
environment:
- DB_NAME=users
- DB_USER=user
- DB_PASSWORD=password
depends_on:
- user-db
user-db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=users
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- user-data:/var/lib/postgresql/data
# Order Service
order-service:
build: ./services/order
environment:
- DB_NAME=orders
- DB_USER=user
- DB_PASSWORD=password
depends_on:
- order-db
order-db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=orders
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- order-data:/var/lib/postgresql/data
# Product Service
product-service:
build: ./services/product
environment:
- DB_NAME=products
- DB_USER=user
- DB_PASSWORD=password
depends_on:
- product-db
product-db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=products
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- product-data:/var/lib/postgresql/data
volumes:
user-data:
order-data:
product-data:
第六步:Nginx 配置
# nginx/nginx.conf
server {
listen 80;
location /users/ {
proxy_pass http://user-service:5000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /orders/ {
proxy_pass http://order-service:5000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /products/ {
proxy_pass http://product-service:5000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
完整项目结构
microservices/
├── services/
│ ├── user/
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ └── app.py
│ ├── order/
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ └── app.py
│ └── product/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── app.py
├── nginx/
│ └── nginx.conf
└── docker-compose.yml
最佳实践
- 服务独立数据库:每个服务有自己的数据库
- API 网关:统一入口,路由分发
- 服务发现:使用 Docker 网络的服务名
- 健康检查:每个服务提供健康检查端点
- 错误处理:服务间调用要有错误处理
常见问题
Q: 如何处理服务间通信失败? A: 使用重试机制和断路器模式。
Q: 如何查看服务日志?
A: docker-compose logs -f [service-name]
Q: 如何扩展单个服务?
A: docker-compose up -d --scale user-service=3
扩展挑战
- 添加服务注册与发现
- 实现分布式追踪
- 添加消息队列
相关课程
| 课程 | 相关章节 |
|---|---|
| Python | 使用 Flask 构建 REST API |
| Docker | Docker Compose 基础 |
| Docker | Bridge 网络 |
| Docker | Docker Compose |