4. 微服务示例应用部署:使用 Spring Boot / Python Flask 构建两个模拟服务并部署到 K8s
本章将构建课程后续所有事务测试实验的“靶场”——两个轻量级微服务:订单服务(Order Service)与库存服务(Inventory Service)。我们分别使用 Spring Boot(Java)和 Python Flask 实现,并完整部署到 Kubernetes 集群。通过这一过程,你将掌握多语言微服务的容器化、镜像推送、K8s 资源清单编写以及服务间调用验证。
4.1 服务职责与 API 契约
两个服务共同模拟一次“下单扣库存”的原子操作场景。下表定义了核心 API 接口与预期行为:
| 服务 | 端点 | 方法 | 请求体(JSON) | 成功响应 | 失败场景 |
|---|---|---|---|---|---|
| 订单服务 | /api/order/create |
POST | {"productId": "P001", "quantity": 2} |
{"orderId": "ORD-xxx", "status": "CREATED"} |
库存不足、服务不可用 |
| 库存服务 | /api/inventory/deduct |
POST | {"productId": "P001", "quantity": 2} |
{"success": true, "remaining": 98} |
库存不足、重复扣减 |
| 库存服务 | /api/inventory/query |
GET | 路径参数 ?productId=P001 |
{"productId": "P001", "stock": 100} |
商品不存在 |
设计意图: 订单服务是事务发起方,库存服务是资源参与方。后续章节将围绕这两个服务之间的 API 调用链,注入网络延迟、服务崩溃、数据不一致等故障,验证分布式事务边界的正确性。
4.2 服务实现要点
4.2.1 订单服务(Spring Boot)
订单服务使用 Spring Boot 3.x + Maven 构建,核心逻辑包含:
- 接收 HTTP POST 请求,解析 JSON 负载。
- 通过
RestTemplate调用库存服务的/api/inventory/deduct端点。 - 根据库存服务返回结果决定订单状态(CREATED / FAILED)。
- 返回标准化的订单响应。
关键代码片段(Controller 层):
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Value("${inventory.service.url}")
private String inventoryServiceUrl;
@PostMapping("/create")
public ResponseEntity<Map<String, Object>> createOrder(@RequestBody OrderRequest request) {
Map<String, Object> response = new HashMap<>();
try {
// 调用库存服务扣减
InventoryResponse invResp = restTemplate.postForObject(
inventoryServiceUrl + "/api/inventory/deduct",
request,
InventoryResponse.class
);
if (invResp != null && invResp.isSuccess()) {
response.put("orderId", "ORD-" + UUID.randomUUID().toString().substring(0, 8));
response.put("status", "CREATED");
return ResponseEntity.ok(response);
} else {
response.put("status", "FAILED");
response.put("reason", "库存不足");
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
}
} catch (Exception e) {
response.put("status", "FAILED");
response.put("reason", "库存服务不可用: " + e.getMessage());
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
}
}
}
4.2.2 库存服务(Python Flask)
库存服务使用 Python 3.11 + Flask 实现,使用内存字典模拟数据库,支持并发安全(线程锁):
from flask import Flask, request, jsonify
import threading
app = Flask(__name__)
# 模拟库存数据
stock = {"P001": 100, "P002": 50}
lock = threading.Lock()
@app.route('/api/inventory/deduct', methods=['POST'])
def deduct():
data = request.get_json()
product_id = data.get('productId')
quantity = data.get('quantity', 0)
with lock:
if product_id not in stock:
return jsonify({"success": False, "message": "商品不存在"}), 404
if stock[product_id] < quantity:
return jsonify({"success": False, "message": "库存不足"}), 409
stock[product_id] -= quantity
return jsonify({"success": True, "remaining": stock[product_id]}), 200
@app.route('/api/inventory/query', methods=['GET'])
def query():
product_id = request.args.get('productId')
if product_id in stock:
return jsonify({"productId": product_id, "stock": stock[product_id]}), 200
return jsonify({"message": "商品不存在"}), 404
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
注意: 生产环境不应使用内存存储。此处仅为简化实验环境,后续章节会引入 MySQL / Redis 作为真实数据源,并测试事务边界在持久化层的行为。
4.3 容器化与镜像构建
两个服务分别使用 Dockerfile 构建镜像。下表对比了构建方式:
| 服务 | 基础镜像 | 构建方式 | 暴露端口 | 镜像大小(约) |
|---|---|---|---|---|
| 订单服务 | eclipse-temurin:17-jre-alpine |
Maven 编译 + JAR 包运行 | 8080 | 180 MB |
| 库存服务 | python:3.11-slim |
pip 安装依赖 + 直接运行 | 5000 | 120 MB |
构建命令示例(订单服务):
# 构建镜像
docker build -t order-service:1.0.0 -f Dockerfile.order .
# 推送至私有仓库(假设 registry 地址为 registry.example.com)
docker tag order-service:1.0.0 registry.example.com/order-service:1.0.0
docker push registry.example.com/order-service:1.0.0
4.4 Kubernetes 部署清单
每个服务包含 Deployment 和 Service 两个核心资源。以下为订单服务的完整 YAML 清单:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: microservices-demo
spec:
replicas: 2
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: registry.example.com/order-service:1.0.0
ports:
- containerPort: 8080
env:
- name: INVENTORY_SERVICE_URL
value: "http://inventory-service:5000"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: microservices-demo
spec:
selector:
app: order-service
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
库存服务的清单结构与订单服务类似,仅需调整镜像、端口(5000)和健康检查路径(/api/inventory/query?productId=P001)。
4.5 部署验证与调用链路
执行以下命令完成部署:
# 创建命名空间
kubectl create namespace microservices-demo
# 部署两个服务
kubectl apply -f order-service-deployment.yaml
kubectl apply -f inventory-service-deployment.yaml
# 检查 Pod 状态
kubectl get pods -n microservices-demo -w
# 等待所有 Pod 处于 Running 状态
部署成功后,使用临时 Pod 测试服务间调用:
# 进入集群内临时测试 Pod
kubectl run test-pod --image=curlimages/curl -it --rm --restart=Never -- sh
# 测试库存服务
curl -s http://inventory-service:5000/api/inventory/query?productId=P001
# 测试订单服务(会触发库存扣减)
curl -s -X POST http://order-service:80/api/order/create \
-H "Content-Type: application/json" \
-d '{"productId": "P001", "quantity": 2}'
验证结果: 首次调用应返回
{"orderId":"ORD-xxxx","status":"CREATED"},再次查询库存应显示剩余 98。若库存服务故意返回失败,订单服务应返回 HTTP 409 并携带失败原因。
4.6 服务间调用架构图
下图展示了两个微服务在 Kubernetes 集群内的部署拓扑与 API 调用关系:
4.7 部署清单速查表
下表汇总了部署过程中需要关注的关键配置项,便于快速排查问题:
| 配置项 | 订单服务 | 库存服务 | 说明 |
|---|---|---|---|
| 命名空间 | microservices-demo |
microservices-demo |
统一命名空间便于网络策略管理 |
| Service 名称 | order-service |
inventory-service |
作为 DNS 名称被其他服务引用 |
| Service 端口 | 80 → 8080 | 5000 → 5000 | ClusterIP 类型,仅集群内可达 |
| 环境变量 | INVENTORY_SERVICE_URL |
无 | 订单服务通过此变量定位库存服务 |
| 健康检查 | /actuator/health |
/api/inventory/query?productId=P001 |
livenessProbe 确保 Pod 自动恢复 |
| 资源限制 | 256Mi/512Mi | 128Mi/256Mi | 避免单 Pod 耗尽节点资源 |
实验建议: 部署完成后,手动删除一个订单服务 Pod,观察 K8s 如何自动重建并保持服务可用。这是后续测试事务边界时“服务崩溃”场景的基础操作。