The requested URL was not found on this server. 不管你信不信,反正我是没找到
elaseticsearch

elaseticsearch

请问基于es数组进行query_string查询时,如何在highlight中返回最佳匹配的数据?

ElasticsearchOmbres 回复了问题 • 2 人关注 • 2 个回复 • 2276 次浏览 • 2024-01-17 13:37 • 来自相关话题

elasticsearch 缺乏足够的无分段虚拟地址空间,导致集群故障,请问有什么优化方案吗

ElasticsearchFFFrp 回复了问题 • 3 人关注 • 2 个回复 • 1063 次浏览 • 2023-08-25 10:48 • 来自相关话题

请教一个问题,ES must_not 多条件查询时不符合预期

ElasticsearchCharele 回复了问题 • 2 人关注 • 1 个回复 • 1002 次浏览 • 2023-08-10 20:11 • 来自相关话题

Web Scraper + Elasticsearch + Kibana + SearchKit 打造的豆瓣电影top250 搜索演示系统

Elasticsearch 发表了文章 • 0 个评论 • 4951 次浏览 • 2023-04-09 10:56 • 来自相关话题

Web Scraper + Elasticsearch + Kibana + SearchKit 打造的豆瓣电影top250 搜索演示系统

作者:小森同学

声明:电影数据来源于“豆瓣电影”,如有侵权,请联系删除

1.png

2.png

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

参考 https://github.com/searchkit/searchkit-starter-app

elasticsearch es如何统计用户文档数量范围内容聚合?

ElasticsearchCharele 回复了问题 • 5 人关注 • 3 个回复 • 2270 次浏览 • 2022-06-01 13:42 • 来自相关话题

es nested 根据多条件排除数据

Elasticsearchdachuxing 回复了问题 • 3 人关注 • 2 个回复 • 2734 次浏览 • 2022-05-20 17:36 • 来自相关话题

Elasticsearch 分片分配失败

ElasticsearchCharele 回复了问题 • 2 人关注 • 1 个回复 • 1630 次浏览 • 2022-05-07 20:11 • 来自相关话题

logstash推送数据到es执行两次脚本

Logstashmedcl 回复了问题 • 2 人关注 • 1 个回复 • 2130 次浏览 • 2022-04-02 13:28 • 来自相关话题

elasticsearch [[node.max_local_storage_nodes] (was [1])

Elasticsearchyimusidian 回复了问题 • 2 人关注 • 2 个回复 • 3029 次浏览 • 2022-03-25 16:07 • 来自相关话题

elasticsearch 分索引后如何快速更新指定数据?

Elasticsearchliujiacheng 回复了问题 • 2 人关注 • 1 个回复 • 1125 次浏览 • 2022-03-24 09:41 • 来自相关话题

elasticsearch IllegalArgumentException[Operation term is newer than the current term; current term[2

Elasticsearchlocatelli 回复了问题 • 2 人关注 • 1 个回复 • 1358 次浏览 • 2022-02-15 11:17 • 来自相关话题

给Zblogphp插上Elasticsearch的翅膀

Elasticsearch 发表了文章 • 0 个评论 • 1438 次浏览 • 2021-10-11 23:00 • 来自相关话题

# 给Zblogphp插上Elasticsearch的翅膀 找遍了zblog的应用中心,未发现有使用Elasticsearch搜索引擎的插件。国庆闲来无事,根据zblogphp的机制,开发了一个基于Elasticsearch的插件。 本插件使用简单,需要有一个Elasticsearch7.x的环境(基于7.x版本开发),Elasticsearch 安装[IK](https://github.com/medcl/elasticsearch-analysis-ik)、[pinyin](https://github.com/medcl/elast ... pinyin),[中文简繁體转换](https://github.com/medcl/elast ... onvert) 插件。安装好该插件后,只需要配置好账号密码,点击创建索引模板即可。发布和编辑文章时,会自动根据索引模板,创建post索引,同步文章数据。搜索时,直接接管原有的搜索逻辑,无需调整程序和模板。 后台配置截图:
123.jpg
配置好连接,端口,账号和密码,点击“测试连接”,弹出连接成功,展示版本号,即可点击保存配置,如果这4项错误,连接不上Elasticsearch,获取不到ES的版本号,将无法保存配置。
Dingtalk_20211009115321.jpg
看到这个提示,便可以点击“保持配置”。这里有一项“切换搜索 Elasticsearch”,开启,前端搜索即切换到了Elastisearch搜索引擎。
234.jpg
在配置好了基本设置以后,点击索引模板,可以预览到索引模板,点击“创建索引模板”,即可在Elasticsearch服务器创建好索引模板,成功后,会在说明栏展示绿色的“已创建”,如果未创建,展示红色的“未创建”。发布和编辑文章时,会根据该索引模板,自动创建好索引,同步文章。 以下是搜索效果截图:
345.jpg

通过python脚本迁移ES的template模板

Elasticsearch 发表了文章 • 0 个评论 • 1991 次浏览 • 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版本高亮查询不生效的问题

Elasticsearchtongchuan1992 回复了问题 • 2 人关注 • 1 个回复 • 1303 次浏览 • 2021-08-22 14:51 • 来自相关话题

elasticsearch query与agg分开是否可以提高性能?

Elasticsearchtongchuan1992 回复了问题 • 2 人关注 • 1 个回复 • 1702 次浏览 • 2021-07-08 09:24 • 来自相关话题

条新动态, 点击查看
kennywu76

kennywu76 回答了问题 • 2018-08-01 18:34 • 5 个回复 不感兴趣

Elasticsearch查询时指定分词器

赞同来自:

有三种方式可以指定查询分析器:
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 个回复 • 5715 次浏览 • 2019-02-22 15:42 • 来自相关话题

请问基于es数组进行query_string查询时,如何在highlight中返回最佳匹配的数据?

回复

ElasticsearchOmbres 回复了问题 • 2 人关注 • 2 个回复 • 2276 次浏览 • 2024-01-17 13:37 • 来自相关话题

elasticsearch 缺乏足够的无分段虚拟地址空间,导致集群故障,请问有什么优化方案吗

回复

ElasticsearchFFFrp 回复了问题 • 3 人关注 • 2 个回复 • 1063 次浏览 • 2023-08-25 10:48 • 来自相关话题

请教一个问题,ES must_not 多条件查询时不符合预期

回复

ElasticsearchCharele 回复了问题 • 2 人关注 • 1 个回复 • 1002 次浏览 • 2023-08-10 20:11 • 来自相关话题

elasticsearch es如何统计用户文档数量范围内容聚合?

回复

ElasticsearchCharele 回复了问题 • 5 人关注 • 3 个回复 • 2270 次浏览 • 2022-06-01 13:42 • 来自相关话题

es nested 根据多条件排除数据

回复

Elasticsearchdachuxing 回复了问题 • 3 人关注 • 2 个回复 • 2734 次浏览 • 2022-05-20 17:36 • 来自相关话题

Elasticsearch 分片分配失败

回复

ElasticsearchCharele 回复了问题 • 2 人关注 • 1 个回复 • 1630 次浏览 • 2022-05-07 20:11 • 来自相关话题

logstash推送数据到es执行两次脚本

回复

Logstashmedcl 回复了问题 • 2 人关注 • 1 个回复 • 2130 次浏览 • 2022-04-02 13:28 • 来自相关话题

elasticsearch [[node.max_local_storage_nodes] (was [1])

回复

Elasticsearchyimusidian 回复了问题 • 2 人关注 • 2 个回复 • 3029 次浏览 • 2022-03-25 16:07 • 来自相关话题

elasticsearch 分索引后如何快速更新指定数据?

回复

Elasticsearchliujiacheng 回复了问题 • 2 人关注 • 1 个回复 • 1125 次浏览 • 2022-03-24 09:41 • 来自相关话题

elasticsearch IllegalArgumentException[Operation term is newer than the current term; current term[2

回复

Elasticsearchlocatelli 回复了问题 • 2 人关注 • 1 个回复 • 1358 次浏览 • 2022-02-15 11:17 • 来自相关话题

关于elasticsearch 5.4版本高亮查询不生效的问题

回复

Elasticsearchtongchuan1992 回复了问题 • 2 人关注 • 1 个回复 • 1303 次浏览 • 2021-08-22 14:51 • 来自相关话题

elasticsearch query与agg分开是否可以提高性能?

回复

Elasticsearchtongchuan1992 回复了问题 • 2 人关注 • 1 个回复 • 1702 次浏览 • 2021-07-08 09:24 • 来自相关话题

elasticsearch如何使用script(python)的复杂语句?

回复

ElasticsearchCharele 回复了问题 • 3 人关注 • 3 个回复 • 3545 次浏览 • 2021-07-02 14:57 • 来自相关话题

關於watcher 判斷

回复

ElasticsearchWalterX 发起了问题 • 1 人关注 • 0 个回复 • 1368 次浏览 • 2021-07-01 16:04 • 来自相关话题

elasticsearch中match无法进行匹配查询(匹配查询match查不结果)

回复

Elasticsearchguoyanbiao520 回复了问题 • 5 人关注 • 4 个回复 • 6909 次浏览 • 2021-05-21 15:50 • 来自相关话题

Web Scraper + Elasticsearch + Kibana + SearchKit 打造的豆瓣电影top250 搜索演示系统

Elasticsearch 发表了文章 • 0 个评论 • 4951 次浏览 • 2023-04-09 10:56 • 来自相关话题

Web Scraper + Elasticsearch + Kibana + SearchKit 打造的豆瓣电影top250 搜索演示系统

作者:小森同学

声明:电影数据来源于“豆瓣电影”,如有侵权,请联系删除

1.png

2.png

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

参考 https://github.com/searchkit/searchkit-starter-app

给Zblogphp插上Elasticsearch的翅膀

Elasticsearch 发表了文章 • 0 个评论 • 1438 次浏览 • 2021-10-11 23:00 • 来自相关话题

# 给Zblogphp插上Elasticsearch的翅膀 找遍了zblog的应用中心,未发现有使用Elasticsearch搜索引擎的插件。国庆闲来无事,根据zblogphp的机制,开发了一个基于Elasticsearch的插件。 本插件使用简单,需要有一个Elasticsearch7.x的环境(基于7.x版本开发),Elasticsearch 安装[IK](https://github.com/medcl/elasticsearch-analysis-ik)、[pinyin](https://github.com/medcl/elast ... pinyin),[中文简繁體转换](https://github.com/medcl/elast ... onvert) 插件。安装好该插件后,只需要配置好账号密码,点击创建索引模板即可。发布和编辑文章时,会自动根据索引模板,创建post索引,同步文章数据。搜索时,直接接管原有的搜索逻辑,无需调整程序和模板。 后台配置截图:
123.jpg
配置好连接,端口,账号和密码,点击“测试连接”,弹出连接成功,展示版本号,即可点击保存配置,如果这4项错误,连接不上Elasticsearch,获取不到ES的版本号,将无法保存配置。
Dingtalk_20211009115321.jpg
看到这个提示,便可以点击“保持配置”。这里有一项“切换搜索 Elasticsearch”,开启,前端搜索即切换到了Elastisearch搜索引擎。
234.jpg
在配置好了基本设置以后,点击索引模板,可以预览到索引模板,点击“创建索引模板”,即可在Elasticsearch服务器创建好索引模板,成功后,会在说明栏展示绿色的“已创建”,如果未创建,展示红色的“未创建”。发布和编辑文章时,会根据该索引模板,自动创建好索引,同步文章。 以下是搜索效果截图:
345.jpg

通过python脚本迁移ES的template模板

Elasticsearch 发表了文章 • 0 个评论 • 1991 次浏览 • 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 个评论 • 10346 次浏览 • 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 个评论 • 7096 次浏览 • 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

Elasticsearchrochy 发表了文章 • 5 个评论 • 7356 次浏览 • 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 做为原始数据的存储层,可以很好的进行数据去重、数据质量分析,还可以提供一些即时的数据服务,例如趋势展示、汇总分析等。

对 Hadoop 数据进行交互分析

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 中文分词插件

Adventrochy 发表了文章 • 0 个评论 • 6786 次浏览 • 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 基本使用流程:

  1. 实例化 TokenStream,向 AttributeSource 添加/获取属性(词汇单元文本、位置增量、偏移量、词汇类型等);
  2. 调用 reset() 方法,将流(stream)重置到原始(clean)状态;
  3. 循环调用 incrementToken() 方法,并处理 Attribute 属性信息,直到它返回 false 表示流处理结束;
  4. 调用 end() 方法,确保流结束(end-of-stream)的操作可以被执行;
  5. 调用 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离做好搜索还差多远?

Adventnodexy 发表了文章 • 2 个评论 • 8426 次浏览 • 2018-12-21 17:27 • 来自相关话题

作者:杨振涛 搜索引擎架构师@vivo  首次发布:Elasticsearch中文社区 发布日期:2018-12-22

搜索引擎作为互联网发展历史中一个非常典型的产品/业务形态,时至今日并没有太大的突破性变化;主流形态可以划分为大搜、垂搜、企业级搜索和站内/app内搜索等。除了Google, Yahoo, Bing, Ask 等以及国内百度、搜狗、360、神马等是人们熟识的大搜之外,非业内人士还真不知道其他还有哪些公司以及有哪些搜索产品或业务场景。 实际上,在信息爆炸的时代,几乎每家有点儿规模的公司都或多或少要涉及到搜索引擎,最起码你需要接触SEO/SEM。本文将从非大搜企业的搜索需求出发,并基于开源技术栈来介绍和探讨搜索引擎在实践中的几个核心任务及其主要解决思路。同时为了避免重复,本文以外链形式引用了大量网络已有的国内外公开资料,方便大家参考,需要注意的是部分内容可能会随着时间推移而过期或链接失效。 提到开源搜索引擎,在Java技术栈里以Lucene, Solr/SolrCloud及Elasticsearch为代表的几个项目可能最为流行。本文的写作初衷是解答”熟练使用ES等开源搜索引擎解决方案以后,要如何才能做好搜索产品/业务?“ 希望对你有所帮助,如果你有关于此话题的更多实践经验或不同见解,欢迎留言评论交流。    1. 做好搜索引擎意味着什么?   有一位同行的文章总结了好的搜索引擎的衡量维度:
  • 相关性
  • 体验
  • 性能
其中相关性是非常重要的一个维度,这里我将通过引用一篇文章来介绍什么是”相关性工程“以及”相关性工程师“ http://www.flax.co.uk/blog/201 ... ound/ 。 相关性工程中的“相关性”,主要是指代用户的Query与索引库中的Doc之间的相关性,所以可以分别从索引数据和Query两个方面来考虑。 相关性工程考虑的第一个特征就是基于已有索引数据的文本相关度计算,通常有TF-IDF、BM25、BM25F等。Elasticsearch早期的版本默认都是TF-IDF,目前已更改为BM25。对于中文数据,分词方法和策略也会直接影响到文本相关度的计算;其次匹配方式也非常重要;最后就是基于此的相关性算分了。 相关性工程还可以考虑更多的特征,尤其是从索引数据之外来挖掘出的特征,比如索引文档的权威性、时效性、专业性、质量与口碑评分、热度与流行度等。结合NLP技术,相关性工程还可以考虑语义距离等特征,丰富召回结果。当然这些特征的处理与机器学习中的特征工程基本一致,比如涉及归一化问题、权重问题、稀疏性问题、非典型分布等等。 相关性工程考虑的另一个重要特征是用户点击反馈数据,即对于用户所看到的搜索结果列表,点击行为被看作是对当前搜索结果的一种认可,用户点了哪个位置的doc对于继续优化相关性至关重要。这两天有个著名案例就是Google的劈柴在听证会上解释为什么在Google搜索Idiot出现的都是特朗普的照片。 体验涉及的方面较多,最重要的就是产品功能和交互方面的体验了,比如一个典型的搜索产品,C端可能具备以下功能:
  • 搜索前:搜索框,搜索入口,热搜榜/飙升榜/大家都在搜,搜索发现,默认搜索词,历史搜索记录,猜你想搜,分类搜索,语音输入搜索/图片搜索; 广告位
  • 搜索中:搜索联想直达,搜索联想词,输入纠错,关键词匹配高亮
  • 搜索后:搜索结果列表,列表页推荐/广告,特形展示,列表穿插,搜了还搜,搜索详情页,详情页搜索推荐,无结果及少结果填充 ,筛选条件/筛选器,自主排序,列表样式切换(宫格 | 列表)
除了产品功能,还需要考虑搜索引擎的可运营性,比如搜索运营管理系统,至少要具备基本功能的各种黑白灰名单,包括人工干预,优化分词的自定义词典、同义词典、停用词典,以及对查询词的强制改写或者升降权;对索引内容的管控,比如对检索字段的;对召回和排序的相关参数的优化和调整等等;此外,还有配套的SEO或ASO系统,以及各种数据指标相关的看板系统。 而搜索结果中的特形展示,也是目前比较主流的产品形式,不管是自然结果还是搜索广告,都可以提供更快捷的体验,甚至一度成为知识图谱在搜索产品中应用的代表性功能。另外搜索联想中的直达服务,也是目前比较流行的,可以进一步缩短用户的操作路径,直达目标内容或服务。 更多关于产品体验和设计类问题可以参考  http://www.woshipm.com/tag/搜索功能  性能方面,搜索引擎的每一次查询理论上都是实时运算,大部分搜索引擎系统都是实时或准实时的,这就要求在用户感知上要有基本的响应时间(RT)保障,比如在国内公网环境下,200ms是比较优秀的体验,300ms-500ms是正常的体验,500ms+就需要尽快去优化。除去其中的网络I/O等开销,对于后端搜索服务的RT,一般是T99在100ms以内,T90在50ms以内,具体标准取决与当事业务和产品。除了RT,可用性也是非常重要的,一般要求99.9%以上;另外,索引数据的生效时间也很重要,比如新加入的索引,或者已有索引的更新和删除,秒级生效是比较好的体验。需要明确的一点是,这里的性能指标我们针对的是To C用户,如果是企业级搜索甚至是基于ES的一个即时查询分析系统,可能复杂查询的秒级响应也是很正常的。 延伸阅读: 阿里云-开放搜索-最佳实践-功能篇-相关性实践 https://help.aliyun.com/document_detail/29186.html  Defining relevance engineering 什么是相关性工程 http://www.flax.co.uk/blog/201 ... ound/    2. 搜索引擎是典型的机器学习问题   云计算、大数据、AI 先后成为IT与互联网行业的热点,三者经常被称为CBA或ABC技术,而这些都与一家公司密切相关,那就是 Google !   众所周知 Google 是一家著名的全球搜索引擎公司,但其产品远不止搜索引擎。从Google的三驾马车 GFS, MapReduce, BigTable开始,后来有了 Yahoo牵头的开源实现Hadoop(Hadoop最早来自于Nutch,是的,没错,就是那个开发了 Lucene 的 Doug Cutting所开源的Nutch,他被称作Hadoop之父),到后来的云计算与大数据技术蓬勃,到今天的AI热潮,各种深度学习各种NN, Google Brain,开源的Tensorflow,对Google来说这一切都是搜索引擎业务驱动的水到渠成的发展轨迹。 可以说搜索引擎是天生的机器学习问题,有着诸多的机器学习/深度学习应用场景。这里顺便DISS下一些眼高手低的迷糊党,互联网圈儿曾有人遇到求职者表示想做AI,却不做搜索不做推荐不做广告!(本人内心:你咋不上天呢!请记住AI is a buzzword. )当今的互联网或移动互联网,搜索、推荐与广告是三大典型的所谓AI应用落地方向,其他的也有但并未发展成熟,至少还没有成熟的变现模式;而这三者或多或少有些交集,搜索几乎是最基础的一个,比如推荐也需要建立索引,需要检索TOP N,搜索广告也需要做召回和排序。 如果我们把搜索引擎的核心模型简化下,其实主要是在解决三大类问题:  - 数据: 内容侧的爬虫,预处理,内容分析和理解,索引建立和存储 ;用户侧的Query理解,改写,意图识别等 - 算法/模型 :把相关性和排序等业务问题抽象为回归或分类/聚类问题,特征工程,离线训练模型,在线预测 - 策略:为满足业务需求而制定并持续优化的一系列规则、模型或模型组合、参数优化等活动,并通过工程化实现体现到线上系统,以及配套的试验和评估系统    2.1 Ranking  排序 常见的排序策略有:
  • 单维度排序:顾名思义按照单个维度来排序,没有任何复杂性可言,在召回结果集不太大的情况下实时排序即可。
  • 优先级排序:相对单维度排序而已一般是先按维度A排,当A的排序依据一样或相等时再按维度B排序,以此类推。
  • 加权排序   :针对多个维度或特征,赋予不同权重,并按求和之后的得分来排序;实践中通常会采用分层加权排序(第一层加权排序之后,得到不少于2个得分,继续加权后排序),或者分组加权排序(第一层分组来加权排序后,对所得到的得分可能按业务需求进行非求和类的运算比如乘法,再按最终得分排序)的策略。 加权排序的难点在于,如何设置并持续优化这些权重,通常会建模为典型的机器学习问题来拟合。
  • 机器学习排序 :即所谓LTR,根据用户点击或人工标注数据集建立学习目标,然后通过特征工程来挖掘与目标有关系的一系列特征,并建立学习模型,通过训练集获得模型参数,以该组参数为基准做预测,上线后再基于用户点击数据持续优化该模型的参数。LTR是一个通用方法的称谓,不是某一个具体算法的名称,具体算法名称参见下文。
排序问题可以简单抽象成为预测用户点击列表中对象的概率问题。   2.2 ES生态内的 LTR  关于LTR的理论和方法学,已经有很多论文和资料了 ( 参考 wikipedia LTR简介-微软亚研院, LTR Pairwise to Listwise ,  大规模LTR-GoogleLTR书籍 ),感兴趣的可以阅读,这里主要提供几个JAVA和ES生态的工程实现参考。 es-ltr插件  http://es-learn-to-rank.labs.o19s.com/  Set of command line tools for Learning To Rank https://github.com/SeaseLtd/ltr-tools es的ranking evaluation api https://www.elastic.co/guide/e ... .html  Java LTR类库: RankLib  https://sourceforge.net/p/lemur/wiki/RankLib/ 支持算法如下:
  • MART (Multiple Additive Regression Trees, a.k.a. Gradient boosted regression tree) 
  • RankNet 
  • RankBoost 
  • AdaRank 
  • Coordinate Ascent 
  • LambdaMART 
  • ListNet 
  • Random Forests  
延伸阅读:   2.3 典型垂搜 电商与O2O搜索 案例: 天猫,淘宝,京东,美丽说蘑菇街,有赞 ,美团点评,饿了么  阿里研究员徐盈辉:在线AI技术在搜索与推荐场景的应用 https://yq.aliyun.com/articles/107941   阿里巴巴资深算法专家三桐:人工智能在搜索中的应用 https://yq.aliyun.com/articles/288065   阿里巴巴年度技术总结 - 人工智能在搜索的应用和实践 http://www.sohu.com/a/214123235_680198  电子商务搜索系统架构参考 (京东) https://blog.csdn.net/hongseji ... 08067   电商搜索之动态属性值(特征值)聚合 (举例 京东和solr实现)  https://blog.csdn.net/hu948162 ... 80071   劈开迷雾,蘑菇街电商搜索架构及搜索排序实现 https://blog.csdn.net/huangshu ... 46694 有赞搜索引擎实践(工程篇)  https://www.cnblogs.com/hsydj/p/5303050.html 有赞搜索引擎实践(算法篇)  https://www.cnblogs.com/hsydj/p/5402945.html  有赞搜索系统的架构演进   https://tech.youzan.com/search-tech-1/    有赞搜索系统的技术内幕  https://tech.youzan.com/search-tech-2/   电商系统如何做搜索引擎? https://blog.csdn.net/zysgdhf4 ... 53999   电商检索系统总结——功能篇 https://www.cnblogs.com/wanghuaijun/p/7112952.html  App搜索  案例:Google Play, 应用宝,各种手机助手,Apple App Store及各大其他手机厂商的应用商店/应用市场以及互联网电视/机顶盒等的应用商店/应用市场     3. 搜索引擎的效果评价 我们在团队内有句戏言:看一个搜索团队是否专业,就看他们是否做效果评价。 在与国内外的搜索工程师交流和学习过程中,还有一个说法是:搜素引擎的优化就像一个打地鼠游戏,你解决一类bad case的同时很难确认其是否会带来新的bad case以及会带来多少。 需要区别的是,搜索引擎中使用到的机器学习/深度学习算法本身的效果评估(如分类算法的Accuracy、Precision、Recall、F1、ROC、AUC 等)并不能直接代替搜索引擎的效果评价。通常我们分为人工主观评测和业务指标评测。 参考:   4. NLP 自然语言处理 我们知道搜索引擎的上游学科是信息检索(IR),这也是搜索引擎的理论基础。而自然语言处理(NLP)在信息检索领域尤其是搜索引擎中有着至关重要的地位和作用。一方面我们对于被搜索的内容数据的理解,需要借助NLP来提升语义性和智能程度,另一方面我们对于用户Query和意图的理解,也需要借助NLP相关方法和技术来完成。 实际上ES的很多特性已经非常强大, 可以作为基本的文本分析和挖掘工具使用,这也是解释了ES官方博客以及其他博客有分享一些文章,主题是关于使用ES来进行文本分类或者实现推荐系统。 总结一下,想要做好一个典型的搜索引擎产品,除了熟练使用ES,还需要考虑搜索产品的功能完备性、体验优劣、性能以及相关性,而相关性涉及对内容数据的理解和挖掘、对用户Query的理解和意图识别,以及检索过程中的特征选取和权重优化、算分、排序,最后是比较重要的效果评价。这个过程中NLP的应用也非常多,除了基本的分词,还可能涉及非必留、词性识别、纠错、繁简体、多语言、文本向量化及语义距离计算等。 最后推荐一本搜索必读书籍—— 吴军的《数学之美》第二版 https://book.douban.com/subject/26163454/    第1 章 文字和语言 vs 数字和信息 第2 章 自然语言处理 — 从规则到统计 第3 章 统计语言模型 第4 章 谈谈分词 第5 章 隐含马尔可夫模型 第6 章 信息的度量和作用 第7 章 贾里尼克和现代语言处理 第8 章 简单之美 — 布尔代数和搜索引擎 第9 章 图论和网络爬虫 第10章 PageRank — Google的民主表决式网页排名技术 第11章 如何确定网页和查询的相关性 第12章 有限状态机和动态规划 — 地图与本地 第13章 Google AK-47 的设计者 — 阿米特· 辛格博士 第14章 余弦定理和新闻的分类 第15章 矩阵运算和文本处理中的两个分类问题 第16章 信息指纹及其应用 第17章 由电视剧《暗算》所想到的 — 谈谈密码学的数学原理 第18章 闪光的不一定是金子 — 谈谈搜索引擎 第19章 谈谈数学模型的重要性 第20章 不要把鸡蛋放到一个篮子里 — 谈谈最 第21章 拼音输入法的数学原理 第22章 自然语言处理的教父马库斯和他的优秀弟子们 第23章 布隆过滤器 第24章 马尔可夫链的扩展 — 贝叶斯网络 第25章 条件随机场、文法分析及其他 第26章 维特比和他的维特比算法 第27章 上帝的算法 — 期望最大化算法 第28章 逻辑回归和搜索广告 第29章 各个击破算法和Google 云计算的基础 第30章 Google 大脑和人工神经网络 第31章 大数据的威力——谈谈数据的重要性          

Day 7 - Elasticsearch中数据是如何存储的

Adventweizijun 发表了文章 • 7 个评论 • 68635 次浏览 • 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。

elasticsearch_store_arc.png

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的一些相关信息。

文件示例

elasticsearch_store_segments.png

具体实现类

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&lt;String, String&gt; 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对应的文件列表等信息。

文件示例

elasticsearch_store_si.png

具体实现类

/**
 * 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,包括是否存储、是否索引,是否分词,是否需要列存等等。

文件示例

elasticsearch_store_fnm.png

具体实现类

/**
 *  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的具体内容。

文件示例

elasticsearch_store_fdt.png

具体实现类

/**
 * 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 图为:

elasticsearch_store_tip1.png

elasticsearch_store_tip2.png

文件示例

elasticsearch_store_tip3.png

具体实现类

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中的一些高级特性

文件示例

elasticsearch_store_doc.png

具体实现类

/**
 * 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的映射。

文件示例

elasticsearch_store_dvd.png

具体实现类

/** 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;
  }

参考资料

lucene source code

lucene document

lucene字典实现原理——FST

Day 4 - PB级规模数据的Elasticsearch分库分表实践

Adventouyangchucai 发表了文章 • 4 个评论 • 18429 次浏览 • 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集群节点配置

pb001.jpg

二、索引设计。

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个并发查询)

pb002.jpg

测试分词效果: 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台写入服务器):

pb003.jpg

SSD盘集群查询性能测试

pb004.jpg

SATA盘集群写入性能测试(测试环境:Elasticsearch5.5.3集群,单节点56核CPU、128G内存、12块 6T SATA盘,分别写入1亿、3亿、5亿、30亿、300亿条记录,单条记录1KB,0副本,50台写入服务器):

pb005.jpg

SATA盘集群查询性能测试

pb006.jpg

参考文档:

  1. 阿里云Elasticsearch帮助文档 https://help.aliyun.com/product/57736.html
  2. Elasticsearch参考 https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
  3. 《Elasticsearch: 权威指南》 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
  4. 《深入理解Elasticsearch》https://detail.tmall.com/item.htm?id=551001166567
  5. 《死磕Elasticsearch方法论》https://blog.csdn.net/laoyang360/article/details/79293493
  6. Elasticsearch索引别名和零停机 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index-aliases.html
  7. Elasticsearch自动按天创建索引脚本 https://blog.csdn.net/reblue520/article/details/80553317
  8. Elasticsearch NGram分词器 https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html
  9. Elasticsearch开源权限管理认证插件Search Guard https://github.com/floragunncom/search-guard
  10. Elasticsearch开源可视化管理插件cerebro https://github.com/lmenezes/cerebro
  11. Elasticsearch开源SQL插件 https://github.com/NLPchina/elasticsearch-sql
  12. Elasticsearch快照及恢复 https://help.aliyun.com/document_detail/65675.html

Elasticsearch技术交流钉钉群

dingdingpng.png

【 报名开启】2018 Elastic & 袋鼠云 & 阿里云技术沙龙(杭州)

ElasticsearchAllwang 发表了文章 • 1 个评论 • 2692 次浏览 • 2018-12-02 20:40 • 来自相关话题

互联网时代,十亿、百亿、千亿的数据日志呈井喷式增长,基于日志搜索分析的需求也越来越强烈。Elasticsearch 作为一个分布式、可扩展、实时的搜索与数据分析引擎, 在大体量的数据处理上,无论实在全文搜索,还是在结构化数据统计中,都有非常大的优势。然而在真正实践过程中海量数据如何高效采集,如何合理优化分配索引,如何规划集群,如何满足业务分析需求都是我们可能会面临的问题。 本次袋鼠云联合阿里云、Elastic 中文社区,共同邀请滴滴、有赞等行业技术专家一同分享和探讨各自领域Elastic的实践。   本次活动时间12月15日 周六人数限制100人,大家抓紧报名哈,报名链接https://meetup.elasticsearch.c ... .html。 参与线下互动还有机会获得技术书籍与精美礼品哦!!! 不出意外,这应该是2018年Elastic在杭州的最后一次沙龙,小伙伴们抓紧今年的尾巴,不放过任何学习的机会哦!!!
互联网时代,十亿、百亿、千亿的数据日志呈井喷式增长,基于日志搜索分析的需求也越来越强烈。Elasticsearch 作为一个分布式、可扩展、实时的搜索与数据分析引擎, 在大体量的数据处理上,无论实在全文搜索,还是在结构化数据统计中,都有非常大的优势。然而在真正实践过程中海量数据如何高效采集,如何合理优化分配索引,如何规划集群,如何满足业务分析需求都是我们可能会面临的问题。 本次袋鼠云联合阿里云、Elastic 中文社区,共同邀请滴滴、有赞等行业技术专家一同分享和探讨各自领域Elastic的实践。   本次活动时间12月15日 周六人数限制100人,大家抓紧报名哈,报名链接https://meetup.elasticsearch.c ... .html。 参与线下互动还有机会获得技术书籍与精美礼品哦!!! 不出意外,这应该是2018年Elastic在杭州的最后一次沙龙,小伙伴们抓紧今年的尾巴,不放过任何学习的机会哦!!!

Day 2 - ES 6.x拼音分词高亮爬坑记

Adventabia 发表了文章 • 10 个评论 • 8805 次浏览 • 2018-12-02 16:29 • 来自相关话题

大家好,我是来自尚德机构ES平台的负责人,白凡,今天为大家分享一些在6.x版本中拼音分词高亮问题爬坑的心路历程~,其实问题不复杂,主要介绍下思路。 首先简单讲下背景~可能在很多公司的很多部门,都有使用到ES。包括在尚德,由于很多部门的业务都涉及到ES,于是我们为了统一管理及维护,专门成立了ES平台部门,主要扮演的是类似于op dba角色,帮助业务部门部署维护ES集群,并根据业务需求提供解决方案。当然,除此之外,我们也会在公司内部推荐业务方去尝试除了日志和搜索以外的应用场景,比如分布式计算存储、监控、安全等方面。毕竟ES相比于其他组建,搭建部署更加方便,更轻量级,查询方式更丰富。所以,现如今在尚德机构,ES平台不仅用于了传统的日志和搜索领域,也在分布式数据存储和计算方面有很多应用。当然,这里只是为大家提供一些ES应用场景及其团队构建的思路。主要还是ES这个工具确实好用。 广告先做到这,回到正文。所以,前段日子,我们接收了一个新的业务部门需求,大致是:他们之前使用的自己搭建ES 2.x集群,现在接入到我们6.x的平台上来。我们帮忙设计了mapping,数据写入及同步方案之后,数据就慢慢接入进来。但问题也随即出现,原来在2.x上使用正常的拼音高亮mapping,在6.x上只能检索但无法高亮了? 2.x field如下: "index" : { "analysis" : { "analyzer" : { "pinyin_analyzer" : { "tokenizer" : "my_pinyin" } }, "tokenizer" : { "my_pinyin" : { "type" : "pinyin", "keep_full_pinyin" : false, "limit_first_letter_length" : 16, "lowercase" : true, "remove_duplicated_term":true, "keep_first_letter":true, "keep_separate_first_letter" :true } } } } POST /medcl/doc/_mapping { "properties": { "name":{ "analyzer": "pinyin_analyzer", "type": "string" } } } 可以从上面例子看出,这个analyzer并没有问题,但是在搜索时,能得到结果,却无法高亮,即分词结果中start_offset及end_offset为0,这个如何解决呢? 回到medcl的拼音分词项目: https://github.com/medcl/elast ... inyin 其中,有个配置项引起了我们的注意:
图片1.png
没跑了,应该是要将这个参数设置为false。 并且查看了源码,在PinyinTokenizer这个类下面,看到了这一行:
图片2.png
确定了我们的思路,于是乎,在tokenizer中将此参数设为false,如下: "tokenizer" : { "my_pinyin" : { "type" : "pinyin", "keep_full_pinyin" : true, "keep_original" : false, "limit_first_letter_length" : 16, "lowercase" : true, "remove_duplicated_term":true, "ignore_pinyin_offset": false, "keep_first_letter":true, "keep_separate_first_letter" :true } } 写入一条数据,高亮没问题了,问题“看似”解决了。 当然,没有那么简单。因为在批量写入一部分数据后,总会报如下异常: startOffset must be non-negative, and endOffset must be >= startOffset 这个异常,导致数据无法写入集群。 这里又是为什么呢? 这个问题,我也搞了一段时间,始终没找到很好的解决方案,此处只能先@medcl。 只是猜测在end()或者reset()方法内,需要lastOffset置0或者offsetAtt清空。但尝试了几次,依然报错。。。 这就比较头疼了,不过好在条条道路通罗马。在某次蹲坑过程中,灵感如尿崩。 如果Tokenizer解决不了,为何不仅用filter就行了呢?可以先用其他分词器,按我们业务的需求进行分词,再用filter,将分词过滤为拼音呢? 大致思路如下: 目前我们这个业务,需要如对于“尚德机构”这个词,搜索“shang”,“shangde”,“deji”时,能返回结果并高亮。 所以我们先用ngram分词,将“尚德机构”这个词分为“尚”,“尚德”,“徳机”,“德机构”等等。。 再用pinyin filter将各分词过滤为拼音,即“shang”,“shangde”,“deji”等。 并在搜索时,采用standard分词。 Mapping如下: { "settings": { "analysis": { "analyzer": { "pinyin_analyzer": { "tokenizer": "my_ngram", "filter": [ "pinyin_filter" ] } }, "tokenizer": { "my_ngram": { "type": "ngram", "min_gram": 1, "max_gram": 50, "token_chars": [ "letter", "digit", "punctuation", "symbol" ] } }, "filter": { "pinyin_filter": { "type": "pinyin", "keep_full_pinyin": false, "keep_joined_full_pinyin": true, "keep_none_chinese_in_joined_full_pinyin": true, "none_chinese_pinyin_tokenize": false, "remove_duplicated_term": true } } } }, "mappings": { "abia321": { "properties": { "name": { "type": "text", "analyzer": "pinyin_analyzer", "search_analyzer": "standard", "term_vector": "with_positions_offsets" } } } } } 最后,高亮问题解决,数据写入问题同样解决。 当然有朋友肯定还会需要搜索拼音首字母进行搜索,如搜“s”,“sd”,“dj”,也返回结果。 其实,只需要再专门设置个field,并调整pinyin filter参数, 搜索时用bool查询,逻辑should查询,同时对完整拼音field和拼音首字母field进行搜索即可。 在此就不做过多赘述。 当然,这里仅仅只是提供一种ES在选择analyzer,tokenizer,filter解决需求的思路。拼音分词这个问题,还是需要等待后续修复 最后,这里有较为完整的issue: https://github.com/medcl/elast ... s/169

社区日报 第462期 (2018-11-28)

社区日报sterne vencel 发表了文章 • 0 个评论 • 1453 次浏览 • 2018-11-28 16:58 • 来自相关话题

Elastic日报 第462期 (2018-11-28) 1. 让天下没有难用的搜索 阿里搜索如何成长为贴心“暖男” http://t.cn/E2UjZ6d 2.Elasticsearcharch索引生命周期管理探索 http://t.cn/RDUxF3t 3. 实例展示Elasticsearch集群生态,分片以及水平扩展 http://t.cn/RzIQzrR 编辑:江水 归档:http://elasticsearch.cn/article/6160 订阅:https://tinyletter.com/elastic-daily
Elastic日报 第462期 (2018-11-28) 1. 让天下没有难用的搜索 阿里搜索如何成长为贴心“暖男” http://t.cn/E2UjZ6d 2.Elasticsearcharch索引生命周期管理探索 http://t.cn/RDUxF3t 3. 实例展示Elasticsearch集群生态,分片以及水平扩展 http://t.cn/RzIQzrR 编辑:江水 归档:http://elasticsearch.cn/article/6160 订阅:https://tinyletter.com/elastic-daily

CentOS 7.4 下安装 ES 6.5.1 搜索集群

Elasticsearchrochy 发表了文章 • 3 个评论 • 7009 次浏览 • 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 等。

使用 Kibana 监控节点状态

五、设置索引模板

具体请参考之前发布的文章基于 IK 分词器的 ES 通用索引模板


Any Code,Code Any!

扫码关注『AnyCode』,编程路上,一起前行。

ELK 使用小技巧(第 1 期)

Elasticsearchrochy 发表了文章 • 1 个评论 • 8629 次浏览 • 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 维护问题

  1. 如果不想保存 sincedb,可以使用下面配置:sincedb_path => "/dev/null"
  2. 如果希望被扫描的记录超过一段时间后自动被清除,可以使用 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_TypeAIndexB_TypeB,reindex 语句如下所示:

POST _reindex
{
  "source": {
    "index": "IndexA",
    "type": "TypeA",
    "size": 10000
  },
  "dest": {
    "index": "IndexA_TypeA"
  }
}

最后给 6.x 集群的IndexA_TypeAIndexB_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 表格默认排序

在设计表格的时候直接点击需要排序的那一列,然后让它按照倒序或者正序排序,然后点击保存即可,这样这个表格默认就是按照这一列倒序或者正序排列的。

kibana 排序设置


Any Code,Code Any!

扫码关注『AnyCode』,编程路上,一起前行。