共计 2699 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点
在高并发查询场景下,传统关系型数据库的性能瓶颈尤为明显。以下是几个常见问题:

- 全表扫描:当查询条件无法命中索引时,数据库会进行全表扫描,导致响应时间急剧上升。
- 锁竞争:高并发写入和读取操作可能导致锁竞争,尤其是在事务隔离级别较高的情况下。
- JOIN 操作:复杂的多表关联查询会消耗大量计算资源,进一步拖慢响应速度。
- 单点瓶颈:传统数据库通常采用主从架构,主节点的写入压力容易成为系统瓶颈。
这些问题在高并发场景下会被放大,导致用户体验下降甚至系统崩溃。
技术选型
针对上述问题,搜索技术(如 Elasticsearch 和 Solr)提供了更高效的解决方案。以下是两者的核心特性对比:
| 特性 | Elasticsearch | Solr |
|---|---|---|
| 索引机制 | 基于 Lucene,支持实时索引 | 基于 Lucene,支持近实时索引 |
| 分布式架构 | 原生支持,自动分片和副本 | 需要手动配置分片和副本 |
| 查询性能 | 适合复杂查询和高并发场景 | 适合静态数据和高吞吐量查询 |
| 扩展性 | 动态扩展节点,无需重启集群 | 扩展节点需手动干预 |
| 社区生态 | 活跃,插件丰富 | 稳定,但插件相对较少 |
对于大多数高并发场景,Elasticsearch 因其分布式特性和实时索引能力成为首选。
实现方案
倒排索引工作原理
倒排索引是搜索技术的核心。其基本原理如下:
- 分词:将文档中的文本拆分为独立的词项(Term)。
- 建立映射:记录每个词项出现在哪些文档中,以及出现的位置和频率。
- 存储结构:词项作为键,文档列表作为值,形成高效的键值对结构。
这种结构使得查询时只需定位到词项,即可快速获取相关文档,避免了全表扫描。
分片和副本配置策略
分片(Sharding)和副本(Replica)是分布式搜索集群的关键配置。以下是一些建议策略:
- 分片数量:根据数据量和查询负载决定。通常建议每个分片大小不超过 50GB,分片数量为节点数的 1 - 3 倍。
- 副本数量:至少设置 1 个副本以提高可用性。高并发场景下可增加副本数以分担读取压力。
示例配置(Elasticsearch):
PUT /my_index
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 2
}
}
SDK 示例代码
以下是 Java 中使用 Elasticsearch High Level REST Client 的示例代码,包含连接池配置和批量写入:
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
public class ElasticsearchDemo {
private static final String INDEX_NAME = "my_index";
public static void main(String[] args) {
// 初始化客户端
try (RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")
))) {
// 批量写入示例
BulkRequest bulkRequest = new BulkRequest();
for (int i = 0; i < 100; i++) {IndexRequest request = new IndexRequest(INDEX_NAME)
.id(String.valueOf(i))
.source("field", "value" + i);
bulkRequest.add(request);
}
client.bulk(bulkRequest);
} catch (Exception e) {e.printStackTrace();
}
}
}
性能优化
冷热数据分离
对于时间序列数据,可以将热数据(最近数据)和冷数据(历史数据)存储在不同的节点上:
- 节点标记:将节点分为 ”hot” 和 ”warm” 两类。
- 索引生命周期管理:使用 ILM(Index Lifecycle Management)自动迁移冷数据到 warm 节点。
示例配置:
PUT _ilm/policy/my_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {"max_size": "50GB"}
}
},
"warm": {
"actions": {
"allocate": {
"require": {"data": "warm"}
}
}
}
}
}
}
查询 DSL 调优
- 使用 filter 代替 query:filter 不计算相关性分数,性能更高。
- 避免脚本查询:脚本查询会显著降低性能。
- 限制返回字段:只查询需要的字段,减少网络传输。
示例:
GET /my_index/_search
{
"query": {
"bool": {
"filter": [{"term": {"status": "active"}}
]
}
},
"_source": ["title", "price"]
}
监控指标
关键监控指标包括:
- JVM 堆内存:确保使用率不超过 70%。
- 查询延迟:99% 的查询应在 100ms 内完成。
- 磁盘 IO:高 IO 等待时间可能表明磁盘瓶颈。
避坑指南
深分页问题
避免使用 from+size 进行深分页,改用search_after:
GET /my_index/_search
{
"size": 10,
"query": {"match_all": {}},
"sort": [{"_id": "asc"}
],
"search_after": ["last_id"]
}
字段类型映射
常见错误包括:
- 将数值字段映射为 text 类型,导致无法进行范围查询。
- 未定义日期格式,导致日期查询失败。
集群脑裂预防
- 配置 minimum_master_nodes:设置为
(master_eligible_nodes / 2) + 1。 - 网络隔离检测:确保集群节点间的网络延迟低且稳定。
总结
优化前后性能对比:
| 指标 | 优化前(MySQL) | 优化后(Elasticsearch) |
|---|---|---|
| QPS | 1,000 | 10,000 |
| 平均延迟 | 200ms | 20ms |
| 99% 延迟 | 500ms | 50ms |
延伸学习建议:
- 结合 Redis 作为二级缓存,进一步减轻搜索集群压力。
- 探索 Elasticsearch 的聚合功能,实现复杂数据分析。
通过合理配置和优化,搜索技术可以显著提升高并发场景下的查询性能,为用户提供更流畅的体验。
正文完
发表至: 技术分享
近一天内
