MVC 配置(非Boot)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
<!-- 配置es包扫描 -->
<elasticsearch:repositories base-package="com.huangliwei.elasticsearch.dao" />
<!-- 配置service包扫描 -->
<context:component-scan base-package="com.huangliwei.elasticsearch.service" />
<!-- 配置elasticsearch连接 -->
<elasticsearch:transport-client id="client"
cluster-nodes="127.0.0.1:9300" />
<!-- springdata整合elasticsearch提供template -->
<bean id="elasticsearchTemplate"
class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"></constructor-arg>
</bean>
</beans>
2018年Q2, Elasticsearch 更新到6.2版本, 6.3版本还未正式发布,如果准备在生产环境使用,比较推荐使用较老的5.6.x版本或2.x版本,一方面比较稳定、另外资料也比较多
如果使用Java技术栈,你很可能会使用Spring Boot全家桶,当前Spring Boot更新到2.x版本, 默认spring-boot-starter-data-elasticsearch 默认的ES版本为5.6.9;如果你仍然使用Spring Boot 1.x版本,那么默认的Elastisearch版本为2.x
客户端
Java技术栈目前有三种可以选择 Node Client, Transport Client, Rest API, 需要注明的是,官方已经标明NodeClient 已经过期,Transport Client 将在7.x版本开始不再支持, 最终会在7.x 统一到Rest API。目前Transport Client使用范围比较广;Rest API方式兼容性较好;除非在In-memory模式下运行单元测试,否则不推荐NodeClient
ElasticSearch的基本概念
ES是什么:
Elasticsearch是高度可伸缩的开源全文搜索和分析引擎。它允许我们快速实时地存储、搜索、分析大数据。
Elasticsearch使用Lucene作为内部引擎,但是在你使用它做全文搜索时,只需要使用统一开发好的API即可,而不需要了解其背后复杂的Lucene的运行原理。它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
不过,Elasticsearch不仅仅是Lucene和全文搜索,我们还能这样去描述它:
- 分布式的实时文件存储,每个字段都被索引并可被搜索
- 分布式的实时分析搜索引擎
- 可以扩展到上百台服务器,处理PB级结构化或非结构化数据
ES特点:
- ES中,每一个字段都会默认被建立索引。也就是说,每一个字段都会有一个反向索引以便快速搜索
- ES可以在同一个查询中使用所有的反向索引,以惊人的速度返回查询结果。
- ES是面向文档型数据库,这意味着它存储的是整个对象或者文档,它不但会存储它们,还会为他们建立索引。
- ES使用JSON作为文档序列化的格式
正序索引和倒叙索引:
我们都知道搜索引擎搜索一个词是非常快的,但你有没有想过为什么搜索引擎能够以这么快的速度从数以亿计的网页中找到你想要的内容?一个很重要的原因是,现代的搜索引擎基本上都使用了倒序索引技术。
如果不使用倒序索引技术,在每次进行检索时,搜索引擎必须遍历每一个网页,查找网页中是否包含你指定的关键词。这个工作量是十分巨大的,主要原因有二:
- 互联网的网页基数非常大;
- 在每一个网页中检索是否含有指定的关键词不是一件简单的事情,它需要遍历网页的每个字符。
为了更好的建立被搜索的关键字和含有这些关键字的页面之间的映射关系,倒序索引产生了。简单的说,倒序索引的倒序,指的是这个索引是从关键词中查找对应的源的,而不是从源中检索对应的关键词。
举例如下:为了检索关键词 A,首先从倒序索引的索引表中,找到关键词 A,然后查找 A 所在的页。由于倒序索引表排序后,在其中查找一个关键词可以使用二分查找,特别是在采用分布式数据、服务器集群、多线程技术等条件下,效率极高,所以,查找含有某个关键词的页变得非常简单。
假设数据库中含有1000000条记录,其中有 10 条记录符合搜寻条件,如果使用倒序索引,可以很快找到这些关键词,并且定位到含有这些关键词的十条记录;否则,需要遍历1000000条记录,效率的差异可想而知。
所以,倒序索引相当于一本出处大字典,查阅其中的每个词汇,都可以告诉你它的所有出处。
正向索引(文档到关键字):
- 以词为单位,记录每个关键词的词频、格式、位置等权重信息,把页面转换为一个关键词组成的集合。正排表是以文档的ID为关键字,表中记录文档中每个字的位置信息,查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。
这种组织方法在建立索引的时候结构比较简单,建立比较方便且易于维护;因为索引是基于文档建立的,若是有新的文档加入,直接为该文档建立一个新的索引块,挂接在原来索引文件的后面。若是有文档删除,则直接找到该文档号文档对应的索引信息,将其直接删除。但是在查询的时候需对所有的文档进行扫描以确保没有遗漏,这样就使得检索时间大大延长,检索效率低下。
(正向)举个例子:我们用不同的数字索引不同的句子(比如以下三句在文本中是按照0,1,2的顺序排列的)
0 : “I love you”
1 :“I love you too”
2: “I dislike you”
(反向)如果要用单词作为索引,而句子的位置作为被索引的元素,那么索引就发生了倒置:
“I” :{0, 1, 2}
“love” :{0, 1}
“you” :{0, 1, 2}
“dislike” :{2}
如果要检索“I dislike you ”这句话,那么就可以这么计算:{0,1,2} {2} {0, 1,2}
反向索引( 关键字到文档):
- 把文件对应到关键词的映射转换为关键词到文件的映射。关键词是主键,每个关键词都对应着一系列文件,只需要查询关键字就可以找到对应的文章,这样不需要进行全文扫描,这样就大大提高了速度,也提升了服务器性。倒排序索引利于查找但不利于构建,特别不利于删除。
由于每个字或词对应的文档数量在动态变化,所以倒排表的建立和维护都较为复杂,但是在查询的时候由于可以一次得到查询关键字所对应的所有文档,所以效率高于正排表。在全文检索中,检索的快速响应是一个最为关键的性能,而索引建立由于在后台进行,尽管效率相对低一些,但不会影响整个搜索引擎的效率。
关键字概念:
Index:
类似于mysql数据库中的database
Type (在ES 6.0中,type已被弃用。):
类似于mysql数据库中的table表,es中可以在Index中建立type(table),通过mapping进行映射。
Document:
由于es存储的数据是文档型的,一条数据对应一篇文档即相当于mysql数据库中的一行数据row,一个文档中可以有多个字段也就是mysql数据库一行可以有多列。
Field:
es中一个文档中对应的多个列与mysql数据库中每一列对应
Mapping:
可以理解为mysql或者solr中对应的schema,只不过有些时候es中的mapping增加了动态识别功能,感觉很强大的样子,其实实际生产环境上不建议使用,最好还是开始制定好了对应的schema为主。
indexed:
就是名义上的建立索引。mysql中一般会对经常使用的列增加相应的索引用于提高查询速度,而在es中默认都是会加上索引的,除非你特殊制定不建立索引只是进行存储用于展示,这个需要看你具体的需求和业务进行设定了。
Query DSL:
类似于mysql的sql语句,只不过在es中是使用的json格式的查询语句,专业术语就叫:QueryDSL
GET/PUT/POST/DELETE
分别类似与mysql中的select/update/delete…
分片和复制(shards & replicas)https://www.jianshu.com/p/cc06f9adbe82:
分布式存储系统为了解决单机容量以及容灾的问题,都需要有分片以及副本机制。Elasticsearch 没有采用节点级别的主从复制,而是基于分片。一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。
分片之所以重要,主要有两方面的原因:
- 允许你水平分割/扩展你的内容容量
- 允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量
至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了。这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。
复制之所以重要,主要有两方面的原因:
- 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。
- 扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行
总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制数量,但是不能改变分片的数量。
默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。
一个索引的多个分片可以存放在集群中的一台主机上,也可以存放在多台主机上,这取决于你的集群机器数量。主分片和复制分片的具体位置是由ES内在的策略所决定的。
主分片:
在一个多分片的索引中写入数据时,通过路由来确定具体写入哪一个分片中,大致路由过程如下:
shard = hash(routing) % number_of_primary_shards
routing 是一个可变值,默认是文档的
_id
,也可以设置成一个自定义的值。routing 通过 hash 函数生成一个数字,然后这个数字再除以number_of_primary_shards
(主分片的数量)后得到余数 。这个在 0 到number_of_primary_shards
之间的余数,就是所寻求的文档所在分片的位置。这解释了为什么要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。
索引中的每个文档属于一个单独的主分片,所以主分片的数量决定了索引最多能存储多少数据(实际的数量取决于数据、硬件和应用场景)。
复制分片:
复制分片只是主分片的一个副本,它可以防止硬件故障导致的数据丢失,同时可以提供读请求,比如搜索或者从别的 shard 取回文档。
每个主分片都有一个或多个副本分片,当主分片异常时,副本可以提供数据的查询等操作。主分片和对应的副本分片是不会在同一个节点上的,所以副本分片数的最大值是 n -1(其中 n 为节点数)。
当索引创建完成的时候,主分片的数量就固定了,但是复制分片的数量可以随时调整,根据需求扩大或者缩小规模。如把复制分片的数量从原来的 1 增加到 2 :
curl -H "Content-Type: application/json" -XPUT localhost:9200/blogs/_settings -d ' { "number_of_replicas": 2 }'
分片本身就是一个完整的搜索引擎,它可以使用单一节点的所有资源。主分片或者复制分片都可以处理读请求——搜索或文档检索,所以数据的冗余越多,能处理的搜索吞吐量就越大。
对文档的新建、索引和删除请求都是写操作,必须在主分片上面完成之后才能被复制到相关的副本分片,ES 为了提高写入的能力这个过程是并发写的,同时为了解决并发写的过程中数据冲突的问题,ES 通过乐观锁的方式控制,每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。一旦所有的副本分片都报告写成功才会向协调节点报告成功,协调节点向客户端报告成功。
ElasticSearch Head (安装插件)
ElasticSearch Head
使用ElasticSearch API 实现CRUD(ES基本命令操作)
分词查看:
#添加索引
#带自定义配置的添加:
PUT /lib/
{
"settings":{
"index":{
"number_of_shards": 5, # 5个主分片确认好以后无法更改 (每个主分片都有一个复制分片)
"number_of_replicas": 1 # 随时修改,默认1个复制分片
}
}
}
# 使用默认配置的添加: lib前后加 / 和不加没区别,后面加了/ 反而在kibana中没提示
PUT lib
# 查看索引命令
GET /lib/_settings # 查询单个
GET _all/_settings # 查询所有
# 添加文档:
# 指定id为1,如果不指定,需要用post提交,elasticsearch自动生成
PUT /lib/user/1
{
"first_name" : "Jane",
"last_name" : "Smith",
"age" : 32,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
# 这里没有id,自动生成,使用POST
POST /lib/user/
{
"first_name" : "Douglas",
"last_name" : "Fir",
"age" : 23,
"about": "I like to build cabinets",
"interests": [ "forestry" ]
}
# _source元数据分析其实就是我们在添加文档时request body中的内容指定返回的结果中含有哪些字段:
GET /lib/user/1
GET /lib/user/
GET /lib/user/1?_source=age,interests # _source:指定查询id为1的age和interests两个字段
# 覆盖更新,相当于重新插入,这里没覆盖到的字段,更新后就没了,可能会引起丢失字段
PUT /lib/user/1
{
"first_name" : "Jane",
"last_name" : "Smith",
"age" : 36,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
# 正确更新
POST /lib/user/1/_update
{
"doc":{
"age":33
}
}
# 删除一个文档
DELETE /lib/user/1
# 删除一个索引
DELETE /lib
使用es提供的Multi Get API:
使用Multi Get API可以通过索引名、类型名、文档id一次得到一个文档集合,文档可以来自同一个索引库,也可以来自不同索引库
# 可以指定具体的字段 也可以不指定
GET /_mget
{
"docs":[
{
"_index": "lib",
"_type": "user",
"_id": 1,
"_source": "interests" # 可以指定具体的字段
},
{
"_index": "lib",
"_type": "user",
"_id": 2,
"_source": ["age","interests"] # 可以指定具体的字段
}
]
}
# 获取同索引同类型下的不同文档:
GET /lib/user/_mget
{
"docs":[
{
"_id": 1
},
{
"_type": "user",
"_id": 2
}
]
}
GET /lib/user/_mget
{
"ids": ["1","2"]
}
使用Bulk API 实现批量操作
bulk的格式:(_mget只能获取,bulk能增删改查)
# create:文档不存在时创建
# update:更新文档
# index:创建新文档或替换已有文档
# delete:删除一个文档
POST lib/user/_bulk
{"delete":{"_index":"lib","_type":"user","_id":"1"}}
# 批量添加
POST /lib2/books/_bulk
{"index":{"_id":1}} # 插入_id为1,内容为java、55的
{"title":"Java","price":55}
{"index":{"_id":2}} # 插入_id为2...
{"title":"Html5","price":45}
{"index":{"_id":3}}
{"title":"Php","price":35}
{"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":"tt","_type":"ttt","_id":"100"}}
{"name":"lisi"}
{"index":{"_index":"tt","_type":"ttt"}}
{"name":"zhaosi"}
{"update":{"_index":"lib2","_type":"books","_id":"4"}}
{"doc":{"price":58}}
bulk一次最大处理多少数据量:
bulk会把将要处理的数据载入内存中,所以数据量是有限制的,最佳的数据量不是一个确定的数值,
它取决于你的硬件,你的文档大小以及复杂性,你的索引以及搜索的负载。
一般建议是1000-5000个文档,大小建议是5-15MB,默认不能超过100M,
可以在es的配置文件(即$ES_HOME下的config下的elasticsearch.yml)中。
支持的数据类型:
(1)核心数据类型(Core datatypes)
字符型:string,string 类型包括text 和 keyword
最主要区别:text会被分词,keyword不会被分词
text类型被用来索引长文本,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。
允许es来检索这些词语。text类型不能用来排序和聚合。Keyword类型不需要进行分词,
可以被用来检索过滤、排序和聚合。keyword 类型字段只能用本身来进行检索
数字型:long, integer, short, byte, double, float
日期型:date
布尔型:boolean
二进制型:binary
(2)复杂数据类型(Complex datatypes)
数组类型(Array datatype):数组类型不需要专门指定数组元素的type,例如:
字符型数组: [ “one”, “two” ]
整型数组:[ 1, 2 ]
数组型数组:[ 1, [ 2, 3 ]] 等价于[ 1, 2, 3 ]
对象数组:[ { “name”: “Mary”, “age”: 12 }, { “name”: “John”, “age”: 10 }]
对象类型(Object datatype):_ object _ 用于单个JSON对象;
嵌套类型(Nested datatype):_ nested _ 用于JSON数组;
(3)地理位置类型(Geo datatypes)
地理坐标类型(Geo-point datatype):_ geo_point _ 用于经纬度坐标;
地理形状类型(Geo-Shape datatype):_ geo_shape _ 用于类似于多边形的复杂形状;
(4)特定类型(Specialised datatypes)
IPv4 类型(IPv4 datatype):_ ip _ 用于IPv4 地址;
Completion 类型(Completion datatype):_ completion 提供自动补全建议;
Token count 类型(Token count datatype): token_count _ 用于统计做了标记的字段的index数目,该值会一直增加,不会因为过滤条件而减少。
mapper-murmur3
类型:通过插件,可以通过 _ murmur3 _ 来计算 index 的 hash 值;
附加类型(Attachment datatype):采用 mapper-attachments
插件,可支持_ attachments _ 索引,例如 Microsoft Office 格式,Open Document 格式,ePub, HTML 等。
支持的属性:
"store":false //是否单独设置此字段的是否存储而从_source字段中分离,默认是false,只能搜索,不能获取值
"index": true // 分词,不分词是:false,设置成false,字段将不会被索引,默认每个都会创建索引
"analyzer":"ik" // 指定分词器,默认分词器为standard analyzer
"boost":1.23 // 字段级别的分数加权,默认值是1.0
"doc_values":false // 对not_analyzed字段,默认都是开启,分词字段不能使用,对排序和聚合能提升较大性能,节约内存
"fielddata":{"format":"disabled"} // 针对分词字段,参与排序或聚合时能提高性能,不分词字段统一建议使用doc_value
"fields":{"raw":{"type":"string","index":"not_analyzed"}} //可以对一个字段提供多种索引模式,同一个字段的值,一个分词,一个不分词
"ignore_above":100 //超过100个字符的文本,将会被忽略,不被索引
"include_in_all":ture //设置是否此字段包含在_all字段中,默认是true,除非index设置成no选项
"index_options":"docs"//4个可选参数docs(索引文档号) ,freqs(文档号+词频),positions(文档号+词频+位置,通常用来距离查询),offsets(文档号+词频+位置+偏移量,通常被使用在高亮字段)分词字段默认是position,其他的默认是docs
"norms":{"enable":true,"loading":"lazy"}//分词字段默认配置,不分词字段:默认{"enable":false},存储长度因子和索引时boost,建议对需要参与评分字段使用 ,会额外增加内存消耗量
"null_value":"NULL"//设置一些缺失字段的初始化值,只有string可以使用,分词字段的null值也会被分词
"position_increament_gap":0//影响距离查询或近似查询,可以设置在多值字段的数据上火分词字段上,查询时可指定slop间隔,默认值是100
"search_analyzer":"ik"//设置搜索时的分词器,默认跟ananlyzer是一致的,比如index时用standard+ngram,搜索时用standard用来完成自动提示功能
"similarity":"BM25"//默认是TF/IDF算法,指定一个字段评分策略,仅仅对字符串型和分词类型有效
"term_vector":"no"//默认不存储向量信息,支持参数yes(term存储),with_positions(term+位置),with_offsets(term+偏移量),with_positions_offsets(term+位置+偏移量) 对快速高亮fast vector highlighter能提升性能,但开启又会加大索引体积,不适合大数据量用
映射的分类:
# dynamic设置可以适用在根对象上或者object类型的任意字段上,给索引lib2创建映射类型
# dynamic 的值为下列3个:
# true:默认值。动态添加字段
# false:忽略新字段
# strict:如果碰到陌生字段,抛出异常
POST /lib2
{
"settings":{
"number_of_shards" : 3,
"number_of_replicas" : 0
},
"mappings":{
"books":{
"properties":{
"title":{"type":"text"},
"name":{"type":"text","index":false},
"publish_date":{"type":"date","index":false},
"price":{"type":"double"},
"number":{"type":"integer"}
}
}
}
}
POST /lib2
{
"settings":{
"number_of_shards" : 3,
"number_of_replicas" : 0
},
"mappings":{
"books":{
"properties":{
"title":{"type":"text"},
"name":{"type":"text","index":false},
"publish_date":{"type":"date","index":false},
"price":{"type":"double"},
"number":{
"type":"object",
"dynamic":true
}
}
}
}
}
2.7基本查询(Query查询)
PUT /lib3
{
"settings":{
"number_of_shards" : 3,
"number_of_replicas" : 0
},
"mappings":{
"user":{
"properties":{
"name": {"type":"text"},
"address": {"type":"text"},
"age": {"type":"integer"},
"interests": {"type":"text"},
"birthday": {"type":"date"}
}
}
}
}
PUT lib3/user/1
{
"name":"zhang san",
"address": "shatian",
"age":18,
"interests": "drink,dance",
"birthday": "2018-05-21"
}
PUT lib3/user/2
{
"name":"li si",
"address": "tongfan",
"age":28,
"interests": "shopping",
"birthday": "2000-01-21"
}
PUT lib3/user/3
{
"name":"wang wu",
"address": "guangfeng",
"age":20,
"interests": "reading,drink,dance",
"birthday": "2015-05-21"
}
GET /lib3/user/_search?q=name:lisi
GET /lib3/user/_search?q=name:zhaoliu&sort=age:desc # 排序字段是long或者integer等数值
term查询和terms查询(不分词)
# term query会去倒排索引中寻找确切的term,它并不知道分词器的存在。这种查询适合
# keyword 、numeric、date没有分词的。【直接查name没有结果,因为name是text,term中输入的词不会被分词,match会被分词】
term:查询某个字段里含有某个关键词的文档
# 精确查找,term中的field不能有空格等,因为倒排索引后都是一堆的词,而不是短语,(短语使用# match_phrase),
# 所以如果字段内容是:a,b,c ,使用term去查b,c 肯定是查不到的。因为term中field不会被分词,那么就是# 说需要匹配b,c整体,
# 但是a,b,c被拆分了a b c 所以匹配不到。
# term:查找interests 字段为 changge
GET /lib3/user/_search/
{
"query": {
"term": {"interests": "changge"}
}
}
# terms:查询某个字段里含有多个关键词的文档
GET /lib3/user/_search
{
"query":{
"terms":{
"interests": ["hejiu","changge"]
}
}
}
# 控制查询返回的数量
GET /lib3/user/_search
{
"from":0, # 从哪个开始
"size":2, # 取几个
"query":{
"terms":{
"interests": ["hejiu","changge"]
}
}
}
# 返回版本号
GET /lib3/user/_search
{
"version":true, # 默认没有版本号
"query":{
"terms":{
"interests": ["hejiu","changge"]
}
}
}
match查询(分词)
# match query知道分词器的存在,会对filed进行分词操作,然后再查询
GET /lib3/user/_search
{
"query":{
"match":{
"name": "zhaoliu"
}
}
}
GET /lib3/user/_search
{
"query":{
"match":{
"age": 20
}
}
}
# match_all:查询所有文档
GET /lib3/user/_search
{
"query": {
"match_all": {}
}
}
# multi_match:可以指定多个字段
GET /lib3/user/_search
{
"query":{
"multi_match": {
"query": "lvyou",
"fields": ["interests","name"]
}
}
}
# match_phrase:短语匹配查询
# ElasticSearch引擎首先分析(analyze)查询字符串,从分析后的文本中构建短语查询,这意味着必须匹配
# 短语中的所有分词,并且保证各个分词的相对位置不变:
GET lib3/user/_search
{
"query":{
"match_phrase":{
"interests": "duanlian,shuoxiangsheng"
}
}
}
# 指定返回的字段
GET /lib3/user/_search
{
"_source": ["address","name"],
"query": {
"match": {
"interests": "changge"
}
}
}
# 控制加载的字段
GET /lib3/user/_search
{
"query": {
"match_all": {}
},
"_source": {
"includes": ["name","address"], # 包含的字段
"excludes": ["age","birthday"] # 排除的字段
}
}
# 使用通配符 *
GET /lib3/user/_search
{
"_source": {
"includes": "addr*",
"excludes": ["name","bir*"]
},
"query": {
"match_all": {}
}
}
# 使用sort实现排序
# desc:降序,asc升序:
GET /lib3/user/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": {
"order":"asc"
}
}
]
}
GET /lib3/user/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": {
"order":"desc"
}
}
]
}
# 前缀匹配查询
GET /lib3/user/_search
{
"query": {
"match_phrase_prefix": {
"name": {
"query": "zhao" # 匹配一个词的前缀
}
}
}
}
# range:实现范围查询
# from,to:这2个默认包含边界
# gte:大于等于,gt大于;
# lte :小于等于,lt小于;
# include_lower:是否包含范围的左边界,默认是true
# include_upper:是否包含范围的右边界,默认是true
GET /lib3/user/_search
{
"query": {
"range": {
"birthday": {
"from": "1990-10-10",
"to": "2018-05-01"
}
}
}
}
GET /lib3/user/_search
{
"query": {
"range": {
"age": {
"from": 20,
"to": 25,
"include_lower": true,
"include_upper": false
}
}
}
}
# wildcard查询(wildcard中文:通配符)
# 允许使用通配符* 和 ?来进行查询*代表0个或多个字符?代表任意一个字符
GET /lib3/user/_search
{
"query": {
"wildcard": {
"name": "zhao*"
}
}
}
GET /lib3/user/_search
{
"query": {
"wildcard": {
"name": "li?i"
}
}
}
# fuzzy实现模糊查询,性能低value:查询的关键字boost:查询的权值,默认值是1.0
# fuzziness :参考,默认0.5,填写“auto”,或者>5表示能编辑2次,老实说,没太明白……,估计也不常用吧
# prefix_length:指明区分词项的共同前缀长度,默认是0,前缀必须匹配串的长度
# max_expansions:查询中的词项可以扩展的数目,默认可以无限大
GET /lib3/user/_search
{
"query": {
"fuzzy": {
"interests": "chagge"
}
}
}
GET /lib3/user/_search
{
"query": {
"fuzzy": {
"interests": {
"value": "chagge"
}
}
}
}
# 高亮搜索结果
GET /lib3/user/_search
{
"query":{
"match":{
"interests": "changge"
}
},
"highlight": {
"fields": {
"interests": {}
}
}
}
# boost提升权重创建mapping时,将title的权重设置为普通的2倍,那么最终匹配的得分会受影响,以达到获# 取用户想要的排名结果。
#设置mapping时设定。
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"title": {
"type": "text",
"boost": 2 # 将title的值匹配的权重设置为普通的2倍
},
"content": {
"type": "text"
}
}
}
}
}
#查询时设定:
GET lib3/user/_search
{
"query": {
"match": {
"name": {
"query": "zhan si san",
"boost":"2" # 查询提升匹配的分值_score
}
}
}
}
# Filter查询filter是不计算相关性的,同时可以cache。因此,filter速度要快于query。
# 插入数据等待查询
POST /lib4/items/_bulk
{"index": {"_id": 1}}
{"price": 40,"itemID": "ID100123"}
{"index": {"_id": 2}}
{"price": 50,"itemID": "ID100124"}
{"index": {"_id": 3}}
{"price": 25,"itemID": "ID100124"}
{"index": {"_id": 4}}
{"price": 30,"itemID": "ID100125"}
{"index": {"_id": 5}}
{"price": null,"itemID": "ID100127"}
# 简单的过滤查询
GET /lib4/items/_search
{
"post_filter": {
"term": {
"price": 40
}
}
}
GET /lib4/items/_search
{
"post_filter": {
"terms": {
"price": [25,40]
}
}
}
GET /lib4/items/_search
{
"post_filter": {
"term": {
"itemID": "ID100123" # 这里无法查询出来,因为itemId是text类型会被分词,分词后存
储为小写,所以这里有2种处理办法,第一种,把id改为小写:id100123,第二种设置字段index:false
}
}
}
# 查看分词器分析的结果,不希望商品id字段被分词,则重新创建映射
# GET /lib4/_mapping
DELETE lib4
PUT /lib4
{
"mappings": {
"items": {
"properties": {
"itemID": {
"type": "text",
"index": false
}
}
}
}
}
# bool过滤查询可以实现组合过滤查询
{
"bool": {
"must": [],
"should": [],
"must_not": []
}
}
# must:必须满足的条件—and
# filter 不must不同,filter分值被忽略,过滤器字句在过滤器上下文执行,
# should:可以满足也可以不满足的条件–or
# must_not:不需要满足的条件–not
GET /lib4/items/_search
{
"post_filter": {
"bool": {
"should": [
{"term": {"price":25}},
{"term": {"itemID": "id100123"}}
],
"must_not": {
"term":{"price": 30}
}
}
}
}
GET /lib4/items/_search
{
"post_filter": {
"bool": {
"should": [
{"term": {"itemID": "id100123"}},
{
"bool": {
"must": [
{"term": {"itemID": "id100124"}},
{"term": {"price": 40}}
]
}
}
]
}
}
}
# 范围过滤
# gt: > lt: < gte: >= lte: <=
GET /lib4/items/_search
{
"post_filter": {
"range": {
"price": {
"gt": 25,
"lt": 50
}
}
}
}
GET /lib4/items/_search
{
"query": {
"bool": {
"filter": {
"exists":{
"field":"price"
}
}
}
}
}
# 过滤非空
GET /lib4/items/_search
{
"query" : {
"constant_score" : {
"filter": {
"exists" : { "field" : "price" }
}
}
}
}
# 过滤器缓存
# ElasticSearch提供了一种特殊的缓存,即过滤器缓存(filter cache),用来存储过滤器的结果,被缓
# 的过滤器并不需要消耗过多的内存(因为它们只存储了哪些文档能与过滤器相匹配的相关信息),而且可供
# 后续所有与之相关的查询重复使用,从而极大地提高了查询性能。
# 注意:ElasticSearch并不是默认缓存所有过滤器
以下过滤器默认不缓存:
numeric_range
script
geo_bbox
geo_distance
geo_distance_range
geo_polygon
geo_shape
and
or
not
以下默认是开启缓存:
exists,missing,range,term,terms
# 开启方式:在filter查询语句后边加上
"_catch":true
# 聚合查询
# sum
GET /lib4/items/_search
{
"size":0,
"aggs": {
"price_of_sum": {
"sum": {
"field": "price"
}
}
}
}
#min
GET /lib4/items/_search
{
"size": 0,
"aggs": {
"price_of_min": {
"min": {
"field": "price"
}
}
}
}
# max
GET /lib4/items/_search
{
"size": 0,
"aggs": {
"price_of_max": {
"max": {
"field": "price"
}
}
}
}
# avg
GET /lib4/items/_search
{
"size":0,
"aggs": {
"price_of_avg": {
"avg": {
"field": "price"
}
}
}
}
# cardinality:求基数,互不相同的数,类似distinct,不包含null。(null,1,2,3,基数为3)
GET /lib4/items/_search
{
"size":0,
"aggs": {
"price_of_cardi": {
"cardinality": {
"field": "price"
}
}
}
}
# terms:分组
GET /lib4/items/_search
{
"size":0,
"aggs": {
"price_group_by": {
"terms": {
"field": "price"
}
}
}
}
# 对那些有唱歌兴趣的用户按年龄分组
GET /lib3/user/_search
{
"query": {
"match": {
"interests": "changge"
}
},
"size": 0,
"aggs":{
"age_group_by":{
"terms": {
"field": "age",
"order": {
"avg_of_age": "desc"
}
},
"aggs": {
"avg_of_age": {
"avg": {
"field": "age"
}
}
}
}
}
}
聚合补充:
基于groovy脚本执行partial update
es有内置的脚本支持,可以基于groovy脚本实现复杂的操作
# 修改年龄
POST /lib/user/4/_update
{
"script": "ctx._source.age+=1"
}
# 修改名字
POST /lib/user/4/_update
{
"script": "ctx._source.last_name+='hehe'"
}
# 添加爱好
POST /lib/user/4/_update
{
"script": {
"source": "ctx._source.interests.add(params.tag)",
"params": {
"tag":"picture"
}
}
}
# 删除爱好
POST /lib/user/4/_update
{
"script": {
"source": "ctx._source.interests.remove(ctx._source.interests.indexOf(params.tag))",
"params": {
"tag":"picture"
}
}
}
# 删除文档
POST /lib/user/4/_update
{
"script": {
"source": "ctx.op=ctx._source.age==params.count?'delete':'none'",
"params": {
"count":29 # 如果这里是数值类型,不要加引号
}
}
}
# upsert (如果存在执行script,如果不存在则,插入更新)
POST /lib/user/4/_update
{
"script": "ctx._source.age += 1",
"upsert": {
"first_name" : "Jane",
"last_name" : "Lucy",
"age" : 20,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
}
# 指定分ik分词器 配合 拼音分词
# 地址 put localhost:9200/ceshi1
{
"settings" : {
"number_of_shards": 2,
"number_of_replicas": 0,
"analysis" : {
"analyzer" : {
"default" : {
"tokenizer" : "ik_max_word"
},
"pinyin_analyzer" : {
"tokenizer" : "my_pinyin"
}
},
"tokenizer" : {
"my_pinyin" : {
"type" : "pinyin",
"keep_separate_first_letter" : false,
"keep_full_pinyin" : true,
"keep_original" : true,
"limit_first_letter_length" : 16,
"lowercase" : true,
"remove_duplicated_term" : true
}
}
}
},
"mappings" : {
"blog1" : {
"properties" : {
"name" : {
"type" : "text",
"analyzer" : "ik_max_word",
"search_analyzer" : "ik_max_word"
},
"title" : {
"type" : "text",
"analyzer" : "ik_smart"
},
"ceshi" : {
"type" : "keyword"
}
}
}
}
}
# 对该index 下添加字段
# 地址 put localhost:9200/ceshi1/blog1/_mapping
{
"properties" : {
"shijian" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"namepinyin" :{
"type" : "text",
"analyzer" : "ik_max_word",
"fields" : {
"pinyin" : {
"type" : "text",
"analyzer" : "pinyin_analyzer",
"search_analyzer" : "pinyin_analyzer"
}
}
}
}
}
# 测试使用拼音搜索
# GET localhost:9200/ceshi1/blog1/_search
{
"query":{
"match": {
"namepinyin.pinyin": "guo"
}
}
}
商城设置案例:
# ik、拼音、数字ngram分词混用 settings 设置 (商城案例)
{
"index": {
"analysis": {
"analyzer": {
"full_pinyin_analyzer": {
"tokenizer" : "ik_smart",
"filter": [
"full_pinyin",
"unique"
]
},
"first_letter_pinyin_analyzer": {
"tokenizer" : "ik_smart",
"filter": [
"first_letter_pinyin"
]
},
"edge_ngram_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"edge_ngram_filter"
]
}
},
"filter": {
"full_pinyin": {
"type": "pinyin",
"lowercase": true,
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_none_chinese": true,
"keep_none_chinese_together": true,
"none_chinese_pinyin_tokenize": false
},
"first_letter_pinyin": {
"type": "pinyin",
"keep_first_letter": true,
"keep_separate_first_letter": true,
"keep_full_pinyin": false,
"keep_joined_full_pinyin": false,
"limit_first_letter_length": 16,
"keep_original": false,
"lowercase": true
},
"edge_ngram_filter": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 10
}
}
}
}
}
# mapping 设置 个字段类型设置 (商城案例)
{
"goods": {
"_all": {
"enabled": false
},
"properties": {
"appCommission": {
"type": "double"
},
"appPrice0": {
"type": "double"
},
"appPrice1": {
"type": "double"
},
"appPrice2": {
"type": "double"
},
"appPriceMin": {
"type": "double"
},
"appUsable": {
"type": "byte"
},
"batchNum0": {
"type": "integer"
},
"batchNum1": {
"type": "integer"
},
"batchNum2": {
"type": "integer"
},
"batchPrice0": {
"type": "double"
},
"batchPrice1": {
"type": "double"
},
"batchPrice2": {
"type": "double"
},
"brandEnglish": {
"type": "text",
"index": "not_analyzed"
},
"brandId": {
"type": "long"
},
"brandName": {
"type": "text",
"index": "not_analyzed"
},
"categoryId": {
"type": "integer"
},
"categoryIds": {
"type": "integer"
},
"categoryName": {
"type": "text",
"index": "not_analyzed"
},
"commissionRate": {
"type": "integer"
},
"commissionTotal": {
"type": "double"
},
"commonId": {
"type": "integer"
},
"evaluateNum": {
"type": "integer"
},
"freightArea": {
"type": "integer"
},
"freightTemplateId": {
"type": "integer"
},
"goodsFavorite": {
"type": "integer"
},
"goodsFreight": {
"type": "double"
},
"goodsModal": {
"type": "byte"
},
"goodsName": {
"type": "text",
"fields": {
"full_pinyin": {
"type": "text",
"analyzer": "full_pinyin_analyzer"
},
"first_letter": {
"type": "text",
"analyzer": "first_letter_pinyin_analyzer"
},
"goodsName": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
},
"goodsNameMax": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
},
"jingle": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
},
"specString": {
"type": "text",
"analyzer": "edge_ngram_analyzer",
"search_analyzer": "standard"
},
"goodsSpecList": {
"type": "nested",
"properties": {
"goodsId": {
"type": "integer"
},
"spec": {
"type": "text",
"analyzer": "edge_ngram_analyzer",
"search_analyzer": "standard"
}
}
},
"areaInfo": {
"type": "text",
"index": "no"
},
"goodsRate": {
"type": "integer"
},
"goodsSaleNum": {
"type": "integer"
},
"goodsState": {
"type": "byte"
},
"goodsStatus": {
"type": "byte"
},
"goodsVerify": {
"type": "byte"
},
"imageName": {
"type": "text",
"index": "no"
},
"goodsImageList": {
"type": "nested",
"properties": {
"imageId": {
"type": "integer"
},
"commonId": {
"type": "integer"
},
"colorId": {
"type": "integer"
},
"imageName": {
"type": "text",
"index": "no"
},
"imageSort": {
"type": "integer"
},
"isDefault": {
"type": "integer"
},
"imageSrc": {
"type": "text",
"index": "no"
}
}
},
"isDistribution": {
"type": "byte"
},
"isGift": {
"type": "byte"
},
"isOwnShop": {
"type": "byte"
},
"ordersCount": {
"type": "integer"
},
"promotionId": {
"type": "integer"
},
"promotionState": {
"type": "byte"
},
"promotionType": {
"type": "byte"
},
"promotionStartTime": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"promotionEndTime": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"sellerId": {
"type": "integer"
},
"storeId": {
"type": "integer"
},
"storeName": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
},
"labelIdList": {
"type": "integer"
},
"joinBigSale": {
"type": "integer"
},
"unitName": {
"type": "text",
"index": "no"
},
"usableVoucher": {
"type": "byte"
},
"webCommission": {
"type": "double"
},
"webPrice0": {
"type": "double"
},
"webPrice1": {
"type": "double"
},
"webPrice2": {
"type": "double"
},
"webPriceMin": {
"type": "double"
},
"webUsable": {
"type": "byte"
},
"WechatCommission": {
"type": "double"
},
"wechatPrice0": {
"type": "double"
},
"wechatPrice1": {
"type": "double"
},
"wechatPrice2": {
"type": "double"
},
"wechatPriceMin": {
"type": "double"
},
"wechatUsable": {
"type": "byte"
},
"searchBoost": {
"type": "integer"
},
"extendString0": {
"type": "text",
"index": "no"
},
"extendString1": {
"type": "text",
"index": "no"
},
"extendString2": {
"type": "text",
"index": "no"
},
"extendString3": {
"type": "text",
"index": "no"
},
"extendString4": {
"type": "text",
"index": "no"
},
"extendString5": {
"type": "text",
"index": "no"
},
"extendString6": {
"type": "text",
"index": "no"
},
"extendString7": {
"type": "text",
"index": "no"
},
"extendString8": {
"type": "text",
"index": "no"
},
"extendString9": {
"type": "text",
"index": "no"
},
"extendInt0": {
"type": "integer"
},
"extendInt1": {
"type": "integer"
},
"extendInt2": {
"type": "integer"
},
"extendInt3": {
"type": "integer"
},
"extendInt4": {
"type": "integer"
},
"extendInt5": {
"type": "integer"
},
"extendInt6": {
"type": "integer"
},
"extendInt7": {
"type": "integer"
},
"extendInt8": {
"type": "integer"
},
"extendInt9": {
"type": "integer"
},
"extendPrice0": {
"type": "double"
},
"extendPrice1": {
"type": "double"
},
"extendPrice2": {
"type": "double"
},
"extendPrice3": {
"type": "double"
},
"extendPrice4": {
"type": "double"
},
"extendPrice5": {
"type": "double"
},
"extendPrice6": {
"type": "double"
},
"extendPrice7": {
"type": "double"
},
"extendPrice8": {
"type": "double"
},
"extendPrice9": {
"type": "double"
},
"extendTime0": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"extendTime1": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"extendTime2": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"extendTime3": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"extendTime4": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"extendTime5": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"extendTime6": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"extendTime7": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"extendTime8": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"extendTime9": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.7.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.7.1</version>
</dependency>
简单例子:
private static String host = "localhost";
private static int prot = 9200;
# 高级端
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost(host, prot)));
# 一.通过文档插入时的ID查询 GET请求获取
GetRequest getRequest = new GetRequest("index", "blog", "1");
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse.getSource());
client.close();
# 二.搜索API 该SearchRequest用于具有与搜索文件,汇总,建议做,也要求提供高亮显示所产生的文件的方式中的任何操作。(1)
SearchRequest searchRequest = new SearchRequest();
# 大多数搜索参数都会添加到SearchSourceBuilder。它为搜索请求正文中的所有内容提供了setter。(2)
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
# 添加match_all查询到SearchSourceBuilder。(查询所有)(3)
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
# 添加SearchSourceBuilder到SeachRequest。(4)
searchRequest.source(searchSourceBuilder);
# 指定只能在哪些文档库中查询:可以添加多个且没有限制,中间用逗号隔开(5)
searchRequest.indices("index");
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
# 返回数据 Hits 循环遍历获取
for (SearchHit hit : response.getHits()) {
String json = hit.getSourceAsString();
System.out.println(json);
}
client.close();
# 插入数据组装
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("id", "2");
jsonMap.put("title", "PHP设计模式");
jsonMap.put("content", "这个是测试的第二个插入数据");
jsonMap.put("postdate", "2018-12-13");
jsonMap.put("url", "www.baidu.com");
# 内置json转换
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject();
{
builder.field("title", "PHP设计模式");
builder.field("content", "这个是测试的第二个插入数据");
builder.timeField("postdate", new Date());
builder.field("url", "www.baidu.com");
}
builder.endObject();
# 插入
IndexRequest indexRequest = new IndexRequest("index", "blog", "1");
indexRequest.source(jsonMap);
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
System.out.println(indexResponse.getResult());
client.close();