从数据库到搜索引擎
数据库擅长精确查询与事务,但遇到全文检索就力不从心:LIKE 模糊匹配走不了索引,数据量一大就全表扫描;多字段组合搜索、相关性排序、分词纠错更不是关系型数据库的设计目标。Elasticsearch 就是为"搜索"而生的分布式搜索引擎,基于 Lucene,把文档倒排成索引,毫秒级返回相关性排序结果。
使用 ES 的关键认知是它与数据库的分工:数据库是事实源,ES 是检索副本。数据写入数据库后,通过同步机制把检索字段灌进 ES,查询走 ES,详情回源数据库。这样两边的职责单一,也避免把 ES 当数据库用带来的数据一致性问题。
- 映射定义字段如何被索引与存储。
- 倒排索引词到文档的映射,搜索的根基。
- DSLES 的 JSON 查询语法。
- 分片数据水平切分与副本的单位。
索引映射与分词
映射决定了字段怎么被处理,写错映射的代价是重建索引。一个字段可以设置 text 用于全文搜索、keyword 用于精确匹配与聚合、integer 用于数值、date 用于日期范围。text 字段会经过分词器切成词项再建倒排索引,keyword 字段则原样保存。
PUT /articles/_mapping
{
"properties": {
"title": { "type": "text", "analyzer": "ik_max_word" },
"tag": { "type": "keyword" },
"views": { "type": "integer" },
"created": { "type": "date" }
}
}
中文分词是 ES 落地的第一道坎。默认 standard 分词器按空格与标点切,中文会被整段当作一个词,搜"后端"匹配不到"后端开发"。生产环境通常安装 IK 分词器,ik_max_word 做细粒度切词用于索引,ik_smart 做粗粒度切词用于查询,配合自定义词典收录业务专有词。
倒排索引与字段类型
倒排索引把"文档里有哪些词"反过来存成"词出现在哪些文档"。搜索"数据库",先查词表定位"数据库"对应的文档链表,再做评分与排序。ES 的 Lucene 底层用跳表与位图压缩文档链表,查询时跳过无关文档,这就是它比 LIKE 快几个数量级的原因。
字段类型的选择直接影响磁盘与查询。能用 keyword 就别用 text,能聚合的字段必须是 keyword 或 doc_values 支持的类型;数值字段尽量用整数与浮点,避免字符串数字;object 与 nested 处理嵌套文档,nested 的数组查询语义才正确但性能开销大,能拍平就拍平。
# 典型电商商品映射片段
"sku": { "type": "keyword" },
"name": { "type": "text", "analyzer": "ik_max_word" },
"price": { "type": "double" },
"stock": { "type": "integer" }
查询 DSL 实战
ES 查询 DSL 是一套 JSON 语法,核心分两类:叶子查询负责匹配,复合查询负责组合。match 做全文匹配,term 做精确匹配,range 做范围过滤,bool 用 must、should、must_not 组合多个条件,是日常最常用的容器。
{
"query": {
"bool": {
"must": [ { "match": { "title": "后端 微服务" } } ],
"filter": [
{ "term": { "status": "published" } },
{ "range": { "views": { "gte": 1000 } } }
]
}
},
"sort": [ { "created": "desc" } ],
"from": 0, "size": 20
}
filter 子句不参与评分但参与过滤,能被缓存,把高频过滤条件放 filter 里能显著提速。排序字段要用 keyword 或开启 doc_values,避免对 text 字段排序;深分页用 search_after 而不是大 from,否则内存会爆。
聚合与分析
聚合让 ES 在检索之外还能做统计。terms 聚合按字段分组计数,类似 SQL 的 group by;date_histogram 按时间桶聚合,是日志分析的主力;metrics 聚合计算 min、max、avg、sum 与 percentiles。聚合的字段必须是 keyword 或数值,text 字段要先加 keyword 子字段。
{
"size": 0,
"aggs": {
"by_status": {
"terms": { "field": "status.keyword", "size": 10 },
"aggs": { "avg_views": { "avg": { "field": "views" } } }
}
}
}
聚合的精度与性能要权衡:terms 默认只取前几个桶,deep 聚合改用 composite 游标分页;date_histogram 的时间间隔按数据量选分钟、小时还是天。聚合是把双刃剑,写得好是分析利器,写不好就是集群杀手,监控聚合查询的耗时与内存占用很有必要。
性能优化
ES 性能优化从模型、查询、集群三层入手。模型层:字段能精简就精简,关闭不需要的 _source 字段的加载,映射里禁用不用的聚合与 doc_values;查询层:优先 filter 缓存,控制 from 深度用 search_after,避免前缀通配查询,能精确匹配就不全文检索。
集群层:分片数在创建索引时定死,主分片按数据量与节点数规划,副本数按读压力调整;写入端用 bulk 批量提交,合并小的请求;刷新间隔 refresh_interval 从默认 1 秒调到 30 秒,写入吞吐能提升数倍,代价是搜索可见延迟变长,适合日志场景。
- 模型精简字段、控制映射、减少存储。
- 查询filter 缓存、search_after、避免通配。
- 集群合理分片、bulk 写入、放宽刷新。
落地清单
ES 上生产前,把映射设计当成数据库表结构评审一样认真对待:字段类型、分词器、是否需要聚合,一次定清楚,改映射就是重建索引。数据同步要打通:binlog 同步、应用双写还是定时全量,选一种并做好失败重放,保证 ES 与数据库最终一致。
上线后持续观察:慢查询日志、聚合内存、分片健康度与磁盘水位。ES 不是万能的,搜索场景它是王者,事务与强一致请留在数据库。把检索交给 ES,把事实留在数据库,这个边界守住了,搜索架构就成功了一半。
- 映射与分词器先评审后建索引。
- 同步链路带失败重放与监控。
- 深分页用 search_after,filter 多用缓存。
- 分片与副本按数据量与读压规划。