Java微服务架构实战:基于Spring Cloud的在线教育平台服务拆分与治理
Java微服务架构实战:基于Spring Cloud的在线教育平台服务拆分与治理
一、业务背景与拆分目标
在线教育平台在业务快速发展的过程中,单体架构逐渐暴露出以下痛点:
- 代码耦合严重,课程、订单、用户、支付模块互相依赖,发布风险高。
- 数据库成为瓶颈,一张
course
表被 8 个业务场景同时写,锁竞争激烈。 - 横向扩展困难,非核心功能(如“学习时长统计”)占用大量资源。
拆分目标
-
以领域驱动设计(DDD)为指导,将平台拆分为 5 个核心微服务:
• 用户服务(user-service)
• 课程服务(course-service)
• 订单服务(order-service)
• 支付服务(payment-service)
• 学习行为服务(behavior-service) -
治理目标:零侵入可观测、统一配置、灰度发布、链路熔断、多租户隔离。
二、领域建模与服务边界划分
2.1 领域事件梳理
事件 | 发布方 | 订阅方 | 说明 |
---|---|---|---|
UserRegistered | user-service | course-service, order-service | 用户注册后自动开通试听课程 |
CoursePurchased | order-service | payment-service, behavior-service | 创建订单后拉起支付并记录学习权限 |
PaymentCompleted | payment-service | order-service | 支付成功后更新订单状态 |
2.2 服务边界代码示例(防腐层)
在 course-service
中通过 OpenFeign 定义对用户服务的防腐层接口,避免直接引用 user-service 的实体类。
@FeignClient(name = "user-service", path = "/users")
public interface UserClient {
@GetMapping("/{userId}/profile")
UserProfileDTO getProfile(@PathVariable Long userId);
class UserProfileDTO {
private Long userId;
private String nickname;
private String avatar;
// getter/setter省略
}
}
通过 DTO 隔离,course-service 只依赖轻量级契约,而非 user-service 的领域实体。
三、Spring Cloud 技术栈选型与版本
组件 | 版本 | 选型理由 |
---|---|---|
Spring Boot | 3.2.x | 基座 |
Spring Cloud | 2023.0.x | 与 Boot 版本对齐,支持虚拟线程 |
Spring Cloud Gateway | 2023.0.x | 响应式网关,支持 WebFlux |
Nacos | 2.3.x | 注册中心 + 配置中心,支持 gRPC 长连接 |
Sentinel | 1.8.6 | 流量治理,支持热点参数限流 |
Seata | 2.0.x | 分布式事务,AT 模式对业务零侵入 |
SkyWalking | 9.7.x | APM,支持 trace 与 log 关联 |
四、服务注册与配置中心落地
4.1 Nacos 命名空间与环境隔离
在 bootstrap.yml
中通过命名空间实现“测试/预发/生产”逻辑隔离:
spring:
application:
name: course-service
cloud:
nacos:
server-addr: nacos:8848
config:
namespace: 7d8e1e8b-3f2a-4a9c-8b0c-6e3d1c6d7b0a # 测试环境
file-extension: yaml
group: edu-platform
discovery:
namespace: 7d8e1e8b-3f2a-4a9c-8b0c-6e3d1c6d7b0a
4.2 共享配置与动态刷新
将数据源、日志、监控等公共配置下沉到 edu-platform-common.yaml
,各服务自动继承:
# 文件:edu-platform-common.yaml
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
hikari:
maximum-pool-size: 20
connection-timeout: 5000
logging:
pattern:
console: "[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %-5level %logger{50} - %msg%n"
在业务代码中通过 @RefreshScope
实现动态刷新:
@RestController
@RefreshScope
public class CourseController {
@Value("${course.audit.enabled:true}")
private boolean auditEnabled;
@GetMapping("/courses/feature-flag")
public Map<String, Boolean> featureFlag() {
return Map.of("auditEnabled", auditEnabled);
}
}
五、统一网关与路由治理
5.1 网关聚合 Swagger
通过 Spring Cloud Gateway 的 SwaggerResource
自动聚合所有微服务的 API 文档:
@Component
public class SwaggerResourceHandler implements SwaggerResourcesProvider {
private final RouteLocator routeLocator;
public SwaggerResourceHandler(RouteLocator routeLocator) {
this.routeLocator = routeLocator;
}
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
routeLocator.getRoutes().subscribe(route -> {
String serviceId = route.getId();
if (serviceId.startsWith("edu-")) {
resources.add(swaggerResource(serviceId,
"/" + serviceId + "/v3/api-docs", "3.0"));
}
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location, String version) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion(version);
return swaggerResource;
}
}
5.2 金丝雀发布(灰度)路由
利用 Gateway 的 Weight
断言实现 5% 流量到 v2 版本:
spring:
cloud:
gateway:
routes:
- id: course-service-canary
uri: lb://course-service-v2
predicates:
- Weight=course-service, 5
- id: course-service-stable
uri: lb://course-service-v1
predicates:
- Weight=course-service, 95
六、分布式事务:Seata AT 模式实战
6.1 场景
用户购买课程 → 扣减库存(course-service)→ 创建订单(order-service)→ 扣减余额(user-service)。
要求最终一致性,且对业务代码零侵入。
6.2 配置 Seata Server
seata-server
使用 db 模式存储事务日志,建表脚本:
-- global_table
CREATE TABLE `global_table` (
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
6.3 全局事务代码
在 order-service 的 OrderApplicationService
中开启全局事务:
@Service
public class OrderApplicationService {
@GlobalTransactional(name = "purchase-course", rollbackFor = Exception.class)
public OrderDTO purchaseCourse(PurchaseCommand cmd) {
// 1. 调用 course-service 锁定库存
courseClient.lockStock(cmd.getCourseId(), cmd.getQuantity());
// 2. 创建本地订单
Order order = Order.create(cmd);
orderRepository.save(order);
// 3. 调用 user-service 扣减余额
userClient.deductBalance(cmd.getUserId(), order.getAmount());
return OrderDTO.from(order);
}
}
Seata 通过代理数据源自动解析 SQL 生成回滚日志,异常时各分支事务回滚。
七、熔断与限流:Sentinel 规则持久化到 Nacos
7.1 资源埋点
在 course-service 的查询热点课程接口上埋点:
@RestController
public class CourseController {
@GetMapping("/courses/hot")
@SentinelResource(value = "hotCourses",
blockHandler = "blockHandler",
fallback = "fallback")
public List<CourseDTO> hotCourses(@RequestParam(defaultValue = "10") int size) {
return courseService.findHotCourses(size);
}
public List<CourseDTO> blockHandler(int size, BlockException ex) {
return List.of(); // 限流返回空列表
}
public List<CourseDTO> fallback(int size, Throwable ex) {
return courseCache.getHotCourses(); // 降级读缓存
}
}
7.2 规则持久化
在 Nacos 创建 sentinel-course-service-flow-rules
:
[
{
"resource": "hotCourses",
"limitApp": "default",
"grade": 1,
"count": 200,
"strategy": 0,
"controlBehavior": 0
}
]
引入 sentinel-datasource-nacos
自动拉取:
spring:
cloud:
sentinel:
datasource:
flow:
nacos:
server-addr: nacos:8848
data-id: sentinel-course-service-flow-rules
group-id: SENTINEL_GROUP
rule-type: flow
八、链路追踪与日志关联
8.1 SkyWalking Agent 启动参数
java -javaagent:/agent/skywalking-agent.jar \
-Dskywalking.agent.service_name=course-service \
-Dskywalking.collector.backend_service=oap:11800 \
-jar course-service.jar
8.2 TraceId 透传日志
在 logback-spring.xml 中添加 %tid
:
<encoder>
<pattern>%d [%tid] [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
示例日志输出:
2025-07-19 14:23:45.123 [TID:7a3f9c2b1e8a4c0d9e6f5a2b4c8d7e1f] [http-nio-8080-exec-1] INFO c.e.c.CourseController - query course list
九、多租户 SaaS 隔离方案
9.1 共享数据库、隔离数据表
在 MyBatis-Plus 中使用 TenantLineInnerInterceptor
:
@Configuration
public class MyBatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
return new LongValue(TenantContext.getTenantId());
}
}));
return interceptor;
}
}
9.2 租户线程上下文
使用 TransmittableThreadLocal
保证线程池间传递:
public class TenantContext {
private static final TransmittableThreadLocal<Long> TENANT_HOLDER = new TransmittableThreadLocal<>();
public static void setTenantId(Long tenantId) {
TENANT_HOLDER.set(tenantId);
}
public static Long getTenantId() {
return TENANT_HOLDER.get();
}
public static void clear() {
TENANT_HOLDER.remove();
}
}
十、灰度发布与零停机滚动升级
10.1 基于 K8s label 的灰度
Deployment 增加 version=v2
label:
apiVersion: apps/v1
kind: Deployment
metadata:
name: course-service-v2
spec:
replicas: 1
selector:
matchLabels:
app: course-service
version: v2
template:
metadata:
labels:
app: course-service
version: v2
10.2 零停机验证
使用 Argo Rollouts 进行蓝绿发布:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: course-service-rollout
spec:
replicas: 10
strategy:
blueGreen:
activeService: course-service
previewService: course-service-preview
autoPromotionEnabled: false
通过 kubectl argo rollouts promote course-service-rollout
手动确认全量切换。
十一、总结与展望
本文从业务痛点出发,完整落地了基于 Spring Cloud 的在线教育平台微服务拆分与治理:
- 通过 DDD + 防腐层定义服务边界;
- 使用 Nacos 统一注册/配置,Sentinel 动态流控,Seata 零侵入事务;
- SkyWalking 实现可观测,Argo CD + Rollouts 实现 GitOps 与灰度发布;
- 多租户隔离为 SaaS 化奠定数据安全基础。
后续规划:
- 引入 Serverless(Spring Cloud Function)对“学习行为埋点”弹性伸缩。
- 使用 GraalVM 将网关和函数编译为 Native Image,冷启动 <50ms。
- 基于 Dapr 的 Sidecar 模式将中间件能力下沉,业务专注领域逻辑。
- 点赞
- 收藏
- 关注作者
评论(0)