文章目录
节点类型
在ES主要分成两类节点,一类是Master节点,一类是DataNode节点。
- 主节点:node.master:true, 节点可以成为主节点
- 数据节点:node.data: true, 节点可以成为数据节点
- 客户端节点:既不是Master节点也不是DataNode节点的就是客户端节点
-
客户端节点
当主节点和数据节点配置都设置为false的时候,该节点只能处理路由请求,处理搜索,分发索引操作等,从本质上来说该客户节点表现为智能负载平衡器。
独立的客户端节点在一个比较大的集群中是非常有用的,他协调主节点和数据节点,客户端节点加入集群可以得到集群的状态,根据集群的状态可以直接路由请求。 -
数据节点
数据节点主要是存储索引数据的节点,主要对文档进行增删改查操作,聚合操作等。数据节点对cpu,内存,io要求较高, 在优化的时候需要监控数据节点的状态,当资源不够的时候,需要在集群中添加新的节点。 -
主节点
主资格节点的主要职责是和集群操作相关的内容,如创建或删除索引,跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点。稳定的主节点对集群的健康是非常重要的,默认情况下任何一个集群中的节点都有可能被选为主节点,索引数据和搜索查询等操作会占用大量的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
重要工作流程
文档写入原理
- 选择任意一个DataNode发送请求,例如:node2。此时,node2就成为一个coordinating node(协调节点)
- 计算得到文档要写入的分片
shard = hash(routing) % number_of_primary_shards
, routing 是一个可变值,默认是文档的 _id - coordinating node会进行路由,将请求转发给对应的primary shard所在的DataNode(假设primary shard在node1、replica shard在node2)
- node1节点上的Primary Shard处理请求,写入数据到索引库中,并将数据同步到Replica shard
- Primary Shard和Replica Shard都保存好了文档,返回client
检索原理
- client发起查询请求,某个DataNode接收到请求,该DataNode就会成为协调节点(Coordinating Node)
- 协调节点(Coordinating Node)将查询请求广播到每一个数据节点,这些数据节点的分片会处理该查询请求
- 每个分片进行数据查询,将符合条件的数据放在一个优先队列中,并将这些数据的文档ID、节点信息、分片信息返回给协调节点
- 协调节点将所有的结果进行汇总,并进行全局排序
- 协调节点向包含这些文档ID的分片发送get请求,对应的分片将文档数据返回给协调节点
- 最后协调节点将数据返回给客户端
准实时索引实现
溢写到文件系统缓存
当数据写入到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 计算底层原理
-
boolean model, 根据用户的query条件,先过滤出包含指定term的doc
-
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源码, 添加数据库配置, 指向扩展常规词库和扩展停用词库, 在数据库中添加扩展词即可
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 }
}
}
}