Spring Cloud微服务架构搭建电商系统:从单体到S2B2C的实战演进
前言
做过电商系统的人都知道,业务从小做到大的过程中,最怕的不是流量问题,而是架构问题。单体架构在业务初期够用,但当你的平台从自营B2C扩展到多商家入驻的B2B2C,再演进到供应链赋能的S2B2C模式时,单体架构就会变成一场灾难——每次发布像拆炸弹,一个小改动可能牵连整个系统。
本文围绕一个S2B2C电商系统的真实落地案例,分享如何用Spring Cloud从单体架构演进到微服务架构的实战经验。不是理论教程,是踩坑之后的真实验证。以小羊云商为参考案例,覆盖了多租户隔离、供应链分销等S2B2C特有场景的架构决策。
一、为什么要从单体走向微服务
1.1 单体架构的瓶颈
该平台最初是一个标准的B2C商城单体应用,所有功能打包在一个WAR包里:商品管理、订单管理、支付、会员、营销、物流……前两年没太大问题,但业务扩展到B2B2C多商家入驻后,问题集中爆发:
- 发布耦合:商品模块改一个接口,整个系统要重新部署,所有功能都被影响
- 技术栈受限:搜索模块想用Elasticsearch,推荐模块想用Python做AI算法,但在单体里很难混用技术栈
- 扩展困难:大促时只有订单和支付模块需要扩容,但单体架构只能整体扩容,资源浪费严重
- 团队协作冲突:多个团队改同一个代码库,代码合并冲突频繁,开发效率直线下降
1.2 为什么选Spring Cloud
选型时我们对比了Dubbo和Spring Cloud两条路线:
| 维度 | Dubbo | Spring Cloud |
|---|---|---|
| 通信协议 | RPC(TCP) | HTTP/REST |
| 生态完整度 | 偏服务治理 | 全套微服务解决方案 |
| 社区活跃度 | 阿里维护 | Spring社区 + Netflix生态 |
| 与Spring Boot集成 | 需额外适配 | 原生集成 |
| 学习曲线 | 相对陡峭 | 对Spring开发者友好 |
最终选Spring Cloud的核心原因:我们整个技术栈就是Spring Boot体系,Spring Cloud的集成成本最低,且Netflix Eureka + Feign + Zuul/Spring Cloud Gateway + Config Server这套组合已经非常成熟。
二、微服务拆分策略
2.1 按业务域拆分
这是最重要的决策。拆分粒度太粗等于没拆,太细则服务数量爆炸,运维成本飙升。我们的拆分原则是按业务域(Domain)拆,不按技术层拆。
最终拆分出以下核心服务:
| 服务名 | 职责 | 技术选型 |
|---|---|---|
mall-gateway | API网关,路由、限流、鉴权 | Spring Cloud Gateway |
mall-auth | 认证授权中心,OAuth2 + JWT | Spring Security |
mall-product | 商品中心,SPU/SKU管理、类目 | Spring Boot + MyBatis-Plus |
mall-trade | 交易中心,订单、购物车、结算 | Spring Boot + Seata分布式事务 |
mall-user | 用户中心,会员、商家、权限 | Spring Boot + Redis缓存 |
mall-pay | 支付中心,聚合支付 | Spring Boot + 对接微信/支付宝 |
mall-promotion | 营销中心,优惠券、满减、拼团 | Spring Boot + 规则引擎 |
mall-search | 搜索中心,商品搜索 | Spring Boot + Elasticsearch |
mall-config | 配置中心 | Spring Cloud Config + Nacos |
mall-monitor | 监控中心 | Spring Boot Admin + Prometheus |
2.2 S2B2C场景的特殊考量
S2B2C模式比普通B2B2C多了"供应链赋能"的概念,拆分时需要额外考虑:
- 多租户隔离:不同商家(B端)的数据必须隔离,我们在
mall-product和mall-trade中引入了租户ID维度,所有查询自动带租户过滤 - 供应链服务:新增了
mall-supply-chain服务,管理S端到B端的供货关系、分销层级、佣金结算 - 分销裂变:
mall-promotion中独立了分销模块,支持多级分销链路计算
三、核心组件实战
3.1 服务注册与发现:Nacos替代Eureka
早期我们用的Eureka,后来切换到Nacos,原因很简单——Nacos同时支持注册中心和配置中心,不需要单独部署Spring Cloud Config Server。
# application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: mall-prod
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
group: MALL_GROUP
3.2 服务间通信:OpenFeign + Sentinel
服务间调用我们用OpenFeign声明式接口,配合Sentinel做熔断降级:
@FeignClient(name = "mall-product", fallback = ProductFallback.class)
public interface ProductFeignClient {
@GetMapping("/api/product/spu/{id}")
Result<ProductVO> getSpuById(@PathVariable("id") Long id);
@GetMapping("/api/product/list")
Result<List<ProductVO>> listProducts(@RequestParam("tenantId") Long tenantId,
@RequestParam("page") int page,
@RequestParam("size") int size);
}
Sentinel的降级策略配置:
@Component
public class ProductFallback implements ProductFeignClient {
@Override
public Result<ProductVO> getSpuById(Long id) {
// 返回兜底数据,而非直接报错
return Result.fail("商品服务暂时不可用,请稍后重试");
}
@Override
public Result<List<ProductVO>> listProducts(Long tenantId, int page, int size) {
return Result.ok(Collections.emptyList());
}
}
3.3 API网关:Spring Cloud Gateway
网关层做了几件关键事情:
spring:
cloud:
gateway:
routes:
- id: mall-product-route
uri: lb://mall-product
predicates:
- Path=/api/product/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY, GATEWAY_TIMEOUT
鉴权过滤器:网关层统一校验JWT Token,下游服务无需重复鉴权:
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StringUtils.isBlank(token)) {
return unauthorized(exchange, "缺少认证信息");
}
// 校验Token有效性
String userId = redisTemplate.opsForValue().get("token:" + token);
if (userId == null) {
return unauthorized(exchange, "Token已失效");
}
// 透传用户信息到下游服务
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-User-Id", userId)
.build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public int getOrder() {
return -100;
}
}
3.4 分布式事务:Seata
电商系统最头疼的就是分布式事务。订单创建涉及mall-trade(创建订单)、mall-product(扣减库存)、mall-promotion(核销优惠券)、mall-user(扣减积分)四个服务。
我们用Seata的AT模式处理:
@GlobalTransactional(timeoutMills = 60000, name = "createOrder")
public OrderVO createOrder(OrderDTO orderDTO) {
// 1. 创建订单(本地事务)
Order order = orderService.save(orderDTO);
// 2. 远程扣减库存
Result result = productFeignClient.deductStock(orderDTO.getSkuId(), orderDTO.getQuantity());
if (!result.isSuccess()) {
throw new BusinessException("库存扣减失败");
}
// 3. 远程核销优惠券
Result couponResult = promotionFeignClient.useCoupon(orderDTO.getCouponId());
if (!couponResult.isSuccess()) {
throw new BusinessException("优惠券核销失败");
}
// 4. 远程扣减积分
userFeignClient.deductPoints(orderDTO.getUserId(), orderDTO.getPointsUsed());
return convertToVO(order);
}
Seata通过undo_log自动回滚,比TCC模式开发量小很多。但注意:AT模式有全局锁,高并发场景下性能不如TCC。我们在大促期间对核心链路改用TCC模式。
四、踩过的坑
4.1 Feign调用丢失Request头
问题:网关鉴权后透传了X-User-Id头,但Feign调用下游服务时头信息丢失了。
解决:加Feign拦截器:
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
template.header("X-User-Id", request.getHeader("X-User-Id"));
template.header("Authorization", request.getHeader("Authorization"));
}
}
}
4.2 Seata与MyBatis-Plus的冲突
MyBatis-Plus的乐观锁插件和Seata的AT模式会冲突——两个都在SQL里加version条件。解决方案是Seata模式下关闭MyBatis-Plus的乐观锁,由Seata的全局锁保证并发安全。
4.3 Nacos配置刷新不生效
配置类必须加@RefreshScope注解,且不能用@Value直接注入到构造函数中。正确做法:
@RestController
@RefreshScope
public class ProductController {
@Value("${mall.product.default-page-size:20}")
private int defaultPageSize;
}
五、架构演进总结
| 阶段 | 架构 | 时间 | 核心问题 |
|---|---|---|---|
| V1.0 | 单体应用 | 2019-2020 | 发布耦合、扩展困难 |
| V2.0 | Spring Cloud微服务(Eureka) | 2020-2022 | 服务间通信、配置管理 |
| V3.0 | Spring Cloud Alibaba(Nacos+Sentinel+Seata) | 2022至今 | 高可用、分布式事务 |
从单体到微服务,不是技术问题,是业务驱动的问题。如果你的业务还没到那个体量,不要为了微服务而微服务——单体够用就先用单体,等到痛点出现再拆也不迟。
该平台目前的微服务架构支撑了多商家入驻、供应链分销、多租户隔离等S2B2C核心场景,日常QPS在5000左右,大促峰值可达3万+。这套架构不是一步到位设计的,是业务一步步推着演进出来的。
更多推荐




所有评论(0)