![elaseticsearch](https://elasticsearch.cn/static/common/topic-mid-img.png)
elaseticsearch
请问基于es数组进行query_string查询时,如何在highlight中返回最佳匹配的数据?
Elasticsearch • Ombres 回复了问题 • 2 人关注 • 2 个回复 • 2297 次浏览 • 2024-01-17 13:37
elasticsearch 缺乏足够的无分段虚拟地址空间,导致集群故障,请问有什么优化方案吗
Elasticsearch • FFFrp 回复了问题 • 3 人关注 • 2 个回复 • 1075 次浏览 • 2023-08-25 10:48
请教一个问题,ES must_not 多条件查询时不符合预期
Elasticsearch • Charele 回复了问题 • 2 人关注 • 1 个回复 • 1011 次浏览 • 2023-08-10 20:11
Web Scraper + Elasticsearch + Kibana + SearchKit 打造的豆瓣电影top250 搜索演示系统
Elasticsearch • 森 发表了文章 • 0 个评论 • 4956 次浏览 • 2023-04-09 10:56
Web Scraper + Elasticsearch + Kibana + SearchKit 打造的豆瓣电影top250 搜索演示系统
作者:小森同学
声明:电影数据来源于“豆瓣电影”,如有侵权,请联系删除
Web Scraper
{
"_id": "top250",
"startUrl": ["https://movie.douban.com/top250?start=[0-225:25]&filter="],
"selectors": [{
"id": "container",
"multiple": true,
"parentSelectors": ["_root"],
"selector": ".grid_view li",
"type": "SelectorElement"
}, {
"id": "name",
"multiple": false,
"parentSelectors": ["container"],
"regex": "",
"selector": "span.title:nth-of-type(1)",
"type": "SelectorText"
}, {
"id": "number",
"multiple": false,
"parentSelectors": ["container"],
"regex": "",
"selector": "em",
"type": "SelectorText"
}, {
"id": "score",
"multiple": false,
"parentSelectors": ["container"],
"regex": "",
"selector": "span.rating_num",
"type": "SelectorText"
}, {
"id": "review",
"multiple": false,
"parentSelectors": ["container"],
"regex": "",
"selector": "span.inq",
"type": "SelectorText"
}, {
"id": "year",
"multiple": false,
"parentSelectors": ["container"],
"regex": "\\d{4}",
"selector": "p:nth-of-type(1)",
"type": "SelectorText"
}, {
"id": "tour_guide",
"multiple": false,
"parentSelectors": ["container"],
"regex": "^导演: \\S*",
"selector": "p:nth-of-type(1)",
"type": "SelectorText"
}, {
"id": "type",
"multiple": false,
"parentSelectors": ["container"],
"regex": "[^/]+$",
"selector": "p:nth-of-type(1)",
"type": "SelectorText"
}, {
"id": "area",
"multiple": false,
"parentSelectors": ["container"],
"regex": "[^\\/]+(?=\\/[^\\/]*$)",
"selector": "p:nth-of-type(1)",
"type": "SelectorText"
}, {
"id": "detail_link",
"multiple": false,
"parentSelectors": ["container"],
"selector": ".hd a",
"type": "SelectorLink"
}, {
"id": "director",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "",
"selector": "span:nth-of-type(1) .attrs a",
"type": "SelectorText"
}, {
"id": "screenwriter",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "(?<=编剧: )[\\u4e00-\\u9fa5A-Za-z0-9/()\\·\\s]+(?=主演)",
"selector": "div#info",
"type": "SelectorText"
}, {
"id": "film_length",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "\\d+",
"selector": "span[property='v:runtime']",
"type": "SelectorText"
}, {
"id": "IMDb",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "(?<=[IMDb:\\s+])\\S*(?=\\d*$)",
"selector": "div#info",
"type": "SelectorText"
}, {
"id": "language",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "(?<=语言: )\\S+",
"selector": "div#info",
"type": "SelectorText"
}, {
"id": "alias",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "(?<=又名: )[\\u4e00-\\u9fa5A-Za-z0-9/()\\s]+(?=IMDb)",
"selector": "div#info",
"type": "SelectorText"
}, {
"id": "pic",
"multiple": false,
"parentSelectors": ["container"],
"selector": "img",
"type": "SelectorImage"
}]
}
elasticsearch
{
"mappings": {
"properties": {
"IMDb": {
"type": "keyword",
"copy_to": [
"all"
]
},
"alias": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"all": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"area": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"director": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"film_length": {
"type": "long"
},
"id": {
"type": "keyword"
},
"language": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"link": {
"type": "keyword"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"number": {
"type": "long"
},
"photo": {
"type": "keyword"
},
"review": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"score": {
"type": "double"
},
"screenwriter": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"year": {
"type": "long"
}
}
}
}
kibana
需要使用pipeline对索引字段进行处理,如对type 通过空格进行分割为数组等,可以参照官方文档或其他博客。
制作仪表板省略, 请自行搜索
SearchKit
elasticsearch es如何统计用户文档数量范围内容聚合?
Elasticsearch • Charele 回复了问题 • 5 人关注 • 3 个回复 • 2279 次浏览 • 2022-06-01 13:42
es nested 根据多条件排除数据
Elasticsearch • dachuxing 回复了问题 • 3 人关注 • 2 个回复 • 2743 次浏览 • 2022-05-20 17:36
Elasticsearch 分片分配失败
Elasticsearch • Charele 回复了问题 • 2 人关注 • 1 个回复 • 1640 次浏览 • 2022-05-07 20:11
logstash推送数据到es执行两次脚本
Logstash • medcl 回复了问题 • 2 人关注 • 1 个回复 • 2138 次浏览 • 2022-04-02 13:28
elasticsearch [[node.max_local_storage_nodes] (was [1])
Elasticsearch • yimusidian 回复了问题 • 2 人关注 • 2 个回复 • 3044 次浏览 • 2022-03-25 16:07
elasticsearch 分索引后如何快速更新指定数据?
Elasticsearch • liujiacheng 回复了问题 • 2 人关注 • 1 个回复 • 1129 次浏览 • 2022-03-24 09:41
elasticsearch IllegalArgumentException[Operation term is newer than the current term; current term[2
Elasticsearch • locatelli 回复了问题 • 2 人关注 • 1 个回复 • 1371 次浏览 • 2022-02-15 11:17
给Zblogphp插上Elasticsearch的翅膀
Elasticsearch • 森 发表了文章 • 0 个评论 • 1446 次浏览 • 2021-10-11 23:00
通过python脚本迁移ES的template模板
Elasticsearch • 森 发表了文章 • 0 个评论 • 1999 次浏览 • 2021-09-30 09:30
通过python脚本迁移ES的template模板
通过python脚本迁移ES的template模板,从192.168.0.1 迁移到 192.168.0.2
import base64
import json
import requests
def putTemplate(templateName, templateDslJson):
print("{0} 索引模板正在迁移中".format(templateName))
res = requests.put("http://192.168.0.2:9200/_template/{0}".format(templateName), json=templateDslJson)
print(res.status_code)
print(res.content)
def getTemplateDslJson():
username = "elastic"
password = "123456"
user_info_str = username + ":" + password
user_info = base64.b64encode(user_info_str.encode()) # 这个得到是个字节类型的数据
headers = {
"Authorization": "Basic {0}".format(user_info.decode()) # 这个就是需要验证的信息
}
url = "http://192.168.0.1:9200/_template/*_template"
res = requests.get(url, headers=headers)
print(res.status_code)
return json.loads(res.content)
if __name__ == '__main__':
jsonTemplate = getTemplateDslJson()
if isinstance(jsonTemplate, dict):
for templateName in jsonTemplate:
templateDslJson = jsonTemplate[templateName]
putTemplate(templateName, templateDslJson)
关于elasticsearch 5.4版本高亮查询不生效的问题
Elasticsearch • tongchuan1992 回复了问题 • 2 人关注 • 1 个回复 • 1319 次浏览 • 2021-08-22 14:51
elasticsearch query与agg分开是否可以提高性能?
Elasticsearch • tongchuan1992 回复了问题 • 2 人关注 • 1 个回复 • 1707 次浏览 • 2021-07-08 09:24
1. 在mapping里指定search_analyzer,例如 PUT my_index
{
"mappings": {
"doc": {
"prope... 显示全部 »
1. 在mapping里指定search_analyzer,例如 PUT my_index
{
"mappings": {
"doc": {
"properties": {
"uid": {
"type": "keyword"
},
"name": {
"type": "text",
"analyzer": "english",
"search_analyzer": "standard"
}
}
}
}
}
2.使用URL Search的时候,指定analyzer参数 ,文档参考: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html , 对应的python代码范例: >>> es.search(index="my_index", analyzer="standard", q='name:"mark AND johnson"') 要注意的是,这里的analyzer只能和q这个参数搭配使用。 你的代码报错,是因为用的body参数,这个参数是没有analyzer参数搭配的。
3.使用Request Body Search,文档参考: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html ,对应的python代码范例: >>> dsl='{"query": {"match": {"name": {"query": "mark","analyzer": "standard"}}}}'
>>> es.search(index="my_index", body=dsl) 注意这个时候,analyzer是写到dsl里面的match query。
或者先看下词库,执行一下_analyze,看看能拆分成什么词(比如,拆分出来有“王”,那就通过“王”可以查到)
如果是ik分词器,如下,其他分词器改一下参数就行
GET _analyze
{
"a... 显示全部 »
或者先看下词库,执行一下_analyze,看看能拆分成什么词(比如,拆分出来有“王”,那就通过“王”可以查到)
如果是ik分词器,如下,其他分词器改一下参数就行
GET _analyze
{
"analyzer": "ik_smart"/“ik_max_word”,
"text": "王者三国杀"
}
【线下活动-分享主题征集-武汉】 2019年3月 Elastic&尚德机构技术沙龙
活动 • medcl 回复了问题 • 3 人关注 • 1 个回复 • 5725 次浏览 • 2019-02-22 15:42
请问基于es数组进行query_string查询时,如何在highlight中返回最佳匹配的数据?
回复Elasticsearch • Ombres 回复了问题 • 2 人关注 • 2 个回复 • 2297 次浏览 • 2024-01-17 13:37
elasticsearch 缺乏足够的无分段虚拟地址空间,导致集群故障,请问有什么优化方案吗
回复Elasticsearch • FFFrp 回复了问题 • 3 人关注 • 2 个回复 • 1075 次浏览 • 2023-08-25 10:48
请教一个问题,ES must_not 多条件查询时不符合预期
回复Elasticsearch • Charele 回复了问题 • 2 人关注 • 1 个回复 • 1011 次浏览 • 2023-08-10 20:11
elasticsearch es如何统计用户文档数量范围内容聚合?
回复Elasticsearch • Charele 回复了问题 • 5 人关注 • 3 个回复 • 2279 次浏览 • 2022-06-01 13:42
es nested 根据多条件排除数据
回复Elasticsearch • dachuxing 回复了问题 • 3 人关注 • 2 个回复 • 2743 次浏览 • 2022-05-20 17:36
elasticsearch [[node.max_local_storage_nodes] (was [1])
回复Elasticsearch • yimusidian 回复了问题 • 2 人关注 • 2 个回复 • 3044 次浏览 • 2022-03-25 16:07
elasticsearch 分索引后如何快速更新指定数据?
回复Elasticsearch • liujiacheng 回复了问题 • 2 人关注 • 1 个回复 • 1129 次浏览 • 2022-03-24 09:41
elasticsearch IllegalArgumentException[Operation term is newer than the current term; current term[2
回复Elasticsearch • locatelli 回复了问题 • 2 人关注 • 1 个回复 • 1371 次浏览 • 2022-02-15 11:17
关于elasticsearch 5.4版本高亮查询不生效的问题
回复Elasticsearch • tongchuan1992 回复了问题 • 2 人关注 • 1 个回复 • 1319 次浏览 • 2021-08-22 14:51
elasticsearch query与agg分开是否可以提高性能?
回复Elasticsearch • tongchuan1992 回复了问题 • 2 人关注 • 1 个回复 • 1707 次浏览 • 2021-07-08 09:24
elasticsearch如何使用script(python)的复杂语句?
回复Elasticsearch • Charele 回复了问题 • 3 人关注 • 3 个回复 • 3550 次浏览 • 2021-07-02 14:57
elasticsearch中match无法进行匹配查询(匹配查询match查不结果)
回复Elasticsearch • guoyanbiao520 回复了问题 • 5 人关注 • 4 个回复 • 6954 次浏览 • 2021-05-21 15:50
Web Scraper + Elasticsearch + Kibana + SearchKit 打造的豆瓣电影top250 搜索演示系统
Elasticsearch • 森 发表了文章 • 0 个评论 • 4956 次浏览 • 2023-04-09 10:56
Web Scraper + Elasticsearch + Kibana + SearchKit 打造的豆瓣电影top250 搜索演示系统
作者:小森同学
声明:电影数据来源于“豆瓣电影”,如有侵权,请联系删除
Web Scraper
{
"_id": "top250",
"startUrl": ["https://movie.douban.com/top250?start=[0-225:25]&filter="],
"selectors": [{
"id": "container",
"multiple": true,
"parentSelectors": ["_root"],
"selector": ".grid_view li",
"type": "SelectorElement"
}, {
"id": "name",
"multiple": false,
"parentSelectors": ["container"],
"regex": "",
"selector": "span.title:nth-of-type(1)",
"type": "SelectorText"
}, {
"id": "number",
"multiple": false,
"parentSelectors": ["container"],
"regex": "",
"selector": "em",
"type": "SelectorText"
}, {
"id": "score",
"multiple": false,
"parentSelectors": ["container"],
"regex": "",
"selector": "span.rating_num",
"type": "SelectorText"
}, {
"id": "review",
"multiple": false,
"parentSelectors": ["container"],
"regex": "",
"selector": "span.inq",
"type": "SelectorText"
}, {
"id": "year",
"multiple": false,
"parentSelectors": ["container"],
"regex": "\\d{4}",
"selector": "p:nth-of-type(1)",
"type": "SelectorText"
}, {
"id": "tour_guide",
"multiple": false,
"parentSelectors": ["container"],
"regex": "^导演: \\S*",
"selector": "p:nth-of-type(1)",
"type": "SelectorText"
}, {
"id": "type",
"multiple": false,
"parentSelectors": ["container"],
"regex": "[^/]+$",
"selector": "p:nth-of-type(1)",
"type": "SelectorText"
}, {
"id": "area",
"multiple": false,
"parentSelectors": ["container"],
"regex": "[^\\/]+(?=\\/[^\\/]*$)",
"selector": "p:nth-of-type(1)",
"type": "SelectorText"
}, {
"id": "detail_link",
"multiple": false,
"parentSelectors": ["container"],
"selector": ".hd a",
"type": "SelectorLink"
}, {
"id": "director",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "",
"selector": "span:nth-of-type(1) .attrs a",
"type": "SelectorText"
}, {
"id": "screenwriter",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "(?<=编剧: )[\\u4e00-\\u9fa5A-Za-z0-9/()\\·\\s]+(?=主演)",
"selector": "div#info",
"type": "SelectorText"
}, {
"id": "film_length",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "\\d+",
"selector": "span[property='v:runtime']",
"type": "SelectorText"
}, {
"id": "IMDb",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "(?<=[IMDb:\\s+])\\S*(?=\\d*$)",
"selector": "div#info",
"type": "SelectorText"
}, {
"id": "language",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "(?<=语言: )\\S+",
"selector": "div#info",
"type": "SelectorText"
}, {
"id": "alias",
"multiple": false,
"parentSelectors": ["detail_link"],
"regex": "(?<=又名: )[\\u4e00-\\u9fa5A-Za-z0-9/()\\s]+(?=IMDb)",
"selector": "div#info",
"type": "SelectorText"
}, {
"id": "pic",
"multiple": false,
"parentSelectors": ["container"],
"selector": "img",
"type": "SelectorImage"
}]
}
elasticsearch
{
"mappings": {
"properties": {
"IMDb": {
"type": "keyword",
"copy_to": [
"all"
]
},
"alias": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"all": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"area": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"director": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"film_length": {
"type": "long"
},
"id": {
"type": "keyword"
},
"language": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"link": {
"type": "keyword"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"number": {
"type": "long"
},
"photo": {
"type": "keyword"
},
"review": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"score": {
"type": "double"
},
"screenwriter": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"copy_to": [
"all"
],
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"year": {
"type": "long"
}
}
}
}
kibana
需要使用pipeline对索引字段进行处理,如对type 通过空格进行分割为数组等,可以参照官方文档或其他博客。
制作仪表板省略, 请自行搜索
SearchKit
给Zblogphp插上Elasticsearch的翅膀
Elasticsearch • 森 发表了文章 • 0 个评论 • 1446 次浏览 • 2021-10-11 23:00
通过python脚本迁移ES的template模板
Elasticsearch • 森 发表了文章 • 0 个评论 • 1999 次浏览 • 2021-09-30 09:30
通过python脚本迁移ES的template模板
通过python脚本迁移ES的template模板,从192.168.0.1 迁移到 192.168.0.2
import base64
import json
import requests
def putTemplate(templateName, templateDslJson):
print("{0} 索引模板正在迁移中".format(templateName))
res = requests.put("http://192.168.0.2:9200/_template/{0}".format(templateName), json=templateDslJson)
print(res.status_code)
print(res.content)
def getTemplateDslJson():
username = "elastic"
password = "123456"
user_info_str = username + ":" + password
user_info = base64.b64encode(user_info_str.encode()) # 这个得到是个字节类型的数据
headers = {
"Authorization": "Basic {0}".format(user_info.decode()) # 这个就是需要验证的信息
}
url = "http://192.168.0.1:9200/_template/*_template"
res = requests.get(url, headers=headers)
print(res.status_code)
return json.loads(res.content)
if __name__ == '__main__':
jsonTemplate = getTemplateDslJson()
if isinstance(jsonTemplate, dict):
for templateName in jsonTemplate:
templateDslJson = jsonTemplate[templateName]
putTemplate(templateName, templateDslJson)
Elasticsearch索引拆分方案
Elasticsearch • 森 发表了文章 • 0 个评论 • 10393 次浏览 • 2020-10-19 09:08
Elasticsearch索引拆分方案
[TOC]
一、概况
项目中,由于Elasticsearch单个索引数据量大,索引中部分数据不常用,在搜索和写入文档时,效率较低。为了减小单个索引的数据量,提升搜索和文档写入效率,将大索引根据一定的规则拆分为小的索引。拆分索引的关键点在于建立索引,文档同步,多索引搜索。
建立索引的关键问题是索引的设置以及字段的属性设置,最常见的问题是,某个字段我们希望Elasticsearch 按照我们的想法进行分词。采用自动生成索引(默认模板),索引字段的类型就会根据第一条文档的数据进行字段转换,无法实现具体某个字段使用我们想要的分词方式。另外就是无法使用自定义分词器,索引的默认分片数为5,无法根据我们制定的分片数进行分配。
为了实现我们这种自动创建索引的特殊要求,Elasticsearch也提供了索引模板API。
索引模板,就是创建索引的模板,模板中包含公共的配置(Settings)和映射(Mappings),并包含一个简单触发条件,及条件满足时使用该模板创建一个新的索引。
模板只在创建索引时应用。更改模板不会对现有索引产生影响。当使用create index API时,作为create index调用的一部分定义的设置/映射将优先于模板中定义的任何匹配设置/映射。
文档同步和搜索,我们都采用了别名的形式。索引别名,就像一个快捷方式或软连接,可以指向一个或多个索引,也可以给任何一个需要索引名的API来使用,别名不能与索引具有相同的名称。别名带给我们极大的灵活性,允许我们在运行的集群中可以无缝的从一个索引切换到另一个索引,给多个索引分组 ,给索引的一个子集创建。因为使用别名,你的应用可以在零停机的情况下从旧索引迁移到新索引。
由于文档同步,必须指定一个唯一的索引才能成功。原来单索引时,我们的索引采取了 “索引名称_v1”的形式,为方便在零停机的情况下重建索引,文档更新也新建了一个专门的索引别名。 拆分索引后,索引名称规范为“索引名称_YYMM”按月拆分(包括但不限于此种方式),就会出现多个索引,此时就不在方便新增专门的索引别名用于文档更新,反而用索引名字直接进行文档更新,就会更加的方便,直接和准确。
文档同步使用索引名称,搜索依旧使用别名的形式。多个索引,有相同的别名,索引拆分,文档分属不同的索引,但因为有相同的别名,使用别名搜索时,依然可以将数据搜索出来。
通过建立索引,文档同步,多索引搜索实现了单索引到多索引的拆分。数据还是那些数据,依然能搜索出来,索引数变多了,每个索引的数据减少,同步文档速度就可以提高。搜索也可以根据业务需求只查询部分索引,提升了查询速度,也可以查询所有数据,根据实际场景可自由变换。
二、索引拆分规则
索引拆分,可以根据创建时间拆分,如:”索引名称_yyyyMM“,”索引名称_yyyy“;也可以根据主键ID求余的方式来进行拆分,如:”索引名称_0“,”索引名称_1“。
具体的拆分规则根据业务需要进行,需要注意的是,无论根据创建时间还是根据主键ID求余来拆分,都要求根据拆分的值,是文档中不变的值,才能唯一确定一个索引,进行文档的存储,如:主键ID,创建时间;不可为变化的值,有可能变化的值,就无法唯一确定一个索引进行文档存储,如:状态,那就会出现当前在这个索引,状态改变后再另外的索引,这样每个索引都有同一条状态不同的数据,搜索时就会不准确。
本文将根据创建时间进行索引拆分。
思路:
- 创建索引模板
- 同步文档时,选用的索引名称以"索引名称_yyyyMM"命名,自动创建带别名的索引
- 如果文档同步到新索引,原索引中的文档需删除
三、创建索引模板
以商品评论索引为例,将单索引拆分为多索引,根据以下规则,在同步文档时,如果无索引会字段根据模板生成:
- 索引名称的规则“goods_comment_202010”
- 索引别名为“goods_comment”
- number_of_shards分片数为3
- 配置Settings
- 定义Mappings字段及其类型
具体模板如下所示:
{
"order" : 0,
"index_patterns" : [
"goods_comment*"
],
"settings" : {
"index" : {
"max_result_window" : "100000",
"analysis" : {
"filter" : {
"by_stop_filter" : {
"type" : "stop",
"stopwords" : [
" "
]
},
"pinyin_first_letter_and_full_pinyin_filter" : {
"keep_none_chinese_in_first_letter" : "true",
"lowercase" : "true",
"keep_original" : "true",
"keep_first_letter" : "true",
"trim_whitespace" : "true",
"type" : "pinyin",
"keep_none_chinese" : "true",
"limit_first_letter_length" : "16",
"keep_full_pinyin" : "true"
},
"by_synonym_filter" : {
"type" : "synonym",
"synonyms_path" : "analysis/synonym.txt"
},
"full_pinyin_filter" : {
"keep_none_chinese_in_first_letter" : "true",
"lowercase" : "true",
"keep_original" : "true",
"keep_first_letter" : "false",
"trim_whitespace" : "true",
"type" : "pinyin",
"keep_none_chinese" : "true",
"limit_first_letter_length" : "16",
"keep_full_pinyin" : "true"
}
},
"char_filter" : {
"by_char_filter" : {
"type" : "mapping",
"mappings" : [
"| => |"
]
}
},
"analyzer" : {
"by_max_word" : {
"filter" : [
"by_synonym_filter",
"lowercase"
],
"char_filter" : [
"html_strip"
],
"type" : "custom",
"tokenizer" : "ik_max_word"
}
},
"tokenizer" : {
"my_pinyin" : {
"lowercase" : "true",
"keep_original" : "true",
"remove_duplicated_term" : "true",
"keep_separate_first_letter" : "false",
"type" : "pinyin",
"limit_first_letter_length" : "16",
"keep_full_pinyin" : "true"
}
}
},
"number_of_shards" : "3",
"number_of_replicas" : "1"
}
},
"mappings" : {
"_doc" : {
"properties" : {
"is_img" : {
"type" : "integer"
},
"gid" : {
"type" : "integer"
},
"pubtime" : {
"type" : "integer"
}
....
}
}
},
"aliases" : {
"goods_comment" : { }
}
}
上述模板定义,看似复杂,拆分来看,主要为如下几个部分:
{
"order": 0, // 模板优先级
"index_patterns": ["goods_comment*"],// 模板匹配的名称方式
"settings": {...}, // 索引设置
"mappings": {...}, // 索引中各字段的映射定义
"aliases": {...} // 索引的别名
}
3.1 模板优先级
有时候,一个模板可能绝大部分符合新建索引的需求,但是局部需要微调,此时,如果复制旧的模板,修改该模板后,成为一个新的索引模板即可达到我们的需求,但是这操作略显重复。此时,可以采用模板叠加与覆盖来操作。模板的优先级是通过模板中的 order 字段定义的,数字越大,优先级越高。 如下为定义所有以 te 开头的索引的模板:
{
"order": 0,
"index_patterns": "te*",
"settings": {
"number_of_shards": 1
},
"mappings": {
"type1": {
"_source": {
"enabled": false
}
}
}
}
索引模板是有序合并的。如何想单独修改某一小类索引的一两处单独设置,可以在累加一层模板。
{
"order": 1,
"index_patterns": "tete*",
"settings": {
"number_of_shards": 2
},
"mappings": {
"type1": {
"_all": {
"enabled": false
}
}
}
}
上述第一个模板的 order 为0,第二个模板的 order 为1,优先级高于第一个模板,其会覆盖第一个模板中的相同项。所以对于所有以 tete 开头的索引模板效果如下:
{
"settings": {
"number_of_shards": 2
},
"mappings": {
"type1": {
"_source": {
"enabled": false
},
"_all": {
"enabled": false
}
}
}
}
两个模板叠加了,项目的字段,优先级高的覆盖了优先级低的,如分片数。
3.2 模板匹配的名称
索引模板中的 "index_patterns" 字段定义的是该索引模板所应用的索引情况。如 "index_patterns": "tete*" 所表示的含义是,当新建索引时,所有以 tete 开头的索引都会自动匹配到该索引模板。利用该模板进行相应的设置和字段添加等。
3.3 settings 部分
索引模板中的 settings 部分一般定义的是索引的主分片、拷贝分片、刷新时间、自定义分析器等。常见的 settings 部分结构如下:
"settings": {
"index": {
"analysis": {...}, // 自定义的分析器
"number_of_shards": "32", // 主分片的个数
"number_of_replicas": "1", // 主分片的拷贝分片个数
"refresh_interval": "5s" // 刷新时间
}
}
建立的索引,不会立马查到,这是为什么 Elasticsearch 为 near-real-time(接近实时)的原因,需要配置刷新时间,默认的是 1s。settings 的设置中,重点是自定义分析器的设置。
-
分析器是三个顺序执行的组件的结合。他们分别是字符过滤器、分词器、标记过滤器。字符过滤器是让字符串在被分词前变得更加整洁。一个分析器可能包含零到多个字符过滤器(character_filter)。
-
分词器将字符串分割成单独的词(terms)或标记(tokens)。一个分析器必须包含一个分词器。
- 分词器分词的结果的标记流会根据各自的情况,传递给特定的标记过滤器。标记过滤器可能修改、添加或删除标记。
创建的创建自定义分析器结构如下:
"settings": {
"index": {
"analysis": {
"char_filter": { ... }, // 用户自定义字符过滤器
"tokenizer": { ... }, // 用户自定义分词器
"filter": { ... }, // 用户自定义标记过滤器
"analyzer": { ... } // 用户自定义分析器
},
...
}
}
3.4 索引类型的字段映射
索引模板中,映射字段所对应的常用结构是:
"mappings": {
"_doc": { // 索引下的类型 _doc 应用该映射
"dynamic_templates": [ ... ], // 动态映射部分,用于未定义的 my_type 下字段
"properties": { ... } // 自定义字段的响应映射
}
}
"_doc" 是索引下的一个类型,Elasticsearch 7.x仅支持"_doc"作为索引类型,Elasticsearch 6.x推荐使用"_doc"为索引类型。
动态映射
动态映射 "dynamic_templates" 字段对应的是一个数组,数组中的元素是一个个字段的映射模板。每个字段的映射模板都有一个名字用户描述这个模板的用途,一个 mapping 字段由于指明这个映射如何使用,和至少一个参数(例如 match)来定义这个模板适用于哪个字段。 dynamic_templates 字段对应的字段模板结构如下:
{
"string_fields": { // 字段映射模板的名称,一般为"类型_fields"的命名方式
"match": "*", // 匹配的字段名为所有
"match_mapping_type": "string", // 限制匹配的字段类型,只能是 string 类型
"mapping": { ... } // 字段的处理方式
}
自定义字段映射
针对索引类型中存在的字段,除了可以采用动态模板的方式,还可以采用定义定义的方式,常见的自定义结构如下:
"mappings": {
"my_type": {
"dynamic_templates": [ ... ],
"properties": {
"user_city": { // 字段名
"analyzer": "lowercase_analyzer", // 字段分析器
"index": "analyzed", // 字段索引方式定义索引
"type": "string", // 字段数据类型定义为 string
"fields": { // 定义一个名为 user_city.raw 的嵌入的不分析字段
"raw": {
"ignore_above": 512,
"index": "not_analyzed",
"type": "string"
}
}
},
"money":{
"type": "double",
"doc_values": true
}
...
}
}
}
3.5 别名
即使你认为现在的索引设计已经是完美的了,当你的应用在生产环境使用时,还是有可能在今后有一些改变的。所以请做好准备:在应用中使用别名而不是索引。然后你就可以在任何时候重建索引。别名的开销很小,应当广泛使用。利用索引别名,可以实现零停机时间重新索引。 定义方式如下:
{
"order": 0, // 模板优先级
"index_patterns": "goods_comment*", // 模板匹配的名称方式
"settings": {...}, // 索引设置
"mappings": {...}, // 索引中各字段的映射定义
"aliases": {
"goods_comment":{}
}
}
以上只是简单的介绍了索引模板和模板内的组成部分的介绍,详情请见Elasticsearch官方文档。
有了以上的知识,我们就可以利用索引模板的API,来对模板进行创建,查询,删除操作了。
3.6 索引模板管理
创建索引模板
PUT _template/goods_comment_template
{
"order": 0, // 模板优先级
"index_patterns": "goods_comment*", // 模板匹配的名称方式
"settings": {...}, // 索引设置
"mappings": {...}, // 索引中各字段的映射定义
"aliases": {
"goods_comment":{}
}
}
查看索引模板
GET _template // 查看所有模板
GET _template/temp* // 查看与通配符相匹配的模板
GET _template/temp1,temp2 // 查看多个模板
GET _template/shop_template // 查看指定模板
判断模板是否存在
HEAD _template/shop_tem
结果: a) 如果存在, 响应结果是: 200 - OK b) 如果不存在, 响应结果是: 404 - Not Found
删除索引模板
DELETE _template/shop_template // 删除上述创建的模板
如果模板不存在, 将抛出404 错误
四、同步文档,自动创建索引
前面创建了商品评论的索引模板(goods_comment_template),同步文档时,指定索引名称为“goods_comment_202010”,如果索引不存在,便会创建名为“goods_comment_202010”的索引,同时创建好“goods_comment”别名。索引的settings和mappings都会根据模板定义的规则生成好。索引创建成功,此时该索引便能正常使用啦。
商品评论业务中,同步文档是在代码中实现,需要根据商品评论的创建时间,以“goods_comment_yyyyMM”的形式获取完整的索引名称(如:goods_comment_202010),同步文档指定goods_comment_202010,即可将数据同步到该索引。
五、别名搜索
多个商品评论索引,每个索引都有“goods_comment“别名,使用别名进行搜索,便能从这多个索引中获取数据。
同理,其他业务索引实现搜索,都要求使用别名形式。
六、可能存在的问题点
索引创建后,并不是一成不变的,随着业务的发展,新增字段也是较常见的。原来单索引,新增一个字段,只需要在mappings新增字段,重建索引,迁移数据,切换别名即可。拆分后的多索引,工作量便会成被增加。
修改索引模板,只会对后续生成的索引有作用,之前生成的索引,如需调整,需要手动或者使用脚本的形式进行重建并迁移数据。
七、附录
demo演示,也是体验索引拆分的一个实现过程。
7.1 查询索引模板列表
查看ES中的所有索引模板列表
命令:
GET _cat/templates?v
结果:
name index_patterns order version
kibana_index_template:.kibana [.kibana] 0
.monitoring-kibana [.monitoring-kibana-6-*] 0 6050399
.management-beats [.management-beats] 0 67000
7.2 创建索引模板
命令:
PUT _template/demo_template
{
"order": 0,
"index_patterns": [
"demo*"
],
"settings": {
"index": {
"number_of_shards": 2,
"number_of_replicas": 0,
"max_result_window": 100000
}
},
"aliases": {
"demo": {}
}
}
结果:
{
"acknowledged" : true
}
7.3 查看索引模板详情
命令:
GET _template/demo_template
结果:
{
"demo_template" : {
"order" : 0,
"index_patterns" : [
"demo*"
],
"settings" : {
"index" : {
"max_result_window" : "100000",
"number_of_shards" : "2",
"number_of_replicas" : "0"
}
},
"mappings" : { },
"aliases" : {
"demo" : { }
}
}
}
7.4 查询索引数据
命令:
GET demo_v1/_search
结果:
{
"error" : {
"root_cause" : [
{
"type" : "index_not_found_exception",
"reason" : "no such index",
"resource.type" : "index_or_alias",
"resource.id" : "demo_v1",
"index_uuid" : "_na_",
"index" : "demo_v1"
}
],
"type" : "index_not_found_exception",
"reason" : "no such index",
"resource.type" : "index_or_alias",
"resource.id" : "demo_v1",
"index_uuid" : "_na_",
"index" : "demo_v1"
},
"status" : 404
}
7.5 创建文档
在此之前demo_v1索引不存在,通过创建文档,自动生成索引,新创建的demo_v1将根据demo_template索引模板生成。
命令:
POST demo_v1/_doc
{
"id": 1,
"title": "这是一条数据"
}
结果:
{
"_index" : "demo_v1",
"_type" : "_doc",
"_id" : "20upIHUBO6Fj2CIJUFPr",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 1,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
查看数据
GET demo_v1/_search 用索引名称进行查询
GET demo/_search 用别名进行查询
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 2,
"successful" : 2,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.0,
"hits" : [
{
"_index" : "demo_v1",
"_type" : "_doc",
"_id" : "20upIHUBO6Fj2CIJUFPr",
"_score" : 1.0,
"_source" : {
"id" : 1,
"title" : "这是一条数据"
}
}
]
}
}
发现使用索引名称和别名都能搜索出来。但是我们并未单独创建索引别名。我们来查看一下demo_v1索引的结构。
GET demo_v1
{
"demo_v1" : {
"aliases" : {
"demo" : { }
},
"mappings" : {
"_doc" : {
"properties" : {
"id" : {
"type" : "long"
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
},
"settings" : {
"index" : {
"number_of_shards" : "2",
"provided_name" : "demo_v1",
"max_result_window" : "100000",
"creation_date" : "1602570768526",
"number_of_replicas" : "0",
"uuid" : "WrXtDB5eRzmU-xX1vAUCrA",
"version" : {
"created" : "6070099"
}
}
}
}
}
我们可以看到,demo_v1 索引中的数据:
- 分片数(number_of_shards): 2
- 副本(number_of_replicas): 0
- 别名(aliases):demo
- 最大结果窗口(max_result_window):100000
这些都是我们在demo_template模板中设置的,在自动创建索引时,根据索引模板的index_patterns值,只要我们的索引名称是以“demo”为前缀,都会根据该模板生成索引。因此,无论是demo_v1,还是demo_v2,只要是以“demo”为前缀,直接创建文档,如果不存在索引,ES也会自动给我们创建以“demo_template”为模板的索引。实现索引拆分最关键的点,就在于索引模板。
同样,我们通过创建文档,来生成一个没有索引模板的索引进行对比。
查询demo
GET demo/_search
确定demo索引不存在
{
"error" : {
"root_cause" : [
{
"type" : "index_not_found_exception",
"reason" : "no such index",
"resource.type" : "index_or_alias",
"resource.id" : "demo",
"index_uuid" : "_na_",
"index" : "demo"
}
],
"type" : "index_not_found_exception",
"reason" : "no such index",
"resource.type" : "index_or_alias",
"resource.id" : "demo",
"index_uuid" : "_na_",
"index" : "demo"
},
"status" : 404
}
创建一条文档
POST demo/_doc
{
"id": 1,
"title": "这是一条数据"
}
创建成功
{
"_index" : "demo",
"_type" : "_doc",
"_id" : "PmXEIHUBwM4PCvJbG7Xw",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
查看数据
GET demo/_search
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 2,
"successful" : 2,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.0,
"hits" : [
{
"_index" : "demo",
"_type" : "_doc",
"_id" : "PmXEIHUBwM4PCvJbG7Xw",
"_score" : 1.0,
"_source" : {
"id" : 1,
"title" : "这是一条数据"
}
}
]
}
}
数据同步成功,索引也因此创建完成,我们来看看这个索引结构
GET demo
{
"demo" : {
"aliases" : { },
"mappings" : {
"_doc" : {
"properties" : {
"id" : {
"type" : "long"
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
},
"settings" : {
"index" : {
"number_of_shards" : "2",
"provided_name" : "demo",
"creation_date" : "1602572524390",
"number_of_replicas" : "1",
"uuid" : "p8kNddGzQzWOaz5xLcSWhA",
"version" : {
"created" : "6070099"
}
}
}
}
}
我们可以看到,demo索引中的数据:
- 分片数(number_of_shards): 2
- 副本(number_of_replicas): 1
- 别名(aliases):无
- 最大结果窗口(max_result_window):无
为了直观比较,请看下表:
有索引模板(demo_v1) | 无索引模板(demo) | |
---|---|---|
number_of_shards | 2 | 2 |
number_of_replicas | 0 | 1 |
aliases | demo | 无 |
max_result_window | 10w | 无,默认是1w |
上表可知,通过索引模板的创建的索引,有利于我们更好的掌控索引的结构。
通过demo演示,我们可以进一步的理解索引拆分的一个过程及其实现原理,重点在索引模板。
八、参考
类比mysql查询,适合新手学习Elasticsearch的DSL查询语句
Elasticsearch • 森 发表了文章 • 0 个评论 • 7126 次浏览 • 2020-04-29 10:44
Mysql查询与Elasticsearch的DSL查询语句对照
作者:
小森同学,互联网公司搜索开发工程师。
前言
作为新入门的后端开发人员,一般对Mysql,SqlServer这类的关系型数据库或多或少都有了解。当入门Elasticsearch时,发现其DSL语句与关系型数据库的查询完全不一样,不再是那熟悉的语法,顿感门槛有点高。为了方便熟悉关系型数据库查询的同学,更加容易,快捷的理解并掌握DSL基础语法,本文将进行Mysql与DSL语句进行类比。
一、Mysql数据库与Elasticsearch的类比
关系型数据库(比如Mysql) | 非关系型数据库(Elasticsearch) |
---|---|
数据库 Database | 索引 Index |
表 Table | 类型 Type |
数据行 Row | 文档 Document |
数据列 Column | 字段 Field |
约束 Schema | 映射 Mapping |
二、Mysql查询语句与DSL查询类比
Mysql查询语句与Elasticsearch的DSL查询类比,主要通过mysql库中的search_lexicon表和es中的search_lexicon_v1索引进行比较。
2.1 search_lexicon 表结构
CREATE TABLE `search_lexicon` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
`keyword` varchar(50) NOT NULL DEFAULT '' COMMENT '关键词',
`keyword_crc32` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '关键词校验',
`search_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '类型',
`consumer_id` varchar(50) NOT NULL DEFAULT '' COMMENT '消费者ID',
`num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '文档数',
`views` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '搜索次数',
`state` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态 0 关闭 1 开启',
`is_del` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 正常 1 删除',
`createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '数据创建时间',
`updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '数据最后更新时间',
PRIMARY KEY (`id`),
KEY `idx_search_lexicon_views` (`views`),
KEY `idx_search_lexicon_updatetime` (`updatetime`) USING BTREE,
KEY `idx_search_lexicon_keyword_type` (`keyword_crc32`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='搜索词库';
2.2 search_lexicon_v1 索引结构
{
"search_lexicon_v1" : {
"mappings" : {
"_doc" : {
"properties" : {
"@timestamp" : {
"type" : "date"
},
"@version" : {
"type" : "long"
},
"consumer_id" : {
"type" : "keyword"
},
"createtime" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss||strict_date_optional_time||epoch_millis"
},
"id" : {
"type" : "integer"
},
"is_del" : {
"type" : "integer"
},
"keyword" : {
"type" : "text",
"fields" : {
"standard" : {
"type" : "text",
"analyzer" : "by_standard_no_synonym"
}
},
"analyzer" : "by_max_word_pinyin_no_synonym"
},
"num" : {
"type" : "long"
},
"search_type" : {
"type" : "integer"
},
"state" : {
"type" : "integer"
},
"updatetime" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss||strict_date_optional_time||epoch_millis"
},
"views" : {
"type" : "long"
}
}
}
}
}
}
2.3 查询语句对照
注意:dsl查询,每次默认展示10(size默认为10)条
以下的查询条件,是为了写查询而构造的,无任何实质性的意义,仅供mysql查询与dsl查询对比用
布尔查询支持的子查询类型共有四种,分别是:must,should,must_not和filter:
查询字句 | 说明 | 类型 |
---|---|---|
must | 文档必须符合must中所有的条件,会影响相关性得分 | 数组 |
should | 文档应该匹配should子句查询的一个或多个 | 数组 |
must_not | 文档必须不符合must_not 中的所有条件 | 数组 |
filter | 过滤器,文档必须匹配该过滤条件,跟must子句的唯一区别是,filter不影响查询的score ,会缓存 | 字典 |
A、查询所有数据
mysql
SELECT * FROM search_lexicon
dsl
GET search_lexicon/_search
{
}
或
GET search_lexicon/_search
{
"query": {
"match_all": {}
}
}
B、 查询一个条件且条件只有一个值(consumer_id=demo)的数据
mysql
SELECT * FROM search_lexicon WHERE consumer_id='demo'
dsl
GET search_lexicon/_search
{
"query": {
"bool": {
"filter": {
"term": {
"consumer_id": "demo"
}
}
}
}
}
或
GET search_lexicon/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"consumer_id": "demo"
}
}
]
}
}
}
两者的区别在于前一个filter是一个对象,filter中只能放一个条件,后者filter是一个数组,里面可以放多个对象(多个查询条件),后续都将按照第二种方式查询
C、 查询一个条件且条件有多个值(consumer_id的值为demo,demo2)的数据
mysql
SELECT * FROM search_lexicon WHERE consumer_id in('demo','demo2')
dsl
GET search_lexicon/_search
{
"query": {
"bool": {
"filter": [
{
"terms": {
"consumer_id": [
"demo",
"demo2"
]
}
}
]
}
}
}
D、 查询consumer_id=demo 且 state=1的数据
mysql
SELECT * FROM search_lexicon WHERE consumer_id ='demo' and state=1
dsl
GET search_lexicon/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"consumer_id": "demo"
}
},
{
"term": {
"state": 1
}
}
]
}
}
}
E、 查询consumer_id=demo , state=1 且 is_del<>1的数据
mysql
SELECT * FROM search_lexicon WHERE consumer_id ='demo' and state=1 and is_del <>1
dsl
GET search_lexicon/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"consumer_id": "demo"
}
},
{
"term": {
"state": 1
}
}
],
"must_not": [
{
"term": {
"is_del": {
"value": 1
}
}
}
]
}
}
}
F、查询Sconsumer_id ='demo' or (state=1 and is_del =0)的数据
mysql
SELECT * FROM search_lexicon WHERE consumer_id ='demo' or (state=1 and is_del =0)
dsl
GET search_lexicon/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"consumer_id": {
"value": "demo"
}
}
},
{
"bool": {
"filter": [
{
"term": {
"state": 1
}
},
{
"term": {
"is_del": 0
}
}
]
}
}
]
}
}
}
G、在F的基础上,查询指定字段
mysql
SELECT id,keyword,consumer_id,num,views,state,is_del FROM search_lexicon WHERE consumer_id ='demo' or (state=1 and is_del =0)
dsl
GET search_lexicon/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"consumer_id": {
"value": "demo"
}
}
},
{
"bool": {
"filter": [
{
"term": {
"state": 1
}
},
{
"term": {
"is_del": 0
}
}
]
}
}
]
}
},
"_source": {
"includes": [
"id",
"keyword",
"num",
"is_del",
"state",
"consumer_id",
"views"
]
}
}
H、在G的基础上,增加排序
mysql
SELECT id,keyword,consumer_id,num,views,state,is_del FROM search_lexicon WHERE consumer_id ='demo' or (state=1 and is_del =0) ORDER BY state DESC,id DESC
dsl
GET search_lexicon/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"consumer_id": {
"value": "demo"
}
}
},
{
"bool": {
"filter": [
{
"term": {
"state": 1
}
},
{
"term": {
"is_del": 0
}
}
]
}
}
]
}
},
"_source": {
"includes": [
"id",
"keyword",
"num",
"is_del",
"state",
"consumer_id",
"views"
]
},
"sort": [
{
"state": {
"order": "desc"
}
},
{
"id": {
"order": "desc"
}
}
]
}
I、在H的基础上,添加分页
mysql
SELECT id,keyword,consumer_id,num,views,state,is_del FROM search_lexicon WHERE consumer_id ='demo' or (state=1 and is_del =0) ORDER BY state DESC,id DESC LIMIT 0,20
dsl
GET search_lexicon/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"consumer_id": {
"value": "demo"
}
}
},
{
"bool": {
"filter": [
{
"term": {
"state": 1
}
},
{
"term": {
"is_del": 0
}
}
]
}
}
]
}
},
"_source": {
"includes": [
"id",
"keyword",
"num",
"is_del",
"state",
"consumer_id",
"views"
]
},
"sort": [
{
"state": {
"order": "desc"
}
},
{
"id": {
"order": "desc"
}
}
],
"from": 0,
"size": 20
}
# from 是一个偏移量,size为每页显示条数
J、去重查询
mysql
SELECT DISTINCT state FROM search_lexicon WHERE consumer_id = 'demo'
dsl
# 通过折叠去重查询
GET search_lexicon/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"consumer_id": {
"value": "demo"
}
}
}
]
}
},
"collapse": {
"field": "state"
}
}
K、分组查询
mysql
SELECT * FROM search_lexicon WHERE consumer_id = 'demo' GROUP BY state
dsl
GET search_lexicon/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"consumer_id": {
"value": "demo"
}
}
}
]
}
},
"size": 0,
"aggs": {
"aaa": {
"terms": {
"field": "state",
"size": 10
}
}
}
}
L、模糊匹配
mysql
SELECT * FROM search_lexicon WHERE consumer_id="demo" and keyword LIKE '%渴望%'
dsl
GET search_lexicon/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"consumer_id": {
"value": "demo"
}
}
}
],
"must": [
{
"match": {
"keyword": "渴望"
}
}
]
}
}
}
三、总结
Mysql查询与DSL查询对照,用心体会二者之间,上下文之间,各查询条件的差异与相似,快速掌握DSL的语法结构,You can do it!
声明:
本文版权归作者所有,未经许可不得擅自转载或引用。 原文地址:https://elasticsearch.cn/article/13760
使用 ES-Hadoop 将 Spark Streaming 流数据写入 ES
Elasticsearch • rochy 发表了文章 • 5 个评论 • 7373 次浏览 • 2019-01-06 00:55
本文将详细介绍利用 ES-Hadoop 将 Spark 处理的数据写入到 ES 中。
一、开发环境
1、组件版本
- CDH 集群版本:6.0.1
- Spark 版本:2.2.0
- Kafka 版本:1.0.1
- ES 版本:6.5.1
2、Maven 依赖
<!-- scala -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.8</version>
</dependency>
<!-- spark 基础依赖 -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.2.0</version>
</dependency>
<!-- spark-streaming 相关依赖 -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>2.2.0</version>
</dependency>
<!-- spark-streaming-kafka 相关依赖 -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
<version>2.2.0</version>
</dependency>
<!-- zookeeper 相关依赖 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.5-cdh6.0.1</version>
</dependency>
<!-- Spark-ES 相关依赖 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-spark-20_2.11</artifactId>
<version>6.5.4</version>
</dependency>
<!-- Spark-ES 依赖的 HTTP 传输组件 -->
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
3、注意事项
如果使用 CDH 版本的 Spark,则在调试及实际部署运行的时候会出现下面的错误:
java.lang.ClassNotFoundException: org.apache.commons.httpclient.protocol.Protocol
很显然是缺少 httpclient 相关依赖造成的,对比开源版本与 CDH 版本的 Spark,发现开源版本多出了 commons-httpclient-3.1.jar
,因此上述 Maven 的 pom 文件添加上对其依赖即可。
二、ES-Hadoop
1、简介
ES-Hadoop 实现了 Hadoop 生态(Hive、Spark、Pig、Storm 等)与 ElasticSearch 之间的数据交互,借助该组件可以将 Hadoop 生态的数据写入到 ES 中,然后借助 ES 对数据快速进行搜索、过滤、聚合等分析,进一步可以通过 Kibana 来实现数据的可视化。
同时,也可以借助 ES 作为数据存储层(类似数仓的 Stage 层或者 ODS 层),然后借助 Hadoop 生态的数据处理工具(Hive、MR、Spark 等)将处理后的数据写入到 HDFS 中。
使用 ES 做为原始数据的存储层,可以很好的进行数据去重、数据质量分析,还可以提供一些即时的数据服务,例如趋势展示、汇总分析等。
2、组成
ES-Hadoop 是一个整合性质的组件,它封装了 Hadoop 生态的多种组件与 ES 交互的 API,如果你只需要部分功能,可以使用细分的组件:
- elasticsearch-hadoop-mr
- elasticsearch-hadoop-hive
- elasticsearch-hadoop-pig
- elasticsearch-spark-20_2.10
- elasticsearch-hadoop-cascading
- elasticsearch-storm
三、elasticsearch-spark
1、配置
es-hadoop 核心是通过 es 提供的 restful 接口来进行数据交互,下面是几个重要配置项,更多配置信息请参阅官方说明:
es.nodes
:需要连接的 es 节点(不需要配置全部节点,默认会自动发现其他可用节点);es.port
:节点 http 通讯端口;es.nodes.discovery
:默认为 true,表示自动发现集群可用节点;es.nodes.wan.only
:默认为 false,设置为 true 之后,会关闭节点的自动 discovery,只使用es.nodes
声明的节点进行数据读写操作;如果你需要通过域名进行数据访问,则设置该选项为 true,否则请务必设置为 false;es.index.auto.create
:是否自动创建不存在的索引,默认为 true;es.net.http.auth.user
:Basic 认证的用户名;es.net.http.auth.pass
:Basic 认证的密码。
val conf = new SparkConf().setIfMissing("spark.app.name","rt-data-loader").setIfMissing("spark.master", "local[5]")
conf.set(ConfigurationOptions.ES_NODES, esNodes)
conf.set(ConfigurationOptions.ES_PORT, esPort)
conf.set(ConfigurationOptions.ES_NODES_WAN_ONLY, "true")
conf.set(ConfigurationOptions.ES_INDEX_AUTO_CREATE, "true")
conf.set(ConfigurationOptions.ES_NODES_DISCOVERY, "false")
conf.set(ConfigurationOptions.ES_NET_HTTP_AUTH_USER, esUser)
conf.set(ConfigurationOptions.ES_NET_HTTP_AUTH_PASS, esPwd)
conf.set("es.write.rest.error.handlers", "ignoreConflict")
conf.set("es.write.rest.error.handler.ignoreConflict", "com.jointsky.bigdata.handler.IgnoreConflictsHandler")
特别需要注意的配置项为 es.nodes.wan.only
,由于在云服务器环境中,配置文件使用的一般为内网地址,而本地调试的时候一般使用外网地址,这样将 es.nodes
配置为外网地址后,最后会出现节点找不到的问题(由于会使用节点配置的内网地址去进行连接):
org.elasticsearch.hadoop.EsHadoopIllegalArgumentException: No data nodes with HTTP-enabled available;
node discovery is disabled and none of nodes specified fit the criterion [xxx.xx.x.xx:9200]
此时将 es.nodes.wan.only
设置为 true 即可。推荐开发测试时使用域名,集群部署的时候将该选项置为 false。
2、屏蔽写入冲突
如果数据存在重复,写入 ES 时往往会出现数据写入冲突的错误,此时有两种解决方法。
方法一:设置 es.write.operation
为 upsert,这样达到的效果为如果存在则更新,不存在则进行插入,该配置项默认值为 index。
方法二:自定义冲突处理类,类似上述配置中设置了自定义的 error.handlers
,通过自定义类来处理相关错误,例如忽略冲突等:
public class IgnoreConflictsHandler extends BulkWriteErrorHandler {
public HandlerResult onError(BulkWriteFailure entry, DelayableErrorCollector<byte[]> collector) throws Exception {
if (entry.getResponseCode() == 409) {
StaticLog.warn("Encountered conflict response. Ignoring old data.");
return HandlerResult.HANDLED;
}
return collector.pass("Not a conflict response code.");
}
}
方法二可以屏蔽写入版本比预期的小之类的版本冲突问题。
3、RDD 写入 ES
EsSpark 提供了两种主要方法来实现数据写入:
saveToEs
:RDD 内容为Seq[Map]
,即一个 Map 对象集合,每个 Map 对应一个文档;saveJsonToEs
:RDD 内容为Seq[String]
,即一个 String 集合,每个 String 是一个 JSON 字符串,代表一条记录(对应 ES 的 _source)。
数据写入可以指定很多配置信息,例如:
es.resource
:设置写入的索引和类型,索引和类型名均支持动态变量;es.mapping.id
:设置文档 _id 对应的字段名;es.mapping.exclude
:设置写入时忽略的字段,支持通配符。
val itemRdd = rdd.flatMap(line => {
val topic = line.topic()
println("正在处理:" + topic + " - " + line.partition() + " : " + line.offset())
val jsonArray = JSON.parseArray(line.value()).toJavaList(classOf[JSONObject]).asScala
val resultMap = jsonArray.map(jsonObj =>{
var tmpId = "xxx"
var tmpIndex = "xxxxxx"
jsonObj.put("myTmpId", tmpId)
jsonObj.put("myTmpIndex", tmpIndex)
jsonObj.getInnerMap
})
resultMap
})
val mapConf = Map(
("es.resource" , "{myTmpIndex}/doc"),
("es.write.operation" , "upsert"),
("es.mapping.id" , "myTmpId"),
("es.mapping.exclude" , "myTmp*")
)
EsSpark.saveToEs(itemRdd, mapConf)
es.mapping.exclude
只支持 RDD 为 Map 集合(saveToEs),当为 Json 字符串集合时(saveJsonToEs)会提示不支持的错误信息;这个配置项非常有用,例如 myTmpId 作为文档 id,因此没有必要重复存储到 _source 里面了,可以配置到这个配置项,将其从 _source 中排除。
Any Code,Code Any!
扫码关注『AnyCode』,编程路上,一起前行。
Day 23 - 基于 HanLP 的 ES 中文分词插件
Advent • rochy 发表了文章 • 0 个评论 • 6815 次浏览 • 2018-12-23 11:51
一、分词插件
1、分词器概念
在 ES 中,分词器的作用是从文本中提取出若干词元(token)来支持索引的存储和搜索,分词器(Analyzer)由一个分解器(Tokenizer)、零个或多个词元过滤器(TokenFilter)组成。
分解器用于将字符串分解成一系列词元,词元过滤器的作用是对分词器提取出来的词元做进一步处理,比如转成小写,增加同义词等。处理后的结果称为索引词(Term),引擎会建立 Term 和原文档的倒排索引(Inverted Index),这样就能根据 Term 很快到找到源文档了。
2、选择分词器
目前 ES 分词插件的选择性还是很多的,分词插件的核心就是提供各种分词器(Analyzer)、分解器(Tokenizer)、词元过滤器(TokenFilter);根据依赖的核心分词包(分词算法)的不同显现出不同的差异性,除了分词算法之外,是否支持用户自定义词典,是否支持词典热更新等其他附加功能也是选择分词插件时需要参考的。
下面列出选择分词插件需要考虑的因素(仅供参考):
- 分词准确性:大家都希望分词结果能够尽可能准确,与分词准确性直接相关的就是用户词典了,此外才是分词算法;
- 分词算法:个人认为无需纠结于分词算法,大多数分词包提供的分词算法都比较类似,选择时不需要过于纠结;
- 分词速度:这个与分词算法直接相关,基于词典的分词算法一般比基于模型的分词算法要快;基于词典如果考虑词频、命名实体识别、词性标注则会慢一些;
- 启动速度:当词典较大时,初始化词典会比较慢,某些分词器会对词典进行缓存,第二次启动会非常速度;
- 内存占用:与分词算法、词典大小、模型大小均有关系,设计精巧的算法对内存占用较小;
- 易用性:分词器是否开箱即用,是否可以直接使用在线链接或者压缩包进行安装,是否需要复杂的配置;
- 扩展性:是否支持用户自定义词典、是否支持自定义分词算法、是否支持热更新等;
- 是否开源:开源的分词器在遇到问题的时候可以自己进行深度调试,甚至可以进行二次开发;
- 社区活跃度:这个看一下 github 的 star 数或者依赖的分词包的 star 数和 issue 数目即可判定;
- 更新频率:是否能够与最新版的 ES 同步更新。
二、HanLP 简介
HanLP 是一系列模型与算法组成的 NLP 工具包,具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点,详情可参考 github 介绍:https://github.com/hankcs/HanLP。
选择 HanLP 作为核心的分词包开发 ES 分词插件,主要考虑以下因素:
- HanLP 是 Java 分词包中最为流行的;
- HanLP 提供了多种分词器,既可以基于词典也可以基于模型(在一亿字的大型综合语料库上训练的分词模型);
- HanLP 坚持使用明文词典,这样可以借助社区的力量对词典不断进行完善;
- 完善的开发文档和代码样例,较为活跃的用户群体;
- 个人参与了部分功能的开发,对代码结构较为熟悉。
三、开发分词插件
1、代码结构
conf
:插件的配置文件、HanLP 的配置文件、Java 安全策略文件;scr.main.java.assemby
:插件打包(maven-assembly-plugin)配置文件;org.elasticsearch.plugin.hanlp.analysis
:分词插件核心构建器;org.elasticsearch.plugin.hanlp.conf
:管理插件配置、分词器配置以及 HanLP 配置;org.elasticsearch.plugin.hanlp.lucene
:HanLP 中文分词 Lucene 插件,对 Lucune 分词进行实现;scr.main.resources
:插件属性文件所在目录
2、TokenStream
Analyzer 类是一个抽象类,是所有分词器的基类,它通过 TokenStream 类将文本转换为词汇单元流;TokenStream 有两种实现 Tokenizer(输入为 Reader) 和 TokenFilter(输入为另一个 TokenStream)。
TokenStream 基本使用流程:
- 实例化 TokenStream,向 AttributeSource 添加/获取属性(词汇单元文本、位置增量、偏移量、词汇类型等);
- 调用
reset()
方法,将流(stream)重置到原始(clean)状态; - 循环调用
incrementToken()
方法,并处理 Attribute 属性信息,直到它返回 false 表示流处理结束; - 调用
end()
方法,确保流结束(end-of-stream)的操作可以被执行; - 调用
close()
方法释放资源。
// 实例化 TokenStream
TokenStream tokenStream = new IKAnalyzer().tokenStream("keywords",new StringReader("思想者"));
// 向 AttributeSource 添加/获取属性
CharTermAttribute attribute = tokenStream.addAttribute(CharTermAttribute.class);
// 将流(stream)重置到原始(clean)状态
tokenStream.reset();
// 判断是否还有下一个 Token
while(tokenStream.incrementToken()) {
System.out.println(attribute);
}
tokenStream.end();
tokenStream.close();
综上,开发 Tokenizer 或者 TokenFilter 时,需要重点关注
reset、incrementToken、end、close
四个方法的实现。
3、开发中的小技巧
获取插件目录或文件目录
//获取插件根目录
private static Path getPluginPath() {
return env.pluginsFile().resolve("analysis-hanlp");
}
//获取插件目录下的文件
private static Path getDefDicConfigPath() {
return env.pluginsFile().resolve("analysis-hanlp/hanlp.properties").toAbsolutePath();
}
插件属性文件
如果希望插件属性文件(plugin-descriptor.properties
)能够自动根据 pom.xml
中的属性进行赋值,则需要将文件防止到 resources 文件夹下。
插件版本兼容性
从实际测试来看:
- ES5.X 及其以上的代码是完全复用的,也就是说代码逻辑不需要调整;
- ES5.X 到 ES6.2.X 的插件是可以通用的,其特征是打包的时候需要将插件的文件全部打包到
elasticsearch
文件夹下; - ES6.3.X 以上的插件是可以通用的,打包的时候插件的文件全部打包到根目录即可。
也就是说,如果你升级了新版本 ES,对于插件升级,大多数情况只需要修改下
plugin-descriptor.properties
文件中 ES 的版本号即可。
4、安全策略文件
在插件开发中经常会使用到文件读取、属性读取、网络链接等功能,如果不提前注册安全策略,在调用这些功能的时候会报以下错误java.security.AccessControlException: access denied
。
官方给出的解决方案就是新建一个 plugin-security.policy
文件,然后在文件中声明需要的权限信息,最后在打包的时候将文件放置到插件的根目录,这样在使用 zip 包进行安装的时候,ES 会提示用户插件所需的权限信息,需要用户确认后插件才能正常安装。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: plugin requires additional permissions @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.io.FilePermission <<ALL FILES>> read,write,delete
* java.lang.RuntimePermission createClassLoader
* java.lang.RuntimePermission getClassLoader
* java.lang.RuntimePermission setContextClassLoader
* java.net.SocketPermission * connect,resolve
* java.util.PropertyPermission * read,write
See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.
Continue with installation? [y/N]y
-> Installed analysis-hanlp
5、安全策略的坑
最开始认为只需要添加了 policy 文件,且打包到正确的位置即可解决插件的权限问题,因为在插件安装的时候 ES 已经提示了所需权限,但是代码在实际执行的时候依旧报 AccessControlException
的错误。
参考了多个 HanLP 的 ES 分词插件,都没有获得较好的方法,后来考虑到 IK 分词器远程加载词典时,需要网络连接权限,就去看了下其远程词典加载的代码,最终找到了正确的使用方法。
// 需要特殊权限的代码
AccessController.doPrivileged((PrivilegedAction<Segment>) () -> {
Segment segment;
if (config.getAlgorithm().equals("extend")) {
segment = new ViterbiSegment();
} else {
segment = HanLP.newSegment(config.getAlgorithm());
}
// 在此处显示调用一下分词,使得加载词典、缓存词典的操作可以正确执行
System.out.println( segment.seg("HanLP中文分词工具包!"));
return segment;
});
四、插件特色
简单介绍一下插件的特点:
- 内置多种分词模式,适合不同场景;
- 内置词典,无需额外配置即可使用;
- 支持外置词典,用户可自定义分词算法,基于词典或是模型;
- 支持分词器级别的自定义词典,便于用于多租户场景;
- 支持远程词典热更新(待开发);
- 拼音过滤器、繁简体过滤器(待开发);
- 基于词语或单字的 ngram 切分分词(待开发)。
Github 地址:https://github.com/AnyListen/elasticsearch-analysis-hanlp
Day22 - 熟练使用ES离做好搜索还差多远?
Advent • nodexy 发表了文章 • 2 个评论 • 8434 次浏览 • 2018-12-21 17:27
搜索引擎作为互联网发展历史中一个非常典型的产品/业务形态,时至今日并没有太大的突破性变化;主流形态可以划分为大搜、垂搜、企业级搜索和站内/app内搜索等。除了Google, Yahoo, Bing, Ask 等以及国内百度、搜狗、360、神马等是人们熟识的大搜之外,非业内人士还真不知道其他还有哪些公司以及有哪些搜索产品或业务场景。 实际上,在信息爆炸的时代,几乎每家有点儿规模的公司都或多或少要涉及到搜索引擎,最起码你需要接触SEO/SEM。本文将从非大搜企业的搜索需求出发,并基于开源技术栈来介绍和探讨搜索引擎在实践中的几个核心任务及其主要解决思路。同时为了避免重复,本文以外链形式引用了大量网络已有的国内外公开资料,方便大家参考,需要注意的是部分内容可能会随着时间推移而过期或链接失效。 提到开源搜索引擎,在Java技术栈里以Lucene, Solr/SolrCloud及Elasticsearch为代表的几个项目可能最为流行。本文的写作初衷是解答”熟练使用ES等开源搜索引擎解决方案以后,要如何才能做好搜索产品/业务?“ 希望对你有所帮助,如果你有关于此话题的更多实践经验或不同见解,欢迎留言评论交流。 1. 做好搜索引擎意味着什么? 有一位同行的文章总结了好的搜索引擎的衡量维度:作者:杨振涛 搜索引擎架构师@vivo 首次发布:Elasticsearch中文社区 发布日期:2018-12-22
- 相关性
- 体验
- 性能
- 搜索前:搜索框,搜索入口,热搜榜/飙升榜/大家都在搜,搜索发现,默认搜索词,历史搜索记录,猜你想搜,分类搜索,语音输入搜索/图片搜索; 广告位
- 搜索中:搜索联想直达,搜索联想词,输入纠错,关键词匹配高亮
- 搜索后:搜索结果列表,列表页推荐/广告,特形展示,列表穿插,搜了还搜,搜索详情页,详情页搜索推荐,无结果及少结果填充 ,筛选条件/筛选器,自主排序,列表样式切换(宫格 | 列表)
- 单维度排序:顾名思义按照单个维度来排序,没有任何复杂性可言,在召回结果集不太大的情况下实时排序即可。
- 优先级排序:相对单维度排序而已一般是先按维度A排,当A的排序依据一样或相等时再按维度B排序,以此类推。
- 加权排序 :针对多个维度或特征,赋予不同权重,并按求和之后的得分来排序;实践中通常会采用分层加权排序(第一层加权排序之后,得到不少于2个得分,继续加权后排序),或者分组加权排序(第一层分组来加权排序后,对所得到的得分可能按业务需求进行非求和类的运算比如乘法,再按最终得分排序)的策略。 加权排序的难点在于,如何设置并持续优化这些权重,通常会建模为典型的机器学习问题来拟合。
- 机器学习排序 :即所谓LTR,根据用户点击或人工标注数据集建立学习目标,然后通过特征工程来挖掘与目标有关系的一系列特征,并建立学习模型,通过训练集获得模型参数,以该组参数为基准做预测,上线后再基于用户点击数据持续优化该模型的参数。LTR是一个通用方法的称谓,不是某一个具体算法的名称,具体算法名称参见下文。
- MART (Multiple Additive Regression Trees, a.k.a. Gradient boosted regression tree)
- RankNet
- RankBoost
- AdaRank
- Coordinate Ascent
- LambdaMART
- ListNet
- Random Forests
- vivo互联网技术: 搜索引擎与其他机器学习有何不同 https://mp.weixin.qq.com/s/uWg3m5xIGBAKNqqDmxzw2Q
- InfoQ:在 Elasticsearch 中应用机器学习排序 LTR https://www.infoq.cn/article/w ... earch
- InfoQ:认识机器学习排序 LTR https://www.infoq.cn/article/m ... g-ltr
- InfoQ:机器学习排序 LTR 入门——线性模型 https://www.infoq.cn/article/2 ... model
- 搜索质量概述 https://opensourceconnections. ... lity/
- nDCG排序打分工具 https://opensourceconnections. ... epid/
- 百度众测任务: Query与广告之间的相关性评估规则 http://test.baidu.com/mark/task/view/id/2584923
- Search Quality Evaluation Tool for Apache Solr & Elasticsearch search-based infrastructures https://github.com/SeaseLtd/ra ... uator
![](https://img12.360buyimg.com/n1 ... c.jpg)
Day 7 - Elasticsearch中数据是如何存储的
Advent • weizijun 发表了文章 • 7 个评论 • 68790 次浏览 • 2018-12-07 13:55
前言
很多使用Elasticsearch的同学会关心数据存储在ES中的存储容量,会有这样的疑问:xxTB的数据入到ES会使用多少存储空间。这个问题其实很难直接回答的,只有数据写入ES后,才能观察到实际的存储空间。比如同样是1TB的数据,写入ES的存储空间可能差距会非常大,可能小到只有300~400GB,也可能多到6-7TB,为什么会造成这么大的差距呢?究其原因,我们来探究下Elasticsearch中的数据是如何存储。文章中我以Elasticsearch 2.3版本为示例,对应的lucene版本是5.5,Elasticsearch现在已经来到了6.5版本,数字类型、列存等存储结构有些变化,但基本的概念变化不多,文章中的内容依然适用。
Elasticsearch索引结构
Elasticsearch对外提供的是index的概念,可以类比为DB,用户查询是在index上完成的,每个index由若干个shard组成,以此来达到分布式可扩展的能力。比如下图是一个由10个shard组成的index。
shard是Elasticsearch数据存储的最小单位,index的存储容量为所有shard的存储容量之和。Elasticsearch集群的存储容量则为所有index存储容量之和。
一个shard就对应了一个lucene的library。对于一个shard,Elasticsearch增加了translog的功能,类似于HBase WAL,是数据写入过程中的中间数据,其余的数据都在lucene库中管理的。
所以Elasticsearch索引使用的存储内容主要取决于lucene中的数据存储。
lucene数据存储
下面我们主要看下lucene的文件内容,在了解lucene文件内容前,大家先了解些lucene的基本概念。
lucene基本概念
- segment : lucene内部的数据是由一个个segment组成的,写入lucene的数据并不直接落盘,而是先写在内存中,经过了refresh间隔,lucene才将该时间段写入的全部数据refresh成一个segment,segment多了之后会进行merge成更大的segment。lucene查询时会遍历每个segment完成。由于lucene* 写入的数据是在内存中完成,所以写入效率非常高。但是也存在丢失数据的风险,所以Elasticsearch基于此现象实现了translog,只有在segment数据落盘后,Elasticsearch才会删除对应的translog。
- doc : doc表示lucene中的一条记录
- field :field表示记录中的字段概念,一个doc由若干个field组成。
- term :term是lucene中索引的最小单位,某个field对应的内容如果是全文检索类型,会将内容进行分词,分词的结果就是由term组成的。如果是不分词的字段,那么该字段的内容就是一个term。
- 倒排索引(inverted index): lucene索引的通用叫法,即实现了term到doc list的映射。
- 正排数据:搜索引擎的通用叫法,即原始数据,可以理解为一个doc list。
- docvalues :Elasticsearch中的列式存储的名称,Elasticsearch除了存储原始存储、倒排索引,还存储了一份docvalues,用作分析和排序。
lucene文件内容
lucene包的文件是由很多segment文件组成的,segments_xxx文件记录了lucene包下面的segment文件数量。每个segment会包含如下的文件。
Name | Extension | Brief Description |
---|---|---|
Segment Info | .si | segment的元数据文件 |
Compound File | .cfs, .cfe | 一个segment包含了如下表的各个文件,为减少打开文件的数量,在segment小的时候,segment的所有文件内容都保存在cfs文件中,cfe文件保存了lucene各文件在cfs文件的位置信息 |
Fields | .fnm | 保存了fields的相关信息 |
Field Index | .fdx | 正排存储文件的元数据信息 |
Field Data | .fdt | 存储了正排存储数据,写入的原文存储在这 |
Term Dictionary | .tim | 倒排索引的元数据信息 |
Term Index | .tip | 倒排索引文件,存储了所有的倒排索引数据 |
Frequencies | .doc | 保存了每个term的doc id列表和term在doc中的词频 |
Positions | .pos | Stores position information about where a term occurs in the index 全文索引的字段,会有该文件,保存了term在doc中的位置 |
Payloads | .pay | Stores additional per-position metadata information such as character offsets and user payloads 全文索引的字段,使用了一些像payloads的高级特性会有该文件,保存了term在doc中的一些高级特性 |
Norms | .nvd, .nvm | 文件保存索引字段加权数据 |
Per-Document Values | .dvd, .dvm | lucene的docvalues文件,即数据的列式存储,用作聚合和排序 |
Term Vector Data | .tvx, .tvd, .tvf | Stores offset into the document data file 保存索引字段的矢量信息,用在对term进行高亮,计算文本相关性中使用 |
Live Documents | .liv | 记录了segment中删除的doc |
测试数据示例
下面我们以真实的数据作为示例,看看lucene中各类型数据的容量占比。
写100w数据,有一个uuid字段,写入的是长度为36位的uuid,字符串总为3600w字节,约为35M。
数据使用一个shard,不带副本,使用默认的压缩算法,写入完成后merge成一个segment方便观察。
使用线上默认的配置,uuid存为不分词的字符串类型。创建如下索引:
PUT test_field
{
"settings": {
"index": {
"number_of_shards": "1",
"number_of_replicas": "0",
"refresh_interval": "30s"
}
},
"mappings": {
"type": {
"_all": {
"enabled": false
},
"properties": {
"uuid": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
首先写入100w不同的uuid,使用磁盘容量细节如下:
health status index pri rep docs.count docs.deleted store.size pri.store.size
green open test_field 1 0 1000000 0 122.7mb 122.7mb
-rw-r--r-- 1 weizijun staff 41M Aug 19 21:23 _8.fdt
-rw-r--r-- 1 weizijun staff 17K Aug 19 21:23 _8.fdx
-rw-r--r-- 1 weizijun staff 688B Aug 19 21:23 _8.fnm
-rw-r--r-- 1 weizijun staff 494B Aug 19 21:23 _8.si
-rw-r--r-- 1 weizijun staff 265K Aug 19 21:23 _8_Lucene50_0.doc
-rw-r--r-- 1 weizijun staff 44M Aug 19 21:23 _8_Lucene50_0.tim
-rw-r--r-- 1 weizijun staff 340K Aug 19 21:23 _8_Lucene50_0.tip
-rw-r--r-- 1 weizijun staff 37M Aug 19 21:23 _8_Lucene54_0.dvd
-rw-r--r-- 1 weizijun staff 254B Aug 19 21:23 _8_Lucene54_0.dvm
-rw-r--r-- 1 weizijun staff 195B Aug 19 21:23 segments_2
-rw-r--r-- 1 weizijun staff 0B Aug 19 21:20 write.lock
可以看到正排数据、倒排索引数据,列存数据容量占比几乎相同,正排数据和倒排数据还会存储Elasticsearch的唯一id字段,所以容量会比列存多一些。
35M的uuid存入Elasticsearch后,数据膨胀了3倍,达到了122.7mb。Elasticsearch竟然这么消耗资源,不要着急下结论,接下来看另一个测试结果。
我们写入100w一样的uuid,然后看看Elasticsearch使用的容量。
health status index pri rep docs.count docs.deleted store.size pri.store.size
green open test_field 1 0 1000000 0 13.2mb 13.2mb
-rw-r--r-- 1 weizijun staff 5.5M Aug 19 21:29 _6.fdt
-rw-r--r-- 1 weizijun staff 15K Aug 19 21:29 _6.fdx
-rw-r--r-- 1 weizijun staff 688B Aug 19 21:29 _6.fnm
-rw-r--r-- 1 weizijun staff 494B Aug 19 21:29 _6.si
-rw-r--r-- 1 weizijun staff 309K Aug 19 21:29 _6_Lucene50_0.doc
-rw-r--r-- 1 weizijun staff 7.0M Aug 19 21:29 _6_Lucene50_0.tim
-rw-r--r-- 1 weizijun staff 195K Aug 19 21:29 _6_Lucene50_0.tip
-rw-r--r-- 1 weizijun staff 244K Aug 19 21:29 _6_Lucene54_0.dvd
-rw-r--r-- 1 weizijun staff 252B Aug 19 21:29 _6_Lucene54_0.dvm
-rw-r--r-- 1 weizijun staff 195B Aug 19 21:29 segments_2
-rw-r--r-- 1 weizijun staff 0B Aug 19 21:26 write.lock
这回35M的数据Elasticsearch容量只有13.2mb,其中还有主要的占比还是Elasticsearch的唯一id,100w的uuid几乎不占存储容积。
所以在Elasticsearch中建立索引的字段如果基数越大(count distinct),越占用磁盘空间。
我们再看看存100w个不一样的整型会是如何。
health status index pri rep docs.count docs.deleted store.size pri.store.size
green open test_field 1 0 1000000 0 13.6mb 13.6mb
-rw-r--r-- 1 weizijun staff 6.1M Aug 28 10:19 _42.fdt
-rw-r--r-- 1 weizijun staff 22K Aug 28 10:19 _42.fdx
-rw-r--r-- 1 weizijun staff 688B Aug 28 10:19 _42.fnm
-rw-r--r-- 1 weizijun staff 503B Aug 28 10:19 _42.si
-rw-r--r-- 1 weizijun staff 2.8M Aug 28 10:19 _42_Lucene50_0.doc
-rw-r--r-- 1 weizijun staff 2.2M Aug 28 10:19 _42_Lucene50_0.tim
-rw-r--r-- 1 weizijun staff 83K Aug 28 10:19 _42_Lucene50_0.tip
-rw-r--r-- 1 weizijun staff 2.5M Aug 28 10:19 _42_Lucene54_0.dvd
-rw-r--r-- 1 weizijun staff 228B Aug 28 10:19 _42_Lucene54_0.dvm
-rw-r--r-- 1 weizijun staff 196B Aug 28 10:19 segments_2
-rw-r--r-- 1 weizijun staff 0B Aug 28 10:16 write.lock
从结果可以看到,100w整型数据,Elasticsearch的存储开销为13.6mb。如果以int型计算100w数据的长度的话,为400w字节,大概是3.8mb数据。忽略Elasticsearch唯一id字段的影响,Elasticsearch实际存储容量跟整型数据长度差不多。
我们再看一下开启最佳压缩参数对存储空间的影响:
health status index pri rep docs.count docs.deleted store.size pri.store.size
green open test_field 1 0 1000000 0 107.2mb 107.2mb
-rw-r--r-- 1 weizijun staff 25M Aug 20 12:30 _5.fdt
-rw-r--r-- 1 weizijun staff 6.0K Aug 20 12:30 _5.fdx
-rw-r--r-- 1 weizijun staff 688B Aug 20 12:31 _5.fnm
-rw-r--r-- 1 weizijun staff 500B Aug 20 12:31 _5.si
-rw-r--r-- 1 weizijun staff 265K Aug 20 12:31 _5_Lucene50_0.doc
-rw-r--r-- 1 weizijun staff 44M Aug 20 12:31 _5_Lucene50_0.tim
-rw-r--r-- 1 weizijun staff 322K Aug 20 12:31 _5_Lucene50_0.tip
-rw-r--r-- 1 weizijun staff 37M Aug 20 12:31 _5_Lucene54_0.dvd
-rw-r--r-- 1 weizijun staff 254B Aug 20 12:31 _5_Lucene54_0.dvm
-rw-r--r-- 1 weizijun staff 224B Aug 20 12:31 segments_4
-rw-r--r-- 1 weizijun staff 0B Aug 20 12:00 write.lock
结果中可以发现,只有正排数据会启动压缩,压缩能力确实强劲,不考虑唯一id字段,存储容量大概压缩到接近50%。
我们还做了一些实验,Elasticsearch默认是开启_all参数的,_all可以让用户传入的整体json数据作为全文检索的字段,可以更方便的检索,但在现实场景中已经使用的不多,相反会增加很多存储容量的开销,可以看下开启_all的磁盘空间使用情况:
health status index pri rep docs.count docs.deleted store.size pri.store.size
green open test_field 1 0 1000000 0 162.4mb 162.4mb
-rw-r--r-- 1 weizijun staff 41M Aug 18 22:59 _20.fdt
-rw-r--r-- 1 weizijun staff 18K Aug 18 22:59 _20.fdx
-rw-r--r-- 1 weizijun staff 777B Aug 18 22:59 _20.fnm
-rw-r--r-- 1 weizijun staff 59B Aug 18 22:59 _20.nvd
-rw-r--r-- 1 weizijun staff 78B Aug 18 22:59 _20.nvm
-rw-r--r-- 1 weizijun staff 539B Aug 18 22:59 _20.si
-rw-r--r-- 1 weizijun staff 7.2M Aug 18 22:59 _20_Lucene50_0.doc
-rw-r--r-- 1 weizijun staff 4.2M Aug 18 22:59 _20_Lucene50_0.pos
-rw-r--r-- 1 weizijun staff 73M Aug 18 22:59 _20_Lucene50_0.tim
-rw-r--r-- 1 weizijun staff 832K Aug 18 22:59 _20_Lucene50_0.tip
-rw-r--r-- 1 weizijun staff 37M Aug 18 22:59 _20_Lucene54_0.dvd
-rw-r--r-- 1 weizijun staff 254B Aug 18 22:59 _20_Lucene54_0.dvm
-rw-r--r-- 1 weizijun staff 196B Aug 18 22:59 segments_2
-rw-r--r-- 1 weizijun staff 0B Aug 18 22:53 write.lock
开启_all比不开启多了40mb的存储空间,多的数据都在倒排索引上,大约会增加30%多的存储开销。所以线上都直接禁用。
然后我还做了其他几个尝试,为了验证存储容量是否和数据量成正比,写入1000w数据的uuid,发现存储容量基本为100w数据的10倍。我还验证了数据长度是否和数据量成正比,发现把uuid增长2倍、4倍,存储容量也响应的增加了2倍和4倍。在此就不一一列出数据了。
lucene各文件具体内容和实现
lucene数据元信息文件
文件名为:segments_xxx
该文件为lucene数据文件的元信息文件,记录所有segment的元数据信息。
该文件主要记录了目前有多少segment,每个segment有一些基本信息,更新这些信息定位到每个segment的元信息文件。
lucene元信息文件还支持记录userData,Elasticsearch可以在此记录translog的一些相关信息。
文件示例
具体实现类
public final class SegmentInfos implements Cloneable, Iterable<SegmentCommitInfo> {
// generation是segment的版本的概念,从文件名中提取出来,实例中为:2t/101
private long generation; // generation of the "segments_N" for the next commit
private long lastGeneration; // generation of the "segments_N" file we last successfully read
// or wrote; this is normally the same as generation except if
// there was an IOException that had interrupted a commit
/** Id for this commit; only written starting with Lucene 5.0 */
private byte[] id;
/** Which Lucene version wrote this commit, or null if this commit is pre-5.3. */
private Version luceneVersion;
/** Counts how often the index has been changed. */
public long version;
/** Used to name new segments. */
// TODO: should this be a long ...?
public int counter;
/** Version of the oldest segment in the index, or null if there are no segments. */
private Version minSegmentLuceneVersion;
private List<SegmentCommitInfo> segments = new ArrayList<>();
/** Opaque Map<String, String> that user can specify during IndexWriter.commit */
public Map<String,String> userData = Collections.emptyMap();
}
/** Embeds a [read-only] SegmentInfo and adds per-commit
* fields.
*
* @lucene.experimental */
public class SegmentCommitInfo {
/** The {@link SegmentInfo} that we wrap. */
public final SegmentInfo info;
// How many deleted docs in the segment:
private int delCount;
// Generation number of the live docs file (-1 if there
// are no deletes yet):
private long delGen;
// Normally 1+delGen, unless an exception was hit on last
// attempt to write:
private long nextWriteDelGen;
// Generation number of the FieldInfos (-1 if there are no updates)
private long fieldInfosGen;
// Normally 1+fieldInfosGen, unless an exception was hit on last attempt to
// write
private long nextWriteFieldInfosGen; //fieldInfosGen == -1 ? 1 : fieldInfosGen + 1;
// Generation number of the DocValues (-1 if there are no updates)
private long docValuesGen;
// Normally 1+dvGen, unless an exception was hit on last attempt to
// write
private long nextWriteDocValuesGen; //docValuesGen == -1 ? 1 : docValuesGen + 1;
// TODO should we add .files() to FieldInfosFormat, like we have on
// LiveDocsFormat?
// track the fieldInfos update files
private final Set<String> fieldInfosFiles = new HashSet<>();
// Track the per-field DocValues update files
private final Map<Integer,Set<String>> dvUpdatesFiles = new HashMap<>();
// Track the per-generation updates files
@Deprecated
private final Map<Long,Set<String>> genUpdatesFiles = new HashMap<>();
private volatile long sizeInBytes = -1;
}
segment的元信息文件
文件后缀:.si
每个segment都有一个.si文件,记录了该segment的元信息。
segment元信息文件中记录了segment的文档数量,segment对应的文件列表等信息。
文件示例
具体实现类
/**
* Information about a segment such as its name, directory, and files related
* to the segment.
*
* @lucene.experimental
*/
public final class SegmentInfo {
// _bl
public final String name;
/** Where this segment resides. */
public final Directory dir;
/** Id that uniquely identifies this segment. */
private final byte[] id;
private Codec codec;
// Tracks the Lucene version this segment was created with, since 3.1. Null
// indicates an older than 3.0 index, and it's used to detect a too old index.
// The format expected is "x.y" - "2.x" for pre-3.0 indexes (or null), and
// specific versions afterwards ("3.0.0", "3.1.0" etc.).
// see o.a.l.util.Version.
private Version version;
private int maxDoc; // number of docs in seg
private boolean isCompoundFile;
private Map<String,String> diagnostics;
private Set<String> setFiles;
private final Map<String,String> attributes;
}
fields信息文件
文件后缀:.fnm
该文件存储了fields的基本信息。
fields信息中包括field的数量,field的类型,以及IndexOpetions,包括是否存储、是否索引,是否分词,是否需要列存等等。
文件示例
具体实现类
/**
* Access to the Field Info file that describes document fields and whether or
* not they are indexed. Each segment has a separate Field Info file. Objects
* of this class are thread-safe for multiple readers, but only one thread can
* be adding documents at a time, with no other reader or writer threads
* accessing this object.
**/
public final class FieldInfo {
/** Field's name */
public final String name;
/** Internal field number */
//field在内部的编号
public final int number;
//field docvalues的类型
private DocValuesType docValuesType = DocValuesType.NONE;
// True if any document indexed term vectors
private boolean storeTermVector;
private boolean omitNorms; // omit norms associated with indexed fields
//index的配置项
private IndexOptions indexOptions = IndexOptions.NONE;
private boolean storePayloads; // whether this field stores payloads together with term positions
private final Map<String,String> attributes;
// docvalues的generation
private long dvGen;
}
数据存储文件
文件后缀:.fdx, .fdt
索引文件为.fdx,数据文件为.fdt,数据存储文件功能为根据自动的文档id,得到文档的内容,搜索引擎的术语习惯称之为正排数据,即doc_id -> content,es的_source数据就存在这
索引文件记录了快速定位文档数据的索引信息,数据文件记录了所有文档id的具体内容。
文件示例
具体实现类
/**
* Random-access reader for {@link CompressingStoredFieldsIndexWriter}.
* @lucene.internal
*/
public final class CompressingStoredFieldsIndexReader implements Cloneable, Accountable {
private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(CompressingStoredFieldsIndexReader.class);
final int maxDoc;
//docid索引,快速定位某个docid的数组坐标
final int[] docBases;
//快速定位某个docid所在的文件offset的startPointer
final long[] startPointers;
//平均一个chunk的文档数
final int[] avgChunkDocs;
//平均一个chunk的size
final long[] avgChunkSizes;
final PackedInts.Reader[] docBasesDeltas; // delta from the avg
final PackedInts.Reader[] startPointersDeltas; // delta from the avg
}
/**
* {@link StoredFieldsReader} impl for {@link CompressingStoredFieldsFormat}.
* @lucene.experimental
*/
public final class CompressingStoredFieldsReader extends StoredFieldsReader {
//从fdt正排索引文件中获得
private final int version;
// field的基本信息
private final FieldInfos fieldInfos;
//fdt正排索引文件reader
private final CompressingStoredFieldsIndexReader indexReader;
//从fdt正排索引文件中获得,用于指向fdx数据文件的末端,指向numChunks地址4
private final long maxPointer;
//fdx正排数据文件句柄
private final IndexInput fieldsStream;
//块大小
private final int chunkSize;
private final int packedIntsVersion;
//压缩类型
private final CompressionMode compressionMode;
//解压缩处理对象
private final Decompressor decompressor;
//文档数量,从segment元数据中获得
private final int numDocs;
//是否正在merge,默认为false
private final boolean merging;
//初始化时new了一个BlockState,BlockState记录下当前正排文件读取的状态信息
private final BlockState state;
//chunk的数量
private final long numChunks; // number of compressed blocks written
//dirty chunk的数量
private final long numDirtyChunks; // number of incomplete compressed blocks written
//是否close,默认为false
private boolean closed;
}
倒排索引文件
索引后缀:.tip,.tim
倒排索引也包含索引文件和数据文件,.tip为索引文件,.tim为数据文件,索引文件包含了每个字段的索引元信息,数据文件有具体的索引内容。
5.5.0版本的倒排索引实现为FST tree,FST tree的最大优势就是内存空间占用非常低 ,具体可以参看下这篇文章:http://www.cnblogs.com/bonelee/p/6226185.html
http://examples.mikemccandless.com/fst.py?terms=&cmd=Build+it 为FST图实例,可以根据输入的数据构造出FST图
输入到 FST 中的数据为:
String inputValues[] = {"mop","moth","pop","star","stop","top"};
long outputValues[] = {0,1,2,3,4,5};
生成的 FST 图为:
文件示例
具体实现类
public final class BlockTreeTermsReader extends FieldsProducer {
// Open input to the main terms dict file (_X.tib)
final IndexInput termsIn;
// Reads the terms dict entries, to gather state to
// produce DocsEnum on demand
final PostingsReaderBase postingsReader;
private final TreeMap<String,FieldReader> fields = new TreeMap<>();
/** File offset where the directory starts in the terms file. */
/索引数据文件tim的数据的尾部的元数据的地址
private long dirOffset;
/** File offset where the directory starts in the index file. */
//索引文件tip的数据的尾部的元数据的地址
private long indexDirOffset;
//semgent的名称
final String segment;
//版本号
final int version;
//5.3.x index, we record up front if we may have written any auto-prefix terms,示例中记录的是false
final boolean anyAutoPrefixTerms;
}
/**
* BlockTree's implementation of {@link Terms}.
* @lucene.internal
*/
public final class FieldReader extends Terms implements Accountable {
//term的数量
final long numTerms;
//field信息
final FieldInfo fieldInfo;
final long sumTotalTermFreq;
//总的文档频率
final long sumDocFreq;
//文档数量
final int docCount;
//字段在索引文件tip中的起始位置
final long indexStartFP;
final long rootBlockFP;
final BytesRef rootCode;
final BytesRef minTerm;
final BytesRef maxTerm;
//longs:metadata buffer, holding monotonic values
final int longsSize;
final BlockTreeTermsReader parent;
final FST<BytesRef> index;
}
倒排链文件
文件后缀:.doc, .pos, .pay
.doc保存了每个term的doc id列表和term在doc中的词频
全文索引的字段,会有.pos文件,保存了term在doc中的位置
全文索引的字段,使用了一些像payloads的高级特性才会有.pay文件,保存了term在doc中的一些高级特性
文件示例
具体实现类
/**
* Concrete class that reads docId(maybe frq,pos,offset,payloads) list
* with postings format.
*
* @lucene.experimental
*/
public final class Lucene50PostingsReader extends PostingsReaderBase {
private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(Lucene50PostingsReader.class);
private final IndexInput docIn;
private final IndexInput posIn;
private final IndexInput payIn;
final ForUtil forUtil;
private int version;
//不分词的字段使用的是该对象,基于skiplist实现了倒排链
final class BlockDocsEnum extends PostingsEnum {
}
//全文检索字段使用的是该对象
final class BlockPostingsEnum extends PostingsEnum {
}
//包含高级特性的字段使用的是该对象
final class EverythingEnum extends PostingsEnum {
}
}
列存文件(docvalues)
文件后缀:.dvm, .dvd
索引文件为.dvm,数据文件为.dvd。
lucene实现的docvalues有如下类型:
- 1、NONE 不开启docvalue时的状态
- 2、NUMERIC 单个数值类型的docvalue主要包括(int,long,float,double)
- 3、BINARY 二进制类型值对应不同的codes最大值可能超过32766字节,
- 4、SORTED 有序增量字节存储,仅仅存储不同部分的值和偏移量指针,值必须小于等于32766字节
- 5、SORTED_NUMERIC 存储数值类型的有序数组列表
- 6、SORTED_SET 可以存储多值域的docvalue值,但返回时,仅仅只能返回多值域的第一个docvalue
- 7、对应not_anaylized的string字段,使用的是SORTED_SET类型,number的类型是SORTED_NUMERIC类型
其中SORTED_SET 的 SORTED_SINGLE_VALUED类型包括了两类数据 : binary + numeric, binary是按ord排序的term的列表,numeric是doc到ord的映射。
文件示例
具体实现类
/** reader for {@link Lucene54DocValuesFormat} */
final class Lucene54DocValuesProducer extends DocValuesProducer implements Closeable {
//number类型的field的列存列表
private final Map<String,NumericEntry> numerics = new HashMap<>();
//字符串类型的field的列存列表
private final Map<String,BinaryEntry> binaries = new HashMap<>();
//有序字符串类型的field的列存列表
private final Map<String,SortedSetEntry> sortedSets = new HashMap<>();
//有序number类型的field的列存列表
private final Map<String,SortedSetEntry> sortedNumerics = new HashMap<>();
//字符串类型的field的ords列表
private final Map<String,NumericEntry> ords = new HashMap<>();
//docId -> address -> ord 中field的ords列表
private final Map<String,NumericEntry> ordIndexes = new HashMap<>();
//field的数量
private final int numFields;
//内存使用量
private final AtomicLong ramBytesUsed;
//数据源的文件句柄
private final IndexInput data;
//文档数
private final int maxDoc;
// memory-resident structures
private final Map<String,MonotonicBlockPackedReader> addressInstances = new HashMap<>();
private final Map<String,ReverseTermsIndex> reverseIndexInstances = new HashMap<>();
private final Map<String,DirectMonotonicReader.Meta> directAddressesMeta = new HashMap<>();
//是否正在merge
private final boolean merging;
}
/** metadata entry for a numeric docvalues field */
static class NumericEntry {
private NumericEntry() {}
/** offset to the bitset representing docsWithField, or -1 if no documents have missing values */
long missingOffset;
/** offset to the actual numeric values */
//field的在数据文件中的起始地址
public long offset;
/** end offset to the actual numeric values */
//field的在数据文件中的结尾地址
public long endOffset;
/** bits per value used to pack the numeric values */
public int bitsPerValue;
//format类型
int format;
/** count of values written */
public long count;
/** monotonic meta */
public DirectMonotonicReader.Meta monotonicMeta;
//最小的value
long minValue;
//Compressed by computing the GCD
long gcd;
//Compressed by giving IDs to unique values.
long table[];
/** for sparse compression */
long numDocsWithValue;
NumericEntry nonMissingValues;
NumberType numberType;
}
/** metadata entry for a binary docvalues field */
static class BinaryEntry {
private BinaryEntry() {}
/** offset to the bitset representing docsWithField, or -1 if no documents have missing values */
long missingOffset;
/** offset to the actual binary values */
//field的在数据文件中的起始地址
long offset;
int format;
/** count of values written */
public long count;
//最短字符串的长度
int minLength;
//最长字符串的长度
int maxLength;
/** offset to the addressing data that maps a value to its slice of the byte[] */
public long addressesOffset, addressesEndOffset;
/** meta data for addresses */
public DirectMonotonicReader.Meta addressesMeta;
/** offset to the reverse index */
public long reverseIndexOffset;
/** packed ints version used to encode addressing information */
public int packedIntsVersion;
/** packed ints blocksize */
public int blockSize;
}
参考资料
Day 4 - PB级规模数据的Elasticsearch分库分表实践
Advent • ouyangchucai 发表了文章 • 4 个评论 • 18483 次浏览 • 2018-12-04 20:50
从2018年7月在开始在某阿里云数据中心部署Elasticsearch软件,到2018年12月共创建了15个集群,服务于客户的文档检索、交通视频检索、地理信息检索、日志安全审计等业务。其中数据规模最大的一个业务,共有800张表,7万亿条数据,每天新增500亿条记录,数据要求存储半年,单条记录大小1KB左右,存储规模约10PB,需要支持1000并发查询。
一、数据存储空间规划。
数据中心能用于搭建Elasticsearch集群的SSD盘共700TB,SATA盘共50PB。根据业务类型、时间范围划分热数据和冷数据,一部分重要数据存储在SSD盘的热数据集群,其它数据存储在SATA盘的冷数据集群。热数据集群主要存储各类实体信息,包括人员、物品、事件、地址、组织数据,以及最新轨迹数据。冷数据集群主要存储历史轨迹信息。热数据和冷数据按照业务拆分多个小集群,每个集群规模保持在50个节点左右,单个集群最大不超过200个节点。利用阿里云平台弹性伸缩的能力,每个Elasticsearch集群可以先从小规模创建,根据资源使用情况来弹性扩展节点规模。
Elasticsearch集群节点配置
二、索引设计。
1.索引别名(alias)。每类数据根据数据源表名建立索引(index),索引中只包含一个类型(type)。配置索引别名(alias),业务上根据别名写入、查询数据,索引重建等数据维护操作可以通过别名切换对业务透明。
2.按时间分表。轨迹类数据按时间(日/月)拆分,每个索引存储数据量保持在1TB(10亿)左右,索引名带上日期/月份后缀,拆分后的索引配置别名区分冷热数据。配置索引模板,指定索引分片数和副本数、字段类型、分词器。配置Linux crontab定时任务,通过shell脚本创建索引。
3.分片(shard)设置。索引按照单个分片10-40GB数据大小设计分片数,数据量少于10GB(1000万)的索引设置1个分片即可,数据量大于1TB(10亿)的索引设置分片数为集群节点数整数倍(例如50个节点的集群配置50个分片)。
4.副本(replica)设置。数据首次批量导入时索引副本数设置为0,快速写入数据。生产环境索引副本数设置为1,避免集群节点故障数据丢失。
三、索引mapping设计。
1.精心设计索引字段类型。在开发环境配置Elasticsearch允许自动创建索引,从数据源每张表取1000条记录批量写入Elasticsearch,自动创建索引mapping,然后再根据业务需要修改mapping配置合适的字段类型,指定字段索引分词器、是否存储、是否索引、是否合并至全文检索字段。 对于数据量大的表尤其要精心设计字段类型,尽量减少索引存储空间占用。在生产环境中建议配置不允许自动创建索引。
2.配置全文检索字段。如果业务需要全文检索,可以配置开启全文字段,同时需要占用更多存储空间;如果业务上只是按字段查询,可以配置禁用全文字段,减少存储空间。Elasticsearch5.X及之前的版本默认启用_all字段,合并所有字段的值。Elasticsearch6.X及之后的版本默认禁用_all字段,可以通过copy_to将多个字段值合并成一个全文字段。对于数据查全率要求高的业务场景,建议对全文字段配置cjk分词器(Elasticsearch和Lucene中自带,对中日韩文进行二元分词的分词器)。
3.通用字段统一命名。各个索引中的姓名、证件号码、时间(开始时间、结束时间)、地点(始发地、目的地)等常用字段统一命名。用户指定证件号、时间范围等精确字段查询条件时,可以使用统一的查询条件并行查询多个索引。
四、分词设置。
1.选择合适的分词器。Elasticsearch中内置了很多分词器:standard、cjk、nGram等,也可以安装ik、pinyin等开源分词器, 可以根据业务场景选择合适的分词器。 常用分词器: standard:Elasticsearch默认分词,英文按空格切分,中文按单个汉字切分。 cjk:根据二元索引(两个相邻的字作为一个词条)对中日韩文分词,可以保证查全率。 NGram:可以将英文按照字母切分,结合Elasticsearch的短语搜索(match_phrase)使用。 ik:比较热门的中文分词,能按照中文语义切分,可以自定义词典。 pinyin:可以让用户输入拼音,就能查找到相关的关键词。 对于查全率要求较高的场景,建议使用cjk分词,同时能支持比较快的响应速度。对于查准率要求较高的场景,建议使用ik分词。
CJK分词和IK分词对比(测试环境:Elasticsearch5.5.3,8335万条人员档案信息,10节点集群,单节点16核CPU、64G内存、2T SSD盘,1个线程批量写入,1个并发查询)
测试分词效果: curl -XPOST "http://localhost:9200/_analyze" -H 'Content-Type: application/json' -d' { "analyzer": "ik_max_word", "text": "南京市长江大桥" }'
2.NGram分词。对于像车牌号之类数字和字母连在一起的字符,默认会被切成一个完整词条,但是业务上又需要支持前缀、后缀模糊匹配,可以根据业务需求进行分词。车牌号建议增加一个分词字段,配置NGram分词器,切分1元至7元的组合。身份证号码建议增加分词字段,根据业务需要切分18位完整词条、前2位(省)、前4位(省市)、前6位(省市区县)、后4位、出生年月日、出生年份、出生年月、出生月日等组合。
3.单字分词。对于像姓名类字段,业务上需要支持完整匹配,又需要支持单字查询。可以配置1个keyword字段(不分词);1个text字段(分词),分词器选择Elasticsearch默认分词器standard,按单个汉字切分。
五、数据写入策略。
1.批量离线数据导入。各类业务数据源主要在数据仓库MaxCompute(原ODPS),为了把表数据从MaxCompute表导入到ElasticSearch集群中, 我们基于MaxCompute MapReduce开发了MaxCompute到ElasticSearch的数据导出作业,通过简单的配置就可以把数据导入到ElasticSearch中。 数据源在关系数据库RDS或者NoSQL的数据,可以通过配置DataWorks(dataX企业版)导入Elasticsearch集群。
2.实时数据导入。实时数据源主要是流式数据服务DataHub, 配置DataHub任务即可同步至Elasticsearch集群。也可以自己开发程序调用DataHub的SDK获取实时数据,经过业务处理后,调用ES Rest Client SDK批量写入Elasticsearch。
3.冷热数据自动迁移。轨迹类实时数据默认先写入热数据集群(SSD盘Elasticsearch集群),对于热数据集群过期的索引(例如1个月前的索引)需要迁移到冷数据集群(SATA盘Elasticsearch)。为了实现数据跨集群迁移,我们开发了snapshot插件将索引备份到对象存储服务OSS或分布式文件系统盘古。配置定时任务,将热数据集群索引备份后,从冷数据集群恢复,然后再删除热集群中的过期索引,保持热数据集群只存储较小规模数据。冷数据集群的索引如果超过半年,则关闭索引,减少JVM堆内存占用。
4.配置索引主键字段。为了保证Elasticsearch集群和数据源记录的一致性,建议所有索引配置主键字段,而不是让Elasticsearch自动生成主键。配置数据业务主键字段作为Elasticsearch主键字段。如果没有主键字段,则将原始数据能确定记录惟一性的几个字段合并为主键,或者将所有字段值合并起来计算MD5值作为主键。
5.配置写入路由。如果业务上需要经常根据某个字段查询,例如用户ID、车牌号等的字段,写入时可以指定路由字段。
6.写入参数调优。调整数据写入任务参数,避免写入操作占用过多磁盘IO和CPU。使用批量请求,配置合理的写入线程数,调大索引刷新时间间隔refresh interval,调整事务日志translog同步策略。
六、数据查询策略。
1.冷热库异步查询。用户输入关键词查询时,优先从热数据集群查询,有结果立即返回,并估算命中记录条数。热数据集群命中结果集不足时,再查询冷数据集群。
2.跨集群搜索。业务上需要多个Elasticsearch集群一起参与检索时,可以通过Cross Cluster Search同时对多个集群发起检索请求合并检索结果。单独创建一个5节点的Cross Cluster,设置远程集群节点信息,用于跨集群搜索,不存储业务数据。
3.快速返回和超时设置。查询请求中设置参数teminate_after指定每个分片(shard)最多匹配N条记录后返回(例如10000),设置查询超时时间timeout(例如10s),避免查询一些宽泛的条件时耗费过多系统资源。
4.查询语法解析。解析用户查询条件,识别用户的查询类型,例如用户输入车牌号、证件号、年龄段等条件时,查询条件改写为字段精确匹配,无法识别的查询条件默认从全文字段匹配。
5.查询条件调优。查询结果不需要相关度排序时使用过滤器(filter),尽量使用路由(routing),设置较少的查询读取记录条数和字段,避免前缀模糊匹配,设置search_after规避深度翻页性能问题。
七、数据写入、查询性能测试。
SSD盘集群写入性能测试(测试环境:Elasticsearch6.3.2集群,单节点16核CPU、64G内存、2T SSD盘,写入10亿条记录,单条记录1KB,副本数为0,1台写入服务器):
SSD盘集群查询性能测试
SATA盘集群写入性能测试(测试环境:Elasticsearch5.5.3集群,单节点56核CPU、128G内存、12块 6T SATA盘,分别写入1亿、3亿、5亿、30亿、300亿条记录,单条记录1KB,0副本,50台写入服务器):
SATA盘集群查询性能测试
参考文档:
- 阿里云Elasticsearch帮助文档 https://help.aliyun.com/product/57736.html
- Elasticsearch参考 https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
- 《Elasticsearch: 权威指南》 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
- 《深入理解Elasticsearch》https://detail.tmall.com/item.htm?id=551001166567
- 《死磕Elasticsearch方法论》https://blog.csdn.net/laoyang360/article/details/79293493
- Elasticsearch索引别名和零停机 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index-aliases.html
- Elasticsearch自动按天创建索引脚本 https://blog.csdn.net/reblue520/article/details/80553317
- Elasticsearch NGram分词器 https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html
- Elasticsearch开源权限管理认证插件Search Guard https://github.com/floragunncom/search-guard
- Elasticsearch开源可视化管理插件cerebro https://github.com/lmenezes/cerebro
- Elasticsearch开源SQL插件 https://github.com/NLPchina/elasticsearch-sql
- Elasticsearch快照及恢复 https://help.aliyun.com/document_detail/65675.html
Elasticsearch技术交流钉钉群
【 报名开启】2018 Elastic & 袋鼠云 & 阿里云技术沙龙(杭州)
Elasticsearch • Allwang 发表了文章 • 1 个评论 • 2702 次浏览 • 2018-12-02 20:40
Day 2 - ES 6.x拼音分词高亮爬坑记
Advent • abia 发表了文章 • 10 个评论 • 8830 次浏览 • 2018-12-02 16:29
社区日报 第462期 (2018-11-28)
社区日报 • sterne vencel 发表了文章 • 0 个评论 • 1459 次浏览 • 2018-11-28 16:58
CentOS 7.4 下安装 ES 6.5.1 搜索集群
Elasticsearch • rochy 发表了文章 • 3 个评论 • 7017 次浏览 • 2018-11-23 00:02
一、准备安装
1、修改系统 hosts
vi /etc/hosts # 修改 hosts 文件,添加下面的内容
192.168.11.1 sky-00
192.168.11.2 sky-01
192.168.11.3 sky-02
192.168.11.4 sky-03
192.168.11.5 sky-04
192.168.11.6 sky-05
192.168.11.7 sky-06
2、角色分配
主机名 | 角色 | 内存分配 |
---|---|---|
sky-00 | Master | 4G |
sky-01 | Master | 8G |
sky-02 | Master+Data | 12G |
sky-03 | Data | 12G |
sky-04 | Data | 12G |
sky-05 | Data | 12G |
sky-06 | Data | 12G |
3、创建 ES 用户
adduser elastic # 新增用户
passwd elastic # 修改用户密码
4、创建 ES 数据和日志目录
cd /data/
mkdir elastic
cd elastic
mkdir data # 创建数据目录
mkdir log # 创建日志目录
chown -R elastic /data/elastic/ # 修改拥有着
5、调整文件句柄数以及可用进程数
Elasticsearch 要求其可用的文件句柄至少为 65536,同时要求其进程数限制至少为 2048,可用按照下面的指令进行修改。
分别对应以下两个报错信息:
- max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]
- max number of threads [1024] for user [es] is too low, increase to at least [2048]
vi /etc/security/limits.conf
* soft nofile 100001
* hard nofile 100002
* soft nproc 4096
* hard nproc 8192
elastic soft memlock unlimited
elastic hard memlock unlimited
6、设置内核交换
为了避免不必要的磁盘和内存交换,影响效率,需要将 vm.swappiness
修改为 1(进行最少量的交换,而不禁用交换)或者 10(当系统存在足够内存时,推荐设置为该值以提高性能),其默认值为 60。
此外需要修改最大虚拟内存 vm.max_map_count
防止启动时报错:max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
。
vi /etc/sysctl.conf
vm.swappiness = 1
vm.max_map_count = 262144
7、下载安装文件
mkdir /opt/downloads/
mkdir /opt/soft/
cd /opt/downloads/
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.5.1.tar.gz
wget https://artifacts.elastic.co/downloads/kibana/kibana-6.5.1-linux-x86_64.tar.gz
wget http://download.oracle.com/otn/java/jdk/xxxxxx/jdk-8u191-linux-x64.tar.gz
tar -zxvf elasticsearch-6.5.1.tar.gz -C /opt/soft/
tar -zxvf jdk-8u191-linux-x64.tar.gz -C /opt/soft/
tar -zxvf kibana-6.5.1-linux-x86_64.tar.gz -C /opt/soft/
chown -R elastic /opt/soft/elasticsearch-6.5.1/
chown -R elastic /opt/soft/kibana-6.5.1/
二、开始安装
1、配置 Java 环境
su elastic #切换到 elastic 用户
vi ~/.bashrc #只修改 elastic 用户自己的环境变量
export JAVA_HOME=/opt/soft/jdk1.8.0_191
export JRE_HOME=/opt/soft/jdk1.8.0_191/jre
export CLASSPATH=.:/opt/soft/jdk1.8.0_191/lib:/opt/soft/jdk1.8.0_191/jre/lib
export PATH=$PATH:/opt/soft/jdk1.8.0_191/bin:/opt/soft/jdk1.8.0_191/jre/bin
2、配置 ES 内存占用
cd /opt/soft/elasticsearch-6.5.1/config/
vi jvm.options
-Xms4g # 请根据自己机器配置调整
-Xmx4g
3、配置 Elasticsearch
下面的配置已经过多个生产环境验证,具体设置值仅供参考,请务必根据实际情况进行调整。
# ---------------------------------- Cluster -----------------------------------
#
# 设置集群名
cluster.name: cluster-name
#
# ------------------------------------ Node ------------------------------------
#
# 设置节点名
node.name: node01
# 设置角色
node.master: true
node.data: false
node.ingest: true
# 设置机架信息
#node.attr.rack: r1
#
# ----------------------------------- Paths ------------------------------------
#
# 设置数据路径
path.data: /data/elastic/data
# 设置日志路径
path.logs: /data/elastic/log
#
# ----------------------------------- Memory -----------------------------------
#
# 设置内存锁定
bootstrap.memory_lock: true
bootstrap.system_call_filter: false
#
# ---------------------------------- Network -----------------------------------
#
# 设置ip和端口
network.bind_host: sky-00
network.publish_host: 0.0.0.0
http.port: 9200
# 设置跨域访问
http.cors.enabled: true
http.cors.allow-origin: "*"
http.max_content_length: 500mb
# --------------------------------- Discovery ----------------------------------
# 设置zen发现范围(只需要填写主节点的 ip 即可)
discovery.zen.ping.unicast.hosts: ["sky-00", "sky-01", "sky-02"]
discovery.zen.no_master_block: write
discovery.zen.fd.ping_timeout: 10s
# 设置最小主节点个数,一般为:(master_node_count+1)/2
discovery.zen.minimum_master_nodes: 2
# ---------------------------------- Gateway -----------------------------------
#
# 设置在有4个节点后进行数据恢复
gateway.recover_after_nodes: 4
gateway.expected_nodes: 7
gateway.recover_after_time: 1m
#
# ---------------------------------- Various -----------------------------------
# 禁止通配符模式删除索引
action.destructive_requires_name: true
indices.recovery.max_bytes_per_sec: 200mb
indices.memory.index_buffer_size: 20%
# 默认开启全部类型脚本,可以通过下面配置进行限制
#script.allowed_types: inline
#script.allowed_contexts: search, update
# 关闭xpack的安全校验
xpack.security.enabled: false
# 开启 monitoring
xpack.monitoring.enabled: true
xpack.monitoring.collection.enabled: true
# 设置 monitoring 写入信息
xpack.monitoring.exporters:
sky:
type: http
host: ["sky-02", "sky-03", "sky-04", "sky-05", "sky-06"]
# 设置 monitoring 索引格式,默认是 YYYY-MM-DD(按天新建)
index.name.time_format: YYYY-MM
headers:
# 设置 Basic 认证信息(详见插件安装部分说明)
Authorization: "Basic XXXXXXXXXXXXXXX"
三、安装插件
1、安装插件
推荐安装的插件有:
- IK 中文分词插件
- Readonlyrest 安全认证插件
- elasticsearch-head 集群监控管理插件(chrome 插件)
插件下载链接: https://pan.baidu.com/s/1r_322unsIjoWlhY8u7pkBA 提取码: aupq
使用下面命令即可安装。
$ES_HOME/bin/elasticsearch-plugin -install file:///data/downloads/elasticsearch-analysis-ik-6.5.1.zip
$ES_HOME/bin/elasticsearch-plugin -install file:///data/downloads/readonlyrest-1.16.29_es6.5.1.zip
2、配置 Readonlyrest 安全认证
下面只简单介绍 Readonlyrest 的 Basic 认证,更高级的用法可以去官方网站查看,在 ES 安装目录的 conf 目录下新建文件 readonlyrest.yml
,并添加下面内容。
readonlyrest:
access_control_rules:
- name: "Require HTTP Basic Auth"
type: allow
auth_key: 用户名:密码
3、启动 ES
全部安装完成后,即可使用 elastic 用户启动 ES。
# 默认 ES 不支持 root 用户启动
su elastic
cd /opt/soft/elasticsearch-6.5.1/bin
./elasticsearch -d
四、在 Kibana 里面监控
在安装 ES 的时候,我们配置了 ES 的监控信息,这样我们就可以在 Kibana 中查看 ES 索引信息、node 信息等。
1、配置 Kibana
进入 Kibana 的解压目录下的 conf 文件夹,打开配置文件 kibana.yml
。
# 配置 kibana ui 的端口
server.port: 5601
# 配置 kibana 访问 ip
server.host: "0.0.0.0"
# 设置 ES 地址
elasticsearch.url: "http://sky-00:9200"
# dashboards. Kibana creates a new index if the index doesn't already exist.
#kibana.index: ".kibana"
# 打开 kibana 时默认页面
#kibana.defaultAppId: "home"
# ES Basic 认证信息
elasticsearch.username: "用户名"
elasticsearch.password: "密码"
# 设置时区信息
#i18n.locale: "en"
# 开启监控
xpack.monitoring.enabled: true
# 关闭 kibana 监控,默认为 true
xpack.monitoring.kibana.collection.enabled: false
2、对 Kibana 配置文件的说明
- ES Basic 认证信息配置(在启动时对 Kibana 索引进行维护)完成后,登陆 kibana 时,依旧需要输入认证信息;
- 由于 kibana 的 monitoring 无法设置新建的索引的索引名(无法配置 index.name.time_format),这样 kibana 每天会新建一个索引,由于 kibana 只是作为管理查看工具,因此关闭了 kibana 监控;
elasticsearch.url
该配置项无法设置多个 es 地址;如果你想实现类似负载均衡的功能,最简单的方法就是在 Kibana 机器上运行一个协调(Coordinating)节点。
3、监控界面
全部配置完成后,启动 kibana,打开 monitoring 即可开始监控 node、index 等。
五、设置索引模板
具体请参考之前发布的文章基于 IK 分词器的 ES 通用索引模板
Any Code,Code Any!
扫码关注『AnyCode』,编程路上,一起前行。
ELK 使用小技巧(第 1 期)
Elasticsearch • rochy 发表了文章 • 1 个评论 • 8640 次浏览 • 2018-10-27 19:16
ELK Tips 主要介绍一些 ELK 使用过程中的小技巧,内容主要来源为 Elastic 中文社区。
一、Logstash
1、Logstash 字符串分割(Split)
以下配置将 message
内容按照 \t
进行切分,为了使 \t
生效需要将 logstah.yml 中配置项 config.support_escapes
设置为 true,当设置为 true 时,带引号的字符串将处理转义字符,默认值为 false。
filter {
mutate {
split => {"message" => "\t"}
add_field => {
"ftimeold" => "%{[message][0]}"
}
}
}
2、Logstash 按文件读取内容并存储到 ES
下面的配置将读取/home/txts/*
下的文件,并读取整个文件内容,然后将文件内容存储到 test-text
索引中,同时该条记录的 _id
为文档的文件名。这里需要注意的是,想读取到文档末尾时,分隔符需设置为 EOF
。
input {
file {
path => ["/home/txts/*"]
start_position => "beginning"
mode => "read"
delimiter => "EOF"
file_completed_action => "log"
file_completed_log_path => "/home/logs/file.log"
}
}
output {
elasticsearch {
hosts => ["http://192.168.3.214:9200/"]
index => "test-text"
document_id => "%{path}"
}
stdout {}
}
3、ES Ingest Node 脚本案例
Ingest Node 可以使用多种过滤器对数据进行处理,其中 Script 脚本的功能非常强大,下面的案例实现了将一个 Json 结构进行了 Flat 化:
{
"script" : {
"lang" : "painless",
"source" : "def dict = ['result': new HashMap()]; for (entry in ctx['json'].entrySet()) { dict['result'][entry.getKey()] = entry.getValue(); } ctx['osquery'] = dict; ctx.remove('json');"
}
}
4、Logstash input file 插件中 sincedb 维护问题
- 如果不想保存 sincedb,可以使用下面配置:
sincedb_path => "/dev/null"
; - 如果希望被扫描的记录超过一段时间后自动被清除,可以使用
sincedb_clean_after => "2 weeks"
来实现,sincedb_clean_after
表示当一个文件在设定的时间内没有发生过任何变化,则关于这个文件的扫描记录将不会存储到 sincedb 里面,简单来说就是一条记录的过期时间。
二、Elasticsearch
1、ES 查询结果的一致性
为了保证用户每次查询结果的一致性(文档在结果中的顺序),可以在查询 url 里添加 preference=<some string>
这个参数,其中<some string>
可以是用户的 session ID,这样某一个用户查询的时候,查询会被固定在某几个 shard。
2、同义词的扩展或收缩
- 简单扩展,把同义词列表中的任意一个词扩展成同义词列表所有的词:
jump,hop,leap
; - 简单收缩,把左边的多个同义词映射到了右边的单个词:
leap,hop => jump
; - 类型扩展,完全不同于简单收缩或扩张,并不是平等看待所有的同义词,而是扩大了词的意义使被拓展的词更为通用:
"cat => cat,pet", "kitten => kitten,cat,pet", "dog => dog,pet" "puppy => puppy,dog,pet"
3、设置某个索引为只读状态
index.blocks.write
设置为 true 来禁止对索引的写操作,但索引的 metadatra 可以正常写。
PUT indexName/_settings
{
"index.blocks.write": true
}
4、Failed to process cluster event (put-mapping) within 30s
这个是创建 mapping 的时候超时了,默认是 30s 应该是集群处理不过来了。索引文件太多,使得集群的状态数据过多过大,在每个小时新建索引和设置索引 mapping 的时候,就产生集群状态更新任务交给 master 处理,master 在变更状态数据的时候是单线程处理的,如果集群总的状态数据很大,master处理这些任务就容易出现超时。
解决办法:
- 控制集群的总的索引数量,shard 数量;
- 如果同时创建的索引非常多,最好避免通过写入数据自动创建索引;
- 可以通过 cron 任务,预先顺序的创建好索引。
5、Get 查询获取不到数据,但是用 _search 就可以查询到
这种情况一般在索引时候加入了路由字段(routing),那么在 get,delete,update 操作中都必须使用路由字段。
PUT my_index/my_type/1?routing=user1&refresh=true
{
"title": "This is a document"
}
GET my_index/my_type/1?routing=user1
6、ES 5.X 版本多个 type 的数据迁移到 6.X
把 5.x 集群中的索引按不同 type 拆分 reindex 到 6.x 集群索引中,然后将拆分出来的多个索引使用别名进行组织;例如 5.x 集群中有索引 IndexA,该索引上有 typeA 和 typeB,reindex 到 6.x 集群IndexA_TypeA
和IndexB_TypeB
,reindex 语句如下所示:
POST _reindex
{
"source": {
"index": "IndexA",
"type": "TypeA",
"size": 10000
},
"dest": {
"index": "IndexA_TypeA"
}
}
最后给 6.x 集群的IndexA_TypeA
和IndexB_TypeB
添加别名 IndexA,用户查询时使用的索引名称就不用变化。
POST _aliases
{
"actions": [
{"add": {"index": "IndexA_TypeA", "alias": "IndexA"}},
{"add": {"index": "IndexA_TypeB", "alias": "IndexA"}}
]
}
7、reindex 将多个索引合并成一个索引,需要重新设置新索引的 mapping 吗?
需要在 reindex 之前为新索引重新设置 mapping ,reindex 只是通过类似 scroll 的方式把数据 bulk 到新的索引,不会自动同步原索引的 mappings 信息。
8、集群的 discovery.zen.ping.unicast.hosts 配置
只需要配置主节点(master)地址即可。
discovery.zen.ping.unicast.hosts:
- 192.168.1.10:9300
- 192.168.1.11
- seeds.mydomain.com
9、ES 的 path.data 配置多个盘的路径,查询效率与单个存储盘的效率比,哪个效率高些?
想最大程度发挥磁盘读写 io,还是推荐 RAID0。
使用多路径不一定会提升读写速度,和集群 shard 的数量有关系;主要是因为一个 shard 对应的文件,只会放到其中一块磁盘上,不会跨磁盘存储。比如一个极端的场景,集群 shard 数量比较少,每个结点上就一个shard,那么读写只会有一块磁盘发挥作用,其他磁盘都空闲的。
多路径对读写有提升比较大的场景,是每个结点上 shard 数量至少比盘的数量多,并且 shard 大小也差别不太多;shard 数量比较少,shard 大小差别太大,可能产生读写热点问题,即有的磁盘磁盘很忙,有的很闲。
ES 不会将一个索引的主副分片分配到同一台机器,所以即使一台机器的 RAID0 坏了,不会导致数据丢失,仅仅是副本没有了。
用 RAID0 的负面影响主要是磁盘损坏的时候,需要恢复的数据比较多;多路径磁盘,坏一块只会丢一部分数据,恢复数据会比较快;但是他也有缺陷,比如容易出现读写热点问题以及磁盘空间使用不均匀问题。
10、查询索引分片(Shard)位置的接口
# 推荐
GET /_cat/shards/<index_name>?v
GET /_cluster/state/routing_table
11、multi_match 与 match_phrase 的区别
- multi_match 是对
boolQuery().should(matchQuery(field, keyword))
的一种简化,简单说就是一个关键词,匹配多个字段,匹配方式为 matchQuery,正常的全文匹配。 - match_phrase 简单说就是要匹配一个短语,例如你输入的文本为:中国人,如果被分词为:中国/人,那么查找时候会在指定的字段先查找到 "中国" 这个 term,然后在 "中国" 这个 term 后面去查找 "人"这个term(有顺序要求),如果匹配到则认为匹配成功;所以更像是在匹配一个短语(连贯的句子)。
12、analyzer, tokenizer, token-filter 有什么区别
- analyzer :分析器,analyzer = 1 个 tokenizer + 若干个 token-filter;
- tokenizer :分词器,主要用于对文本进行切割;
- token-filter :过滤器,主要对 tokenizer 切割后的 term 进行再次处理。
13、_source 字段的用途
简单来说:_source 字段用于存储最原始的 JSON 文档内容(创建索引时传递的),这个字段不能被搜索,它可以在 get 或者 search 请求阶段进行返回;此外它会参与字段高亮计算、文档的更新等操作,一般不推荐关闭 _source 字段。
三、Kibana
1、kibana 表格默认排序
在设计表格的时候直接点击需要排序的那一列,然后让它按照倒序或者正序排序,然后点击保存即可,这样这个表格默认就是按照这一列倒序或者正序排列的。
Any Code,Code Any!
扫码关注『AnyCode』,编程路上,一起前行。