Elasticsearch实践
原理
基于lucence 集群 –> 节点 –> 索引 –> 分片
集群
节点发现机制:ZenDiscovery
节点
集群中有一个主节点,单个节点可以同时为候选主节点和数据节点,通过配置指定。 主节点:维护集群节点状态; 候选主节点:可参与选举,成为主节点; 数据节点:执行实际的数据查询和存储操作; 协调节点:client直连的节点,可以是集群中的任何节点,负责将请求转发到实际的主分片节点上;
分片
分片数提前规划,不可变更,单分片 20-50G 主从分片不能在同一个实例节点中;
索引
ES默认会把所有字段都建成索引,字符串字段会默认被分词,作为全文检索,分词器使用的默认分词器,中文会一个字一个字的分词,英文会根据空格分成一个一个单词,之后会全部转换成小写存储,分词后就会形成一个一个的token(词条),后续ES的查询基本上就是根据每个字段的tokens查询的,token这个东西就很关键了,不同的分词器分成的token不一样,一个字段会分词成多个token,存放的是一个token数组,所以就会影响查询结果。
索引不可变更。
mapping
对应数据库表结构 properties:具体字段,以及相关类型,不同类型的查询支持情况不尽相同,text类型支持分词,long,keyword类型支持精确匹配。
multi field
对同一个字段构建不同的索引,以应对不同的查询需要,不会改变_source的存储数据,额外创建索引,增加存储开销; 新增一个filed时,会对后续的indice数据有效,已有数据不会重建索引;
倒排索引
建立 关键字 –> 文档的 映射关系,即为倒排索引! 关键字构建词典,词典中存放关键字,关键字指向具体的原始文档列表;
深度分页
索引分页过大时,性能损耗严重,可通过max_result_window控制最大条目数,默认为10000. 可使用scroll游标处理
常用操作
索引信息查看
’‘’ curl -v “http://hostname:7000/_cat/indices?pretty=true” ’‘’
索引删除
curl -v -XDELETE "http://host:9201/index-name"
数据检索
curl -X POST -H "Content-type:application/json" "http://hostname:7000/indexName/_search?pretty=true" -d '{"size": 5,
"sort" : [{"dt" : {"order" : "asc"}}], "query": {"range":{"dt":{"gte":1627747200000}}}
}'
数据删除
curl -X POST -H "Content-type:application/json" "http://hostname:7000/indexName/_delete_by_query" -d '
{"query": {"range":{"dt":{"lt":1630425600000,"gte":1627747200000}}}}'
分词器
分词器是针对text字段进行文本分析的工具。
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html
写入分词器
在settings中定义分词器,并在mapping中使用: “analyzer” : “analyzer_name”;
查询分词器
- 1,在settings中定义分词器,并在mapping中使用: “search_analyzer” : “analyzer_name”;
- 2,在查询接口中指定使用特定分词器;
注意:默认分词之后,为or的关系,只要有一个存在也会返回!
更新分词器配置
官方说明:https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html
1,停止索引
POST /*-2021.05/_close?wait_for_active_shards=0
2,更新settings
PUT /*-2021.05/_settings
{
"settings": {
"analysis": {
"analyzer": {
"log_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word"
},
"search_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": [
"stemmer",
"remove_letters"
]}},
"filter": {
"remove_letters": {
"type": "keep_types",
"mode": "exclude",
"types": [
"LETTER"
]}}}}}
3,重启索引
POST /*-2021.05/_open
字段分词器更新
官方说明:https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html
更新mapping
PUT /*-2021.05/_mapping
{
"properties": {
"message": {
"type" : "text",
"analyzer" : "log_analyzer",
"search_analyzer": "search_analyzer"
}}}
分词结果分析
官方说明:https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html
数据类型
text类型:
- 会进行分词,分词后建立索引。
- 支持模糊查询,支持准确查询。
- 不支持聚合查询
- 最大支持的字符长度无限制,适合大字段存储;
keyword类型:
- 不分词,直接建立索引。【依据此特点,可以使用keyword类型+wildcardQuery(通配查询)实现类似sql的like查询(模糊搜索)】
- 支持模糊查询,支持准确查询。
- 支持聚合查询。
- 最大支持的长度为——32766个UTF-8类型的字符,可以通过设置ignore_above指定自持字符长度,超过给定长度后的数据将不被索引,无法通过term精确匹配检索返回结果。
查询类型
term
不会分词,需要完全匹配才可以。 keyword 字段不分词,可直接查询;text类型分词后也可以被term查询。
match
match 分词,text 也分词,只要 match 的分词结果和 text 的分词结果有相同就匹配,这里可以通过operator(AND,OR)控制分词后各token匹配关系,默认为OR,即分词后,其中部分token匹配即可。
match_phrase
match_phrase 是分词的,text 也是分词的。match_phrase 的分词结果必须再 text 字段分词中都包含,而且顺序必须相同,默认必须都是连续的(可以通过slop控制间隔)。
注意:
实际不要使用*开头做通配,严重影响性能,并可能导致系统崩溃。
检索不到分析
当前实际使用的分词器为:log_analyzer(写入使用),search_analyzer(查询使用)。
通过分析不同分词器对给定文本的分词结果,可以快速定位当前检索不到数据的原因。(通常为数据写入的分析器,跟查询分词器的分词结果不一致)
POST qiyelog.qiye-domainservice-email20-2021.07/_analyze
{
"text" : """openauthcode""",
"analyzer": "search_analyzer"
}
失败场景 | 失败原因 | 处理方式 |
---|---|---|
xxxx:yyyy格式的数据,查询”xxxx“无法获取 | 默认分词器不支持”:“的分词 | 调整默认分词器,引入ik_max_word;备注:实际通过调整检索类型(match_prefix)也可支持 |
使用邮箱域名查询日志,无法正常检索 | 写入分词器包含LETTER类型分词,必须完全匹配,包括域名后缀 | 查询分词器补充对LETTER类型的分词检索过滤 |
使用带e,ing等后缀的关键字,无法正常检索 | 查询分词器包含了stemmer过滤器,将特殊后缀进行了截断 | 查询分析器删除对的stemmer过滤 |
索引迁移
对现有索引新增/修改字段时,无法对历史数据生效,需要进行索引迁移,同时在迁移过程中需要保证平滑切换,对业务无感知。
前提
索引需要有别名,业务使用别名操作。
步骤
1,已有索引查看
curl -X GET -H "Content-type:application/json" "http://${host}:7000/archive_log_2021/?pretty=true"
其中这里dynamic字段这是为false,不自动新增字段(避免推断类型不准确),但是可以通过接口显式修改。
2,创建新索引
具体属性以及mapping字段,可直接复制已有索引进行修改。
curl -X PUT -H "Content-type:application/json" "http://${host}:7000/archive_log_2021_new" -d '{
"aliases" : {
"archive_log_2021" : {
}
},
"mappings" : {
"dynamic" : "false",
"_meta" : {
"init_mark" : true
},
"properties" : {
"content" : {
"type" : "text"
},
"did" : {
"type" : "long"
},
...
}
},
"settings" : {
"index" : {
"number_of_shards" : "3",
"number_of_replicas" : "2"
}
}
}'
3,别名更新
对新建的索引添加别名,并且设置为可读,同时需要删除原有索引的别名。
_aliases该接口可实现多个操作的原子性(同一别名下保证只有一个索引写入)。 对于同一个别名,每次只能有一个索引可写。该操作之后,数据会写入新的索引中。
curl -X POST -H "Content-type:application/json" "http://${host}:7000/_aliases" -d '{
"actions": [
{
"add": {
"index": "archive_log_2021_new",
"alias": "archive_log_2021",
"is_write_index": true
}
},
{
"remove": {
"index": "archive_log_2021_old",
"alias": "archive_log_2021"
}
}
]
}'
4,索引迁移
此时将老的索引中的数据,重做到新索引中。
根据索引数据量不同,这个过程耗时会比较久,可以通过reindex的其他参数进行更精细的控制,此处可采用异步方式,默认冲突不插入。
该操作执行完毕后,所有数据可以在新的索引中正常检索到。
curl -X POST -H "Content-type:application/json" "http://${host}:7000/_reindex?wait_for_completion=false" -d '{
"source": {
"index": "archive_log_2021_old"
},
"dest": {
"index": "archive_log_2021_new"
}
}'
总结
通过上述操作,可以实现平滑的索引更新。 注意:如果字段有更新操作,那么该方案仍然存在漏洞,即更新数据写入新索引后,历史数据再重做过来,实际会出现两条记录。