背景:目前国内有大量的公司都在使用 Elasticsearch,包括阿里、京东、滴滴、今日头条、小米、vivo等诸多知名公司。除了搜索功能之外,Elasticsearch还结合Kibana、Logstash、Elastic Stack还被广泛运用在大数据近实时分析领域,包括日志分析、指标监控等多个领域。
本节内容:了解企业实际业务当中ElasticSearch的六大搜索能力。
目录
Elasticsearch的六大搜索能力
0 准备工作
创建一个student演示索引
创建索引演示数据
1、轻量搜索
2、表达式搜索
3、复杂搜索
4、全文搜索(相关性分析)
5、短语搜索
6、高亮搜索
前面文章提到过,Elasticsearch最大的优势在于其检索能力。那为了适配日常不同业务的多种查询需求,Elasticsearch为我们提供了六大搜索方式: 轻量搜索、表达式搜索、复杂搜索、全文搜索、短语搜索和高亮搜索。
基础工具参考前文 7.X增删改查实战
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"love": {
"type": "keyword"
},
"createTime": {
"format": "yyyy-MM-dd HH:mm:ss",
"type": "date"
}
}
}
}
1)索引实体对象
import java.util.Date;
public class Student extends BaseDto {
private String name;
private Integer age;
private String love;
private Date createTime;
// get set方法省略
}
2)索引数据
//2、添加文档
for(int i = 1; i<=20; i++) {
Student student = new Student();
student.setId(""+i);
student.setCreateTime(new Date());
student.setName("test"+i);
student.setAge(i+10);
if(i%2 == 0) {
student.setLove("I love to go rock climbing");
}else{
student.setLove("I like to collect rock albums");
}
Boolean add = IndexOperateUtil.addDocument(student, indexName);
System.out.println("文档新增结果" + add);
}
我们先用GET 尝试一个几乎是最简单的搜索。如下使用下列请求来搜索所有学生:
http://127.0.0.1:9200/student/_search
可以看到,我们仍然使用索引库student ,但与指定一个文档 ID 不同的是,使用 _search返回结果包括了所有三个文档放在数组 hits 中。(一个搜索默认返回十条结果)
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 30,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "student",
"_type": "_doc",
"_id": "21",
"_score": 1,
"_source": {
"love": "I like to collect rock albums",
"createTime": "2022-05-27 09:47:38",
"name": "test9",
"id": "21",
"age": 11
}
},
{
"_index": "student",
"_type": "_doc",
"_id": "22",
"_score": 1,
"_source": {
"love": "I love to go rock climbing",
"createTime": "2022-05-27 09:47:38",
"name": "test9",
"id": "22",
"age": 12
}
},
...省略
]
}
}
从上面的结果可以看出,返回结果不仅告知匹配了哪些文档,还包含了整个文档本身,将显示搜索结果给最终用户所需的全部信息。
接下来,我们搜索学生姓名为 “test9”的学生。因此,需要使用一个高亮搜索。这个方法一般涉及到一个查询字符串搜索(query-string), 因为我们通过一个URL参数来传递查询信息给搜索接口。
http://127.0.0.1:9200/student/_search?q=name:test9
我们仍然在请求路径中使用_search,并将查询本身赋值给参数 q= 。返回结果给出了所有的 test9。
{
"took": 275,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 11,
"relation": "eq"
},
"max_score": 1.4060969,
"hits": [
{
"_index": "student",
"_type": "_doc",
"_id": "21",
"_score": 1.4060969,
"_source": {
"love": "I like to collect rock albums",
"createTime": "2022-05-27 09:47:38",
"name": "test9",
"id": "21",
"age": 11
}
},
{
"_index": "student",
"_type": "_doc",
"_id": "22",
"_score": 1.4060969,
"_source": {
"love": "I love to go rock climbing",
"createTime": "2022-05-27 09:47:38",
"name": "test9",
"id": "22",
"age": 12
}
},
...省略
]
}
}
综上,轻量搜索就介绍完了。那在实际生产当中,轻量搜索也是经常使用的一种搜索方式。Query-string 搜索通过命令虽然非常方便地进行临时性的及时搜索 ,但它有自身的局限性,参数传递不是很灵活,比如不利于我们传输一些复杂的查询。
Elasticsearch 提供一个丰富灵活的查询语言叫做 查询表达式 , 它支持构建更加复杂和健壮的查询。这中查询也叫做领域特定语言(DSL), 会使用 JSON 构造了一个请求。
http://127.0.0.1:9200/student/_search
{
"query": {
"match": {
"name": "test9"
}
}
}
返回结果与轻量搜索的查询一样,但还是可以看到有一些变化。请求不再使用 query-string 参数,而是一个JSON 体替代。同时使用了一个 match 查询(属于查询类型之一,老王会在后面文章继续介绍)。
前面我们以及大致了解了Elasticsearch基本的一些查询方式,接下来我们尝试一些稍微复杂的搜索。
现在有这样一个业务场景:需要搜索名字为test9且年龄大于20岁以上的学生。那在表达式查询需要稍作调整下,此处需要使用过滤器filter,它可以支持高效执行一个结构化的JSON查询。
我们造几条测试数据,代码如下:
//2、添加文档
for(int i = 21; i<=30; i++) {
Student student = new Student();
student.setId(""+i);
student.setCreateTime(new Date());
student.setName("test9");
student.setAge(i-10);
if(i%2 == 0) {
student.setLove("I love to go rock climbing");
}else{
student.setLove("I like to collect rock albums");
}
Boolean add = IndexOperateUtil.addDocument(student, indexName);
System.out.println("文档新增结果" + add);
}
请求如下,
http://127.0.0.1:9200/student/_search
{
"query": {
"bool": {
"must": {
"match": {
"name": "test9"
}
},
"filter": {
"range": {
"age": {
"gt": 20
}
}
}
}
}
}
此时查询结果如下,
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 11,
"relation": "eq"
},
"max_score": 0.9916401,
"hits": [
{
"_index": "student",
"_type": "_doc",
"_id": "9",
"_score": 0.9916401,
"_source": {
"love": "I like to collect rock albums",
"createTime": "2022-05-27 06:04:53",
"name": "test9",
"id": "9",
"age": 19
}
},
{
"_index": "student",
"_type": "_doc",
"_id": "21",
"_score": 0.9916401,
"_source": {
"love": "I love to go rock climbing",
"createTime": "2022-05-27 06:50:38",
"name": "test9",
"id": "21",
"age": 11
}
},
...省略
]
}
}
其中这里的match与我们之前使用到的match查询是一样的,不同之处在于引入了range 过滤器 , 它可以根据范围进行检索,类似的查询还比较多,在这里就不逐一给大家介绍了,有兴趣的可以看官网。
前面的搜索相对都很简单。现在我们来尝试一个稍微高级的全文搜索,这个搜索对于传统数据比较难搞定——模糊查询性能比较差。
业务场景:需要搜索所有学生中喜欢收集摇滚唱片的学生:
http://127.0.0.1:9200/student/_search
{
"query" : {
"match" : {
"love" : "rock albums"
}
}
}
我们依然使用之前的match查询在 love 属性上搜索 “rock albums” , 匹配到的文档如下:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 30,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "student",
"_type": "_doc",
"_id": "21",
"_score": 0.016878020, //相关性得分
"_source": {
"love": "I like to collect rock albums",
"createTime": "2022-05-27 09:47:38",
"name": "test9",
"id": "21",
"age": 11
}
},
{
"_index": "student",
"_type": "_doc",
"_id": "22",
"_score": 0.016878019, //相关性得分
"_source": {
"love": "I love to go rock climbing",
"createTime": "2022-05-27 09:47:38",
"name": "test9",
"id": "22",
"age": 12
}
},
...省略
]
}
}
我们发现,查询结果除了“rock albums”的数据外,还包含了“rock climbing”。
为什么会这样呢?
这里面有一个很重要的概念——相关性分析(_score)。Elasticsearch 默认按照相关性得分排序,即每个文档跟查询的匹配程度。最高得分的结果会排在最前面,以此类推。
但为什么 climbing 也作为结果返回了? 原因是love属性里提到了 “rock” 。因为只有 “rock” 而没有 albums ,所以相关性得分低于前者。
Elasticsearch中的相关性概念非常重要,这也是完全区别于传统关系型数据库的一个概念,传统数据库中一条记录要么匹配要么不匹配。
上面的需求找出一个属性中的独立单词是问题的,但有时候业务当中需要精确匹配一系列单词或者_短语_ 。 这时候该怎么办呢?
比如, 现在业务需要仅匹配同时包含 “rock” 和 “albums” ,并且二者是以短语 “rock albums” 的形式紧挨着的学生记录。
为此我们需要对match查询进行稍作调整,使用 match_phrase的查询:
http://127.0.0.1:9200/student/_search
{
"query" : {
"match_phrase" : {
"love" : "rock albums"
}
}
}
此时我们发现,仅返回了需要的“rock albums”。
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 11,
"relation": "eq"
},
"max_score": 0.9916401,
"hits": [
{
"_index": "student",
"_type": "_doc",
"_id": "9",
"_score": 0.9916401,
"_source": {
"love": "I like to collect rock albums",
"createTime": "2022-05-27 06:04:53",
"name": "test9",
"id": "9",
"age": 19
}
},
...省略
]
}
}
有些情况下,许多应用都会在每个搜索结果中高亮部分文本片段,以便让用户知道为何该文档符合查询条件。比如日常我们都会去百度搜索一下自己需要的关键内容。
那在 Elasticsearch 中检索出高亮片段也很容易。再次执行前面的查询,并增加一个新的 highlight 参数:
http://127.0.0.1:9200/student/_search
{
"query" : {
"match_phrase" : {
"love" : "rock climbing"
}
},
"highlight": {
"fields" : {
"love" : {}
}
}
}
当执行该查询时,返回结果与之前一样,此时返回结果中多了一个叫做 highlight
的节点。这个部分包含了love属性匹配的文本片段,并以 HTML 标签 封装。
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 11,
"relation": "eq"
},
"max_score": 0.9916401,
"hits": [
{
"_index": "student",
"_type": "_doc",
"_id": "9",
"_score": 0.9916401,
"_source": {
"love": "I like to collect rock albums",
"createTime": "2022-05-27 06:04:53",
"name": "test9",
"id": "9",
"age": 19
},
"highlight": {
"about": [
"I love to go rock albums"
]
}
},
...省略
]
}
}