ElasticSearch 架构原理

博文目录


节点类型

在ES主要分成两类节点,一类是Master节点,一类是DataNode节点。

  • 主节点:node.master:true, 节点可以成为主节点
  • 数据节点:node.data: true, 节点可以成为数据节点
  • 客户端节点:既不是Master节点也不是DataNode节点的就是客户端节点
  1. 客户端节点
    当主节点和数据节点配置都设置为false的时候,该节点只能处理路由请求,处理搜索,分发索引操作等,从本质上来说该客户节点表现为智能负载平衡器。
    独立的客户端节点在一个比较大的集群中是非常有用的,他协调主节点和数据节点,客户端节点加入集群可以得到集群的状态,根据集群的状态可以直接路由请求。

  2. 数据节点
    数据节点主要是存储索引数据的节点,主要对文档进行增删改查操作,聚合操作等。数据节点对cpu,内存,io要求较高, 在优化的时候需要监控数据节点的状态,当资源不够的时候,需要在集群中添加新的节点。

  3. 主节点
    主资格节点的主要职责是和集群操作相关的内容,如创建或删除索引,跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点。稳定的主节点对集群的健康是非常重要的,默认情况下任何一个集群中的节点都有可能被选为主节点,索引数据和搜索查询等操作会占用大量的cpu,内存,io资源,为了确保一个集群的稳定,分离主节点和数据节点是一个比较好的选择。

在一个生产集群中我们可以对这些节点的职责进行划分,建议集群中设置3台以上的节点作为master节点,这些节点只负责成为主节点,维护整个集群的状态。再根据数据量设置一批data节点,这些节点只负责存储数据,后期提供建立索引和查询索引的服务,这样的话如果用户请求比较频繁,这些节点的压力也会比较大,所以在集群中建议再设置一批client节点(node.master: false node.data: false),这些节点只负责处理用户请求,实现请求转发,负载均衡等功能

Master节点

在ES启动时,会选举出来一个Master节点。当某个节点启动后,然后使用Zen Discovery机制找到集群中的其他节点,并建立连接。

discovery.seed_hosts: ["192.168.2.134", "192.168.2.135", "192.168.2.136"]

并从候选主节点中选举出一个主节点。

cluster.initial_master_nodes: ["node-1", "node-2","node-3"]

Master节点主要负责:

  • 管理索引(创建索引、删除索引)、分配分片
  • 维护元数据
  • 管理集群节点状态
  • 不负责数据写入和查询,比较轻量级

一个ES集群中,只有一个Master节点。在生产环境中,内存可以相对小一点,但机器要稳定。

DataNode节点

在ES集群中,会有N个DataNode节点。DataNode节点主要负责:数据写入、数据检索,大部分ES的压力都在DataNode节点上
在生产环境中,内存最好配置大一些

分片和副本机制

分片(Shard)

ES是一个分布式的搜索引擎,索引的数据也是分成若干部分,分布在不同的服务器节点中

分布在不同服务器节点中的索引数据,就是分片(Shard)。Elasticsearch会自动管理分片,如果发现分片分布不均衡,就会自动迁移
一个索引(index)由多个shard(分片)组成,而分片是分布在不同的服务器上的

副本(Replica)

为了对Elasticsearch的分片进行容错,假设某个节点不可用,会导致整个索引库都将不可用。所以,需要对分片进行副本容错。每一个分片都会有对应的副本。

在Elasticsearch中,默认创建的索引为1个分片、每个分片有1个主分片和1个副本分片。

每个分片都会有一个Primary Shard(主分片),也会有若干个Replica Shard(副本分片),Primary Shard和Replica Shard不在同一个节点上

指定分片、副本数量

创建索引印射的时候, 可以指定分片和副本的配置, 分片数指定后不可修改

PUT /es
{
	"mappings": {
		"properties": {
			"...": {
				...
			},
			...
		}
	},
	"settings": {
		"number_of_shards": 3,
		"number_of_replicas": 2
	}
}

查看分片、主分片、副本分片

GET /_cat/indices?v
GET方式访问 http://192.68.2.134:9200/_cat/indices?v

重要工作流程

文档写入原理

在这里插入图片描述

  1. 选择任意一个DataNode发送请求,例如:node2。此时,node2就成为一个coordinating node(协调节点)
  2. 计算得到文档要写入的分片 shard = hash(routing) % number_of_primary_shards, routing 是一个可变值,默认是文档的 _id
  3. coordinating node会进行路由,将请求转发给对应的primary shard所在的DataNode(假设primary shard在node1、replica shard在node2)
  4. node1节点上的Primary Shard处理请求,写入数据到索引库中,并将数据同步到Replica shard
  5. Primary Shard和Replica Shard都保存好了文档,返回client

检索原理

在这里插入图片描述

  1. client发起查询请求,某个DataNode接收到请求,该DataNode就会成为协调节点(Coordinating Node)
  2. 协调节点(Coordinating Node)将查询请求广播到每一个数据节点,这些数据节点的分片会处理该查询请求
  3. 每个分片进行数据查询,将符合条件的数据放在一个优先队列中,并将这些数据的文档ID、节点信息、分片信息返回给协调节点
  4. 协调节点将所有的结果进行汇总,并进行全局排序
  5. 协调节点向包含这些文档ID的分片发送get请求,对应的分片将文档数据返回给协调节点
  6. 最后协调节点将数据返回给客户端

准实时索引实现

溢写到文件系统缓存

当数据写入到ES分片时,会首先写入到内存中,然后通过内存的buffer生成一个segment,并刷到文件系统缓存中(这个刷新动作默认每1秒执行一次),数据可以被检索(注意不是直接刷到磁盘),数据在内存中时不可检索,在文件系统缓存中时就可以检索了,即默认最多1秒,数据就可以被检索到

HBase数据库是实时检索,因为其数据在内存的时候就可以被检索到

写translog保障容错

在数据写入到内存中的同时,也会记录translog日志,在refresh期间出现异常,会根据translog来进行数据恢复。等到文件系统缓存中的segment数据都刷到磁盘中后,清空translog文件

flush到磁盘

ES默认每隔30分钟会将文件系统缓存的数据刷入到磁盘

segment合并

Segment太多时,ES定期会将多个segment合并成为大的segment,减少索引查询时的IO开销,此阶段ES会真正的物理删除(之前执行过的delete的数据)

在这里插入图片描述

ES 文档分值 _score 计算底层原理

  1. boolean model, 根据用户的query条件,先过滤出包含指定term的doc

  2. relevance score 算法,简单来说,就是计算出一个索引中的文本,与搜索文本之间的关联匹配程度

ES使用的是 term frequency / inverse document frequency算法,简称为TF/IDF算法

  • Term frequency:搜索文本中的各个词条在field文本中出现了多少次,出现次数越多,就越相关
  • Inverse document frequency:搜索文本中的各个词条在整个索引的所有文档中出现了多少次,出现的次数越多,就越不相关
    如: condition: hello world, doc1: hello, you are very good, doc2: hi world, how are you, doc1中有hello, doc2中有world, 两者的TF值是一样的, 假如整个index中有1000个doc, hello出现了100次, world出现了10次, 则doc2的IDF值比较高
  • Field-length norm:field长度,field越长,相关度越弱

可通过如下查询查看es索引下文档1的TF分值和IDF分值

GET /es/_doc/1/_explain
{
  "query": {
    "match": {
      "remark": "java developer"
    }
  }
}

具体的计算方式非常复杂, 涉及到向量空间模型

ES 分词器

分词器工作流程

切分词语,normalization(正规化)

给你一段句子,然后将这段句子拆分成一个一个的单个的单词,同时对每个单词进行normalization(时态转换,单复数转换),分词器

recall,召回率:搜索的时候,增加能够搜索到的结果的数量

  • character filter:在一段文本进行分词之前,先进行预处理,比如说最常见的就是,过滤html标签(hello --> hello),& --> and(I&you --> I and you)
  • tokenizer:分词,hello you and me --> hello, you, and, me
  • token filter:lowercase,stop word(停用词),synonymom(同义词),liked --> like,Tom --> tom,a/the/an --> 干掉,small --> little

停用词: 停用词是指在信息检索中,为节省存储空间和提高搜索效率,在处理自然语言数据(或文本)之前或之后会自动过滤掉某些字或词,这些字或词即被称为Stop Words(停用词)。这些停用词都是人工输入、非自动化生成的,生成后的停用词会形成一个停用词表。但是,并没有一个明确的停用词表能够适用于所有的工具。甚至有一些工具是明确地避免使用停用词来支持短语搜索的。对于一个给定的目的,任何一类的词语都可以被选作停用词。通常意义上,停用词大致分为两类。一类是人类语言中包含的功能词,这些功能词极其普遍,与其他词相比,功能词没有什么实际含义,比如’the’、‘is’、‘at’、‘which’、‘on’等。但是对于搜索引擎来说,当所要搜索的短语包含功能词,特别是像’The Who’、'The The’或’Take The’等复合名词时,停用词的使用就会导致问题。另一类词包括词汇词,比如’want’等,这些词应用十分广泛,但是对这样的词搜索引擎无法保证能够给出真正相关的搜索结果,难以帮助缩小搜索范围,同时还会降低搜索的效率,所以通常会把这些词从问题中移去,从而提高搜索性能。

分词器将一段文本进行各种处理,处理好的结果才会拿去建立倒排索引

内置分词器

分词测试, Set the shape to semi-transparent by calling set_trans(5)

  • standard analyzer:set, the, shape, to, semi, transparent, by, calling, set_trans, 5
  • simple analyzer:set, the, shape, to, semi, transparent, by, calling, set, trans
  • whitespace analyzer:Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
  • stop analyzer: set, shape, semi, transparent, calling, set, trans, 移除停用词,比如 a the it by to 等等
POST _analyze
{
	"analyzer":"standard",
	"text":"Set the shape to semi-transparent by calling set_trans(5)"
}

定制分词器

standard tokenizer:以单词边界进行切分
standard token filter:什么都不做
lowercase token filter:将所有字母转换为小写
stop token filer(默认被禁用):移除停用词,比如a the it等等

以standard分词器为标准, 创建myanalyzer分词器, 启用英文环境下停用词过滤功能, 在myindex索引下即可使用myanalyzer分词器了

PUT /myindex
{
	"settings": {
		"analysis": {
			"analyzer": {
				"myanalyzer": {
					"type": "standard",
					"stopwords": "_english_"
				}
			}
		}
	}
}

GET /my_index/_analyze
{
  "analyzer": "standard", 
  "text": "a dog is in the house"
}

// 测试发现使用myanalyzer分词器, 结果是 dog, house
GET /my_index/_analyze
{
  "analyzer": "myanalyzer",
  "text":"a dog is in the house"
}
// 在my_index索引库中自定义了分词器, 使用了自定义的char_filter和filter
PUT /my_index
{
	"settings": {
		"analysis": {
			"char_filter": {
				"&_to_and": {
					"type": "mapping",
					"mappings": ["&=> and"]
				}
			},
			"filter": {
				"my_stopwords": {
					"type": "stop",
					"stopwords": ["the", "a"]
				}
			},
			"analyzer": {
				"my_analyzer": {
					"type": "custom",
					"char_filter": ["html_strip", "&_to_and"],
					"tokenizer": "standard",
					"filter": ["lowercase", "my_stopwords"]
				}
			}
		}
	}
}

// tomandjerry, are, friend, in, house, haha,
GET /my_index/_analyze
{
	"text": "tom&jerry are a friend in the house, <a>, HAHA!!",
	"analyzer": "my_analyzer"
}

PUT /my_index/_mapping/my_type
{
"properties": {
	"content": {
		"type": "text",
		"analyzer": "my_analyzer"
		}
	}
}

IK分词器

配置说明

ik配置文件地址:es/plugins/ik/config目录

  • IKAnalyzer.cfg.xml:用来配置自定义词库
  • main.dic:ik原生内置的中文词库,总共有27万多条,只要是这些单词,都会被分在一起
  • quantifier.dic:放了一些单位相关的词
  • suffix.dic:放了一些后缀
  • surname.dic:中国的姓氏
  • stopword.dic:英文停用词

最重要的两个配置文件

  • main.dic:包含了原生的中文词语,会按照这个里面的词语去分词
  • stopword.dic:包含了英文的停用词, 在分词的时候会被直接干掉, 不会建立倒排索引

词库扩展

IKAnalyzer.cfg.xml 中配置本地词库扩展

  • 扩展常规词库: ext_dict 上配置 custom/mydict.dic, 并创建目录文件, 扩展词放到这里, 重启生效
  • 扩展停用词库: ext_stopword 上配置 custom/mystopword.dic, 并创建目录文件, 扩展停用词放到这里, 重启生效

词库热更新

  • IKAnalyzer.cfg.xml 中配置远程词库扩展, 如配置 http://192.168.1.100/mydict.dic, 每次改这个文件即可, 无需重启ES, IK会定时加载
  • 修改IK源码, 添加数据库配置, 指向扩展常规词库和扩展停用词库, 在数据库中添加扩展词即可

IK分词器源码

ES 高亮显示

在搜索中,经常需要对搜索关键字做高亮显示

PUT /news_website
{
	"mappings": {
		"properties": {
			"title": {
				"type": "text",
				"analyzer": "ik_max_word"
			},
			"content": {
				"type": "text",
				"analyzer": "ik_max_word"
			}
		}
	}
}

PUT /news_website
{
    "settings" : {
        "index" : {
            "analysis.analyzer.default.type": "ik_max_word"
        }
    }
}

PUT /news_website/_doc/1
{
	"title": "这是我写的第一篇文章",
	"content": "大家好,这是我写的第一篇文章,特别喜欢这个文章门户网站!!!"
}

// highlight.fileds.title 的json里可以指定高亮的展示方式
GET /news_website/_doc/_search 
{
	"query": {
		"match": {
			"title": "文章"
		}
	},
	"highlight": {
		"fields": {
			"title": {}
		}
	}
}

// 结果示例, <em></em>包裹的内容在浏览器中会变成红色
{
	...
    "hits" : [
      {
        "_index" : "news_website",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "title" : "我的第一篇文章",
          "content" : "大家好,这是我写的第一篇文章,特别喜欢这个文章门户网站!!!"
        },
        "highlight" : {
          "title" : [
            "我的第一篇<em>文章</em>"
          ]
        }
      }
    ]
  }
}
// highlight中的field,必须跟query中的field对应, 才能高亮显示, 如果只有title没有content, 却要高亮两个字段, 则content不会被高亮
GET /news_website/_doc/_search 
{
	"query": {
		"bool": {
			"should": [
				{
					"match": {
						"title": "文章"
					}
				},
				{
					"match": {
						"content": "文章"
					}
				}
			]
		}
	},
	"highlight": {
		"fields": {
			"title": {},
			"content": {}
		}
	}
}

高亮

高亮方式

  • plain highlight,来自于 lucene highlight,是默认的高亮方式
  • posting highlight,需要指定 index_options=offsets, (1)性能比plain highlight要高,因为不需要重新对高亮文本进行分词(2)对磁盘的消耗更少
  • fast vector highlight, index-time term_vector设置在mapping中,就会用fast verctor highlight, 对大field而言(大于1mb),性能更高

一般情况下,用 plain highlight 也就足够了,不需要做其他额外的设置
如果对高亮的性能要求很高,可以尝试启用 posting highlight
如果field的值特别大,超过了1M,那么可以用 fast vector highlight

// 两个字段, title采用默认高亮方式, content指定为posting高亮方式
PUT /news_website
{
	"mappings": {
		"properties": {
			"title": {
				"type": "text",
				"analyzer": "ik_max_word"
			},
			"content": {
				"type": "text",
				"analyzer": "ik_max_word",
				"index_options": "offsets"
			}
		}
	}
}

PUT /news_website/_doc/1
{
	"title": "我的第一篇文章",
	"content": "大家好,这是我写的第一篇文章,特别喜欢这个文章门户网站!!!"
}

// 高亮查询content的时候, 默认采用定义的posting高亮方式做高亮
GET /news_website/_doc/_search 
{
	"query": {
		"match": {
			"content": "文章"
		}
	},
	"highlight": {
		"fields": {
			"content": {}
		}
	}
}
//  两个字段, title采用默认高亮方式, content指定为fast vector高亮方式
PUT /news_website
{
	"mappings": {
		"properties": {
			"title": {
				"type": "text",
				"analyzer": "ik_max_word"
			},
			"content": {
				"type": "text",
				"analyzer": "ik_max_word",
				"term_vector": "with_positions_offsets"
			}
		}
	}
}

// 强制使用某种highlighter,比如对于开启了term_vector的field而言,可以强制使用plain highlight
GET /news_website/_doc/_search 
{
	"query": {
		"match": {
			"content": "文章"
		}
	},
	"highlight": {
		"fields": {
			"content": {
				"type": "plain"
			}
		}
	}
}

高亮标签

// 使用 <span color='red'> 做前标签, </span> 做后标签
GET /news_website/_doc/_search 
{
	"query": {
		"match": {
			"content": "文章"
		}
	},
	"highlight": {
		"pre_tags": ["<span color='red'>"],
		"post_tags": ["</span>"],
		"fields": {
			"content": {
				"type": "plain"
			}
		}
	}
}

高亮片段

  • fragment_size: 一个字段长度是1万,我们不可能在页面上显示这么长, 设置要显示出来的fragment文本判断的长度,默认是100
  • number_of_fragments:可能高亮的文本片段有多个,可以指定显示几个片段
GET /_search
{
    "query" : {
        "match": { "content": "文章" }
    },
    "highlight" : {
        "fields" : {
            "content" : {"fragment_size" : 150, "number_of_fragments" : 3 }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值