Java+SpringBoot构建高可用外卖系统实战
1. 项目概述从零构建一个高可用的外卖系统去年我接手了一个餐饮连锁品牌的外卖系统升级项目客户原有的系统已经无法支撑日均5000订单的业务量。经过技术选型我们最终采用JavaSpringBootSSM这套技术栈仅用三个月就完成了系统重构。现在这个系统已经稳定运行一年多日均处理订单超过2万单。本文将分享这套外卖系统的完整实现方案包括技术架构设计、核心功能实现和实际部署中的经验教训。这个外卖系统包含商家端、用户端和配送端三个核心模块采用前后端分离架构。商家端主要处理菜单管理、订单处理和营业统计用户端实现浏览、下单和支付功能配送端则负责订单分配和骑手管理。系统采用微服务架构各个模块可以独立部署和扩展特别适合快速发展的餐饮业务。提示在开始开发前建议先绘制完整的业务流程图和数据ER图这能避免后期大量的接口返工。我们最初就因为没有明确配送状态流转逻辑导致后来不得不重构整个订单状态机。2. 技术栈选型与架构设计2.1 为什么选择JavaSpringBootSSM组合在技术选型阶段我们对比了多种方案最终确定使用Java 11作为基础语言SpringBoot 2.7作为框架基础MyBatis作为ORM框架Redis作为缓存MySQL 8.0作为主数据库这套组合有以下几个显著优势开发效率高SpringBoot的自动配置和起步依赖让项目搭建变得极其简单原本需要数天的环境配置现在只需几分钟性能稳定Java的强类型和JVM优化保证了系统在高并发下的稳定性生态完善SSM框架有丰富的中间件支持如Redis、RabbitMQ等易于维护清晰的MVC分层让后续维护和迭代更加方便2.2 系统架构设计我们采用分层架构设计整体分为表现层使用Thymeleaf模板引擎HTML5适配PC和移动端业务层SpringBoot处理核心业务逻辑数据访问层MyBatis实现数据库操作存储层MySQL主从复制Redis缓存// 典型的控制器层代码示例 RestController RequestMapping(/order) public class OrderController { Autowired private OrderService orderService; PostMapping public Result createOrder(RequestBody OrderDTO orderDTO) { return orderService.createOrder(orderDTO); } }数据库设计方面核心表包括用户表(user)商家表(merchant)商品表(product)订单表(order)订单明细表(order_detail)配送表(delivery)注意订单表一定要设计好索引我们最初因为缺少合适的索引在订单量达到1万时查询性能急剧下降。后来添加了(user_id, create_time)的联合索引才解决问题。3. 核心功能实现细节3.1 用户端关键功能实现3.1.1 智能推荐与搜索功能外卖系统的搜索功能不能简单使用LIKE查询我们采用Elasticsearch实现商品搜索配合用户历史行为数据实现个性化推荐。// 搜索服务实现示例 Service public class SearchServiceImpl implements SearchService { Autowired private RestHighLevelClient esClient; public ListProduct searchProducts(String keyword, Long userId) { // 构建复合查询基础关键词用户偏好 BoolQueryBuilder boolQuery QueryBuilders.boolQuery() .must(QueryBuilders.matchQuery(name, keyword)) .should(QueryBuilders.termQuery(tags, getUserPreference(userId))); SearchRequest request new SearchRequest(products) .source(new SearchSourceBuilder().query(boolQuery)); // 执行查询并处理结果 // ... } }3.1.2 购物车与下单流程购物车设计需要考虑并发问题我们采用Redis存储临时购物车数据下单时再持久化到MySQL。// 购物车服务关键代码 Service public class CartServiceImpl implements CartService { Autowired private RedisTemplateString, Object redisTemplate; public void addToCart(Long userId, Long productId, Integer quantity) { String key cart: userId; redisTemplate.opsForHash().put(key, productId.toString(), quantity); redisTemplate.expire(key, 7, TimeUnit.DAYS); } public void checkout(Long userId) { // 1. 从Redis获取购物车 MapObject, Object cart redisTemplate.opsForHash() .entries(cart: userId); // 2. 创建订单 Order order createOrder(userId, cart); // 3. 清空购物车 redisTemplate.delete(cart: userId); } }3.2 商家端功能实现3.2.1 菜单管理与库存控制商家需要实时更新菜单和库存我们使用WebSocket实现变更实时推送。Controller public class MenuController { Autowired private SimpMessagingTemplate messagingTemplate; PostMapping(/menu) public String updateMenu(RequestBody MenuDTO menuDTO) { // 更新数据库 menuService.updateMenu(menuDTO); // 通知所有连接的客户端 messagingTemplate.convertAndSend(/topic/menuUpdate, menuDTO); return success; } }3.2.2 订单处理与打印商家端需要处理大量并发订单我们采用线程池队列的方式平滑处理订单高峰。// 订单处理服务 Service public class OrderProcessService { private ExecutorService executor Executors.newFixedThreadPool(10); private BlockingQueueOrder queue new LinkedBlockingQueue(1000); PostConstruct public void init() { // 启动10个消费者线程 for (int i 0; i 10; i) { executor.submit(this::processOrder); } } private void processOrder() { while (true) { try { Order order queue.take(); // 处理订单逻辑 // 1. 更新数据库状态 // 2. 通知厨房打印 // 3. 通知配送系统 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public void submitOrder(Order order) { if (!queue.offer(order)) { throw new RuntimeException(系统繁忙请稍后再试); } } }3.3 配送系统实现3.3.1 骑手调度算法我们实现了基于地理位置的最优骑手分配算法// 骑手调度服务 Service public class DispatchService { Autowired private RedisTemplateString, Object redisTemplate; public Rider dispatchOrder(Long orderId, Location restaurantLoc) { // 1. 获取3公里内的在线骑手 SetObject riderIds redisTemplate.opsForGeo() .radius(riders, restaurantLoc.getLng(), restaurantLoc.getLat(), 3, Metrics.KILOMETERS) .getMembers(); // 2. 选择最近且订单最少的骑手 return selectBestRider(riderIds); } }3.3.2 实时轨迹追踪使用WebSocket高德地图API实现骑手实时位置更新Controller public class LocationController { Autowired private SimpMessagingTemplate messagingTemplate; PostMapping(/location) public void updateLocation(RequestBody LocationDTO locationDTO) { // 更新Redis中的位置 redisTemplate.opsForGeo().add(riders, new Point(locationDTO.getLng(), locationDTO.getLat()), locationDTO.getRiderId()); // 广播给相关用户 messagingTemplate.convertAndSendToUser( locationDTO.getOrderId().toString(), /queue/location, locationDTO); } }4. 性能优化与安全实践4.1 高并发优化策略缓存策略使用Redis缓存热门商家和商品采用多级缓存本地缓存(Caffeine)分布式缓存(Redis)// 多级缓存实现示例 Service public class ProductServiceImpl implements ProductService { Autowired private RedisTemplateString, Object redisTemplate; private CacheLong, Product localCache Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(); public Product getProduct(Long id) { // 1. 检查本地缓存 Product product localCache.getIfPresent(id); if (product ! null) return product; // 2. 检查Redis product (Product)redisTemplate.opsForValue().get(product:id); if (product ! null) { localCache.put(id, product); return product; } // 3. 查询数据库 product productMapper.selectById(id); if (product ! null) { redisTemplate.opsForValue().set(product:id, product, 1, TimeUnit.HOURS); localCache.put(id, product); } return product; } }数据库优化主从分离写操作走主库读操作走从库分库分表订单表按月分表商家数据按地区分库4.2 安全防护措施支付安全使用HTTPS传输敏感数据支付接口实现防重放攻击机制金额校验在前后端都要进行// 支付接口安全示例 RestController RequestMapping(/payment) public class PaymentController { PostMapping public Result pay(RequestBody PaymentDTO paymentDTO, HttpServletRequest request) { // 1. 验证签名 if (!verifySign(paymentDTO, request)) { throw new SecurityException(非法请求); } // 2. 防重放检查 if (redisTemplate.hasKey(payment:paymentDTO.getNonce())) { throw new SecurityException(请勿重复提交); } redisTemplate.opsForValue().set(payment:paymentDTO.getNonce(), 1, 30, TimeUnit.MINUTES); // 3. 执行支付逻辑 return paymentService.processPayment(paymentDTO); } }API防护使用Spring Security实现权限控制敏感操作添加二次验证接口限流防止恶意刷单5. 部署与监控方案5.1 容器化部署我们使用DockerJenkins实现CI/CD流水线# Dockerfile示例 FROM openjdk:11-jre WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java,-jar,app.jar]Jenkins流水线关键步骤代码检出Maven构建单元测试Docker镜像构建部署到K8s集群5.2 监控与告警应用监控SpringBoot Actuator暴露健康指标Prometheus收集指标数据Grafana展示监控仪表盘业务监控订单异常监控支付成功率监控配送超时监控# Prometheus配置示例 scrape_configs: - job_name: takeaway metrics_path: /actuator/prometheus static_configs: - targets: [app1:8080, app2:8080]6. 典型问题与解决方案6.1 订单超卖问题问题现象热门商品在促销时出现超卖解决方案使用Redis分布式锁乐观锁// 库存扣减解决方案 Service public class InventoryServiceImpl implements InventoryService { Autowired private RedissonClient redissonClient; public boolean deductInventory(Long productId, int quantity) { RLock lock redissonClient.getLock(inventory_lock: productId); try { lock.lock(3, TimeUnit.SECONDS); Product product productMapper.selectById(productId); if (product.getStock() quantity) { product.setStock(product.getStock() - quantity); int rows productMapper.updateById(product); return rows 0; } return false; } finally { lock.unlock(); } } }6.2 高峰期系统响应慢问题现象中午12点系统响应时间明显变长优化措施增加自动扩容机制非核心功能降级处理静态资源CDN加速数据库查询优化6.3 分布式事务问题问题场景下单需要同时操作订单服务、库存服务和支付服务解决方案采用Seata实现分布式事务// 分布式事务示例 GlobalTransactional public Result createOrder(OrderDTO orderDTO) { // 1. 扣减库存 inventoryService.deduct(orderDTO.getItems()); // 2. 创建订单 Order order orderService.create(orderDTO); // 3. 发起支付 paymentService.createPayment(order); return Result.success(order); }7. 项目演进与扩展方向在实际运营过程中我们陆续添加了以下增强功能智能调度系统基于历史数据预测订单高峰提前调配骑手资源会员成长体系增加用户粘性和复购率营销活动平台支持多种促销活动配置数据分析看板帮助商家优化经营策略未来可能的扩展方向接入智能语音点餐实现无人配送对接增加供应链管理系统开发小程序端应用这套系统经过多个项目的验证已经形成了一套成熟的实现方案。在开发过程中最重要的经验是前期要做好充分的需求分析和架构设计特别是订单状态流转和分布式事务处理这些核心环节。我们在第二个项目迭代时重写了80%的订单相关代码就是因为初期设计考虑不够全面。