基本概念:索引、文档和RESTAPI
基本概念
索引:名词:一类相似文档的集合,类比与mysql的一张表 动词:把数据保存到es的过程
文档:json格式,类比于mysql的一条记录,每个文档有一个Unique ID,可以自己指定也可以由es生成文档的元数据(用于标注文档的相关信息)
{
"_index": "movies", // 文档所属的索引名
"_type": "_doc", // 文档所属类型名,es7.0后已无意义,用_doc占位
"_id": "1", // 文档唯一ID
"_score": 14.56256, // 相关性打分
"_version": 2, // 文档的版本信息
"_source": { // 文档原始json数据
"user_id": 9527,
"username": "阿凯"
}
}
索引的mapping和setting
mapping定义文档字段的类型,类比于mysql的表结构
setting定义不同数据的分布,定义索引分布的分片信息使用场景
es: 相关性/高性能全文检索
关系型数据库:事务性/Join
有时候需要把es和关系型数据库结合使用
集群、节点、分片和副本
集群
es集群是分布式的,节点结合负载均衡与数据的水平拆分节点
节点是一个es实例(本质是一个java进程,建议生产环境一台机器只运行一个es实例,不要多个)
data node: 保存数据的节点,当存储不够用时,可以增加data node进行扩容
coordinating node:负责接收client的请求,分发到合适的节点并汇总结果,每个节点默认都起到coordinating node的职责
hot&warm node:不同硬件配置的data node,实现冷热架构,降低机器成本
machine learning node:负责跑机器学习的job,用来做异常检测
生产环境应尽量配置单一职责的节点,通过elasticsearch.yml文件配置分片及副本
主分片:用于解决数据水平扩展,可以将数据分布到不同节点上,主分片数在索引创建时指定,后续不允许修改,除非reindex,类比于mysql的分表
副本:主分片的拷贝,用于解决高可用,即负载均衡
分片数过小,后续无法增加节点扩展,分片数过大,影响结果的相关性打分,影响准备性,单个节点分片过多也会造成资源浪费,影响性能。7.0的默认主分片是1。
文档的CURD和批量操作
- create 创建一个文档,如果对应id的文档已存在,会报错
PUT users/_create/1 // 索引名/_create/_id
{
"user_id": 1,
"username": "jack"
}
- get 获取一个文档
GET users/_doc/1 // 索引名/_type/_id
- index 索引一个文档,如果对应id的文档不存在,则创建文档,如果已存在,则删除原文档再创建文档,版本号+1
PUT users/_doc/1 // 索引名/_type/_id
{
"user_id": 1,
"username": "jack"
}
- update 更新一个文档
POST users/_update/1
{
"doc": {
"username": "jack"
}
}
- delete 删除一个文档
DELETE users/_doc/1 // 索引名/_type/_id
- Bulk API 支持一次调用中,对不同索引进行写操作,支持index create update delete,单条失败不影响其他操作
POST _bulk
{"index": {"_index": "hb_user", "_id": 3}}
{"user_id": 2, "username": "周月"}
{"create": {"_index": "hb_user","_id": 4}}
{"user_id": 9300,"username": "夸哥","salary": 50000}
{"delete": {"_index": "hb_user","_id": 1}}
{"update": {"_index": "hb_user","_id": 4}}
{"doc": {"user_id": "9300","username": "阿夸"}}
- mget 批量获取
GET _mget
{
"docs": [
{"_index": "hb_user", "_id": 3},
{"_index": "hb_login_history", "_id": 4}
]
}
倒排索引
以书本为例,书的目录就是正排索引,书最后的索引的词条就是倒排索引
文档中 正排索引是文档ID到文档内容,倒排索引是文档内容中的词条到文档ID
倒排索引包含两个部分:
单词词典:记录文档中的所有单词,单词到倒排列表的对应关系
倒排列表:由倒排索引项组成:文档ID,词频,位置,偏移
例子:
单词词典
字段 | 倒排列表id |
---|---|
超人 | 1,2,3 |
蝙蝠侠 | 4,5 |
倒排列表
倒排列表id | 文档ID | 词频TF | 位置 | 偏移 |
---|---|---|---|---|
1 | 52562 | 1 | 1 | <10,23> |
2 | 542562 | 2 | 1 | <11,23> |
3 | 542562 | 1 | 1 | <12,23> |
4 | 542562 | 1 | 1 | <13,23> |
5 | 542562 | 1 | 1 | <14,23> |
es的json文档的每个字段都有自己的倒排索引,可以指定某些字段不做索引(优点:节省存储空间,缺点:字段无法被搜索)
通过Analyzer进行分词
Analysis - 文本分析是把全文本转换成一系列单词(term/token)的过程,也叫分词。
Analysis是通过Analyzer来实现的
除了在数据写入时转换词条,匹配Query语句时也需要用相同的分析器对查询语句进行分析
Analyzer由三部分组成:
- Character Filter 针对原始文本进行处理,es自带的:去除html标签,字符串替换,正则匹配替换,会影响tokenizer的position和offset信息
- Tokenizer 按照规则切分为单词
- Token Filter 将切分的单词进行加工,小写,删除stopwords,增加同义词
ik分析器安装:
bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.1.0/elasticsearch-analysis-ik-7.1.0.zip
kibana 演示:
POST _analyze
{
"analyzer": "ik_smart",
"text": "中华人民共和国国歌"
}
POST _analyze
{
"analyzer": "ik_max_word",
"text": "中华人民共和国国歌"
}
ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;
ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”,适合 Phrase 查询
Search API
- URI Search : 在URI中使用查询参数
- Request Body Search: DSL
指定查询的索引:
- /index1/_search
- /index1,index2/_search
- /index*/_search
URI查询示例:
curl -XGET "http://xxxxxxxxxx/index1/_search?q=customer_first_name:Eddie"
DSL查询示例
curl -XGET "http://xxxxxxxxxx/index1/_search" -H 'Content-Type:application/json' -d'
{
"query": {
"match_all": {}
}
}'
相关性指标
- Percision(查准率):正确的内容/全部返回的结果,尽可能返回较少的无关文档
- Recall(查全率):正确的内容/全部应该返回的结果,尽量返回较多的相关文档
- Ranking 是否能按照相关度进行排序
es提供很多参数来改善Percision和Recall
URI Search
GET /movies/_search?q=2012&df=title&sort=year:desc&from=0&size=10&timeout=1s
{
"profile": "true"
}
- q指定查询语句,使用query string syntax
- df默认字段,不指定时,会对所有字段进行查询
- sort排序/from和size用于分页
- profile可以查询时如何被执行的
- 指定字段查询
GET /movies/_search?q=title:2012
GET /movies/_search?q=2012&df=title
- 泛查询,对所有字段查询,性能不好
GET /movies/_search?q=2012
Term和Phrase
Beautiful Mind 等效于 Beautiful OR Mind
”Beautiful Mind“ 等效于 Beautiful AND Mind。Phrase查询,还要求前后顺序保持一致
分组和引号
title:(Beautiful Mind) 默认含义Beautiful OR Mind
title:"Beautiful Mind"等同于title:(Beautiful AND Mind)
title:(Beautiful NOT Mind)
这里分组中的OR AND NOT等同于 && || !
也可以用+ -来实现,+表示must, - 表示must not
title:(+Beautiful -Mind)
范围查询, []闭区间,{}开区间
year:{2019 TO 2018}
year:[* TO 2018]算数符号
year:>2010
year:(>2010 && <=2018)
year:(+>2010 && +<=2018)通配符查询(查询效率低,占用内存大,不建议使用。特别是放在最前面)
?代表1个字符,代表0或者多个字符:title:mi?d title:be正则表达
title:[bt]oy模糊匹配与近似匹配
title:beautifl~1
title:"lord rings"~2
Request Body Search
一些复杂的查询只能通过DSL实现,推荐使用DSL
POST /movies/_search
{
"_source": ["order_date", "category.keyword"],
"sort": [{"order_date": "desc"}],
"from": 10,
"size": 20,
"query": {
"match_all": {}
}
}
- 获取靠后的翻页成本较高
- 最好在数字型与日期型的字段上排序
- 如果_source没有存储,那就只返回匹配的文档的元数据
- _source支持通配符,"name*"
脚本字段
GET /movies/_search
{
"script_fields": {
"dollor_price": {
"script": {
"lang": "painless",
"source": "doc['order_date'].value+'hello'"
}
}
}
"from": 10,
"size": 20,
"query": {
"match_all": {}
}
}
DSL实现Term Query和Phrase Query
GET /movies/_search
{
"query": {
"match": {
"comment": "Last Christmas" // Last OR Christmas
}
}
}
GET /movies/_search
{
"query": {
"match": {
"comment": {
"query": "Last Christmas",
"operator": "AND"
}
}
}
}
GET /movies/_search
{
"query": {
"match_phrase": {
"comment": {
"query": "Last Christmas", // 在match_phrase查询里,每个词必须是按顺序出现的
"slop": 1
}
}
}
}
query string&simple query string查询
POST users/_search
{
"query": {
"query_string": {
"default_field": "name",
"query": "Ruan AND Yiming"
}
}
}
POST users/_search
{
"query": {
"query_string": {
"fields": ["name", "about"],
"query": "(Ruan AND Yiming) OR (Java AND Elasticsearch)"
}
}
}
POST users/_search
{
"query": {
"simple_query_string": {
"fields": ["name", "about"],
"query": "Ruan Yiming",
"default_operator": "AND"
}
}
}
- simple_query_string不支持AND OR NO,会当作字符串处理,支持(+替代AND,代替OR, -代替NOT)Term之前的关系默认是OR,可以指定operator
Dynamic Mapping和常见字段类型
mapping类似数据库中的schema(表结构),作用如下:
- 定义索引中字段的名称
- 定义字段的数据类型,例如字符串,数字,布尔......
- 字段,倒排索引的相关配置(Analyzed or Not Analyzed, Analyzer)
字段的数据类型
- 简单类型:Text/Keyword Date Integer/Floating Boolean IPv4&IPv6
- 复杂类型:对象类型/嵌套类型
- 特殊类型(地理位置):geo_point & geo_shape / percolator
Dynamic Mapping:es根据文档信息,自动推断字段的类型,但可能不是最正确的
json类型 | es类型 |
---|---|
字符串 | 1. 匹配日期格式,设置为Date 2. 配置数字设置为float或者long,该选项默认关闭 3. 设置为Text,并且增加keyword子字段 |
布尔值 | boolean |
浮点数 | float |
整数 | long |
对象 | object |
数组 | 由第一个非空数值的类型所决定 |
空值 | 忽略 |
更改mapping的字段类型
- 新增字段
- Dynamic设为true时,一旦有新增字段的文档写入,mapping也同时被更新
- Dynamic设为false时, mapping不会被更新,新增字段的数据无法被索引,但是信息会出现在_source中
- Dynamic设为strict时,文档写入失败
- 已有字段
一旦已经有数据写入,就不再支持修改字段定义,如果要修改,必须调用Reindex API,重建索引。
PUT /hb_user/_mapping
{
"dynamic": false
}
GET /hb_user/_mapping
显式mapping设置和常见参数
PUT my_index
{
"mappings": {
"properties": {
"first_name": {
"type": "text",
"copy_to": "full_name" // 可以用full_name进行搜索,但是full_name不会出现在_source中
},
"last_name": {
"type": "text",
"copy_to": "full_name"
},
"mobile": {
"type": "text",
"index": false
},
"bio": {
"type": "text",
"index_options": "offsets"
},
"alias": { // 需要对null值进行搜索,只有keyword类型支持null_value
"type": "keyword",
"null_value": "NULL"
},
"interests": {
"type": "text"
}
}
}
}
四种不同级别的Index Option配置,可以控制倒排索引记录的内容
- docs:记录doc id
- freqs: 记录doc id / term frequencies
- position: 记录doc id / term frequencies/ term position
- offsets: 记录doc id / term frequencies / term position / character offects
Text类型默认记录positions,其他默认为docs
记录内容越多,占用存储空间越大
多字段特性及Mapping中配置自定义Analyzer
多字段特性
PUT products
{
"mappings": {
"properties": {
"company": {
"type": "text",
"fields": {
"company_keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"comment": {
"type": "text",
"fields": {
"english_comment": {
"type": "text",
"analyzer": "english",
"search_analyzer": "english"
}
}
}
}
}
}
Exact Value:无需分词,包括数字/日期/具体一个字符串,es中的keyword
Full Text:全文本,需要分词,es中的text
配置自定义Analyzer
GET _analyze
{
"tokenizer": "whitespace",
"filter": ["lowercase", "stop"],
"text": ["The gilrs in China are playing this game!"]
}
PUT my_index1
{
"settings": {
"analysis": {
"analyzer": {
"my_custom_analyzer": {
"type": "custom",
"char_filter": [
"emotions"
],
"tokenizer": "punctuation",
"filter": [
"lowercase",
"english_stop"
]
}
},
"char_filter": {
"emotions":{
"type": "mapping",
"mappings": [
":) => _happy_",
"(: => _sad_"
]
}
},
"tokenizer":{
"punctuation":{
"type":"pattern",
"pattern": "[ .,!?]"
}
},
"filter":{
"english_stop":{
"type":"stop",
"stopwords":"_english_"
}
}
}
}
}
GET my_index1/_analyze
{
"analyzer": "my_custom_analyzer",
"text": "I`m a :) person,and you?"
}
Index Template和Dynamic Template
什么是Index Template
Index Template帮你设置mappings和settings,并按照一定规则,自动匹配到新创建的索引之上
- 修改模板不会影响已创建的索引
- 你可以创建多个索引模板,这些设置会被”merge“在一起
- 你可以指定”order“的数值,控制”merge“过程
PUT _template/template_default
{
"index_patterns": ["test*"],
"order": 0,
"settings": {
"number_of_shards": 1,
"number_of_replicas": 2
},
"mappings": {
"date_detection": false,
"numeric_detection": true
}
}
当一个索引被新创建时
- 应用es默认的settings和mappings
- 应用order数值低的Index Template中的设定
- 应用order数值高的Index Template中的设定,之前的设定会被覆盖
- 应用创建索引时,用户所指定的settings和mappings,并覆盖之前的设定
查看index template
GET _template/template_default
什么是Dynamic Template
根据es识别的数据类型,结合字段名称,来动态设定字段类型,比如:
- 所有的字符串类型都设定成keyword,或者关闭keyword字段
- is开头的字段都设置成boolean
- long_开头的字段都设定成long类型
目的:让es的动态字段类型推断符合你的预期
PUT test_index2
{
"mappings": {
"dynamic_templates":[
{
"full_name":{
"path_match": "name.*",
"path_unmatch": "*.middle",
"mapping":{
"type":"text",
"copy_to":"full_name"
}
}
},
{
"string_as_boolean": {
"match_mapping_type": "string",
"match": "is*",
"mapping": {
"type": "boolean"
}
}
}
]
}
}
PUT test_index2/_doc/1
{
"name":{
"first":"chen",
"middle":"liang",
"last":"hui"
},
"is_vip":"true"
}
GET test_index2/_search?q=full_name:(chen)
es的聚合查询简介
- Bucket Aggregation 一些满足特定条件的文档的集合 类似sql中 group by
- Metric Aggregation 一些数学运算,可以对文档字段进行统计分析,如sql中的 select count(*)
- Pipeline Aggregation 对其它聚合查询进行二次聚合
- Matrix Aggregation 支持对多个字段的操作并提供一个结果矩阵
GET hb_user/_search
{
"size": 0,
"aggs": {
"gender_group": {
"terms": {
"field": "gender"
},
"aggs": {
"avg_birth_year": {
"avg": {
"field": "birth_year"
}
},
"max_birth_year": {
"max": {
"field": "birth_year"
}
},
"min_birth_year": {
"min": {
"field": "birth_year"
}
},
"birth_month_group":{
"terms": {
"field": "birth_month"
}
}
}
}
}
}