Spring Boot分布式商城微服务架构设计与实践 📅 发布时间:2026/9/16 21:09:31 👁 浏览次数: 简介这是一份基于SpringBoot和Vue的分布式架构网上商城系统源代码适合正在准备毕业设计、课程设计或期末大作业的Java方向学生也适合希望了解前后端分离与分布式服务组合方式的入门到中级开发者。代码围绕商城常见业务展开包含商品管理、订单处理、购物车、用户中心等完整闭环能够帮助读者把SpringBoot、Mybatis、MySQL以及Vue等知识点串联起来理解企业级项目的分层思路。整个资源包共包含791个文件整体只有约19MB体积紧凑其中Java后端源码、Vue前端组件、JavaScript和CSS样式的数量最多另有HTML页面、XML映射、Maven脚本和数据库配置文件并附带一套可运行的构建与启动脚本导入集成开发环境即可启动体验。目前该资源已有102人学习下载所有源码均经过测试验证适合直接作为毕业设计项目底子来扩展预览中还能看到完整的后台管理界面组件例如侧边栏、头部导航、面包屑和修改密码页面配合SQLyog或Navicat导入数据库能很快复现商城运行效果。1. 这个标题说的是什么从单体商城到 Spring Boot 微服务的必然演进如果你在 2024 年之后还在用一套 Spring Boot 单体应用扛商城系统要么流量还没到需要拆的规模要么重构的账还没算清楚。网上搜「基于 springboot 的分布式架构网上商城系统代码」本质上是两个诉求叠在一起第一业务模型要覆盖商城的主链路——商品、购物车、订单、库存、支付、用户第二架构上要体现分布式能力——服务拆分、注册发现、配置管理、网关路由、分布式事务。这套组合在毕业设计里是加分项在企业内部是中小规模电商的常规形态在面试里则是「微服务落地」最常被追问的题材。常见做法并不是把 Spring Boot 用得多花哨而是用「Spring Boot 做每个微服务的底座Spring Cloud Alibaba 做分布式能力Redis 扛热点RabbitMQ 解耦异步」这一套标准组合。本文把这条链路从选型到可运行的代码一步一步铺开覆盖服务怎么拆、组件怎么配、下单减库存这种分布式事务怎么落地、最后怎么部署验证。适合两类人准备做商城类项目的 Java 开发以及接手了类似系统但还没理清全貌的工程师。2. 服务拆分与组件选型Spring Boot 分布式商城的架构骨架2.1 按业务域拆服务而不是按技术层拆拆服务是分布式架构的第一步也是最容易拍脑袋的一步。我见过有人把 service、controller、dao 各拆成一个服务结果一个下单请求穿四个网络调用事务没法保证排查问题时链路长得让人崩溃。正确的做法是按业务域拆遵循领域驱动设计的限界上下文思想。商城系统最稳定的拆分方式是五个核心服务加两个支撑服务服务名职责关键依赖user-service注册登录、用户信息、收货地址MySQL、Redisproduct-service商品分类、商品详情、库存查询MySQL、Redis、Elasticsearch可选order-service订单创建、订单状态流转、超时取消MySQL、Redis、RabbitMQcart-service购物车读写、合并、数量变更Redispayment-service支付单创建、支付回调、对账MySQL、RabbitMQgateway-service统一入口、路由转发、鉴权Spring Cloud Gatewayauth-servicetoken 签发与校验Redis、JWT支付服务不一定每个商城都需要如果你的项目只是演示性质可以省略把支付回调直接做成模拟接口。但订单和商品这两个服务是必须拆的因为下单减库存这个动作天然跨服务它是整个分布式架构里最有教学价值的业务场景。2.2 用 Nacos 做注册中心和配置中心选它的理由服务之间要互相调用第一步是互相找得到。Eureka 已经停止维护Zookeeper 用起来偏重Consul 在国内资料少所以当前最主流的方案是 Nacos——它是阿里巴巴开源的产品与 Spring Cloud Alibaba 生态绑定紧密同时提供注册中心和配置中心两个能力少部署一套组件。Spring Boot 与 Spring Cloud 的版本对应关系容易踩坑我用的是 Spring Boot 2.7.x 搭配 Spring Cloud Alibaba 2021.0.5.0这个组合在市面上资料最全、问题最少。如果你是新建项目直接选 Spring Boot 2.7.18不要追 Spring Boot 3.x——Spring Boot 3 基于 Jakarta EE很多老版本的依赖要跟着升级对于学习型项目没必要增加排查成本。在pom.xml中引入依赖dependencyManagement dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version2021.0.5/version typepom/type scopeimport/scope /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version2021.0.5.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement提示版本号不要自己随意组合。Spring Cloud、Spring Cloud Alibaba、Spring Boot 三者有严格的对应关系配错的表现通常是启动时报NoSuchMethodError或ClassNotFoundException而且报错位置千奇百怪很难定位。每个服务都要引入 Nacos 注册发现与配置中心的能力dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId /dependency在bootstrap.yml中配置连接信息spring: application: name: order-service cloud: nacos: server-addr: 192.168.1.100:8848 discovery: namespace: mall group: DEFAULT_GROUP config: namespace: mall file-extension: yaml shared-configs: ->server: port: 8080 spring: application: name: gateway-service cloud: nacos: server-addr: 192.168.1.100:8848 gateway: routes: - id: user-route uri: lb://user-service predicates: - Path/api/user/** filters: - StripPrefix1 - id: product-route uri: lb://product-service predicates: - Path/api/product/** filters: - StripPrefix1 - id: order-route uri: lb://order-service predicates: - Path/api/order/** filters: - StripPrefix1 # 限流过滤器按 Redis Token Bucket 实现 # - name: RequestRateLimiter # args: # redis-rate-limiter.replenishRate: 10 # redis-rate-limiter.burstCapacity: 20lb://order-service是负载均衡协议头Gateway 会去 Nacos 拉取 order-service 的实例列表然后按负载均衡策略转发。StripPrefix1的作用是去掉第一段路径例如前端的/api/order/create转发到后端时变成/order/create。注释里的是限流配置RequestRateLimiter基于 Redis 的令牌桶算法replenishRate10表示每秒放 10 个请求burstCapacity20表示突发容量 20。生产环境建议开启学习项目可以先注释掉。网关还应该统一处理 JWT 校验。最简单的方式是写一个全局过滤器检查请求头里的 Authorization在白名单外的请求一律拦截。逻辑不复杂但一定要做否则所有服务都要自己维护一份鉴权代码那就失去网关的意义了。3. 商品服务与订单服务Spring Boot 商城核心业务代码实现3.1 商品服务先读缓存再查库防止缓存穿透商品详情是商城系统读压力最大的接口。用户逛商城时 90% 的请求都在看商品列表和商品详情如果不加缓存数据库扛不住。Redis 是首选因为它是单线程模型、IO 多路复用读写性能极高而且支持过期时间。商品详情的查询逻辑应该这样设计Service public class ProductServiceImpl implements ProductService { Autowired private StringRedisTemplate stringRedisTemplate; Autowired private ProductMapper productMapper; private static final String PRODUCT_CACHE_KEY mall:product:; Override public ProductVO getProductById(Long productId) { // 1. 先查 Redis 缓存 String cacheKey PRODUCT_CACHE_KEY productId; String json stringRedisTemplate.opsForValue().get(cacheKey); if (StringUtils.hasText(json)) { return JSON.parseObject(json, ProductVO.class); } // 2. 缓存未命中查数据库 Product product productMapper.selectById(productId); if (product null) { // 3. 防止缓存穿透空值也缓存设置短过期时间 stringRedisTemplate.opsForValue().set(cacheKey, , Duration.ofMinutes(5)); throw new BizException(商品不存在); } // 4. 写回缓存设置 30 分钟过期 ProductVO vo convert(product); stringRedisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(vo), Duration.ofMinutes(30)); return vo; } }这段代码里有三个关键设计。第一先读缓存再读库命中直接返回Redis 单键读写耗时通常在 1ms 以内比查 MySQL 快一两个数量级。第二防穿透的处理方式容易忽略——如果一个商品 ID 在数据库中不存在每次都穿透到数据库恶意攻击时可以大量用不存在的 ID 请求直接把数据库打挂。处理方式是把空值也写进缓存过期时间设短一点比如 5 分钟。第三缓存更新策略用「更新数据库后删缓存」而不是「更新数据库后更新缓存」——因为复杂对象的缓存更新逻辑容易出错删掉让下次请求重建更安全这也是旁路缓存模式的精髓。3.2 订单服务状态机设计是下单流程的骨架订单服务是整个分布式商城中最复杂的服务它的核心是订单状态机。一个订单从创建到完成要经过待支付 → 已支付 → 已发货 → 已完成中间还有取消和退款两个分支。如果不把状态流转收敛到一处而是让业务代码里到处setStatus不出三个版本就会变成垃圾代码。我一般用状态模式加一张状态流转表来控制代码结构如下public enum OrderStatus { PENDING_PAYMENT(0, 待支付), PAID(1, 已支付), SHIPPED(2, 已发货), COMPLETED(3, 已完成), CANCELED(4, 已取消); private final int code; private final String desc; private static final MapInteger, SetInteger TRANSITIONS new HashMap(); static { // 定义合法的状态流转key 是当前状态value 是可以流转到的状态集合 TRANSITIONS.put(0, new HashSet(Arrays.asList(1, 4))); // 待支付 - 已支付 / 已取消 TRANSITIONS.put(1, new HashSet(Arrays.asList(2, 4))); // 已支付 - 已发货 / 已取消退款 TRANSITIONS.put(2, new HashSet(Collections.singletonList(3))); // 已发货 - 已完成 } public static void validateTransition(int from, int to) { SetInteger allowed TRANSITIONS.get(from); if (allowed null || !allowed.contains(to)) { throw new IllegalStateException(非法的订单状态流转: from - to); } } }状态机的价值在于把非法流转拦截在代码入口。比如一个已完成订单被重复点击取消validateTransition会直接抛出异常不会出现「已完成订单被改成已取消」这种生产事故。在OrderService中每次状态变更前先调用校验方法再执行 SQL 更新加个乐观锁版本号防止并发问题。实际订单的状态可能比这个复杂比如待支付订单超过 30 分钟自动取消已支付但未发货时可以申请退款这些都可以在状态机里加状态和流转规则但骨架不变。3.3 下单减库存的分布式事务用本地消息表加 RabbitMQ 保证最终一致性单体应用里下单减库存只是一个数据库事务的事但拆成两个服务后order-service 和 product-service 各自有自己的数据库本地事务管不到对方。这里必须引入分布式事务方案。常见方案有 Seata 的 AT 模式、TCC 模式、本地消息表加消息队列。我的建议是学习项目用本地消息表加 RabbitMQ理由有两个一是 Seata 需要额外部署 TC 服务增加部署复杂度二是本地消息表的方案让你理解分布式事务的本质——最终一致性而不是强一致。核心思路在 order-service 的数据库里建一张order_event表下单时把订单数据和事件记录写在同一个本地事务里然后通过一个定时任务把事件发送到 RabbitMQproduct-service 消费消息完成减库存。CREATE TABLE order_event ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id varchar(64) NOT NULL COMMENT 订单号, event_type varchar(32) NOT NULL COMMENT 事件类型CREATE_ORDER, payload text NOT NULL COMMENT 事件内容JSON格式, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0-待发送1-已发送2-已确认, retry_count int(11) NOT NULL DEFAULT 0 COMMENT 重试次数, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status_create_time (status, create_time) ) ENGINEInnoDB COMMENT本地消息表;下单接口的写入逻辑Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateRequest request) { // 1. 生成订单号保存订单主表 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(request.getUserId()); order.setTotalAmount(request.getTotalAmount()); order.setStatus(OrderStatus.PENDING_PAYMENT.getCode()); orderMapper.insert(order); // 2. 保存订单明细 orderItemMapper.insertBatch(request.getItems()); // 3. 写本地消息表 OrderEvent event new OrderEvent(); event.setOrderId(order.getId()); event.setEventType(CREATE_ORDER); event.setPayload(JSON.toJSONString(request)); event.setStatus(0); orderEventMapper.insert(event); // 4. 返回订单号 return order.getId(); }关键在设计第 3 步的时机它与订单写入在同一个Transactional事务里要么一起成功要么一起回滚。这样保证订单数据和待发送的消息是原子的。然后通过定时任务扫描状态为 0 的事件发送到 RabbitMQComponent public class OrderEventPublisher { Autowired private RabbitTemplate rabbitTemplate; Autowired private OrderEventMapper orderEventMapper; // 每 5 秒扫一次待发送事件 Scheduled(fixedDelay 5000) public void publishPendingEvents() { ListOrderEvent pendingEvents orderEventMapper.selectByStatus(0); for (OrderEvent event : pendingEvents) { try { rabbitTemplate.convertAndSend( mall.order.exchange, order.create, event.getPayload() ); // 发送成功后把消息标记为已发送 orderEventMapper.markSent(event.getId()); } catch (Exception e) { // 发送失败不做处理下轮重试 orderEventMapper.increaseRetryCount(event.getId()); log.error(发送订单事件失败, eventId: {}, event.getId(), e); } } } }product-service 那边消费消息减库存消费成功后再调用 order-service 的一个回调接口确认消息已处理。如果消费失败消息会进入死信队列定时任务扫描死信队列人工处理。这套方案的核心论点是「 把分布式问题转化为本地事务加异步重试」。它不保证强一致但保证最终一致。极端情况下可能有多发一条消息的问题所以消费者要做幂等——比如用事件 ID 做去重表。这个细节如果写进博客里面试时非常加分。4. 服务间调用与配置Spring Boot 集成 OpenFeign 与 Sentinel4.1 用 OpenFeign 声明式调用替代 RestTemplate服务间调用是分布式架构的日常操作。order-service 创建订单前要查用户信息支付完成后要通知 order-service 更新状态。如果每个地方都用 RestTemplate 拼 URL代码里全是魔法字符串而且没有负载均衡。OpenFeign 是 Spring Cloud 生态的标准答案——用接口加注解的方式声明 HTTP 客户端底层由 Ribbon 做负载均衡代码简洁可维护。order-service 调用 user-service 的示例FeignClient(name user-service, fallback UserClientFallback.class) public interface UserClient { GetMapping(/user/{userId}) UserVO getUserById(PathVariable(userId) Long userId); PutMapping(/user/address) Boolean updateDefaultAddress(RequestBody AddressUpdateRequest request); }用FeignClient注解声明接口name指定目标服务在注册中心的名称。方法注解的写法与 Spring MVC 完全一致PathVariable和RequestBody都会正常生效。fallback参数指定熔断降级实现类——当 user-service 不可用时调用 fallback 中的方法返回一个默认值避免异常向上抛导致整个下单流程中断。Feign 在 Spring Boot 中要先开启SpringBootApplication EnableFeignClients public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }EnableFeignClients放在启动类上Spring 会扫描所有继承FeignClient的接口生成代理类注入到 Spring 容器。需要注意扫描包路径——如果你的 Feign Client 不在启动类所在包下必须指定basePackages属性这是新手最容易踩的坑。4.2 用 Sentinel 给下单接口加流量控制防止雪崩商城系统最怕的是大促流量瞬间打进来一个接口挂了把整个服务拖垮。Sentinel 是阿里开源的流量控制组件与 Hystrix 相比它更轻量、控制台功能更全、支持热点参数限流和系统自适应保护。Spring Cloud Alibaba 集成了 Sentinel接入成本极低。在 order-service 中加入依赖dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependency添加配置spring: cloud: sentinel: transport: dashboard: 192.168.1.100:8858 eager: true datasource: # 生产环境建议把规则配置在 Nacos这里先用本地文件 file: file: classpath:sentinel-rules.json rule-type: flow在业务代码上加上SentinelResource注解SentinelResource(value createOrder, blockHandler createOrderBlock, fallback createOrderFallback) public Long createOrder(OrderCreateRequest request) { // 执行业务逻辑 return orderService.createOrder(request); } // 限流后执行的方法返回友好提示 public Long createOrderBlock(OrderCreateRequest request, BlockException ex) { throw new BizException(当前下单人数较多请稍后重试); } // 业务异常时执行的方法 public Long createOrderFallback(OrderCreateRequest request, Throwable ex) { log.error(下单失败, ex); throw new BizException(下单失败请稍后重试); }blockHandler处理的是 Sentinel 拦截的异常比如限流、熔断fallback处理的是业务代码自身的异常。两者不要混在一起用否则会把真实的业务异常也吞掉。Sentinel 的规则既可以在控制台动态配置也可以通过SentinelResource注解配合 Nacos 持久化。控制台配置的规则默认保存在内存服务重启就丢了生产环境必须持久化到 Nacos。4.3 用分布式定时任务处理超时订单商城系统里有一个经典业务场景用户下单后一直没支付系统需要在 30 分钟后自动取消订单。单体时代直接用 Spring 的Scheduled注解就能做但微服务环境下要考虑一个问题——order-service 如果部署了多个实例定时任务会在每个实例上同时执行导致重复扫描和重复取消。常见做法是引入 XXL-JOB它自带分布式锁和任务分片功能。如果你的项目不想多部署一套调度中心退而求其次可以用 Redis 的SETNX命令实现一个简单的分布式锁Component public class OrderTimeoutCanceller { Autowired private StringRedisTemplate stringRedisTemplate; Autowired private OrderMapper orderMapper; private static final String LOCK_KEY lock:order-timeout-cancel; Scheduled(cron 0 */5 * * * ?) // 每 5 分钟执行一次 public void cancelTimeoutOrders() { // 尝试获取分布式锁加 5 分钟过期防止死锁 Boolean acquired stringRedisTemplate.opsForValue() .setIfAbsent(LOCK_KEY, 1, Duration.ofMinutes(5)); if (!Boolean.TRUE.equals(acquired)) { log.info(另一个实例正在执行超时订单取消任务跳过); return; } try { // 查询超时订单并取消 ListOrder timeoutOrders orderMapper.selectTimeoutOrders(Duration.ofMinutes(30)); for (Order order : timeoutOrders) { // 校验状态机只有待支付状态才能取消 OrderStatus.validateTransition(order.getStatus(), OrderStatus.CANCELED.getCode()); orderMapper.updateStatus(order.getId(), OrderStatus.CANCELED.getCode()); // 发送消息通知库存回滚 rabbitTemplate.convertAndSend(mall.order.exchange, order.cancel, order.getId()); } } finally { // 释放锁 stringRedisTemplate.delete(LOCK_KEY); } } }注意finally块里的锁释放逻辑必须在任务执行完之后删除锁否则下次任务无法执行。但如果有多个实例同时竞争可能会出现「一个实例还没执行完锁已经过期被另一个实例拿走」的情况这就是为什么还需要看业务容忍度。学习项目用 Redis 分布式锁足够生产环境更稳妥的方案是引入 XXL-JOB 的调度锁它能确保同一时刻只有一个实例的同一个 Job 在执行。5. 一个 Spring Boot 分布式商城代码的运行验证与压测技巧代码写完了怎么证明这套分布式架构真的能跑、能扛住流量最后的收尾章节给三个具体动作本地启动顺序、压测参数、验证分布式事务是否生效的排查方法。5.1 本地启动顺序与依赖检查启动整套系统时必须遵守固定的依赖顺序先启动基础设施再启动微服务。顺序错了会出现「服务启动时注册不到 Nacos」之类的假象。推荐顺序为MySQL 与 Redis用 Docker Compose 或者本机服务Nacos Server单机模式启动startup.cmd -m standaloneRabbitMQ依赖 Erlang 环境gateway-service、auth-serviceuser-service、product-service、cart-serviceorder-service、payment-service每个服务启动后在 Nacos 控制台的「服务管理」页面中确认服务列表已经出现再启动下一个。跳过这一步会在后续接口排查时浪费大量时间。5.2 用 JMeter 压测下单接口验证 Sentinel 限流是否生效启动完成后从网关调用下单接口确认链路通了。然后用 JMeter 做一个简单的压测验证 Sentinel 配置的限流规则真正生效。创建一个线程组设置为 200 个线程、循环 10 次请求/api/order/create观察下面的现象未加 Sentinel 规则时2000 个请求全部成功服务响应时间随并发升高而变长加上规则后超过阈值的请求被快速返回「当前下单人数较多请稍后重试」而不是堆积在服务端服务本身的 CPU 占用率维持稳定没有出现线程池打满和内存飙升如果压测时发现 JMeter 报连接超时而不是业务提示大概率是网关线程池被占满需要调server.tomcat.threads.max参数。相反地如果是大量请求走了blockHandler说明服务端限流生效了。5.3 验证分布式事务最终一致性的简单方法验证最终一致性比验证普通接口复杂一些。一个建议的验证步骤是启动订单服务和商品服务请求创建订单成功以后直接停掉 RabbitMQ观察订单状态是否创建成功、库存是否没减少此时订单事件表里应该有一条status0的待发送记录重启 RabbitMQ等待不超过 5 秒定时任务轮询间隔观察库存被正确扣减。整个过程无需写测试代码只需要操作 RabbitMQ 的后台管理界面。如果想做更严格的幂等验证可以在 product-service 的消费逻辑里手动抛一次异常观察消息是否进入死信队列、事件表里retry_count是否递增、重试后最终是否成功。这套排查路径是分布式商城系统最值得自己做一遍的演练也是从「代码能跑」到「出了问题能查」的分水岭。本文还有配套的精品资源点击获取