写在最前,本人也只是个大三的学生,如果你发现任何我写的不对的,请在评论中指出。
用的jar是:elasticsearch-rest-high-level-client
现在已经是在公司实习了大概三个星期,基本都是在自己摸索着学习,老前辈还是没有带着我写业务的意思,忧虑呀。不过开心的是最近在学习ElasticSearch API的时候, 偶然在gitee上发现了一位大佬编写的API 工具集,我觉得相当厉害,大概浏览下来,记录一下该工具集一些函数编写的思路,工具集的地址如下:
X-Pacific大佬的作品
在开始之前(你可以直接去看源码跟大佬的文档,比我的好),先需要了解一下QueryBuilder
的常用用法展示,当然有更好的展示:更多QueryBuilder详见。
- 1、termQuery
term查询是代表完全匹配,也就是精确查询,一般是用在数字、日期和枚举等结构化类型, 而不是全文本字段哦, 一般来说,如果你对一个mapping结构是默认创建的文本进行查询,如下:
# ES7以上版本
POST /index/_doc/_search
{
"query":{
"term":{
"address": "Avenue"
}
}
}
一般来说hits
就是个空数组,字符串字段可以是文本类型或关键字。而精确值有在添加到倒排索引的字段中指定的确切值,以使其可被搜索。(这里你去GET _analyze
就会发现Avenue成了avenue导致无法被索引,中文的话这样的精准也有问题,text会先把词分词,keyword不分词,你照样是null),解决方案也有,因为默认创建的mapping里text类型会携带一个子属性是keyword,所以可以这样:
"address.keyword": "Avenue"
// 在java中则是:
// 另外term是代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇
QueryBuilder queryBuilder = QueryBuilders.termQuery("appli_name.keyword", "456");
// 这个被坑过才写这么多
- 2、matchQuery
matchQuery
查询比较好理解,会对文本分词,比如:“明天上班”就会被分词为“明天 天上 上班”(我用的ik分词器的ik_max_word),你只要匹配中一个就会返回对应的文档,使用如下:
QueryBuilder queryBuilder = QueryBuilders.matchQuery("appli_name","中男儿");
// 还可以设置最少匹配参数, 这里设置的最少匹配词语数量是75%, 少于无法查询到
QueryBuilder queryBuilder = QueryBuilders.matchQuery("appli_name","中 男 儿 美 丽 人 生").minimumShouldMatch("75%");
// 你还可以进行fuzzy纠错查询
QueryBuilder queryBuilder = QueryBuilders.matchQuery("appli_name","spting");
((MatchQueryBuilder) queryBuilder).fuzziness(Fuzziness.AUTO);
// 纠错查询还可以单独拎出来 原文是spring
QueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("appli_name","spting");
// 或者设定查询条件的逻辑关系,是AND还是OR,你自己看需求查文档
QueryBuilder queryBuilder = QueryBuilders.matchQuery("appli_name","spring sps").operator(Operator.AND);
- 3、matchPhraseQuery
短语查询,与上面的最大区别是,它不会进行分词,是以短语的方式进行查询:
//必须相邻的查询条件
QueryBuilder queryBuilder = QueryBuilders.matchPhraseQuery("appli_name","国好");
//slop设置为2,中和男最多能移动两次并完成匹配
QueryBuilder queryBuilder = QueryBuilders.matchPhraseQuery("appli_name","中男").slop(2);
- 4、boost权重
权重默认为1 大于1后增加查询到该子句的相对权重, 如果是0-1之间 则是减少相对权重
//查询结果appli_name为spring的会被优先展示其次456,再次123
QueryBuilder queryBuilder1 = QueryBuilders.termQuery("appli_name.keyword","spring").boost(5);
QueryBuilder queryBuilder2 = QueryBuilders.termQuery("appli_name.keyword","456").boost(3);
QueryBuilder queryBuilder3 = QueryBuilders.termQuery("appli_name.keyword","123");
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.should(queryBuilder1).should(queryBuilder2).should(queryBuilder3);
- 5、prefixQuery、wildcardQuery、regexpQuery
名字已经暴露了, 前缀查询、通配符查询与正则查询
//性能差,扫描整个倒排索引,前缀越短,要处理的doc越多,性能越差,尽可能用长前缀搜索
//查询appli_name字段值前缀为1的内容
QueryBuilder queryBuilder = QueryBuilders.prefixQuery("appli_name","1");
//性能较差不建议使用
//?:任意字符
//*:0个或任意多个字符
QueryBuilder queryBuilder = QueryBuilders.wildcardQuery("appli_name","1?3");
//性能较差不建议使用
QueryBuilder queryBuilder = QueryBuilders.regexpQuery("appli_name","[0-9].+");
//[0-9]:指定范围内的数字
//[a-z]:指定范围内的字母
//.:一个字符
//+:前面的正则表达式可以出现一次或多次
剩下的一起讲, 组合查询一共有三种:与must、或should、非mustNot,是字面意思,最佳匹配best_fields会对所有查询条件中只取匹配度最高的那个条件的分数作为最终分数, 最多匹配most_fields:能匹配到更多字段的记录优先(不管其中某一个字段有多么匹配),跨字段匹配cross_fields:综合起来最匹配的,即类似将所有字段合并到一个字段中,搜索这个字段看谁分高 最佳实践:可以代替copy_to节省了一个字段的倒排空间。
源码部分
大佬的源码部分,它的设计思路是这样的:
1、首先是设定注解@ESMetaData
来获取该类的一些元数据,比如索引名、类型名(ES7之后就是_doc)、主分片数量、副分配数量、别名以及scroll设置等等。 之后再用@ESMapping
设置类属性对应ES mapping中的字段,用@ESId
标识出ID,方便之后可以通过反射获取ID的值。
2、通过这样的设计,我们只要获取了类的CLASS信息,我们就能通过反射获取到类的属性与值,所以基本所有方法开头,你都会看到:
// 元数据
MetaData metaData = IndexTools.getIndexType(clazz);
// 索引名
String indexname = metaData.getIndexname();
// 索引类型
String indextype = metaData.getIndextype();
// 然后被丢到更详细的函数里被调用
3、在ElasticSearch API里,每一种请求都被封装成了不同的类,大致可以分为: 保存是IndexRequest
、更新是UpdateRequest
、删除是DeleteRequest
、搜索是SearchRequest
、批量操作是BulkRequest
4、像是save方法,源码流程就是:获取元数据、判断元数据安全、调用client.index发起请求并用response接收,最后判断是否操作成功,返回布尔结果:
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
5、批量保存操作的话,也就是new 一个bulkRequest
,在循环里json化参数,并每次循环就为bulkRequest
新增一个IndexRequest
,最后发送请求,并判断返回boolean结果:
T tt = list.get(i);
clazz = tt.getClass();
String id = Tools.getESId(tt);
String souce = JsonUtils.obj2String(tt);
request.add(new IndexRequest(indexname, indextype, id).source(souce, XContentType.JSON));
6、更新的话过程类似,只不过就换了一个中间过程,需要更新的参数需要转换为map类型。批量更新是在循环中每次为bulkRequest
新增一个UpdateRequest
。
updateRequest.doc(Tools.getFieldValue(t));
7、删除的话也朴实无华就更不用讲了,一样朴实无华。
8、搜索应该是最重要的,但最基础的搜索(非分页查询),你可以直接选择new一个MatchAllQueryBuilder
来全搜索。其实可以看最长的那个search方法,该方法支持支持分页、高亮、排序、指定返回字段、路由的查询(跨索引),但是我对scroll以及search after不熟,所以导致有部分不太懂。
一些概念上的问题
1、什么是scroll
答:类似传统数据库的游标, 游标是为用户开设的一个缓冲区,存放SQL语句的执行结果,每个游标去都有一个名字,用户可以用SQL语句逐一从游标中获取记录,通俗讲就是将数据暂时放到了一个内存区域的虚表中,而这个虚表就是游标。而scroll和游标类似
2、为什么使用scroll
答:当Elasticsearch响应请求时,它必须确定docs的顺序,排列响应结果。如果请求的页数较少(假设每页20个docs), Elasticsearch不会有什么问题,但是如果页数较大时,比如请求第20页,Elasticsearch不得不取出第1页到第20页的所有docs,再去除第1页到第19页的docs,得到第20页的docs。
解决的方法就是使用Scroll。因为Elasticsearch要做一些操作(确定之前页数的docs)为每一次请求,所以,我们可以让Elasticsearch储存这些信息为之后的查询请求。这样做的缺点是,我们不能永远的储存这些信息,因为存储资源是有限的。所以Elasticsearch中可以设定我们需要存储这些信息的时长(但是大佬不推荐使用,数据量大的时候会造成内存溢出)。
3、聚合查询是什么
答:在Mysql中,我们可以获取一组数据的 最大值(Max)、最小值(Min)。同样我们能够对这组数据进行 分组(Group)。那么对于Elasticsearch中我们也可以实现同样的功能:
Metric(指标): 指标分析类型,如计算最大值、最小值、平均值等等 (对桶内的文档进行聚合分析的操作)
Bucket(桶): 分桶类型,类似SQL中的GROUP BY语法 (满足特定条件的文档的集合)
Pipeline(管道): 管道分析类型,基于上一级的聚合分析结果进行在分析
Matrix(矩阵): 矩阵分析类型(聚合是一种面向数值型的聚合,用于计算一组文档字段中的统计信息)