1.1 倒排索引 倒排索引原理??
ElasticSearch使用一种称为 ==倒排索引== 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表够成,对于其中每个词,有一个包含它的文档列表
1.1.2 分词器介绍及内置分词器
分词器:从一串文本中切分出一个一个的词条,并对每个词条进行标准化
包括三部分:
- character filter : 分词之前的预处理,过滤掉HTML标签,特殊符号转换等
- tokenizer : 分词
- token filter : 标准化
内置分词器:
- standard分词器: (默认)将词汇单词转为小写形式,并去除停用词和标点符号,支持中文采用的方法为单字切分
- simple分词器: 首先会通过非字母字符来分割文本信息,然后将词汇单元统一为小写形式。该分析器会去掉数字类型的字符。
- Whitespace分词器: 仅仅是去除空格,对字符没有lowcase化,不支持中文;并且不对生成的词汇单元进行其他的标准化处理。
- language分词器: 特定语言的分词器,不支持中文
1.1.3 安装中文(IK)分词器
- 下载中文分词器 下载地址 进入es下bin目录下 使用命令 安装分词器资料
./elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.2.2/elasticsearch-analysis-ik-6.2.2.zip - 重启es 若在重启的过程中出现 Exception in thread "main" java.nio.file.AccessDeniedException 异常,重新对此用户赋予es此文件夹的权限即可
- 测试IK分词器基本功能 分词器中ik_max_word和ik_smart的区别
#使用ik_smart进行分词
GET _analyze?pretty
{
"analyzer": "ik_smart",
"text": "湖南省邵阳市双清区"
}
#使用ik_max_word进行分词
GET _analyze?pretty
{
"analyzer": "ik_max_word",
"text": "湖南省邵阳市双清区"
}
#新词
GET _analyze?pretty
{
"analyzer": "ik_smart",
"text": "斗罗大陆"
}
- 查看已有词典 打开目录cd /usr/local/software/elasticsearch-6.2.2/config/analysis-ik/
- !!自定义词典 在已有词典目录下使用命令 mkdir custom 新建自定义词典目录;
使用命令 vi custom/new_word.dic 新建编辑自定义词典 - 更新配置(将自定义词典配置进去) 使用命令 vim IKAnalyzer.cfg.xml 编辑配置文件
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">custom/new_word.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
- 重启es
1.1.4 使用ElasticSearch API 实现CRUD
- 添加索引
# 添加索引
PUT lib
{
"settings": {
"index": {
# 分片 每个索引包含多个分片 分布在不同的node上
"number_of_shards": 3,
# 备份
"number_of_replicas": 0
}
}
}
- 查看索引信息
# 查看索引信息
GET lib/_settings
# 查看所有所以的配置
GET _all/_settings
- 添加文档(数据) ==指定Id时使用PUT 不指定时使用POST==
# 添加文档(PUT POST)
PUT lib/user/1
{
"username": "伍磊",
"age": 23,
"sex":"男"
}
# 结果
{
"_index": "lib",
"_type": "user",
"_id": "1",
"_version": 3,
"result": "updated",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}
- 查看
# 查看某一文档记录
GET lib/user/1
# 查看具体字段
GET lib/user/1?_source=username,age
- 更新文档
# 覆盖(PUT)全局替换,相当于先删除再增加
PUT lib/user/1
{
"username": "伍磊",
"age": 24,
"sex":"男"
}
# 直接修改(POST)更新某几个字段 修改哪个字段填写哪个字段
POST lib/user/1/_update
{
"doc": {
"age": 23
}
}
- 删除
# 删除文档
DELETE lib/user/1
# 删除索引
DELETE lib
1.1.5 批量获取文档
使用es提供的 Multi Get API:
可以通过索引名、类型名、文档ID一次得到一个文档集合,文档可以来自于同一个索引库,也可以来自不同索引库
# 批量获取 通过此命令获取多个文档信息
GET /_mget
{
"docs": [
{
"_index": "lib",
"_type": "user",
"_id": 1,
"_source": "username"
},
{
"_index": "lib",
"_type": "user",
"_id": 2
}
]
}
# 获取同索引同类型下不同的文档(指定获取的id集合)
GET lib/user/_mget
{
"ids": [1,2]
}
1.1.6 使用Bulk API 实现批量操作
bulk的格式:
{action:{metadata}}\n
{requestbody}\n
action:(行为)
- create: 文档不存在时创建
- update: 更新文档
- index : 创建新文档或替换已有文档
- delete:删除一个文档
metadata: _index,_type,_id
create和index的区别
- 如果数据存在,使用create操作失败,会提示文档已经存在,使用index则可以成功执行。
示例:
# 批量添加 index:文档存在则替换否则添加
POST lib2/books/_bulk
{"index": {"_id":1}}
{"title": "java", "price": 55}
{"index": {"_id":2}}
{"title": "c++", "price": 48}
{"index": {"_id":3}}
{"title": "php", "price": 36}
{"index": {"_id":4}}
{"title": "Python", "price": 50}
# 查看刚刚批量添加的数据
GET lib2/books/_mget
{
"ids": [1,2,3,4]
}
# 同时执行增删改 删除时不需要请求体
POST lib2/books/_bulk
{"delete": {"_index": "lib2", "_type": "books", "_id": 4}}
{"create": {"_index": "temp", "_type": "tt", "_id": 100}}
{"name": "测试-create"}
{"index": {"_index": "temp", "_type": "tt"}}
{"name": "测试-index"}
{"update": {"_index": "lib2", "_type": "books", "_id": 3}}
{"doc": {"price": 58}}
bulk一次最大处理多少数据量
bulk会把将要处理的数据载入内存中,所以数据量是有限制的,最佳的数据量不是一个确定的数值,它取决于你的硬件,你的文档大小以及复杂性,你的索引以及搜索的负载。一般建议是1000-5000个文档,大小建议是5-15MB,默认不能超过100M,可以在es的配置文件中进行配置
1.1.7 版本控制
ElasticSearch采用==乐观锁==来保证数据的一致性,也就是说,当用户对document进行操作时,并不需要对该document作加锁和解锁的操作,只需要==指定操作的版本==即可。当版本号一致时,ElasticSearch会允许该操作顺利执行,而当版本号存在冲突时,ElasticSearch会提示冲突并抛出异常(version_conflict_engine_exception)
# 修改时指定版本与es中版本不一致那么就会出现异常
PUT lib2/books/2?version=1
{
"doc":{
"price": 49
}
}
ElasticSearch版本号取值为1~2^63-1
内部版本控制:使用的是_version
外部版本控制:ElasticSearch处理外部版本号时会对内部版本号的处理有些不同。不再是检查_version是否与请求中指定的数值相同,而是检查当前的_version是否比指定的数小。如果请求成功,那么外部的版本号就会被存储到文档中的_version中,为了保持_version与外部版本控制的数据一致,使用version_type=external
# 使用外部版本控制,在指定的版本号后加上version_type=external,若此指定# 的版本号>es中的版本号,那么添加成功,否则异常
PUT lib2/books/2?version=99&version_type=external
{
"doc":{
"price": 49
}
}
1.1.8 Mapping
查看es自动创建的mapping
# 查看lib2索引下books类型的mapping
GET lib2/books/_mapping
# 结果 mapping(dynamic mapping) ——> 映射
{
"lib2": {
"mappings": {
"books": {
"properties": {
"doc": {
"properties": {
"price": {
"type": "long"
}
}
},
"price": {
"type": "long"
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
mapping定义了type中的每个字段的数据类型以及这些字段如何分词等相关属性
Elasticsearch 映射参数 fields
创建索引的时候,可以预先定义字段的类型以及相关属性,这样就能够把日期字段处理成日期,把数字字段处理成数字,把字符串字段处理字符串等支持的数据类型
核心数据类型(Core datatypes)
字符型:String
String类型包括 text 和 keyword
text类型被用来索引长文本,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。允许es来检索这些词语。text类型不能用来排序和聚合。
Keyword类型不需要进行分词,可以被用来检索过滤,排序和聚合。Keyword类型字段只能用本身来进行检索。
数字型:long,integer,short,byte,double,float
日期型:date
布尔型:boolean
二进制型:binary
复杂数据类型(Complex datatypes)
数组类型(Array datatypes): 数组类型不需要专门指定数组元素的type。例如:
字符型数组: ["one", "two"]
整形数组: [1, 2]
数组型数组: [1, [2, 3]] 等价于 [1, 2, 3]
对象数组: [{"name": "leiwu", "age": 23}, {"name": "wulei", "age": 23}]
对象类型(Object datatype): _object_ 用于单个json对象
嵌套类型(Nested datatype): _nested_ 用于json数组
地理位置类型(Geo datatypes)
地理坐标类型(Geo-point datatypes): _geo_point_ 用于经纬度坐标
地理形状类型(Geo-shape datatypes): _geo_shape_ 用于类似余多边形的复杂形状
特定类型(Specialised datatypes)
IPv4类型(IPv4 datatype): _ip_ 用于IPv4地址
Completion类型(Completion datatype): _completion_提供补全建议
Token count类型(Token count datatype): _token_count_ 用于统计做了标记的字段的index数目,该值会一直增加,不会因为过滤条件而减少
手动创建mapping
# 创建索引 并为类型books设置mapping 可以设置字段的类型,分词器的选择,以及此字段查询时分词器的选择
PUT lib4
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 0
},
"mappings": {
"books": {
"properties": {
"name": {"type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart"},
"price": {"type": "double"}
}
}
}
}
# 向索引中添加一条数据
POST lib4/books/2
{
"name": "伍磊的java",
"price": 19
}
# 指定分词器进行查询 (一般使用ik_max_word进行分词 使用ik_smart进行分词查询 这样能最大限度查询自己想要的结果)
GET lib4/books/_search
{
"query": {
"match": {"name": {"query": "伍磊", "analyzer": "ik_smart"}}
}
}
2.1 查询相关
2.1.1 基本查询
term查询和terms查询
term query 会去倒排索引中寻找准确的term,它并不知道分词器的存在,这种查询适合keyword、numeric、date (倒排索引中有则能够查到)
term: 查询某个字段里面含有某个关键词的文档
# term/terms 关键词查询-倒排索引中有的才能查询出来 terms查询只要符合其中一个关键词就能查询出来
GET lib4/books/_search
{
"query": {
"term": {"name": "伍磊的"}
}
}
# 查询从第"from"个开始 一共查询"size"个 按照"sort"进行排序 "version"表示同时将版本号查询出来
GET lib4/books/_search
{
"from": 0,
"size": 2,
"version": true,
"sort": [
{
"price": {
"order": "desc"
}
}
],
"query": {
"term": {"name": "java"}
}
}
match查询
match query 知道分词器的存在,会对filed进行分词操作,然后再查询
analyzed字段无法使用term精确查询 这是因为analyzed字段会默认进行分词之后再建立倒排索引,而此时使用term查找这个短语,因为这个短语分词后才建立的倒排索引,对于这个短语来说是没有对应的倒排索引的,所以就查询不到。
match_phrase主要用来精确查询可以分词的字段 查询的短语与想要匹配到的字段要完全一致,且分词之后也要完全一致,这就必须在精确查询的同时指定分词器,此分词器与此索引mapping中指定的分词器要一致才能查询出来
基本查询代码
# 通过match查询和指定分词器 可以将需要查询的字段分词之后再进行查询
GET lib4/books/_search
{
"query": {
"match": {
"name": {"query": "伍磊很厉就说是吧", "analyzer": "ik_smart"}
}
}
}
# match_all 查询所有文档
GET lib4/books/_search
{
"query": {
"match_all": {}
}
}
# multi_match: 可以指定多个字段 "query"表示需要寻找的关键字 "fileds":表示从这些字段数组中匹配关键字
GET lib4/books/_search
{
"query": {
"multi_match": {
"query": "伍磊",
"fields": ["name"]
}
}
}
# match_phrase:短语匹配查询
# ES引擎首先分析查询字符串,从分析后的文本中构建短语查询,这意味着必须匹配短语中的!!所有分词!!且保证各个分词的相对位置不变(查询时指定与此索引此字段mapping中同样的分词器才能实现精确查询)
GET lib4/books/_search
{
"query": {
"match_phrase": {
"name": {"query": "伍磊的java", "analyzer": "ik_max_word"}
}
}
}
# "_source": "name" 表明返回的字段
GET lib4/books/_search
{
"_source": "name",
"query": {
"match_phrase": {
"name": {"query": "伍磊的java", "analyzer": "ik_max_word"}
}
}
}
# "_source"下"includes" 表示返回需要包含哪些字段 "excludes"表示不包含哪些字段 上面两个均可用通配符表示
GET lib4/books/_search
{
"_source": {
"includes": ["nam*", "price"],
"excludes": "pric*"
},
"query": {
"match_phrase": {
"name": {"query": "伍磊的java", "analyzer": "ik_max_word"}
}
}
}
# "match_phrase_prefix"前缀匹配查询
GET lib4/books/_search
{
"_source": {
"includes": ["nam*", "price"],
"excludes": "pric*"
},
"query": {
"match_phrase_prefix": {
"name": {"query": "伍磊的jav", "analyzer": "ik_max_word"}
}
}
}
# 范围查询 表示查询价格从19到88的数据 其中include_lower(true)表示包含下界 include_upper(false)表示不包含上届
GET lib4/books/_search
{
"query": {
"range": {
"price": {
"from": 19,
"to": 88,
"include_lower": true,
"include_upper": false
}
}
}
}
# wildcard查询 允许使用通配符 "*" "?" 进行查询
# "*" 代表0或多个字符
# "?" 代表任意一个字符
# !!还是根据倒排索引进行查询 所以这个查询条件必须得是符合某个倒排索引才能查询出来
GET lib4/books/_search
{
"query": {
"wildcard": {
"name": "伍*"
}
}
}
# fuzzy实现模糊查询
# 高亮显示字段 "highlight"
GET lib4/books/_search
{
"query": {
"wildcard": {
"name": "伍?"
}
},
"highlight": {"fields": {"name": {}}}
}
2.1.2 Filter查询
filter是不计算相关性的,同时可以cache。因此,filter速度要快于query
# 通过filetr查询价格为19的书
GET lib4/books/_search
{
"query": {
"bool": {
"filter": [
{"term": {"price": 19}}
]
}
}
}
# 通过filetr查询价格为19或88的书
GET lib4/books/_search
{
"query": {
"bool": {
"filter": [
{"terms": {"price": [19, 88]}}
]
}
}
}
bool过滤查询
可以实现组合过滤查询
格式 {"bool": {"must": [], "should": [], "must_not": []}}
must:必须满足的条件 ==> and
should: 可以满足也可以不满足的条件 ==> or
must_not: 不需要满足的条件 ==> not
# 通过bool过滤器匹配名字为java的书或价格为19的书
GET lib4/books/_search
{
"query": {
"bool": {
"should": [
{"match": {
"name": "java"
}},
{"term": {
"price": {
"value": 19
}
}}
]
}
}
}
# 通过bool过滤器匹配名字为伍磊的书或名字包含java且价格为19的书(可以嵌套)
GET lib4/books/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"name": "伍磊"
}
},
{
"bool": {
"must": [
{
"term": {
"name": {
"value": "java"
}
}
},
{
"term": {
"price": {
"value": "19"
}
}
}
]
}
}
]
}
}
}
范围过滤
gt : > lt : < gte : 大于等于 lte : 小于等于
# 通过filetr查询价格为19到88区间的书
GET lib4/books/_search
{
"query": {
"bool": {
"filter": {
"range": {
"price": {
"gte": 19,
"lte": 88
}
}
}
}
}
}
# 通过filetr查询价格存在的书
GET lib4/books/_search
{
"query": {
"bool": {
"filter": {
"exists": {
"field": "price"
}
}
}
}
}
聚合查询
# 聚合查询 size表示只要获取聚合查询的结果 "aggs"表示聚合查询 "price_of_sum"自定义名称 "sum"表示总和 "min"表示最小值
# "avg"表示平均数 "cardinality"表示基数(字段上互不相同的数的个数) "terms"表示分组
GET lib4/books/_search
{
"size": 0,
"aggs": {
"price_of_sum": {
"sum": {
"field": "price"
}
}
}
}
GET lib4/books/_search
{
"size": 0,
"aggs": {
"price_of_min": {
"min": {
"field": "price"
}
}
}
}
# 对名字为java的书按价格分组,并计算每组中价格的平均值,按平均价格倒序排序
GET lib4/books/_search
{
"size": 0,
"query": {
"match": {
"name": "java"
}
},
"aggs": {
"price_of_group": {
"terms": {
"field": "price",
"order": {
"price_of_avg": "desc"
}
},
"aggs": {
"price_of_avg": {
"avg": {
"field": "price"
}
}
}
}
}
}