Java微服务架构实战:基于Spring Cloud的在线教育平台服务拆分与治理

举报
江南清风起 发表于 2025/07/19 18:18:22 2025/07/19
【摘要】 Java微服务架构实战:基于Spring Cloud的在线教育平台服务拆分与治理 一、业务背景与拆分目标在线教育平台在业务快速发展的过程中,单体架构逐渐暴露出以下痛点:代码耦合严重,课程、订单、用户、支付模块互相依赖,发布风险高。数据库成为瓶颈,一张 course 表被 8 个业务场景同时写,锁竞争激烈。横向扩展困难,非核心功能(如“学习时长统计”)占用大量资源。拆分目标以领域驱动设计(D...

Java微服务架构实战:基于Spring Cloud的在线教育平台服务拆分与治理

一、业务背景与拆分目标

在线教育平台在业务快速发展的过程中,单体架构逐渐暴露出以下痛点:

  1. 代码耦合严重,课程、订单、用户、支付模块互相依赖,发布风险高。
  2. 数据库成为瓶颈,一张 course 表被 8 个业务场景同时写,锁竞争激烈。
  3. 横向扩展困难,非核心功能(如“学习时长统计”)占用大量资源。

拆分目标

  • 以领域驱动设计(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 化奠定数据安全基础。

后续规划:

  1. 引入 Serverless(Spring Cloud Function)对“学习行为埋点”弹性伸缩。
  2. 使用 GraalVM 将网关和函数编译为 Native Image,冷启动 <50ms。
  3. 基于 Dapr 的 Sidecar 模式将中间件能力下沉,业务专注领域逻辑。

image.png

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。