Elasticsearch倒排索引结构与FST压缩算法
Elasticsearch基于Apache Lucene构建,核心数据结构是倒排索引(Inverted Index)。与传统B+树索引按行ID查找值不同,倒排索引按词项(term)查找包含该词的文档列表。全文检索的高效性正源于此:搜索”分布式”时,直接定位到倒排表中”分布式”对应的文档ID列表,无需扫描全表。
倒排索引由两部分组成:词典(Term Dictionary)和倒排表(Posting List)。词典存储所有去重排序的词项,倒排表存储每个词项对应的文档ID、词频、位置信息。Lucene使用FST(Finite State Transducer)压缩词典,将词典编码为有限状态机。FST的优势是极低的内存占用和O(len)的查询复杂度,len为查询词的字符长度而非词典大小。
// FST压缩原理示意
// 词典: ["apple", "apply", "banana", "band", "bandage"]
// FST编码后共享前缀和后缀,状态机如下:
//
// 0 --a--> 1 --p--> 2 --p--> 3 --l--> 4 --e--> 5 (apple)
// --y--> 6 (apply)
// 0 --b--> 7 --a--> 8 --n--> 9 --d--> 10 (band)
// --ana--> 11 (banana)
// --age--> 12 (bandage)
//
// 共享前缀"a"和"b"只存储一次,后缀也通过状态合并压缩
// 倒排表示例(Posting List)
// term "elasticsearch" -> [(doc_1, freq=3, pos=[12,45,78]),
// (doc_5, freq=1, pos=[23]),
// (doc_12, freq=2, pos=[5,67])]
// 位置信息(pos)用于短语查询和proximity匹配
倒排表使用Roaring Bitmaps编码文档ID列表,在稀疏和密集场景下都有高效压缩。文档ID密集时使用位图压缩,稀疏时使用数组压缩,Elasticsearch自动选择最优编码方式。For posting list的数值压缩,Lucene采用Frame of Reference(FOR)算法,将递增的docID转为delta差值再做位打包,压缩比可达4:1。
IK中文分词器安装配置与自定义词典
Elasticsearch内置的Standard分词器对中文按单字切分,”分布式系统”被切为[“分”,”布”,”式”,”系”,”统”],无法匹配”分布式”或”系统”的整词搜索。IK分词器是中文全文检索的事实标准,支持细粒度(ik_smart)和智能(ik_max_word)两种分词模式。
# 安装IK分词器(需匹配ES版本)
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.12.0/elasticsearch-analysis-ik-8.12.0.zip
# 重启Elasticsearch后验证分词效果
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "分布式系统架构设计模式"
}
# ik_max_word 结果: ["分布式", "分布", "式", "系统", "架构", "设计", "模式"]
# ik_smart 结果: ["分布式", "系统", "架构", "设计", "模式"]
POST /_analyze
{
"analyzer": "ik_smart",
"text": "Elasticsearch搜索引擎"
}
# 结果: ["elasticsearch", "搜索引擎", "搜索", "引擎"]
ik_max_word尽可能多地分词,适合索引阶段,确保搜索词能匹配到更多文档。ik_smart做最少切分,适合搜索阶段,减少无关匹配。生产环境推荐索引时用ik_max_word,搜索时用ik_smart,两者配合实现高召回率和高精确率的平衡。
自定义词典处理专业术语和新词:
# config/analysis-ik/IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 配置</comment>
<entry key="ext_dict">custom_dict.dic</entry>
<entry key="ext_stopwords">stop_words.dic</entry>
<entry key="remote_ext_dict">http://10.0.0.100/dict/custom_dict.dic</entry>
</properties>
# custom_dict.dic - 每行一个词
分布式系统
微服务架构
云原生
Kubernetes
Service Mesh
服务网格
链路追踪
# stop_words.dic - 停用词
的
了
和
是
在
remote_ext_dict支持远程词典热更新,IK分词器定期拉取远程词典检测MD5变化,有更新时自动重载,无需重启Elasticsearch。这在新闻、电商等需要频繁更新热词的场景中非常实用。
全文检索Query DSL与相关性评分调优
Elasticsearch的Query DSL分为两类:Query context计算相关性评分(_score),Filter context只判断是否匹配不计算评分。Filter结果会被缓存,高频过滤场景应优先使用filter而非query。
POST /articles/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": {
"query": "分布式架构设计",
"analyzer": "ik_smart",
"minimum_should_match": "75%",
"boost": 2
}
}
},
{
"match": {
"content": "微服务"
}
}
],
"filter": [
{ "term": { "status": "published" } },
{ "range": { "publish_date": { "gte": "2026-01-01" } } },
{ "terms": { "tags": ["架构", "后端"] } }
],
"should": [
{ "match": { "author": "专家" } }
],
"minimum_should_match": 0
}
},
"highlight": {
"pre_tags": ["<em>"],
"post_tags": ["</em>"],
"fields": {
"title": { "number_of_fragments": 0 },
"content": { "fragment_size": 150, "number_of_fragments": 3 }
}
},
"sort": [
{ "_score": "desc" },
{ "publish_date": "desc" }
],
"size": 20,
"from": 0
}
相关性评分基于BM25算法(默认,Lucene 6+取代了TF-IDF)。BM25的公式考虑词频(TF)、逆文档频率(IDF)和文档长度归一化。关键参数:k1控制词频饱和速度(默认1.2),b控制文档长度归一化强度(默认0.75)。短文本搜索(标题、关键词)建议降低b到0.3,减少长标题的惩罚。
// 自定义BM25参数
PUT /articles/_settings
{
"index": {
"similarity": {
"custom_bm25": {
"type": "BM25",
"k1": 1.2,
"b": 0.3
}
}
}
}
// 在mapping中指定使用自定义相似度
PUT /articles/_mapping
{
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"similarity": "custom_bm25"
}
}
}
Elasticsearch集群分片副本与索引生命周期管理
Elasticsearch的索引由多个分片(shard)组成,每个分片是一个独立的Lucene索引。分片数在索引创建时确定且不可更改(除非reindex)。分片数选择直接影响写入吞吐和查询并行度:分片过少导致单分片过大、查询慢、恢复慢;分片过多增加集群管理开销和资源浪费。
# 分片数规划经验公式
# 分片大小 = 索引总数据量 / 分片数
# 单分片建议大小: 30-50GB(搜索场景),10-30GB(日志场景)
# 分片数 = ceil(总数据量 / 50GB)
# 例如: 日数据量500GB,保留30天
# 单日分片数 = ceil(500/50) = 10
# 30天总分片数 = 10 * 30 = 300
PUT /logs-2026.08.14
{
"settings": {
"number_of_shards": 10,
"number_of_replicas": 1,
"index.refresh_interval": "30s",
"index.translog.durability": "async",
"index.translog.sync_interval": "30s"
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"level": { "type": "keyword" },
"service": { "type": "keyword" },
"message": { "type": "text", "analyzer": "ik_max_word" }
}
}
}
refresh_interval控制索引刷新频率,默认1秒。增大到30秒可提升写入吞吐2-3倍,代价是数据延迟可见。日志场景对实时性要求不高,30秒刷新完全够用。translog的async模式减少fsync次数,批量写入时性能提升显著,但异常崩溃可能丢失少量数据。
// 索引生命周期管理(ILM)策略
PUT /_ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50gb",
"max_age": "1d",
"max_docs": 100000000
},
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 },
"set_priority": { "priority": 50 }
}
},
"cold": {
"min_age": "30d",
"actions": {
"freeze": {},
"set_priority": { "priority": 0 }
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}
ILM策略实现索引从hot到delete的全生命周期自动管理。hot阶段做rollover按条件滚动创建新索引;warm阶段做shrink合并分片和forcemerge合并segment减少资源占用;cold阶段freeze索引减少内存使用;delete阶段自动清理过期数据。这套机制让日志、指标类数据的存储成本和查询性能得到自动平衡。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/elasticsearch-dao-pai-suo-yin-gou-jian-yuan-li-yu-zhong-wen/