1. 项目背景与核心挑战
去年参与了一个日均请求量超过5000万的电商数据采集项目,系统需要从数百个数据源实时抓取商品价格、库存和评价数据。在初期架构设计中,我们低估了流量突增带来的冲击,导致上线首日就出现了严重的服务雪崩。这次评审就是针对系统重构方案的技术复盘,重点解决高并发场景下的三个核心问题:
- 如何应对数据源响应时间不稳定(从50ms到10s不等)导致的线程阻塞
- 如何设计分布式去重机制避免重复采集
- 如何实现采集任务的动态负载均衡
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 架构设计关键决策
2.1 异步化改造方案选型
最初采用同步HTTP客户端+线程池的方案,在QPS超过3000时出现大量线程阻塞。对比了三种异步方案:
| 方案 | 吞吐量(QPS) | CPU占用 | 代码复杂度 | 最终选择 |
|---|---|---|---|---|
| Java CompletableFuture | 12,000 | 65% | 中等 | |
| Reactor Netty | 18,000 | 45% | 较高 | ✅ |
| Kotlin协程 | 15,000 | 50% | 低 |
选择Reactor Netty的核心考量是其背压机制能更好应对慢数据源,实测在单个数据源响应延迟10秒时,系统仍能维持8000+QPS的稳定吞吐。
2.2 分布式去重设计
采用Redis+本地布隆过滤器的二级缓存方案:
java复制// 去重服务核心逻辑
public boolean isDuplicate(String key) {
// 第一层:本地布隆过滤器(1000万容量,误判率0.1%)
if (localBloomFilter.mightContain(key)) {
// 第二层:Redis集群校验
return redisTemplate.opsForValue().setIfAbsent(
"dedup:" + key, "1", 5, TimeUnit.MINUTES) == null;
}
return false;
}
实测显示该方案在10节点集群环境下,去重准确率99.99%,Redis集群带宽占用控制在120MB/s以内。
3. 核心组件实现细节
3.1 动态负载均衡算法
基于历史响应时间的加权轮询算法:
code复制权重计算公式:
weight = base_weight * (1 / (avg_response_time + 1))
调度流程:
1. 每5分钟统计各数据源平均响应时间
2. 剔除连续超时3次的数据源
3. 按权重分配下一周期请求量
该算法使慢数据源的请求分配比例从初始的20%自动降至5%以下,整体采集成功率从82%提升到99.7%。
3.2 连接池优化配置
针对高频短连接的优化参数:
yaml复制reactor-netty:
connection-pool:
max-connections: 1000
acquire-timeout: 500ms
max-idle-time: 30s
eviction-interval: 10s
http-client:
response-timeout: 15s
keep-alive: false # 短连接场景禁用keep-alive
4. 性能压测数据对比
重构前后的关键指标对比:
| 指标 | 旧架构 | 新架构 | 提升幅度 |
|---|---|---|---|
| 最大QPS | 3,200 | 19,500 | 6.1x |
| 平均延迟 | 480ms | 68ms | 85%↓ |
| 99分位延迟 | 8.2s | 320ms | 96%↓ |
| 错误率 | 18% | 0.3% | 98%↓ |
| 服务器成本 | 32核/128G × 8 | 16核/64G × 5 | 60%↓ |
5. 踩坑经验实录
-
Netty内存泄漏问题
初期未正确释放ByteBuf,导致运行12小时后OOM。解决方案:java复制// 必须添加释放回调 response.receive().asByteArray() .doOnDiscard(ReferenceCounted.class, ReferenceCounted::release) .subscribe(data -> process(data)); -
Redis热点Key问题
去重计数器最初设计为单Key结构,造成单个分片CPU飙升至100%。改进为分片Key:java复制// 原始设计(问题代码) String key = "request_count"; // 优化后设计 String key = "request_count:" + ThreadLocalRandom.current().nextInt(10); -
背压策略选择
早期使用DROP策略导致重要数据丢失,改为BUFFER策略后配合以下参数:java复制.onBackpressureBuffer(5000, buffer -> log.warn("Buffer overflow"), BufferOverflowStrategy.DROP_OLDEST)
6. 监控体系搭建
关键监控指标配置:
- 采集成功率告警(<99%触发P1事件)
- 单数据源超时率看板(超过30%自动降级)
- JVM直接内存监控(Netty堆外内存)
- Redis分片负载均衡检测
Grafana监控看板包含以下核心图表:
- 实时QPS与线程池使用率热力图
- 分数据源的响应时间百分位图
- 去重缓存命中率趋势图
- 异常请求的聚类分析
这套架构最终支撑了618期间峰值32,000 QPS的流量冲击,期间CPU利用率稳定在70%以下。最大的收获是认识到高并发系统不能简单堆机器,必须针对业务特点做精细化设计。比如我们发现商品评价采集对实时性要求不高但数据量大,后来单独拆出了批处理通道,又节省了40%的计算资源。
