在电商系统中,商品 API 的响应速度直接影响用户体验和平台转化率。本文将详细揭秘某电商平台(模拟淘宝场景)将商品详情 API 从 500ms 优化至 50ms 的完整技术实践,包含具体优化思路、代码实现及效果验证。
一、背景与问题分析
1.1 原始架构痛点
原始商品 API 架构存在以下问题:
- 同步调用 7 个下游服务(价格、库存、评价、推荐等)
- 数据库未做分库分表,单表数据量超 5000 万
- 无缓存策略,每次请求均穿透到数据库
- 序列化使用 JSON 库性能较差
- 线程池参数配置不合理,存在频繁上下文切换
1.2 性能基准测试
通过压测工具 JMeter 对原始 API 进行测试(100 并发):
- 平均响应时间:512ms
- 95% 响应时间:786ms
- QPS:195
- 错误率:3.2%(超时导致)
二、优化方案实施
2.1 缓存架构重构
采用多级缓存策略,从内存到分布式缓存逐层优化:
// 1. 本地Caffeine缓存(10分钟过期,3万容量)
private final LoadingCache<String, ProductDO> localCache = Caffeine.newBuilder()
.maximumSize(30000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats()
.build(key -> loadFromRedis(key));
// 2. Redis分布式缓存(30分钟过期,预热+降级)
private ProductDO loadFromRedis(String productId) {
String json = redisTemplate.opsForValue().get("product:" + productId);
if (json != null) {
return JSON.parseObject(json, ProductDO.class);
}
// 缓存穿透防护:空值缓存5分钟
ProductDO product = productMapper.selectById(productId);
if (product == null) {
redisTemplate.opsForValue().set("product:" + productId, "{}", 5, TimeUnit.MINUTES);
return null;
}
redisTemplate.opsForValue().set("product:" + productId, JSON.toJSONString(product), 30, TimeUnit.MINUTES);
return product;
}
// 3. 缓存更新策略(Canal监听数据库binlog)
@Component
public class ProductDataListener implements EntryHandler<ProductDO> {
@Override
public void insert(ProductDO product) {
updateCache(product);
}
@Override
public void update(ProductDO before, ProductDO after) {
updateCache(after);
}
private void updateCache(ProductDO product) {
// 先更新Redis,再删除本地缓存(避免缓存不一致)
redisTemplate.opsForValue().set("product:" + product.getId(),
JSON.toJSONString(product), 30, TimeUnit.MINUTES);
localCache.invalidate(product.getId());
}
}
2.2 数据库优化
-
分库分表:按商品 ID 哈希分片,分为 8 个库 32 张表
-
索引优化 :新增联合索引
idx_category_price
,优化查询语句-- 优化前查询(全表扫描)
SELECT * FROM product WHERE category_id = ? AND price < ? ORDER BY sales DESC LIMIT 10;-- 优化后查询(索引覆盖)
SELECT id,name,price,sales FROM product
WHERE category_id = ? AND price < ?
ORDER BY sales DESC LIMIT 10;-- 分表路由配置(ShardingSphere)
spring.shardingsphere.rules.sharding.tables.product.actual-data-nodes=db{0..7}.product_{0..3}
spring.shardingsphere.rules.sharding.tables.product.database-strategy.inline.sharding-column=id
spring.shardingsphere.rules.sharding.tables.product.database-strategy.inline.algorithm-expression=db${id % 8}
2.3 服务调用异步化
将同步调用改为 CompletableFuture 并行调用,减少等待时间:
// 优化前:同步调用(串行执行,总耗时=各服务耗时之和)
ProductPrice price = priceService.getPrice(productId);
ProductStock stock = stockService.getStock(productId);
List<Comment> comments = commentService.getTopComments(productId, 5);
// 优化后:异步并行调用(总耗时≈最长单个服务耗时)
public ProductDetailDTO getProductDetail(String productId) {
// 1. 并行调用各服务
CompletableFuture<ProductPrice> priceFuture = CompletableFuture.supplyAsync(
() -> priceService.getPrice(productId), executor);
CompletableFuture<ProductStock> stockFuture = CompletableFuture.supplyAsync(
() -> stockService.getStock(productId), executor);
CompletableFuture<List<Comment>> commentFuture = CompletableFuture.supplyAsync(
() -> commentService.getTopComments(productId, 5), executor);
// 2. 等待所有结果返回
CompletableFuture.allOf(priceFuture, stockFuture, commentFuture).join();
// 3. 组装结果
return ProductDetailDTO.builder()
.price(priceFuture.join())
.stock(stockFuture.join())
.comments(commentFuture.join())
.build();
}
// 线程池优化配置
@Bean
public Executor serviceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20); // CPU核心数*2
executor.setMaxPoolSize(50);
executor.setQueueCapacity(1000);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("product-async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
2.4 序列化与协议优化
-
替换 JSON 为 Protobuf,减少数据传输量和序列化耗时
-
接口返回字段裁剪,只返回前端需要的字段
// 商品详情Protobuf定义
syntax = "proto3";
message ProductDetail {
string id = 1;
string name = 2;
Price price = 3;
Stock stock = 4;
repeated Comment comments = 5;message Price { double original = 1; double current = 2; } message Stock { int32 quantity = 1; bool limited = 2; } message Comment { string id = 1; string content = 2; int32 score = 3; }
}
// Protobuf与Java对象转换工具
@Component
public class ProtoConverter {
public ProductDetailDTO protoToDto(ProductDetail proto) {
// 只转换需要的字段,减少数据处理量
return ProductDetailDTO.builder()
.id(proto.getId())
.name(proto.getName())
.price(convertPrice(proto.getPrice()))
.stock(convertStock(proto.getStock()))
.build();
}
}
三、优化效果验证
3.1 性能测试对比
指标 | 优化前 | 优化后 | 提升比例 |
---|---|---|---|
平均响应时间 | 512ms | 48ms | 90.6% |
95% 响应时间 | 786ms | 72ms | 90.8% |
QPS | 195 | 2380 | 1120% |
错误率 | 3.2% | 0% | 100% |
3.2 缓存命中率
- 本地缓存命中率:89.2%
- Redis 缓存命中率:98.7%
- 数据库访问量:降低 97.3%
四、总结与经验
- 多级缓存是核心:本地缓存解决热点数据访问,分布式缓存解决数据一致性
- 异步化提升并行效率:将串行调用改为并行,减少整体响应时间
- 数据库优化是基础:分库分表 + 索引优化解决数据瓶颈
- 序列化协议影响显著:Protobuf 相比 JSON 减少 60% 数据传输量
- 持续监控与调优:通过 APM 工具(SkyWalking)实时监控性能指标,动态调整参数
本次优化通过系统性的架构调整和代码优化,成功将核心 API 响应时间从 500ms 级降至 50ms 级,极大提升了用户体验和系统承载能力。性能优化是一个持续迭代的过程,需要结合业务场景不断探索更优方案。