首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…
一 . 前言
这一篇主要针对 ES 查询的相关逻辑 (😜文档收集专家) , 深度直到日常使用 ,不会过多涉及理论~~
基础概念补充 :
// 简介 :
Elasticsearch 是一个分布式、可扩展、实时的搜索与数据分析引擎
Elasticsearch 通常被用作 全文检索、结构化搜索、分析 . Elasticsearch 基于 Lucence
Elasticsearch 将所有的功能打包成一个单独的服务,这样你可以通过程序与它提供的简单的 RESTful API 进行通信.
// 特点 :
Elasticsearch 不仅存储文档,而且 索引 每个文档的内容,使之可以被检索。
Elasticsearch 使用 JavaScript Object Notation(或者 JSON)作为文档的序列化格式
// 数据结构 :
Elastic 集群
|- 索引 : 类似于一个数据库(注意区分名词和动词) , 索引是具有某种特征文档的集合
|- 索引 // 一个集群中可以包含多个索引(indices -> 数据库 )
|- 类型 : 类似于一个表
|- // 每个索引可以包含多个类型 (types -> 表)
|- 文档 : 类似于表中的一行数据 , index 里面的单条记录被称为文档
|- // 每个类型包含多个文档 (documents ->行 )
|- 属性 : 类似于一行数据中的一个属性
|- // 每个文档包含多个字段(fileds - > 列)
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
PS : Type 的变迁 在 5.X 版本和之前版本,一个 index 下可以创建多个 type
在 6.X 版本中,一个 index 下只能存在一个 type
在 7.X 版本中,去除type 概念 , index 中不在存在type
以下版本会以 > 7.x 来写案例 , 查询方式与历史版本一致 ,只不过默认使用_doc
// 注意 ,以下2种查询方式等效 ,为了方便理解 , 演示使用_doc
GET /order/_search
GET /order/_doc/_search
二 . ES 查询基础概念
1.1 系统查询条件
系统详情可以查看以下文档 : blog.csdn.net/qq_28988969…
// 系统信息
查看路由信息 : GET _cat/aliases?v
显示分片信息 : GET _cat/allocation?v
// 健康信息
查看系统健康 : GET _cat/health
查看健康详情 : GET _cat/health?v
// PS : Green 一切正常 / Yellow 所有数据可获取 ,部分复制品未分配 / Red 部分是数据获取不到 ,集群不可用
// PS : node.total 节点总数 / node.data 数据节点总数 / active_shards_percent 活动分片百分比
// 节点信息
查看Master节点 : GET _cat/master?v
查看Node节点 : GET _cat/nodes?v
查看Node节点属性 : GET _cat/nodeattrs?v
// 任务信息
查看等待的任务 : GET _cat/pending_tasks?v
// 插件信息
查看节点的插件 : GET _cat/plugins?v
// 索引详情
显示索引 : GET _cat/indices?v
显示分段 : GET _cat/segments?v
1.2 ES 查询的方式
ES 常见的操作方式为 REST API , 通过操作类型 (POST , GET , PUT , DELETE)来区别具体的操作类型 , 查询主要基于 GET 方式下 :
ES 请求 :URL 格式
curl -X<VERB> '<PROTOCOL>://<HOST>/<PATH>?<QUERY_STRING>' -d '<BODY>'
curl -XGET 'http://localhost:9200/_count?pretty' -d '
ES 请求 : Body 格式
GET /order/_search
{
"query": {
"match_all": {}
}
}
1.3 查询关键字
// 基础查询方式
- term : 完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇
- match : 先对搜索词进行分词,分词完毕后再逐个对分词结果进行匹配
- fuzzy : 相似度匹配 , 区别与一般的模糊匹配 , 相似度不是逐字匹配
// 基础查询标准格式
- matchAll :
- multiMatch :多词匹配
- matchPhrase : 短语匹配
- ids : 通过 ID 检索
- prefix : 前缀匹配
- range : 范围匹配
- wildcard : 模糊匹配 ,通过通配符进行匹配
- regexp : 正则匹配
// 其他关键字 :
keyword : 该关键字主要放在 field 后面 , 用于
1.4 基础请求和返回含义
ES 返回是以 JSON 格式进行范围 ,为了后文的案例 ,这里统一把字段含义进行整理 :
// 以一个基础的检索为例 :
GET /order/_doc/1
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1", // _id : 当前 Doc ID , 可以使用自增或者自建
"_version" : 1, // _version : 每次修改会修改版本号 ,以做多版本管理
"found" : true, // found : 是否查询到数据 ,此处如果是 false ,标识数据未查询到
"_source" : { // _source : 实际资源对象
"_class" : "com.gang.study.elasticsearch.demo.entity.AOrder", // Spring 默认会提供创建类
"username" : "gang",
"last_name" : "test",
"age" : 25,
"about" : "ElasticSearch",
"interests": [ "test", "12345" ]
}
}
============== 分页
{
"took": 6, // 整个搜索请求花费的毫秒数
"timed_out": false, // 是否超时 , 将返回在请求超时前收集到的结果
"_shards": { ... }, // 参与查询的分片数 , 有多少是成功的(successful 字段),有多少的是失败的(failed 字段)
"hits": {
"total": 3, // 查询总数
"max_score": 1,
"hits":[内部为单次查询的数组结果]
}
}
1.5 filtered 查询
filtered 是比较旧的查询 , 在5.0版本更新后 , filtered 被更新为了 bool , 以下文章部分还是使用的 Filtered , 有兴趣可以比较下2者的写法 :
具体可以参考这篇文档 :# es中filtered和filter的区别
GET _search
{
"query": {
"filtered": {
"query": {"match": {"outAddress": "wuhan123"}},
"filter": { "term": { "status": "1" } }
}
}
}
// 新版 Bool 写法
{
"query": {
"bool": {
"must": {"match": {"outAddress": "wuhan123"}},
"filter": {"term": {"status": "1"}
}
}
}
}
二. 基础查询案例
2.1 Paramter 查询
基础查询
// 通过 ID 查询 : GET /索引/类型/ID
GET /order/_doc/1
// 简单搜索 > 全量
GET /order/_doc/_search
// 简单搜索 > 带条件 (通过 q 来设置查询条件)
GET /order/_doc/_search?q=last_name:Smith
GET /order/_doc/_search?q=+name:john +tweet:mary
GET /_search?q=mary // 查询包含 mary 的所有文档
// 简单搜索 > 包含语句 (或) , 不指定name: 则表示查询所有
GET /order/_doc/_search?q=name:(mary john)
// 简单查询 > 时间比较
GET /order/_doc/_search?q=date:>2014-09-10
// 简单查询 > 返回指定字段
GET /order/_doc/123?_source=title,text
// 简单查询 > 返回_source (不会返回_index ,_id 等元数据)
GET /order/_doc/123/_source
GET /_search // 空搜索
GET /gb/_search // 在指定索引下所有类型种搜索
GET /gb,us/_search // 在 gb 和 us 索引下搜索
GET /g*,u*/_search // 模糊索引搜索
GET /gb/user/_search // 指定索引和类型进行搜索
GET /gb,us/user,tweet/_search // 在多个索引和类型下搜索
GET /_all/user,tweet/_search // 在所有所有下查询多个类型
分页索引
// 简单分页查询
GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
2.2 请求体查询
2.2.1 Match 匹配
match 匹配先对搜索词进行分词,分词完毕后再逐个对分词结果进行匹配
// DSL 查询 > 单 Query (DSL 是通过 ReqeustBody 传入查询信息进行复杂查询)
GET /order/_doc/_search
{
"query" : {
"match" : {
"outAddress" : "武汉12"
}
}
}
2.2.2 Term 匹配
完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇
GET /order/_search
{
"query": {
"term": {
"outAddress": "12"
}
}
}
// 通过 keyword 进行精确完整匹配
{
"query": {
"term": {
"outAddress.keyword": "武汉12"
}
}
}
Match 匹配 和 Term 匹配的区别 :
Match 会进行分词处理 , 例如一个查询语句AntBlack刚 , 会被分割成2个词 : Antblack , 刚 . 再通过倒排序进行搜索 , 判读是否存在匹配的索引
而对于 Term , 进行的是精准查询 ,如果分词器没有对应的分词 , 可能会出现查询不到的情况.
2.2.3 profix 前缀匹配
前缀匹配适用于自动补全功能 , 这里可以看成就是 FST 功能:
GET /order/_search
{
"query": {
"prefix": {
"outAddress.keyword": "武汉"
}
}
}
2.2.4 wildcard 模糊匹配
通过通配符表达式来进行模糊匹配
GET /order/_search
{
"query": {
"wildcard": {
"outAddress.keyword": "武汉?2"
}
}
}
// 补充 :
* : 匹配任何字符序列
? : 匹配当个字符
2.2.5 match_phrase
// DSL 查询 > 短语搜索 (会全匹配短语)
GET /order/_doc/_search
{
"query" : {
"match_phrase" : { // 只有完整匹配到以下词才会匹配成功
"about" : "rock climbing"
}
}
}
2.2.6 multi_match
多字段检索 , 同时对多个字段检索 , 判断是否匹配 Query 条件
GET /order/_search
{
"query": {
"multi_match": {
"query": "武汉12",
"fields": [
"outAddress",
"extension"
]
}
}
}
2.2.7 Query String 表达式
这事一种比较独特的方式 ,通过表达式的方式进行查询 , 表达式也很通俗易懂, 括号和 AND OR 即可
GET /order/_search
{
"query": {
"query_string": {
"default_field": "outAddress",
"query": "(武 AND 77) OR (武 AND 12)"
}
}
}
2.2.8 组合匹配
// 组合过滤 : 多重嵌套 (组合过滤可以bool 嵌套 bool 以实现复杂的查询)
// 以 SQL 对比如下表达式 :
{
"query": {
"filtered": {
"filter": {
"bool": {
"should": [
{
"term": {
"productID": "KDKE-B-9947-#kL5"
}
},
{
"bool": { // 双重 Bool
"must": [
{
"term": {
"productID": "JODL-X-1937-#pV7"
}
},
{
"term": {
"price": 30
}
}
]
}
}
]
}
}
}
}
}
// 组合过滤 : 指通过 多个 Boolean 结果判断是否符合条件
> must :所有分句都必须匹配,与 AND 相同
> must_not :所有分句都必须不匹配,与 NOT 相同
> should :至少有一个分句匹配,与 OR 相同
GET /order/_doc/_search
{
"query": {
"filtered": {
"filter": {
"bool": { // 标识需要满足范围内所有判断
"should": [
{
"term": {
"price": 20
}
},
{
"term": {
"productID": "XHDK-A-1293-#fJ3"
}
}
],
"must_not": {
"term": {
"price": 30
}
}
}
}
}
}
}
2.2.9 正则匹配
通过正则表达式进行匹配 , 前面还是字段 , 后面为表达式 @ www.elastic.co/guide/en/el…
GET /order/_search
{
"query": {
"regexp":{
"outAddress":"[0-3][2-9]"
}
}
}
2.2.10 存在匹配
返回包含字段索引值的文档 @ www.elastic.co/guide/en/el…
GET /order/_search
{
"query": {
"exists": {
"field": "outAddress"
}
}
}
2.2.11 fuzzy 相似度匹配
这个匹配就比较玄学了 , 会根据 Levenshtein 判断词的相似度 , 进而匹配 . 类似于百度搜出来的乱七八糟的东西 @ www.elastic.co/guide/en/el…
GET /order/_search
{
"query": {
"fuzzy": {
"outAddress": {
"value": "266",
"fuzziness": "AUTO",
"max_expansions": 50,
"prefix_length": 0,
"transpositions": true,
"rewrite": "constant_score"
}
}
}
}
// PS :
(box → fox)
(black → lack)
三. 高级查询案例
3.1 分组和统计
复杂搜索中通过聚合等方式对结果进行分析统计
// 字段分析
GET /order/_doc/_search
{
"aggs": {
"all_interests": {
"terms": {
"field": "interests" // 对兴趣字段进行分析
}
}
}
}
// 查询平均年龄
GET /order/_doc/_search
{
"aggs": {
"all_interests": { // 分析名
"terms": {
"field": "interests"
},
"aggs": {
"avg_age": { // 聚合名
"avg": { // 统计平均年龄
"field": "age"
}
}
}
}
}
}
3.2 特性搜索
美化类
// 搜索高亮 (highlight)
GET /order/_doc/_search
{
"query": {
"match_phrase": {
"about": "rock climbing"
}
},
"highlight": {
"fields": {
"about": {}
}
}
}
// DSL : 美化查询 (可以通过 pretty 美化查询结果)
GET /order/blog/123?pretty
// Rest : 通过请求判断是否存在
curl -i -XHEAD http://localhost:9200/website/blog/123
- 200 OK : 数据存在
- 404 : 数据不存在
统计类
// 统计总数据量
显示指定索引数量 : GET /page-access/_count
显示文档总数 : GET _cat/count?v
3.2 比较查询
// DSL 查询 > Filter (通过 filter 方式比较范围)
GET /order/_doc/_search
{
"query": {
"filtered": { // 查询类型 : 能同时接收 filter 和 query
"filter": { // filter 标识为查询方法
"range": { // range 标识为范围查询
"age": { // 查询字段
"gt": 30, // 比较方式 : 详见附录1
"lt" : 40 // range 中可以设置多个匹配类型
}
}
},
"query": {
"match": {
"last_name": "smith"
}
}
}
}
}
// DSL > Filter 直接包含
{
"filter": {
"term": { // 可以匹配数字 , 也可以匹配字符串,注意,term 和 terms 是包含关系
"price": 20, // 匹配单个价格
}
}
}
{
"query": {
"filtered": {
"filter": {
"terms": { // 注意 , 这里是 terms
"price": [20,30] // 匹配多个价格
}
}
}
}
}
// DSL > Filter 完全匹配 (上述方式是包含匹配 ,而期望完全匹配短语需要额外的语法)
{
"filter": {
"bool": {
"must": [
{"term": {"tags": "search"}}, // 查询tags 包含 search
{"term": {"tag_count": 1}} // 且 tag_count 为 1 的
]
}
}
}
3.4 多文档检索
多文档检索是同时检索多个文档
// 全库通过 ID 同时获取 2个 文档
GET /_mget
{
"docs": [
{
"_index": "order",
"_type": "_doc",
"_id": 2
},
{
"_index": "website",
"_type": "pageviews",
"_id": 1,
"_source": "views" // 只获取 Views 字段
}
]
}
// 指定库检索多个文档
GET /website/_doc/_mget
{
"docs": [
{
"_id": 2
},
{
"_type": "pageviews",
"_id": 1
}
]
}
// 指定库通过 ids 检索
GET /website/_doc/_mget
{
"ids": [
"2",
"1"
]
}
四. Spring 使用
常规方式是使用Spring Repository实现 , 如果把ElasticSearch 作为一个数据分析库 , 使用 Spring 提供的工具既可以达到大部分的功能 , 使用方式和 JPA 类似, 这里主要讲的是另外一种 , 通过 ElasticClient 实现复杂查询
3.1 Jar 包解析
SpringData 组件中对 elasticSearch 基础组件做了基本的封装 , 日常使用中使用 spring-boot-starter-data-elasticsearch 即可 .
Jar 包的分析可以看之前的 Client 分析 : juejin.cn/post/707716…
3.2 复杂查询 : 从高亮进行分析
高亮查询主要使用 RestHighLevelClient 实现 , 我们可以从一个高亮查询看出Spring 查询的主要方式 :
@Autowired
private RestHighLevelClient restHighLevelClient;
public void hightQuery() throws Exception {
logger.info("进入高亮处理逻辑");
// S1 : 通过 Builder 构建查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder
.query(QueryBuilders.matchQuery("outAddress", "武汉12")) // 设置查询条件
.from(0) // 起始条数(当前页-1)*size的值
.size(10) // 每页展示条数
.sort("age", SortOrder.DESC) // 排序
.highlighter(new HighlightBuilder().field("username").requireFieldMatch(false).preTags("<span style='color:red;'>").postTags("</span>")); // 设置高亮
// S2 : 构建查询请求体
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("order").source(searchSourceBuilder);
// S3 : 发送查询获取 Response
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// S4 : 显示查询结果
logger.info("查询完成 :{} <-------", JSONObject.toJSONString(searchResponse));
Arrays.asList(searchResponse.getHits().getHits()).forEach(item -> {
logger.info("------> ITEM :{} <-------", JSONObject.toJSONString(item.getSourceAsMap()));
});
}
关注点 :
- 高亮查询在里面实际上就一个语句 : .highlighter(....)
- SearchSourceBuilder 是基础的查询构建器 , 通过 QueryBuilders 来进行相关的查询方式
- QueryBuilders 中基本上可以找到大部分的查询方式 , 可以通过简单的 API 进行查询处理
3.3 复杂查询 : 异步处理
// S1 : 通过 Builder 构建查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder
.query(QueryBuilders.matchQuery("outAddress", "武汉12")) // 设置查询条件
.from(0) // 起始条数(当前页-1)*size的值
.size(10); // 每页展示条数
// S2 : 构建查询请求体
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("order").source(searchSourceBuilder);
// S3 : 发送查询获取 Response
Cancellable searchResponse = restHighLevelClient.searchAsync(searchRequest, RequestOptions.DEFAULT, new ActionListener<SearchResponse>() {
@Override
public void onResponse(SearchResponse searchResponse) {
// S4 : 显示查询结果
logger.info("查询完成 :{} <-------", JSONObject.toJSONString(searchResponse));
Arrays.asList(searchResponse.getHits().getHits()).forEach(item -> {
logger.info("------> ITEM :{} <-------", JSONObject.toJSONString(item.getSourceAsMap()));
});
}
@Override
public void onFailure(Exception e) {
logger.error("E----> error :{} -- content :{}", e.getClass(), e.getMessage());
}
});
总结
生产中实际上对理论概念使用率不高 , 所以这一篇文章主要对 ES 的查询方式做了整理 , 便于生产中进行快速查询.
当然作为程序员 , 理论概念也很重要 , 后续会根据时间把 索引 , 倒排序等整理出来.
附录
附录-1 : Range 比较方式
gt : > 大于
lt : < 小于
gte : >= 大于或等于
lte : <= 小于或等于
// range 可以通过很多种方式来处理时间关联 , 支持日期数字操作
"gt" : "now-1h" // 当前时间戳大于当前时间减1小时
"lt" : "2014-01-01 00:00:00||+1M" // 早于指定时间一个月
// range 可以计算字符范围 , 按照字典顺序和字母顺序进行排序
{"gte" : "a","lt" : "b"} // 字符大于等于 a ,但是小于 b
附录2 : 全文搜索排序
// 全文搜索时会返回匹配度("max_score")用于描述当前结果的关联程度 , 同时默认通过结果相关性评分进行排序
附录3 : Null 值的索引
本质上 , null , [] (空数组)和 [null] 是相等的。它们都不存在于倒排索引中!
// exists 过滤器 : 将返回任何包含这个字段的文档
"filter" : {"exists" : { "field" : "tags" }}
// missing 过滤器 : 返回不包含这个字段的文档
"filter": {"missing" : { "field" : "tags" }}
附录4 : 为什么明明有这个数据但是我查不到 ?
ES 中通过倒排序进行查询 , 针对一句查询语句 , 会根据分词结果进行搜索 , 看下面这个例子 :
GET /order/_analyze
{
"analyzer": "standard",
"text": "测试12abd么"
}
// 分词结果
{
"tokens" : [
{
"token" : "测",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
},
{
"token" : "试",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "12abd",
"start_offset" : 2,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "么",
"start_offset" : 7,
"end_offset" : 8,
"type" : "<IDEOGRAPHIC>",
"position" : 3
}
]
}
当然不同的分词器的结果是不同的 , 但是针对原理而言 , 汉字被完全拆开了 , 数字和字母进行了组合 . 这些在倒排序的过程中 ,将会直接影响到结果.
这个时候 , 如果该查询方式没有击中分词 (例如 term 查询 , 不会做分词操作) , 那么就会出现查询不到的情况.
附录5 : 什么是 KeyWord 和 Mapping
ES 会为我们的 doc field 动态生成 Mapping (映射) , 该映射中包含了数据的类型 (type) , ES 中通过 /_mapping ,我们可以查看 Elasticsearch 在一个或多个索引中的一个或多个类型的映射。
Mapping 可以自定义创建, 用来规约一个属性如何创建对应的索引 ,以及其格式 , Mapping 中常见的关键字如下 :
- analyzer : 定义文本字段的分词器
- boost : 设置字段的权重
- coerce : 是否开启自动数据类型转换功能, 默认是true(开启)
- copy_to : 是否将多个字段的值,复制到同一个字段中 (创建一个虚拟字段)
- doc_values : 为了加快排序、聚合操作,在建立倒排索引的时候,额外增加一个列式存储映射,是一个空间换时间的做法
- dynamic : 是否允许根据文档动态添加mapping类型,默认true(允许)
- eager_global_ordinals : 是否开启预加载全局序号,加快查询
- format : 设置日期格式的,多个格式用||隔开
- ignore_above : 在keywor类型下设置一个长度,当字符的长度超过ignore_above的值,那么它不会被索引
- ignore_malformed : 是否忽略不规则的数据,该参数默认为 false
- index : 是否检索
- fields : 具体的索引方式
- normalizer : 解析前(索引或者查询)的标准化配置
- norms : 该字段不分词
GET /order/_mapping
{
"order" : {
"mappings" : {
"properties" : {
"_class" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"updateTime" : {
"type" : "long"
},
"username" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
这里只是概念普及 , 如果想深入 , 可以看看这篇文档 blog.csdn.net/winterking3…
参考文档
- ElasticSearch 权威指南
- 干货 | Elasticsearch 索引设计实战指南
- 一文搞懂 Elasticsearch 之 Mapping
- www.elastic.co/guide/cn/el…