分布式微服务架构的落地,从来不是一场简单的技术选型。对于企学宝这样的在线学习平台,每一步决策都直接牵动着产品的交付质量和客户的信任度。
一、拆分边界:不是“拆得越细越好”
微服务拆分的核心难点,其实就在于“拆到什么粒度”。拆得太粗,服务之间照样互相掣肘,解耦效果大打折扣;拆得太细,分布式事务的协调成本、运维的复杂度和资源开销,都会指数级上升。我们遵循“高内聚、低耦合”的原则,参考DDD(领域驱动设计)的思路,最终确定了这样一套拆分边界。
1. 核心业务域拆分
| 微服务名 | 职责边界 | 数据隔离级别 |
|---|---|---|
| iam-service (认证鉴权) | 登录、注册、SSO、权限校验 | 全局共享 |
| org-service (组织架构) | 部门、岗位、员工信息同步 | 租户隔离 |
| course-service (课程中心) | 课件的CRUD、目录管理、学习进度 | 租户隔离 |
| exam-service (考试中心) | 试卷生成、答题提交、自动阅卷 | 租户隔离 |
| live-service (直播服务) | 直播间管理、互动消息、回放 | 租户隔离 |
| report-service (报表中心) | 学习数据统计、大屏展示 | 租户隔离 |
| message-service (消息中心) | 站内信、信息、邮件、企微推送 | 全局共享 |
| ai-service (AI能力) | ASR语音转写、RAG问答、AI陪练 | 租户隔离 |
2. 关键决策:共用数据库 vs. 独立数据库
那么,数据库是共用同一个物理实例,还是各扫门前雪?在拆分初期,为了降低迁移风险,我们选择了“逻辑分离,物理暂不分离”的策略。
短期来看,各微服务仍然连接同一个MySQL实例,但通过不同的Schema(Database)进行严格区分,代码层面也禁止跨Schema查询。长期来看,随着业务压力增长,我们把report-service的读操作剥离到只读实例,后续还会将高频写入的大表(比如学习记录表、考试提交表)单独拆分数据库,彻底避免热点表拖慢核心交易链路。
3. 共享型服务的下沉
像文件存储(OSS)、信息通道、OCR识别这类通用能力,如果每个微服务都自己对接一遍,那就不是拆分了,而是给自己添堵。所以,我们专门构建了一层基础支撑层(Basic Service),统一封装阿里云OSS SDK,对外提供file/upload接口,让技术栈彻底收敛。
二、灰度发布:SaaS系统的生命线
对于企学宝这种ToB的SaaS产品,“升级零感知”可以说是底线要求。依托阿里云生态,我们搭建了一套相对完善的灰度发布体系。
1. 灰度策略设计
灰度规则的设计要考虑多个维度,确保新版本在可控范围内逐步放量:
- 租户维度(Tenant ID):针对“友好客户”或付费等级高的客户,优先发布。
- 用户维度(User ID):面向内部测试账号,或特定企业内的管理员账号先行验证。
- 流量比例(Traffic %):全平台随机从1%逐步扩大到5%、20%,最后到100%,缓慢释放。
2. 技术实现:基于阿里云MSE + Spring Cloud Gateway
我们没有把灰度逻辑硬编码到业务代码里,而是借助阿里云MSE(微服务引擎)的全链路灰度能力来实现。
整体的运作流程是这样的:
- 打标:在HTTP Header或Dubbo Attachment中注入灰度标识(比如
gray=true或tenant_id=123)。 - 网关路由:Spring Cloud Gateway识别请求头,将灰度流量转发到V2版本的Pod。
- 服务透传:MSE Agent探针会把这个灰度标识在服务间调用(Feign/RPC)中全链路透传,确保灰度请求只调用下游的灰度服务,不会半路“跳”回V1。
# 示例:MSE 灰度规则配置(简化示意,实际通过MSE控制台配置标签路由)
strategy:
conditions:
- type: header
key: X-Tenant-ID
values: [ "888888", "999999" ] # 指定VIP租户ID
- type: cookie
key: gray_user
values: [ "true" ]
fallback: v1 # 非灰度流量走v1
3. 数据库兼容:新旧版本共存的关键
微服务拆分和重构的过程中,表结构难免发生变化。我们的原则很简单:“先兼容,后清理”。
- 新增字段:允许NULL值,确保V1版本写入时不报错。
- 删除字段:先在V1和V2代码里一起停止读取该字段,运行一段时间确认没有依赖后,再物理删除。
- 数据双写:在极端重构场景下,通过Canal监听Binlog或使用MQ进行数据双写,反复对比一致性。
4. 自动化回滚
Jenkins Pipeline里集成了健康检查接口(/actuator/health),发布V2版本后,会自动跑冒烟测试脚本。同时实时监控接口成功率、RT(响应时间)和JVM内存。一旦某个指标异常,30秒内就会自动触发回滚脚本,下线V2 Pod,切回V1版本。
三、踩坑实录
第一个绕不开的坑,是分布式事务。典型现象:员工学完一门课程,学习进度已经成功记录了,但积分却没到账——学习服务成功了,积分服务却失败了。解法其实很明确:尽量避开强一致分布式事务。我们改用RocketMQ事务消息来保证最终一致性(学习记录落库后发送可靠消息,积分服务异步消费)。极少数必须强一致的场景,采用TCC模式(Try-Confirm-Cancel),但业务侵入性比较高,使用起来得格外谨慎。
第二个坑是日志排查困难。一个请求跨5个微服务,排查故障要翻5台机器的日志,效率可想而知。后来引入了SkyWalking做全链路追踪,TraceId在网关层生成,通过MDC透传到所有RPC和HTTP调用。日志统一采集到SLS(阿里云日志服务),有了TraceId,秒级定位故障节点不再是问题。
第三个坑是本地调试地狱。本地启动一个服务,需要依赖十几个微服务,电脑卡得几乎没法用。最终靠Telepresence和阿里云Cloud Toolkit,把本地代码直接“插入”云端K8s集群,调用云端的依赖服务,实现云端联调,这才把效率提上来。
一句话总结:微服务不是用来炫技的,是用来解决实际问题的。清晰的拆分边界是根基,可靠的灰度发布是保障。对于企学宝这种SaaS产品而言,能安全、快速地迭代,才是最大的竞争力。
