ElasticSearch

什么是ElasticSearch

Elasticsearch 是位于 Elastic Stack 中心的分布式搜索和分析引擎。Logstach 和 Beats 促进采集、合计以及充实你的数据并在 Elasticsearch 中存储它们。Kibana 允许你去交互式的探索、可视化和共享对数据的见解,以及监视这个栈(Elastic Stack)。Elasticsearch 是索引、搜索和分析的神奇所在。

Elasticsearch 为各种数据类型提供接近实时的搜索和分析。不论你有结构化或非结构化的文本、数字数据,还是地理空间数据,Elasticsearch 能以支持快速搜索的方式高效地存储和索引它。你可以远超简单数据检索和聚合信息的方式去发现你数据中的趋势和模式。而且,随着你数据和查询量的增长,Elasticsearch 分布式的特性允许你的部署能随着它无缝地增长匹配。

虽然不是每个问题都是搜索问题,但 Elasticsearch 在大量实例中提供了处理数据的速度和灵活性:

  • 为应用或者网站添加搜索框
  • 存储和分析日志、度量和安全事件数据
  • 使用机器学习,实时自动建模你的数据行为
  • 使用 Elasticsearch 作为存储引擎来自动化业务工作流
  • 使用 Elasticsearch 作为地理信息系统(GIS)管理、集成和分析空间信息,以及使用 Elasticsearch 作为生物学信息研究工具处理基因数据

ElasticSearch可以根据Restful接口进行对非关系型数据库的各种操作

何为搜索何为检索

搜索:”搜索”通常指的是在一个数据集或信息源中寻找特定的信息或资源的过程。这可以包括使用搜索引擎在互联网上查找网页、使用文件系统查找文件,或在数据库中查询特定的记录。搜索过程通常是用户主动发起的,而搜索结果可能包含广泛的信息。

检索:”检索”指的是从一个存储系统中获取已存储的信息。这可能是在之前进行过搜索并存储的信息,或者是通过某种方式存储的数据。检索的过程可以是用户主动发起的,也可以是系统自动执行的,以满足特定的查询或请求。

什么是搜索引擎

全文搜索引擎

自然语言处理(NLP)、爬虫、网页处理、大数据处理(如谷歌、百度、搜狗、必应)

  • 全文检索

    • 全文检索:索引系统通过扫描文章中的每一个词,对其创建索引,指明在文章中出现的次数和位置,当用户查询时,索引系统过就会根据事先简历的索引进行查找,并将查找的结果反馈给用户的检索方式

      image-20231124143334710

垂直搜索引擎

有明确搜索目的的搜索行为

搜索引擎所具备的要求

  • 查询速度快
    • 需要高效的压缩算法
    • 快速的编码和解码速度
  • 查询结果准确
    • BM25算法
  • 搜索结果丰富

面对海量数据,如何达到搜索引擎级别的查询效率呢?

  • 索引:以数据结构为载体,以文件形式落地的帮助搜索引擎快速检索的工具

倒排索引

如何理解倒排索引呢?

正常的关系型数据库索引(正排索引)是这样的

1
2
3
4
id: 1      value: 听说北京今天要下雨
id:2 value:听说石家庄今天要下雨
id:3 value:听说北京明天是晴天
id:4 value:听说南京明天要下雨

倒排索引是这样的:

1
2
3
4
5
key:今天       value :1,2
key:下雨 value:1,2,,4
key:北京 value:1,3
key:京 value:1.3.4
key:石家庄 value:2

我们不难看出,倒序索引其实是将正常索引的value值的内容部分作为了倒排索引的key ,利用key来搜索对应可能得句子

倒排索引的数据结构

image-20231124144218445

Mapping-映射

概念:映射是定义文档及其包含字段的存储和索引存储方式的过程

两种映射方式:Dynamic Mapping (动态映射或自动映射) Explicit Mapping (静态映射或手工映射或显示映射)

ES中的mapping有点类似与RDB中”表结构””概念,在MySQL中,表结构里包含了字段名称,字段的类型还有索引信息等。在Mapping里也包含了一些属性,比如字段名称、类型、字段使用的分词器、是否评分、是否创建索引等属性,并且在ES中一个字段可以有多个类型。

查看索引下的所有映射

1
GET /index/_mapping

image-20231125215839367

我们可以看到该索引下有多个字段desc、lv、name、price、tags 他们的类型都如上图所示

ES的数据类型

常见类型

  1. 数字类型: long 、Integer、short、byte、double、float、half_float、unsigned_long
  2. keyword:
    1. keyword︰适用于索引结构化的字段,可以用于过滤、排序、聚合。keyword类型的字段只能通过精确值(exact value)搜索到。ld应该用keyword
    2. constant_keyword:始终包含相同值的关键字字段
    3. wildcard:可针对类似grep的通配符查询优化日志行和类似的关键字值
    4. 关键字字段通常用于排序,汇总和Term查询,例如term 。
  3. Dates(时间类型)︰包括date和date_nanos
  4. alias:为现有阶段别名
  5. binary:二进制
  6. range:integer_range. float_range、long_range.double_range.date_range
  7. text:当一个字段是要被全文搜索的,比如Email内容、产品描述,这些字段应该使用text类型。设置text类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分析器分成一个一个词项。text类型的字段不用于排序,很少用于聚合。(解释一下为啥不会为text创建索引∶大量堆空间,尤其是在加载高基数text字段时。字段数据一旦加载到堆中,就在该段的生命周期内保持在那里。同样,加载字段数据是一个昂贵的过程,可能导致用户遇到延迟问题。这就是默认情况下禁用字段数据的原因)

对象关系类型

  1. Object:用于单个JSON文件
  2. nested:用于JSON数组
  3. flattened: 允许整个JSON作为索引单个字段

两种映射类型

Dynamic Field mapping:

  • 整数 long
  • 浮点数 float
  • true|false boolean
  • 日期 date
  • 数组 取决于数组中第一个有效值
  • 对象 Object
  • 字符串 如果不是数字和日期类型,都会映射为text和keyword类型

映射参数

  1. index:是否对创建对当前字段创建倒排索引,默认true,如果不创建索引,该字段不会通过索引被搜索到,但是仍然会在source元数据中展示
  2. analyzer:指定分析器(character filter、tokenizer、Token filters)。
  3. boost:对当前字段相关度的评分权重,默认1
  4. coerce:是否允许强制类型转换true “1”=> 1 false “1”=<1
  5. copy_to:该参数允许将多个字段的值复制到组字段中,然后可以将其作为单个字段进行查询
  6. doc_values:为了提升排序和聚合效率,默认true,如果确定不需要对字段进行排序或聚合,也不需要通过脚本访问字段值,则可以禁用doc值以节省磁盘空间(不支持text和annotated_text)
  7. dynamic:控制是否可以动态添加新字段
    1. true新检测到的字段将添加到映射中。(默认)
    2. false新检测到的字段将被忽略。这些字段将不会被索引,因此将无法搜索,但仍会出现在_source返回的匹配项中。这些字段不会添加到映射中,必须显式添加新字段。、
    3. strict如果检测到新字段,则会引发异常并拒绝文档。必须将新字段显式添加到映射中
  8. eager_global_ordinals:用于聚合的字段上,优化聚合性能。
    1. Frozen indices (冻结索引)︰有些索引使用率很高,会被保存在内存中,有些使用率特别低,宁愿在使用的时候重新创建,在使用完毕后丢弃数据,Frozen indices的数据命中频率小,不适用于高搜索负载,数据不会被保存在内存中,堆空间占用比普通索引少得多,Frozen indices是只读的,请求可能是秒级或者分钟级。eager_global_ordinals不适用于Frozen indices
  9. enable:是否创建倒排索引,可以对字段操作,也可以对索引操作,如果不创建索引,让然可以检索并在source元数据中展示,谨慎使用,该状态无法修改。
  10. fielddata∶查询时内存数据结构,在首次用当前字段聚合、排序或者在脚本中使用时,需要字段为fielddata数据结构,并且创建倒t系丁1怀付到壮下
    选择语言
    fields:给field创建多字段,用于不同目的(全文检索或者聚合分析排序)
    2format:格式化
    “date”: {
    “type” : “date”” ,
    “format””: “yyyy-MM-dd””
    3ignore_above:超过长度将被忽略ignore_malformed:忽略类型错误
    index_options: 控制将哪些信息添加到反向索引中以进行搜索和突出显示。仅用于text字段6 Index_phrases:提升exact_value查询速度,但是要消耗更多磁盘空间
    Index_prefixes:前缀搜索
    1. min_chars:前缀最小长度,>0,默认2(包含)2) max_chars:前缀最大长度,<20,默认5(包含)

Http请求方法

Http协议有8中请求方法:GET:获取资源,可以理解为读取或者下载数据; HEAD:获取资源的元信息POST:向资源提交数据,相当于写入或上传数据;

PUT:类似 POST; DELETE:删除资源; CONNECT:建立特殊的连接隧道; OPTIONS:列出可对资源实行的方法; TRACE:追踪请求 - 响应的传输路径。

GET:它的含义是请求从服务器获取资源,这个资源既可以是静态的文本、页面、图片、视频,也可以是由 PHP、Java 动态生成的页面或者其他格式的数据。

GET 方法虽然基本动作比较简单,但搭配 URI 和其他头字段就能实现对资源更精细的操作。例如,在 URI 后使用“#”,就可以在获取页面后直接定位到某个标签所在的位置;使用 If-Modified-Since 字段就变成了“有条件的请求”,仅当资源被修改时才会执行获取动作;使用 Range 字段就是“范围请求”,只获取资源的一部分数据。

HEAD: 方法与 GET 方法类似,也是请求从服务器获取资源,服务器的处理机制也是一样的,但服务器不会返回请求的实体数据,只会传回响应头,也就是资源的“元信息”。

HEAD 方法可以看做是 GET 方法的一个“简化版”或者“轻量版”。因为它的响应头与 GET 完全相同,所以可以用在很多并不真正需要资源的场合,避免传输 body 数据的浪费。

比如,想要检查一个文件是否存在,只要发个 HEAD 请求就可以了,没有必要用 GET 把整个文件都取下来。再比如,要检查文件是否有最新版本,同样也应该用 HEAD,服务器会在响应头里把文件的修改时间传回来。

POST/PUT:PUT 的作用与 POST 类似,也可以向服务器提交数据,但与 POST 存在微妙的不同,通常 POST 表示的是“新建”“create”的含义,而 PUT 则是“修改”“update”的含义。

DELETE:方法指示服务器删除资源,因为这个动作危险性太大,所以通常服务器不会执行真正的删除操作,而是对资源做一个删除标记。当然,更多的时候服务器就直接不处理 DELETE 请求。

CONNECT:是一个比较特殊的方法,要求服务器为客户端和另一台远程服务器建立一条特殊的连接隧道,这时 Web 服务器在中间充当了代理的角色。

OPTIONS:方法要求服务器列出可对资源实行的操作方法,在响应头的 Allow 字段里返回。它的功能很有限,用处也不大,有的服务器(例如 Nginx)干脆就没有实现对它的支持。

TRACE:方法多用于对 HTTP 链路的测试或诊断,可以显示出请求 - 响应的传输路径。它的本意是好的,但存在漏洞,会泄漏网站的信息,所以 Web 服务器通常也是禁止使用。

安全与幂等

在 HTTP 协议里,所谓的“安全”是指请求方法不会“破坏”服务器上的资源,即不会对服务器上的资源造成实质的修改。

  • 按照这个定义,只有 GET 和 HEAD 方法是“安全”的,因为它们是“只读”操作,只要服务器不故意曲解请求方法的处理方式,无论 GET 和 HEAD 操作多少次,服务器上的数据都是“安全的”。
  • POST/PUT/DELETE 操作会修改服务器上的资源,增加或删除数据,所以是“不安全”的。

所谓的“幂等”实际上是一个数学用语,被借用到了 HTTP 协议里,意思是多次执行相同的操作,结果也都是相同的,即多次“幂”后结果“相等”。

很显然,GET 和 HEAD 既是安全的也是幂等的,DELETE 可以多次删除同一个资源,效果都是“资源不存在”,所以也是幂等的。

POST 和 PUT 的幂等性质就略费解一点。按照 RFC 里的语义,POST 是“新增或提交数据”,多次提交数据会创建多个资源,所以不是幂等的;而 PUT 是“替换或更新数据”,多次更新一个资源,资源还是会第一次更新的状态,所以是幂等的。

所以常见的请求方法:除了POST其余的都是幂等的,但POST就不是幂等的,如果重复的create会创建多个重复的数据。

ElasticSearch的JavaAPI的基本使用

配置环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.8.0</version>
</dependency>
<!-- elasticsearch的客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.8.0</version>
</dependency>
<!-- elasticsearch依赖2.x的log4j -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>

代码实现

1
2
3
4
5
6
7
8
9
10
public class es_clientTest {
public static void main(String[] args) throws IOException {
//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost",9200,"http"))
);
//关闭ES客户端
esClient.close();
}
}

索引

索引是文档的容器。它类似于关系数据库中的表,但是更动态和灵活。每个索引都有一个唯一的名称,用于标识和区分不同的数据集。

创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class es_clientTest {
public static void main(String[] args) throws IOException {
//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost",9200,"http"))
);
//创建索引名称
CreateIndexRequest request = new CreateIndexRequest("user");
//创建索引响应请求与请求方式
CreateIndexResponse response = esClient.indices().create(request, RequestOptions.DEFAULT) ;
System.out.println("索引操作:"+response.isAcknowledged());

//关闭ES客户端
esClient.close();
}
}

image-20231117104738336

查询索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class es_searchTest {
public static void main(String[] args) throws IOException {


//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
//查询索引
GetIndexRequest request = new GetIndexRequest("user");
GetIndexResponse response = esClient.indices().get(request, RequestOptions.DEFAULT);


System.out.println("索引结果:" + response.getAliases());
System.out.println("索引结果:" + response.getMappings());
System.out.println("索引结果:" + response.getSettings());


//关闭ES客户端
esClient.close();
}
}

image-20231117111345270

删除索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class es_searchTest {
public static void main(String[] args) throws IOException {


//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
//查询索引
DeleteIndexRequest request = new DeleteIndexRequest("user");
AcknowledgedResponse response = esClient.indices().delete(request, RequestOptions.DEFAULT);


System.out.println("索引结果:" + response.isAcknowledged());


//关闭ES客户端
esClient.close();
}
}

向索引中插入文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class es_searchTest {
public static void main(String[] args) throws IOException {


//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
//向索引中插入文档
IndexRequest request = new IndexRequest();
request.index("user").id("1001");
//创建对象
User user=new User();
user.setName("张三");
user.setAge(30);
user.setGender(1);
//将对象转为JSON字符串
ObjectMapper objectMapper=new ObjectMapper();
String userJson = objectMapper.writeValueAsString(user);
//将该索引下的文档写入
request.source(userJson, XContentType.JSON);
// 向ES插入数据
IndexResponse response = esClient.index(request, RequestOptions.DEFAULT);
System.out.println("索引结果:" + response.getResult());
//关闭ES客户端
esClient.close();
}
}

在索引中修改文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class es_searchTest {
public static void main(String[] args) throws IOException {


//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
//创建修改请求
UpdateRequest request2=new UpdateRequest();
// 向ES插入数据
request2.index("user").id("1001");
request2.doc(XContentType.JSON,"gender","2");
request2.doc(XContentType.JSON,"age","21");
UpdateResponse response = esClient.update(request2, RequestOptions.DEFAULT);

System.out.println("索引结果:" + response.getResult());
//关闭ES客户端
esClient.close();
}
}

控制台输出修改结果image-20231122121607035

postman中查看文档已被修改image-20231122121625436

查询数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class es_clinetGetTest {
public static void main(String[] args) throws IOException {
//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost",9200,"http"))
);
//创建查询请求
GetRequest request = new GetRequest();
//添加请求对应的索引名与id
request.index("user").id("1001");
// 获取响应结果
GetResponse response = esClient.get(request, RequestOptions.DEFAULT);
// 打印输出结果
System.out.println(response.getSourceAsString());
esClient.close();
}
}

image-20231122122853065

删除数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class es_clientDeleteTest {
public static void main(String[] args) throws IOException {
//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost",9200,"http"))
);
//创建删除请求
DeleteRequest request=new DeleteRequest();
//添加索引名与id
request.index("user").id("1001");
// 执行删除操作并获取响应结果
DeleteResponse delete = esClient.delete(request, RequestOptions.DEFAULT);
System.out.println(delete);
}
}

image-20231122123350977

此时我们再利用查询数据查询一次

直接结果image-20231122123439287