Java/SOFA
02. Java/SOFA 微服务核心教程
本章进入微服务和 SOFAStack 主线。SOFAStack 官方介绍把它定位为面向金融级分布式架构的中间件集合,包含研发框架、RPC、注册中心、链路追踪、Metrics、分布式事务、服务治理等能力。学习它时不能只看“怎么引依赖”,要同时理解每个组件承担的系统职责。
1. 微服务先问“为什么”,再问“用什么”
微服务是一种架构和组织方式,不是“Spring Cloud + Nacos + Gateway + Feign”的组件堆叠。
适合拆成微服务的信号:
- 不同业务模块变更频率差异很大。
- 模块有独立团队负责。
- 模块有不同的容量、可用性和安全要求。
- 数据所有权可以清晰切分。
- 单体已经因为构建、部署、协作、扩容变得明显低效。
不适合急着拆的信号:
- 业务边界还不稳定。
- 团队规模小,部署和运维能力弱。
- 数据库表高度耦合。
- 只是为了简历或技术新鲜感。
- 一个服务挂了整个链路还是挂,拆分没有带来隔离收益。
V2EX 的微服务路线讨论中,有回复提醒“先理解微服务解决什么问题,有什么优缺点”。InfoQ 的 Sock Shop 文章也提到,演示应用后来成为参考应用,但服务合并也可能比想象中更困难。这两类资料都指向同一条经验:服务边界应该谨慎演进。
2. 金融领域的参考业务域
后续示例使用一个简化的金融支付/账户系统:
customer-service:客户信息。account-service:账户、余额、冻结金额。payment-service:支付订单、支付状态机。risk-service:风控检查。ledger-service:账务分录。notification-service:短信、邮件、站内信。reconciliation-service:对账和差错处理。
微服务边界不是 Controller 边界,而是数据和业务能力边界。例如支付订单和账务分录最好不要随意共用数据库表。支付服务可以调用账务服务,但不能直接写账务库。
3. SOFAStack 组件视图
3.1 SOFABoot
SOFABoot 是基于 Spring Boot 的研发框架。官方文档说明它增强了 Spring Boot,并提供 Readiness Check、类隔离、日志空间隔离等能力,还让用户在 Spring Boot 中方便使用 SOFA 中间件。
学习重点:
- 它不是替代 Spring Boot,而是基于 Spring Boot。
- SOFABoot 版本与 Spring Boot 版本有对应关系,选版本时必须看兼容矩阵。
- 对金融系统来说,Readiness Check、类隔离、日志隔离等能力服务于稳定性和运维。
3.2 SOFARPC
SOFARPC 是 Java RPC 框架。它承担服务发布、引用、协议、负载均衡、过滤器、序列化、注册中心集成等职责。
RPC 的关键不是“比 HTTP 快”,而是:
- 面向接口调用。
- 客户端代理封装远程调用。
- 统一治理超时、重试、路由、过滤器。
- 与注册中心、监控、链路追踪打通。
3.3 SOFARegistry
SOFARegistry 是服务注册中心。官方资料强调它是生产级、低延迟、高可用的服务注册中心,并采用分层架构。学习时要理解三个问题:
- 服务实例如何注册。
- 消费者如何订阅服务列表。
- 实例上下线如何推送到消费者。
生产部署时,官方部署文档区分集成部署和独立部署,并建议生产环境按角色独立部署。学习阶段可以本地最小化,生产设计必须考虑可用性、容量和故障域。
3.4 SOFATracer、Lookout、服务治理
微服务一旦拆开,问题会从“代码调用栈”变成“网络调用链”。你必须知道:
- 一个用户请求经过哪些服务。
- 每段耗时多少。
- 哪个服务返回错误。
- 是否发生重试。
- 是否触发熔断和限流。
- 某个交易单号是否完整走完。
链路追踪和 metrics 不是“运维附加项”,而是微服务可用性的基础。
4. 最小 SOFABoot/SOFARPC 思路
下面是教学级结构,实际版本号要按官方兼容矩阵和公司基线确定。
4.1 接口模块
public interface AccountQueryService {
AccountSnapshot queryByAccountNo(String accountNo);
}
public record AccountSnapshot(
String accountNo,
String currency,
long availableAmount,
long frozenAmount
) {}
接口模块通常被 provider 和 consumer 共同依赖。注意:
- DTO 要稳定。
- 不要暴露 JPA Entity。
- 金额用最小货币单位整数或明确的
BigDecimal规则。 - 字段增加要兼容旧消费者。
4.2 Provider
@SofaService(interfaceType = AccountQueryService.class)
@Service
public class AccountQueryServiceImpl implements AccountQueryService {
private final AccountRepository accountRepository;
public AccountQueryServiceImpl(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Override
public AccountSnapshot queryByAccountNo(String accountNo) {
Account account = accountRepository.findByAccountNo(accountNo)
.orElseThrow(() -> new AccountNotFoundException(accountNo));
return new AccountSnapshot(
account.getAccountNo(),
account.getCurrency(),
account.getAvailableAmount(),
account.getFrozenAmount()
);
}
}
教学重点:
@SofaService表达服务发布。- 实现类仍然可以是 Spring Bean。
- 业务异常要有明确错误码,不要把数据库异常直接抛给消费者。
4.3 Consumer
@Service
public class PaymentApplicationService {
@SofaReference(interfaceType = AccountQueryService.class)
private AccountQueryService accountQueryService;
public PaymentPreview preview(String accountNo) {
AccountSnapshot account = accountQueryService.queryByAccountNo(accountNo);
return PaymentPreview.from(account);
}
}
教学重点:
- 本地看像接口调用,实际上是远程调用。
- 必须配置超时。
- 必须知道异常语义。
- 必须做降级或失败路径设计。
5. RPC 与 REST 的边界
RPC 适合
- 内部服务间高频调用。
- 强类型接口。
- 统一治理和注册发现。
- 公司内部有明确 SDK/接口版本管理。
REST/HTTP 适合
- 对外 API。
- 前后端交互。
- 跨组织、跨语言集成。
- 需要良好 HTTP 语义、缓存、网关、OpenAPI 文档。
金融系统常见模式:
- 外部请求进入 API Gateway,使用 HTTP/REST。
- 内部核心服务之间使用 RPC 或 gRPC。
- 异步事件使用 MQ。
- 报表、对账、数据分析走批处理或数据管道。
6. Spring Cloud 体系的作用
Spring Cloud 官方文档把它描述为帮助构建分布式系统常见模式的工具集合,包括配置管理、服务发现、熔断、路由等。
常见组件能力:
- Config:外部化配置。
- Discovery:服务注册发现。
- Gateway:入口路由、鉴权、限流、灰度。
- Circuit Breaker:熔断隔离。
- OpenFeign:声明式 HTTP 客户端。
- Bus:配置刷新和事件传播。
Spring Cloud Alibaba 关注:
- Nacos:服务发现和配置。
- Sentinel:流控、熔断、系统保护。
- Seata:分布式事务。
- RocketMQ:消息。
SOFA 与 Spring Cloud 的关系不是“只能二选一”。InfoQ 对 SOFAStack 双模微服务平台的报道提到兼容 Dubbo、Spring Cloud 等运行环境以及平滑迁移思路。实际企业里常见多技术栈并存:
- 老系统 Dubbo。
- 新系统 Spring Cloud Alibaba。
- 金融核心内部 SOFARPC/SOFARegistry。
- 边缘 API 使用 Spring Cloud Gateway。
关键是统一治理标准:超时、重试、鉴权、链路追踪、日志、错误码、发布流程不能各搞一套。
7. 服务治理核心能力
7.1 超时
默认无超时是事故隐患。每个远程调用都要有超时预算。
示例:
- 网关总超时:2s。
- payment 调 account:300ms。
- payment 调 risk:500ms。
- payment 写本地 DB:200ms。
- 预留重试和返回时间:1s。
超时不是越大越安全。超时过大时,上游线程会被拖住,下游恢复后也可能被积压流量打垮。
7.2 重试
只对明确可重试的失败重试,例如网络抖动、读接口、幂等写接口。不要对非幂等支付扣款随意重试。
重试必须配合:
- 幂等键。
- 最大次数。
- 退避策略。
- 熔断。
- 指标。
7.3 熔断
熔断的目的不是“修复下游”,而是保护当前服务和整体链路。risk-service 不可用时,payment-service 可以选择:
- 拒绝高风险交易。
- 只允许低风险小额交易。
- 进入人工审核。
- 返回稍后重试。
金融场景不能随便“降级为成功”。降级策略必须由业务和风控确认。
7.4 限流
限流位置:
- 网关按用户、IP、接口限流。
- 服务内按业务动作限流。
- 下游客户端按依赖服务限流。
- MQ 消费端按处理能力限流。
限流返回要可解释:是系统繁忙、额度限制、风控限制,还是重复请求。
7.5 灰度和路由
灰度发布需要考虑:
- 请求按用户、机构、租户、交易类型路由。
- 数据库 schema 是否兼容。
- 新老服务是否能同时消费消息。
- 回滚后是否能处理新版本写入的数据。
8. 服务接口设计
8.1 错误码
错误码至少区分:
- 参数错误。
- 鉴权失败。
- 授权失败。
- 业务规则拒绝。
- 幂等冲突。
- 下游超时。
- 系统内部错误。
不要让调用方解析异常字符串。
8.2 幂等键
支付创建接口示例:
public record CreatePaymentCommand(
String requestId,
String merchantId,
String orderNo,
long amount,
String currency
) {}
幂等键可由 merchantId + orderNo 或全局 requestId 构成。数据库层应该有唯一约束,不能只靠 Redis。
8.3 版本兼容
接口演进建议:
- 只增加可选字段。
- 不改变字段语义。
- 不复用废弃字段。
- 删除字段前先观察消费者。
- 关键接口保留兼容层。
9. 本章练习
练习 1:服务拆分设计
给一个单体电商支付模块,拆出:
- payment-service
- account-service
- risk-service
- ledger-service
- notification-service
写清楚每个服务拥有的数据表,哪些调用同步,哪些调用异步,哪些失败要补偿。
练习 2:SOFARPC 接口契约
设计 RiskDecisionService:
- 输入:用户、账户、交易、设备、IP。
- 输出:通过、拒绝、人工审核、需要二次验证。
- 规定超时、错误码、降级策略。
练习 3:Spring Cloud Gateway 入口
设计网关规则:
/api/payments/**路由到 payment-service。- 添加鉴权 Filter。
- 添加 requestId。
- 限制单用户每分钟请求数。
- 灰度 5% 流量到新版本。
10. 本章检查清单
- 能解释 SOFAStack、SOFABoot、SOFARPC、SOFARegistry 的关系。
- 能解释 RPC、REST、MQ 的适用边界。
- 能说明 Spring Cloud 和 SOFA 并存时需要统一哪些治理标准。
- 能为一个远程调用设计超时、重试、熔断、错误码。
- 能解释为什么金融写接口必须有幂等键和数据库唯一约束。