那是一个普通的周五下午,我正准备收拾东西下班,突然运维同事冲过来:"兄弟,ES集群炸了!用户搜索请求全部超时,老板在群里@所有人了!"
我心里一凉,赶紧打开监控面板。果然,Elasticsearch集群的响应时间从平时的几十毫秒飙升到了30秒+,CPU和内存使用率都快拉满了。这个集群承载着我们电商平台的商品搜索,日均查询量超过千万次,现在全线瘫痪,损失可想而知。
那次事故的罪魁祸首,竟然是一个看似无害的聚合查询。有个产品经理临时加了个需求,要统计某个品牌下所有商品的价格分布情况。开发小哥直接写了个这样的DSL:
{
"query": {
"match": {
"brand": "某知名品牌"
}
},
"aggs": {
"price_histogram": {
"histogram": {
"field": "price",
"interval": 1
}
}
}
}
看起来没啥问题对吧?但这个查询要扫描几百万条数据,还要做价格区间统计。在高并发场景下,瞬间就把集群给拖垮了。
经历过那次事故后,我对ES性能优化有了更深的理解。性能优化不是出了问题再去救火,而是要在架构设计阶段就考虑到的事情。
很多人觉得ES性能不行,直接加机器就完事了。但我发现,盲目堆硬件往往事倍功半。
内存才是王道。 我们生产环境用的是32GB内存的机器,给ES的heap设置16GB,剩下的全部留给系统缓存。你可能会问,为什么不给ES分配更多内存?因为ES底层依赖Lucene,Lucene会大量使用操作系统的page cache来缓存索引文件。heap给太多反而会抢占page cache的空间。
SSD是必需品,不是奢侈品。 机械硬盘在ES面前就是个笑话。我们之前用的SATA SSD,后来升级到NVMe,IOPS从几万直接跳到几十万,查询延迟降低了60%。
索引设计是ES性能的基石。我踩过的坑,你们千万别再踩了。
分片数量不是越多越好。 新手最容易犯的错误就是觉得分片越多,并发能力越强。我见过有人把一个10GB的索引分成100个分片,结果查询性能惨不忍睹。每个分片都有固定的开销,分片太多反而会拖累性能。一般来说,单个分片控制在20-50GB是比较合理的。
mapping设计要精准。 不要什么字段都用text
类型,能用keyword
的就别用text
。我们商品搜索的品牌字段,一开始设置成了text
,导致精确匹配查询性能很差。后来改成keyword
,性能提升了3倍。
{
"mappings": {
"properties": {
"brand": {
"type": "keyword" // 精确匹配用keyword
},
"title": {
"type": "text", // 全文搜索用text
"analyzer": "ik_max_word"
}
}
}
}
能用filter就别用query。 filter是可以缓存的,而且不参与评分计算,性能比query好太多。我们把大部分的精确匹配条件都放在了bool
查询的filter
子句里:
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "手机"
}
}
],
"filter": [
{
"term": {
"brand": "苹果"
}
},
{
"range": {
"price": {
"gte": 1000,
"lte": 8000
}
}
}
]
}
}
}
聚合查询要悠着点。 聚合是性能杀手,特别是在大数据量上做复杂聚合。我们现在的策略是:能预计算的就预计算,实在需要实时聚合的,就加上size: 0
减少不必要的文档返回。
慢查询日志是你的好朋友。 我们设置了200ms的慢查询阈值,所有超过这个时间的查询都会被记录下来。每周我们都会分析这些慢查询,找出可以优化的点。
定期清理无用的索引。 我们的日志索引是按天创建的,超过30天的索引会自动删除。别小看这个操作,清理掉无用索引能显著减少集群的负担。
现在我们的ES集群已经稳定运行了两年多,在亿级数据量下,99%的查询都能在100ms内返回结果。好的性能不是调出来的,而是设计出来的。 从集群规划到索引设计,再到查询优化,每一个环节都不能马虎。
你们的ES集群现在性能如何?遇到过哪些坑?评论区聊聊呗,说不定能碰撞出新的优化思路。