共计 2098 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:为什么传统方案会卡顿?
最近在重构公司的技能目录服务时,发现旧系统在数据量突破 50 万条后,API 响应时间从 200ms 飙升到 2 秒以上。通过火焰图分析,发现瓶颈集中在三个方面:

- 关系型数据库的全表扫描(占总耗时 68%)
- 频繁重复查询相同技能标签(21% 请求命中相同数据)
- 数据更新引发的锁竞争(尤其在上班高峰期)
技术选型:关系型数据库 vsNoSQL
我们对比了三种存储方案在 100 万数据量下的表现:
| 方案 | QPS | 99 分位延迟 | 扩展成本 |
|---|---|---|---|
| MySQL 单节点 | 1200 | 850ms | 高 |
| MongoDB 分片 | 9800 | 210ms | 中 |
| ES+Redis | 15000 | 95ms | 低 |
最终选择组合方案:
- Elasticsearch:处理模糊搜索和聚合统计
- Redis:缓存热门技能标签(TOP 10% 查询占比 80%)
- MySQL:仅作最终数据持久化
核心架构设计
微服务拆分
@startuml
component "技能目录服务" {[API 网关] --> [查询服务]
[API 网关] --> [管理服务]
[查询服务] --> [Elasticsearch 集群]
[查询服务] --> [Redis 集群]
[管理服务] --> [MySQL 集群]
}
@enduml
关键设计点:
- 读写分离:查询走 ES,数据维护走 MySQL
- 异步通道:通过 Kafka 同步数据变更
- 热点隔离:高频访问技能单独部署缓存
代码实现关键点
分片存储策略
// 基于技能 ID 哈希分片
public class SkillShardingStrategy implements PreciseShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<Long> shardingValue) {long skillId = shardingValue.getValue();
return "ds" + (skillId % availableTargetNames.size());
}
}
多级缓存实现
@Cacheable(value = "skill_detail", key = "#skillId",
unless = "#result == null")
public SkillDetail getSkillDetail(long skillId) {
// 1. 先查本地缓存(Caffeine)// 2. 查 Redis 集群
// 3. 回源到 ES
}
@Scheduled(fixedRate = 30_000)
public void refreshHotSkills() {// 定时预热 TOP100 热门技能}
异步索引更新
@KafkaListener(topics = "skill_update")
public void handleSkillUpdate(UpdateEvent event) {
// 异步更新 ES 索引
esTemplate.index(IndexQuery.builder()
.withId(event.getSkillId())
.withObject(convertToDoc(event))
.build());
}
性能优化实战
基准测试对比
优化前后关键指标对比(压测工具 JMeter,100 并发):
| 场景 | 原方案 | 新方案 | 提升 |
|---|---|---|---|
| 精确查询 | 320ms | 28ms | 11x |
| 模糊搜索 | 2100ms | 150ms | 14x |
| 批量获取 (50 条) | 1800ms | 200ms | 9x |
并发查询优化
- 采用 ES 的 scroll API 处理深分页
- Redis 使用 pipeline 批量获取
- 针对技能树查询做结果预聚合
生产环境避坑指南
数据一致性保障
- 采用分布式事务(Seata)处理核心数据
- ES 与 MySQL 差异超过 5% 触发告警
- 每日全量校验任务
缓存防护措施
// Redis 缓存策略配置
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {return RedisCacheManager.builder(factory)
.cacheDefaults(RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues())
.withInitialCacheConfigurations(
Map.of("hot_skills",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(10))))
.build();}
监控体系搭建
关键监控指标:
- ES:segment 内存占用、refresh 间隔
- Redis:命中率、大 key 检测
- JVM:GC 次数、缓存穿透计数
总结与思考
经过三个月的生产验证,这套架构支撑了日均千万级查询。核心收获:
- 冷热数据分离比单纯扩容更有效
- 异步化设计能显著降低系统耦合
- 监控数据要早于功能上线
未来可优化方向:
- 尝试使用 RocksDB 替代部分 Redis 场景
- 探索向量检索实现语义搜索
- 灰度发布机制优化
正文完
