外卖平台每天有1000万笔订单查询怎么优化?
外卖平台每天有1000万笔订单查询怎么优化?
作者:Java开发工程师
外卖平台项目中,每天要处理 超过1000万笔订单查询请求 。这个量级对系统提出了很高的性能要求:高并发、低延迟、强一致性 。这篇文章将结合我的实际经验,从 场景分析、架构优化,到核心代码实现 ,一步步拆解我们是如何做到高效处理这些请求的。
一、问题场景:每天1000万笔订单查询意味着什么?
1000万笔订单查询,平均下来:
- 每天 :10,000,000 次
- 每小时 :416,666 次
- 每分钟 :6,944 次
- 每秒钟 :约 115 次 QPS(查询/秒) #技术分享 #掘金
这还只是平均值,高峰期可能达到 10 倍 ,也就是 1000+ QPS
查询接口特点
- 查询接口: GET /api/order/{orderId}
- 读多写少:95% 是查询操作
- 数据一致性要求高:订单状态必须准确(如支付状态、配送状态)
- 接口响应要求快:用户、商家、客服都会频繁使用此接口
二、优化目标与原则
我们的优化目标:
- 高并发支撑,高峰期不挂
- 响应时间稳定,P99 < 100ms
- 保证一致性,不返回错误状态
优化原则:
- 读写分离 :将读操作尽可能从主库中剥离
- 冷热分离 :大部分查询是“热订单”,少数是历史订单
- 缓存优先 :缓存能解决80%的请求
- 异步与延迟更新 :牺牲一定实时性,换取系统稳定
三、架构设计思路
我们采用了以下架构策略:
客户端 │ ▼ API 网关 │ ▼ 缓存层(Redis) │ ┌─┴────────┐ │ │ ▼ ▼ MySQL 主库 历史订单库(归档库)
查询优先级流程
- 查询 Redis 缓存(热点订单缓存)
- Redis 未命中 → 查询 MySQL 主库
- 订单状态为“已完成” → 异步同步至归档库
- 定期清理 Redis 缓存中过期订单
四、核心代码实现(Spring Boot + MyBatis + Redis)
下面我们以一个简化版本的订单查询接口来说明实现方式。
1. Redis 缓存配置
@Configuration
public class RedisConfig {
@Bean public RedisTemplate<String, OrderDTO> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, OrderDTO> template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new Jackson2JsonRedisSerializer<>(OrderDTO.class)); return template; } }
2. 订单查询 Service
@Service
public class OrderQueryService {
private static final String CACHE_KEY_PREFIX = "order:";
@Autowired private RedisTemplate<String, OrderDTO> redisTemplate;
@Autowired private OrderMapper orderMapper;
public OrderDTO getOrderById(Long orderId) { String cacheKey = CACHE_KEY_PREFIX + orderId;
OrderDTO cachedOrder = redisTemplate.opsForValue().get(cacheKey); if (cachedOrder != null) { return cachedOrder; }
Order order = orderMapper.selectById(orderId); if (order == null) { throw new OrderNotFoundException(orderId); }
OrderDTO dto = convertToDTO(order);
redisTemplate.opsForValue().set(cacheKey, dto, Duration.ofMinutes(5));
return dto; }
private OrderDTO convertToDTO(Order order) { OrderDTO dto = new OrderDTO(); dto.setOrderId(order.getId()); dto.setStatus(order.getStatus()); dto.setAmount(order.getAmount()); dto.setCreatedAt(order.getCreatedAt()); return dto; } }
3. MyBatis Mapper 示例
@Mapper
public interface OrderMapper {
@Select("SELECT * FROM orders WHERE id = #{id}")
Order selectById(@Param("id") Long id);
}
4. Controller 接口暴露
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Autowired private OrderQueryService orderQueryService;
@GetMapping("/{orderId}") public ResponseEntity<OrderDTO> getOrder(@PathVariable Long orderId) { OrderDTO dto = orderQueryService.getOrderById(orderId); return ResponseEntity.ok(dto); } }
五、更多优化技巧
1. 缓存击穿防护(防止热点key失效同时查询DB)
// 使用Double Check + 分布式锁(Redisson)防止缓存击穿
2. 本地缓存(Caffeine)+ 分布式缓存(Redis)双层缓存
3. 数据归档与冷热分离
- 热数据(未完成订单):保留在主库和Redis中
- 冷数据(已完成订单):每晚归档至历史库,并清理Redis缓存
六、高并发压测与优化
- 使用 JMeter/Locust 进行压测,模拟1000 QPS
- 监控Redis命中率、数据库连接数、接口响应时间
- 关键指标:
- Redis命中率 > 90%
- DB QPS 降低 80%
- P99 响应时间 <100ms
七、总结:优化是一场持久战
应对千万级别的订单查询请求,绝不是一次优化就能搞定的,而是一个 持续演进 的过程。我们需要:
- 理解业务特性
- 缓存优先思维
- 合理的架构分层
- 持续监控与压测
希望本文能为你在高并发查询优化的路上提供一点启发!