Elasticsearch 8集群搭建安全功能配置详述

Posted MCNU云原生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Elasticsearch 8集群搭建安全功能配置详述相关的知识,希望对你有一定的参考价值。

Elastic Stack产品栈包含Beats、APM、Elasticsearch、Elasticsearch Hadoop、Kibana、Logstash,这些产品常被作为一个整体搭配使用,其部署需要使用同样的版本,这样子能够有效简化部署操作。

本文主要记录Elasticsearch 8.4.3的安装过程,一方面是记录如何搭建一个Elasticsearch 8 集群,另外一方面是通过安装过程,了解在这个过程中Elasticsearch在背后做了一些什么,有助于我们理解Elasticsearch的启动和集群搭建流程。

1、Elasticsearch安装方式

Elasticsearch的安装支持以下几种方案:

  • 在Linux、MacOS、Windows部署
  • 在Docker中部署
  • 在Elastic Cloud Kubernetes集群上部署
2、Elasticsearch安装包
包类型适用范围
tar.gzLinux、MacOS
.zipWindows
debDebian、Ubuntu以及其他基于Debian体系的Linux系统
rpmRed Hat、Centos、SLES、OpenSuSE和其他基于RPM体系的Linux系统
docker容器
3、在Linux/MacOS上搭建Elasticsearch集群
3.1、下载安装
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.4.3-linux-x86_64.tar.gz
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.4.3-linux-x86_64.tar.gz.sha512
$ shasum -a 512 -c elasticsearch-8.4.3-linux-x86_64.tar.gz.sha512
elasticsearch-8.4.3-linux-x86_64.tar.gz: OK

下载并进行checksum验证,看到"OK"则可以证明验证完成。

如果是MacOS系统,则下载专属于MacOS系统的安装包,其他的类似,不再赘述。

$ curl -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.4.3-darwin-x86_64.tar.gz
$ curl https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.4.3-darwin-x86_64.tar.gz.sha512 | shasum -a 512 -c -

若提示没有shasum命令,则需要安装对应的包

$ yum install perl-Digest-SHA

解压并入解压之后的目录,这个目录将作为$ES_HOME,如果不设置ES_HOME,在默认从执行命令的bin目录的上一层作为ES_HOME。

$ tar -xzf elasticsearch-8.4.3-linux-x86_64.tar.gz
$ cd elasticsearch-8.4.3/
$ pwd
home/elastic/elasticsearch-8.4.3

将以下内容添加到~/.bashrc

ES_HOME=home/elastic/elasticsearch-8.4.3

执行以下命令进行验证

$ echo $ES_HOME
/home/elastic/elasticsearch-8.4.3
3.2、设置自动创建系统索引(非必须)

一些商业功能会自动在 Elasticsearch 中创建索引。 默认情况下,Elasticsearch 配置为允许自动创建索引,不需要额外的步骤。 但是,如果在 Elasticsearch 中禁用了自动创建索引,则必须在 elasticsearch.yml 中配置 action.auto_create_index 以允许商业功能创建以下索引:

action.auto_create_index: .monitoring*,.watches,.triggered_watches,.watcher-history*,.ml*

Logstash和Beats等组件都会自动创建索引,这些索引会有特别的名称,需要根据实际使用到的组件进行设置,如果不确定具体名称,为了方便可以将值设置为*,表示允许自动创建所有索引。

3.3、运行Elasticsearch

因为Elasticsearch运行过程中会占用大量的系统资源,所以对于系统配置有一些要求,按照以下方案对系统进行相关配置。

1、修改文件描述符和最大线程数限制

切换到root用户,修改/etc/security/limits.conf配置文件,添加以下内容并保存。

* soft nofile 65536
* hard nofile 131072
* soft nproc 4096
* hard nproc 4096

2、修改max_map_count参数

打开/etc/sysctl.conf配置文件,添加如下内容并保存,执行sysctl -p命令生效。

vm.max_map_count=262144

执行以下命令启动Elasticsearch

$ ./bin/elasticsearch
warning: ignoring JAVA_HOME=/opt/jdk-17.0.5; using bundled JDK
[2022-12-14T14:30:09,021][INFO ][o.e.n.Node               ] [node1] version[8.4.3], pid[2073], build[tar/42f05b9372a9a4a470db3b52817899b99a76ee73/2022-10-04T07:17:24.662462378Z], OS[Linux/3.10.0-1160.76.1.el7.x86_64/amd64], JVM[Oracle Corporation/OpenJDK 64-Bit Server VM/18.0.2.1/18.0.2.1+1-1]
[2022-12-14T14:30:09,038][INFO ][o.e.n.Node               ] [node1] JVM home [/home/elastic/elasticsearch-8.4.3/jdk], using bundled JDK [true]
......

[2022-12-14T14:30:42,065][INFO ][o.e.c.m.MetadataCreateIndexService] [node1] [.geoip_databases] creating index, cause [auto(bulk api)], templates [], shards [1]/[0]
[2022-12-14T14:30:42,463][INFO ][o.e.c.r.a.AllocationService] [node1] current.health="GREEN" message="Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.geoip_databases][0]]])." previous.health="YELLOW" reason="shards started [[.geoip_databases][0]]"
[2022-12-14T14:30:43,929][INFO ][o.e.i.g.GeoIpDownloader  ] [node1] successfully downloaded geoip database [GeoLite2-ASN.mmdb]
[2022-12-14T14:30:44,355][INFO ][o.e.i.g.DatabaseNodeService] [node1] successfully loaded geoip database file [GeoLite2-ASN.mmdb]
[2022-12-14T14:30:45,144][INFO ][o.e.x.s.InitialNodeSecurityAutoConfiguration] [node1] HTTPS has been configured with automatically generated certificates, and the CA's hex-encoded SHA-256 fingerprint is [8a717506cb9c1f7c8190a6c3a2026cead47790aca632e35e988a65fdda78366d]
[2022-12-14T14:30:45,152][INFO ][o.e.x.s.s.SecurityIndexManager] [node1] security index does not exist, creating [.security-7] with alias [.security]
[2022-12-14T14:30:45,196][INFO ][o.e.x.s.e.InternalEnrollmentTokenGenerator] [node1] Will not generate node enrollment token because node is only bound on localhost for transport and cannot connect to nodes from other hosts
[2022-12-14T14:30:45,325][INFO ][o.e.c.m.MetadataCreateIndexService] [node1] [.security-7] creating index, cause [api], templates [], shards [1]/[0]
[2022-12-14T14:30:45,386][INFO ][o.e.x.s.s.SecurityIndexManager] [node1] security index does not exist, creating [.security-7] with alias [.security]
[2022-12-14T14:30:45,521][INFO ][o.e.c.r.a.AllocationService] [node1] current.health="GREEN" message="Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.security-7][0]]])." previous.health="YELLOW" reason="shards started [[.security-7][0]]"

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Elasticsearch security features have been automatically configured!
✅ Authentication is enabled and cluster connections are encrypted.

ℹ️  Password for the elastic user (reset with `bin/elasticsearch-reset-password -u elastic`):
  LYePogNEis=ogbMaUzmJ

ℹ️  HTTP CA certificate SHA-256 fingerprint:
  8a717506cb9c1f7c8190a6c3a2026cead47790aca632e35e988a65fdda78366d

ℹ️  Configure Kibana to use this cluster:
• Run Kibana and click the configuration link in the terminal when Kibana starts.
• Copy the following enrollment token and paste it into Kibana in your browser (valid for the next 30 minutes):
  eyJ2ZXIiOiI4LjQuMyIsImFkciI6WyIxOTIuMTY4LjU2LjExOjkyMDAiXSwiZmdyIjoiOGE3MTc1MDZjYjljMWY3YzgxOTBhNmMzYTIwMjZjZWFkNDc3OTBhY2E2MzJlMzVlOTg4YTY1ZmRkYTc4MzY2ZCIsImtleSI6Im9aNVVENFVCbFJ4a2VsQU9EbXZxOlpvYXBVWjBUVERTdjk5aDNRV25oNUEifQ==

ℹ️  Configure other nodes to join this cluster:
• On this node:
  ⁃ Create an enrollment token with `bin/elasticsearch-create-enrollment-token -s node`.
  ⁃ Uncomment the transport.host setting at the end of config/elasticsearch.yml.
  ⁃ Restart Elasticsearch.
• On other nodes:
  ⁃ Start Elasticsearch with `bin/elasticsearch --enrollment-token <token>`, using the enrollment token that you generated.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[2022-12-14T14:30:57,162][INFO ][o.e.i.g.GeoIpDownloader  ] [node1] successfully downloaded geoip database [GeoLite2-City.mmdb]
[2022-12-14T14:30:58,768][INFO ][o.e.i.g.GeoIpDownloader  ] [node1] successfully downloaded geoip database [GeoLite2-Country.mmdb]
[2022-12-14T14:30:58,815][INFO ][o.e.i.g.DatabaseNodeService] [node1] successfully loaded geoip database file [GeoLite2-City.mmdb]
[2022-12-14T14:30:58,898][INFO ][o.e.i.g.DatabaseNodeService] [node1] successfully loaded geoip database file [GeoLite2-Country.mmdb]

当启动成功,可以看到类似以上的日志输出。首次启动Elasticsearch,默认会启用安全配置功能:

  • 启用身份认证和授权,内置超级用户elastic,并生成默认密码
  • 为传输层和HTTP层启用TLS,并自动生成用于配置TLS的密钥和证书
  • 为Kibana生成一个注册token,有效期为30分钟

以上这些关键信息都会在启动输出日志中打印出来,默认情况下,Elasticsearch 将其日志打印到控制台 ( stdout) 和日志目录<cluster name>.log中的文件。Elasticsearch 在启动时会记录一些信息,但在完成初始化后,它将继续在前台运行并且不会进一步记录任何信息。

如上日志所示:

  • "Password for the elastic user"段打印的是超级用户elastic的密码。
  • "HTTP CA certificate SHA-256 fingerprint"段打印的是CA证书的指纹。
  • "Configure Kibana to use this cluster"段打印的是用于kibana连接Elasticsearch的token。
  • "Configure other nodes to join this cluster"段打印的是用于其他的Elasticsearch节点加入Elasticsearch时所用的token,如果待加入的节点只开放了localhost,则不会打印这个token。
3.4、验证Elasticsearch运行状态

当 Elasticsearch 运行时,默认开放了9200作为HTTP交互的端口,可以通过这个端口与它交互。使用以下命令可以验证Elasticsearch运行状态

curl --cacert $ES_HOME/config/certs/http_ca.crt -u elastic https://localhost:9200 

结果如下所示:

[elastic@node1 ~]$ curl --cacert ~/elasticsearch-8.4.3/config/certs/http_ca.crt -u elastic https://localhost:9200
Enter host password for user 'elastic':

  "name" : "node1",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "-oF_yhG7TtamNIARhI4-Tg",
  "version" : 
    "number" : "8.4.3",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "42f05b9372a9a4a470db3b52817899b99a76ee73",
    "build_date" : "2022-10-04T07:17:24.662462378Z",
    "build_snapshot" : false,
    "lucene_version" : "9.3.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  ,
  "tagline" : "You Know, for Search"

可以看到Elasticsearch已经正常启动了起来。注意,因为启用了security,所以一定要使用HTTPS进行访问,其中参数–cacert指定了证书。

如果要停止Elasticsearch,只需要Ctrl-C即可。

3.5、将新节点加入一个已存在集群

Elasticsearch启动以后,安全配置将会自动将HTTP层绑定到0.0.0.0,也就是说对于本机的所有地址都是开放的,不管是内网地址还是外网地址,但是transport却默认只绑定了localhost,这是为了确保用户第一次启动的时候是一个单节点的集群。

要加入一个新节点(node2),需要修改transport.host,绑定到一个指定的地址或者0.0.0.0,重启节点(待加入的集群中的节点,用node1指代)在这个过程中可能token(注册令牌)会失效,因此在Elasticsearch启动过程中不会自动生成用于新节点加入的token。

接着跟着以下的步骤:

  1. 在node1上Elasticsearch的目录下生成token

    bin/elasticsearch-create-enrollment-token -s node
    
  2. 在新节点node2使用步骤一生成出来的token启动新节点

    bin/elasticsearch --enrollment-token <enrollment-token>
    
  3. 新节点在config/certs目录下生成证书,自动加入集群

  4. 多个新节点加入,只需要重复进行步骤一~步骤三即可。

我们尝试往前单节点的集群中加入一个新的节点,首先使用elasticsearch-create-enrollment-token工具生成token

[elastic@node1 elasticsearch-8.4.3]$ bin/elasticsearch-create-enrollment-token -s node
warning: ignoring JAVA_HOME=/opt/jdk-17.0.5; using bundled JDK
eyJ2ZXIiOiI4LjQuMyIsImFkciI6WyIxOTIuMTY4LjU2LjExOjkyMDAiXSwiZmdyIjoiOGE3MTc1MDZjYjljMWY3YzgxOTBhNmMzYTIwMjZjZWFkNDc3OTBhY2E2MzJlMzVlOTg4YTY1ZmRkYTc4MzY2ZCIsImtleSI6IjJQdVRESVlCWU1kQnBSaTdnODVQOlRfa1RDR3BlVDJPdTZvMzBzanBPRVEifQ==

使用生成的token启动新节点:

[elastic@node2 elasticsearch-8.4.3]$ bin/elasticsearch --enrollment-token eyJ2ZXIiOiI4LjQuMyIsImFkciI6WyIxOTIuMTY4LjU2LjExOjkyMDAiXSwiZmdyIjoiOGE3MTc1MDZjYjljMWY3YzgxOTBhNmMzYTIwMjZjZWFkNDc3OTBhY2E2MzJlMzVlOTg4YTY1ZmRkYTc4MzY2ZCIsImtleSI6IjJQdVRESVlCWU1kQnBSaTdnODVQOlRfa1RDR3BlVDJPdTZvMzBzanBPRVEifQ==

发现不断提示以下日志,从日志中可以看到访问的IP是127.0.0.1,这是因为node1默认只开放了localhost。

[2023-02-01T18:46:59,135][WARN ][o.e.c.c.ClusterFormationFailureHelper] [node2] master not discovered yet, this node has not previously joined a bootstrapped cluster, and [cluster.initial_master_nodes] is empty on this node: have discovered [node2kuM1xg2lR4myQH6N-AsxGA2cS3Wrm1RDKLxMyFgo_NYQnode2127.0.0.1127.0.0.1:9300cdfhilmrstw]; discovery will continue using [] from hosts providers and [node2kuM1xg2lR4myQH6N-AsxGA2cS3Wrm1RDKLxMyFgo_NYQnode2127.0.0.1127.0.0.1:9300cdfhilmrstw] from last-known cluster state; node term 0, last-accepted version 0 in term 0

为了方便,这里修改node1 elasticsearch的配置,增加transport.host:0.0.0.0network.host:0.0.0.0,修改相关配置以后重新启动。

重新将node2重新加入集群的时候发现仍然提示了其他错误,导致启动失败:

ERROR: Skipping security auto configuration because it appears that the node is not starting up for the first time. The node might already be part of a cluster and this auto setup utility is designed to configure Security for new clusters only.

究其原因是因为在上一次加入集群的时候elasticsearch未能加入某个节点,导致自己成为一个独立的新集群,所以无法再加入其他集群,我们从头开始,重新解压缩一个elasticsearch安装包,再重新使用上述方法加入集群,又提示如下错误:

ERROR: Aborting enrolling to cluster. Could not communicate with the node on any                                                                      of the addresses from the enrollment token. All of [192.168.56.11:9200] were at                                                                     tempted.

这是因为node1从开放localhost到开放了0.0.0.0,故需要在在node1上重新生成token,使用新token加入集群

[elastic@node2 elasticsearch-8.4.3]$ bin/elasticsearch --enrollment-token eyJ2ZXIiOiI4LjQuMyIsImFkciI6WyIxOTIuMTY4LjU2LjExOjkyMDAiXSwiZmdyIjoiOGE3MTc1MDZjYjljMWY3YzgxOTBhNmMzYTIwMjZjZWFkNDc3OTBhY2E2MzJlMzVlOTg4YTY1ZmRkYTc4MzY2ZCIsImtleSI6IlBHUGNESVlCdkt6dldhTUF2SVF0Ok9kRzgzQS1YUVUtOGkwdVRrSnZpZWcifQ==
[2023-02-01T20:04:29,861][INFO ][o.e.n.Node               ] [node2] version[8.4.3], pid[1675], build[tar/42f05b9372a9a4a470db3b52817899b99a76ee73/2022-10-
[2023-02-01T20:04:58,053][INFO ][o.e.c.s.ClusterApplierService] [node2] master node changed previous [], current [node1Ugh2e7ubSb2fw9Wj8U918Al_xB6z5QQee23U2jX8Ctiwnode1192.168.56.11192.168.56.11:9300cdfhilmrstw], added node1Ugh2e7ubSb2fw9Wj8U918Al_xB6z5QQee23U2jX8Ctiwnode1192.168.56.11192.168.56.11:9300cdfhilmrstw, term: 7, version: 93, reason: ApplyCommitRequestterm=7, version=93, sourceNode=node1Ugh2e7ubSb2fw9Wj8U918Al_xB6z5QQee23U2jX8Ctiwnode1192.168.56.11192.168.56.11:9300cdfhilmrstwml.machine_memory=1907675136, ml.max_jvm_size=956301312, xpack.installed=true, ml.allocated_processors=1
......
......
[2023-02-01T20:04:58,299][INFO ][o.e.n.Node               ] [node2] started node2TplD7Q2DT_K1tZaxhL6PjAxgNgZioKTFWFu8jBKOmZJQnode2192.168.56.12192.168.56.12:9300cdfhilmrstwxpack.installed=true, ml.allocated_processors=1, ml.max_jvm_size=956301312, ml.machine_memory=1907675136

此时可以看到,node2启动成功,成功加入集群,在打印的日志中可以看到node1和node2节点的信息。

通过_cat API可以查看集群的节点数量:

curl --cacert $ES_HOME/config/certs/http_ca.crt -u elastic https://192.168.56.11:9200/_cat/nodes?v

ip            heap.percent ram.percent cpu load_1m load_5m load_15m node.role   master name
192.168.56.11           32          91   0    0.01    0.07     0.12 cdfhilmrstw *      node1
192.168.56.12           40          96   0    0.00    0.08     0.10 cdfhilmrstw -      node2

可以看到新节点已经正常加入集群中,总共有2个节点。

如果希望elasticsearch以守护进程的形式存在,可以使用以下命令启动

./bin/elasticsearch -d -p pid

pid为一个具体的文件,用于存放elasticsearch启动后的pid值。

停止Elasticsearch进程,可以采用以下命令:

pkill -F pid
4、Elasticsearch配置

elasticsearch的配置支持两种形式,一是使用elasticsearch.yml,二是使用命令行,所有能够在elasticsearch.yml中配置的项也都可以使用命令行进行配置。

./bin/elasticsearch -d -Ecluster.name=my_cluster -Enode.name=node_1

官方建议是针对集群的配置使用elasticsearch.yml配置文件,例如cluster.name;针对特定节点的配置使用命令行,例如node.name。但是为了方便后期的运维,个人经验是能放配置文件的尽量放配置文件。

5、关于CA证书

由于启用了TLS,所有的客户端连接Elasticsearch都必须信任HTTPS证书,Fleet Server 和 Fleet-managed Elastic Agent 自动配置为信任 CA 证书,而其他客户端可以通过使用 CA 证书的指纹或 CA 证书本身来建立信任。

证书存放在目录$ES_HOME/config/certs中,目录中可以看到有证书http_ca.crt。

如果客户端支持CA指纹的话,可以从Elasticsearch启动打印的日志中获取CA指纹。如果错过了日志中打印出来的CA指纹的话,也可以使用以下命令从证书中重新生成:

openssl x509 -fingerprint -sha256 -in config/certs/http_ca.crt

结果如下:

SHA256 Fingerprint=8A:71:75:06:CB:9C:1F:7C:81:90:A6:C3:A2:02:6C:EA:D4:77:90:AC:A6:32:E3:5E:98:8A:65:FD:DA:78:36:6D

以下是日志中打印出的CA指纹,对比之下可以发现是一致的。

ℹ️  HTTP CA certificate SHA-256 fingerprint:
  8a717506cb9c1f7c8190a6c3a2026cead47790aca632e35e988a65fdda78366d

注意,使用这种方式生成的CA指纹,issuer必须是Elasticsearch security auto-configuration HTTP CA

issuer= /CN=Elasticsearch security auto-configuration HTTP CA

以上基本就完成了Elasticsearch集群的搭建。当然,Elasticsearch的功能非常强大,还有很多复杂的功能和配置,待后面其他文章再详细分析。

ElasticSearch知识概括

ElasticSearch简介

Elasticsearch 是什么:

  • Elasticsearch是一个分布式、RESTful风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。作为Elastic Stack的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。
  • The Elastic Stack, 包括 Elasticsearch、Kibana、Beats 和 Logstash(也称为 ELK Stack)。 能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。
    Elasticsearch:核心中的核心组件,基于著名的全文检索引擎lucence的一个分布式版本。由于扩展成分布式,容量和性能得到极大的提升,使得Elasticsearch得以成为目前许多大数据产品和大数据架构的核心组件。
    Logstash: 一个灵活的数据传输和处理系统,在beats出来之前,还负责进行数据收集。Logstash的任务,就是将各种各样的数据,经过配置转化规则,统一化存入Elasticsearch。使用Ruby开发的Logstash在灵活性上,确实非常出色。不过性能一直是被诟病的问题。
    Kibana:展示组件,基于angularjs。从Elasticsearch中读取数据并展示。具有强大而且灵活的界面配置。
    由于Logstash在数据收集上并不出色,而且作为agent,性能并不达标。elastic发布了beats系列轻量级采集组件。
  • Elaticsearch,简称为 ES, ES 是一个开源的高扩展的分布式全文搜索引擎,是整个 Elastic Stack
    技术栈的核心。它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上 百台服务器,处理 PB 级别的数据。

全文搜索引擎:

  • Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时 候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日 志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。
  • 一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进 行全文检索需要扫描整个表,如果数据量大的话即使对 SQL的语法优化,也收效甚微。建 立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。基于以上原因可以分析得出,在一些生产环境中,使用常规的搜索方式,性能是非常差的:
    ①搜索的数据对象是大量的非结构化的文本数据。
    ②文件记录量达到数十万或数百万个甚至更多。
    ③支持大量基于交互式文本的查询。
    ④需求非常灵活的全文搜索查询。
    ⑤对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
    ⑥对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。
  • 为了解决结构化数据搜索和非结构化数据搜索性能问题,我们就需要专业,健壮,强大的全 文搜索引擎这里说到的全文搜索引擎指的是目前广泛应用的主流搜索引擎。它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

Elasticsearch And Solr:

  • Lucene 是 Apache 软件基金会 Jakarta 项目组的一个子项目,提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在 Java 开发环境里 Lucene 是一个成熟的免费开源 工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费 Java 信息检索程序库。 但 Lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的 服务框架搭建起来进行应用。
  • 目前市面上流行的搜索引擎软件,主流的就两款:Elasticsearch 和 Solr,这两款都是基于 Lucene搭建的,可以独立部署启动的搜索引擎服务软件。由于内核相同,所以两者除了服务器安装、部署、管理、集群以外,对于数据的操作修改、添加、保存、查询等等都十分类似。
  • 在使用过程中,一般都会将 Elasticsearch 和 Solr 这两个软件对比,然后进行选型。这两个搜索引擎都是流行的,先进的的开源搜索引擎。它们都是围绕核心底层搜索库 - Lucene 构建的,但它们又是不同的。像所有东西一样,每个都有其优点和缺点:
特征Solr/SolrCloudElasticsearch
社区和开发者Apache软件基金和社区支持单一商业实体及其员工
节点发现Apache Zookeeper,在大量项目中成熟且经过实战测试zen内置于Elasticsearch本身,需要专用的主节点才能进行分裂脑保护
碎片放置本质上是静态,需要手动工作来迁移分片,从Solr 7开始–Autoscaling API允i许—些动态操作动态,可L以根据群集状态按需移动分片
高速缓存全局,每个段更改无效每段,更适合动态更改数据
分析引擎性能非常适合精确计算的静态数据结果的往确性取决于数据放置
全文搜索功能基于Lucene的语言分析,多建议,拼写检查,丰富的高亮显示支持基于Lucene的语言分析,单—建议API实现,高亮显示重新计算
DevOps支持尚未完全,但即将到来非常好的API
非平面数据处嵌套文档和父-子支持嵌套和对象类型的自然支持允许几乎无限的嵌套和父-子支持
查询DSLJSON(有限),XML(有限)或URL参数JSON
索引/收集领导控制领导者安置控制和领导者重新平衡甚至可以节点上的负载不可能
机器学习内置–在流聚合之上,专注于逻辑回归和学习排名贡献模块商业功能,专注于异常和异常值以及时间序列数据
  • Elasticsearch 和 Solr 都是开源搜索引擎,那么我们在使用时该如何选择呢?
    ① Google 搜索趋势结果表明,与 Solr 相比,Elasticsearch 具有很大的吸引力,但这并不
    意味着 Apache Solr 已经死亡。虽然有些人可能不这么认为,但 Solr 仍然是最受欢迎的
    搜索引擎之一,拥有强大的社区和开源支持。
    ②与 Solr 相比,Elasticsearch 易于安装且非常轻巧。此外,你可以在几分钟内安装并运行
    Elasticsearch。但是,如果 Elasticsearch 管理不当,这种易于部署和使用可能会成为一个
    问题。基于 JSON 的配置很简单,但如果要为文件中的每个配置指定注释,那么它不适
    合您。总的来说,如果你的应用使用的是 JSON,那么 Elasticsearch 是一个更好的选择。
    否则,请使用 Solr,因为它的 schema.xml 和 solrconfig.xml 都有很好的文档记录。
    ③Solr 拥有更大,更成熟的用户,开发者和贡献者社区。ES 虽拥有的规模较小但活跃的
    用户社区以及不断增长的贡献者社区。Solr 贡献者和提交者来自许多不同的组织,而 Elasticsearch 提交者来自单个公司。
    ④Solr 更成熟,但 ES 增长迅速,更稳定。
    ⑤Solr 是一个非常有据可查的产品,具有清晰的示例和 API 用例场景。 Elasticsearch 的
    文档组织良好,但它缺乏好的示例和清晰的配置说明。
  • 那么到底是 Solr 还是 Elasticsearch?
    ①有时很难找到明确的答案。无论您选择 Solr 还是 Elasticsearch,首先需要了解正确的用
    例和未来需求。总结他们的每个属性。
    <1>由于易于使用,Elasticsearch 在新开发者中更受欢迎。一个下载和一个命令就可以启动
    一切。
    <2>如果除了搜索文本之外还需要它来处理分析查询,Elasticsearch 是更好的选择
    <3>如果需要分布式索引,则需要选择 Elasticsearch。对于需要良好可伸缩性和以及性能分
    布式环境,Elasticsearch 是更好的选择。
    <4>Elasticsearch 在开源日志管理用例中占据主导地位,许多组织在 Elasticsearch 中索引它
    们的日志以使其可搜索。
    <5>如果你喜欢监控和指标,那么请使用 Elasticsearch,因为相对于 Solr,Elasticsearch 暴露
    了更多的关键指标

Elasticsearch 应用案例:

  • GitHub: 2013 年初,抛弃了 Solr,采取 Elasticsearch 来做 PB 级的搜索。“GitHub 使用 Elasticsearch 搜索 20TB 的数据,包括 13 亿文件和 1300 亿行代码”。
  • 维基百科:启动以 Elasticsearch 为基础的核心搜索架构
  • SoundCloud:“SoundCloud 使用 Elasticsearch 为 1.8 亿用户提供即时而精准的音乐搜索 服务”。
  • 百度:目前广泛使用 Elasticsearch 作为文本数据分析,采集百度所有服务器上的各类指 标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常 或业务层面异常。目前覆盖百度内部 20多个业务线(包括云分析、网盟、预测、文库、 直达号、钱包、风控等),单集群最大 100 台机器,200 个 ES 节点,每天导入30TB+ 数据。
  • 新浪:使用 Elasticsearch 分析处理 32 亿条实时日志。
  • 阿里:使用 Elasticsearch 构建日志采集和分析体系。
  • Stack Overflow:解决 Bug 问题的网站,全英文,编程人员交流的网站。

Elasticsearch 安装:

  • 链接:
    Elasticsearch 的官方地址
    下载地址
  • Elasticsearch 最新的版本是 7.11.2(截止 2021.3.10),我们选择 7.8.0 版本(最新版本半
    年前的版本)
  • Elasticsearch 分为 Linux 和 Windows 版本,基于我们主要学习的是 Elasticsearch 的 Java
    客户端的使用,所以课程中使用的是安装较为简便的 Windows 版本。
  • 安装软件:Windows 版的 Elasticsearch 的安装很简单,解压即安装完毕,解压后的 Elasticsearch的目录结构如下:
目录含义
bin可执行脚本目录
config配置目录
jdk内置JDK目录
lib类库
logs日志目录
modules模块目录
plugins插件目录
  • 解压后,进入 bin 文件目录,点击 elasticsearch.bat 文件启动 ES 服务
    ①注意:9300 端口为 Elasticsearch 集群间组件的通信端口,9200 端口为浏览器访问的 http
    协议 RESTful 端口。
    ②打开浏览器(推荐使用谷歌浏览器),输入地址:http://localhost:9200,测试结果

	name" : “LAPTOP-J9IRK5BM”",
	"cluster_name” : “elasticsearch~,
	~cluster_uuid”: “fe4UTupLTCKHshF zDY7Gdg”,
	"version: 
		"number” : ”7.8.0”,
		“build_flavor-: "default”,“build_type”: “zip”.
		"build_hash”: “757314695644ea9aldc2fecd26d1a43856725e65","build_date”:”2020-06-14T19:35:50.234439Z”,
		“build_snapshot”: false,
		“lucene_version: “8.5.1~.
		"minimum_wire_compatibility_version": "6.8.0",
		“minimun_index_compatibility_version”: "6.0.0-beta1"
	,
	~tagline” : “You Knov,for Search”

  • 问题解决:
    ①Elasticsearch 是使用 java 开发的,且 7.8 版本的 ES 需要 JDK 版本 1.8 以上,默认安装包带有 jdk 环境,如果系统配置 JAVA_HOME,那么使用系统默认的 JDK,如果没有配 置使用自带的 JDK,一般建议使用系统配置的 JDK。
    ②双击启动窗口闪退,通过路径访问追踪错误,如果是“空间不足”,请修改 config/jvm.options 配置文件
#设置JVM初始内存为1G。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存# Xms represents the initial size of total heap space
#设置JVM最大可用内存为1G
# Xmx represents the maximum size of total heap space
-Xms1g
-xmxlg

Elasticsearch 数据格式:

  • Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档。为了方便大家理解, 我们将 Elasticsearch里存储文档数据和关系型数据库 MySQL 存储数据的概念进行一个类比
  • ES 里的 Index 可以看做一个库,而 Types 相当于表,Documents 则相当于表的行。
  • 这里 Types 的概念已经被逐渐弱化,Elasticsearch 6.X 中,一个 index 下已经只能包含一个
    type,Elasticsearch 7.X 中, Type 的概念已经被删除了。
  • 用 JSON 作为文档序列化的格式,比如一条用户信息:

	"name" : "John", 
	"sex" : "Male", 
	"age" : 25,
	"birthDate": "1990/05/01",
	"about" : "I love to go rock climbing", 
	"interests": [ "sports", "music" ]

Elasticsearch HTTP操作

索引操作:

  • 创建索引:对比关系型数据库,创建索引就等同于创建数据库,在 Postman 中向 ES 服务器发PUT请求 :http://127.0.0.1:9200/shopping。请求后服务器返回响应:
响应结果:

"acknowledged"【响应结果】: true, # true操作成功
"shards_acknowledged"【分片结果】: true, # 分片操作成功 
"index"【索引名称】: "shopping"


# 注意:创建索引库的分片数默认 1片,在 7.0.0之前的 Elasticsearch版本中,默认 5片
如果重复添加索引,会返回错误信息
  • 查看所有索引:在 Postman 中,向 ES 服务器发 GET请求 :http://127.0.0.1:9200/_cat/indices?v。这里请求路径中的_cat 表示查看的意思,indices 表示索引,所以整体含义就是查看当前 ES
    服务器中的所有索引,就好像 MySQL 中的 show tables 的感觉,服务器响应结果如下:
表头含义
health当前服务器健康状态:green(集群完整) yellow(单点正常、集群不完整) red(单点不正常)
status索引打开、关闭状态
index索引名
uuid索引统一编号
pri主分片数量
rep副本数量
docs.count可用文档数量
docs.deleted文档删除状态(逻辑删除)
store.size主分片和副分片整体占空间大小
pri.store.size主分片占空间大小
  • 查看单个索引:在 Postman 中,向 ES 服务器发GET请求:http://127.0.0.1:9200/shopping。查看索引向 ES 服务器发送的请求路径和创建索引是一致的。但是 HTTP方法不一致。这里 可以体会一下 RESTful 的意义。
响应结果:

	"shopping"【索引名】:  
		"aliases"【别名】: ,
		"mappings"【映射】: ,
		"settings"【设置】: 
			"index"【设置- 索引】: 
				"creation_date"【设置- 索引- 创建时间】: "1614265373911", 
				"number_of_shards"【设置- 索引- 主分片数量】: "1",
				"number_of_replicas"【设置- 索引- 副分片数量】: "1",
				"uuid"【设置- 索引- 唯一标识】: "eI5wemRERTumxGCc1bAk2A", 
				"version"【设置- 索引- 版本】: 
					"created": "7080099" 
				,
				"provided_name"【设置- 索引- 名称】: "shopping" 
			
		 
	

  • 删除索引:在 Postman 中,向 ES 服务器发 DELETE请求:http://127.0.0.1:9200/shopping。重新访问索引时,服务器返回响应:索引不存在。

文档操作:

  • 创建文档:索引已经创建好了,接下来我们来创建文档,并添加数据。这里的文档可以类比为关系型数据库中的表数据,添加的数据格式为 JSON 格式,在Postman 中,向 ES 服务器发 POST 请求:http://127.0.0.1:9200/shopping/_doc。此处发送请求的方式必须为 POST,不能是 PUT,否则会发生错误。
    ①上面的数据创建后,由于没有指定数据唯一性标识(ID),默认情况下,ES 服务器会随机生成一个。如果想要自定义唯一性标识,需要在创建时指定:http://127.0.0.1:9200/shopping/_doc/1
    ②此处需要注意:如果增加数据时明确数据主键,那么请求方式也可以为 PUT。
    PUT 和 POST 的区别:
    <1>POST 新增,如果不指定id,会自动生成 id。指定 id 就会修改这个数据,并新增版本号。
    1、 post 方法不带 id 时是新增,带 id 不存在时也是新增,带 id 且数据存在时是更新操作。
    <2>PUT 可以新增也可以修改,PUT 必须指定 id,由于 PUT 需要指定 id,我们一般都用来做修改。
响应结果:

	"_index"【索引】: "shopping", 
	"_type"【类型-文档】: "_doc",
	"_id"【唯一标识】: "Xhsa2ncBlvF_7lxyCE9G", #可以类比为  MySQL中的主键,随机生成 
	"_version"【版本】: 1,
	"result"【结果】: "created", #这里的  create表示创建成功 
	"_shards"【分片】: 
		"total"【分片-总数】: 2,
		"successful"【分片- 成功】: 1, 
		"failed"【分片- 失败】: 0
	,
	"_seq_no": 0,
	"_primary_term": 1

  • 查看文档:查看文档时,需要指明文档的唯一性标识,类似于 MySQL 中数据的主键查询
    在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/shopping/_doc/1
响应结果:

	"_index"【索引】: "shopping",
	"_type"【文档类型】: "_doc",
	"_id": "1",
	"_version": 2,
	"_seq_no": 2,
	"_primary_term": 2,
	"found"【查询结果】: true, # true表示查找到,false表示未查找到
	"_source"【文档源信息】:  
		"title": "华为手机", 
		"category": "华为",
		"images": "http://www.gulixueyuan.com/hw.jpg", 
		"price": 4999.00
	

  • 修改文档(全量修改):和新增文档一样,输入相同的 URL 地址请求,如果请求体变化,会将原有的数据内容覆盖在Postman 中,向 ES 服务器发 POST 请求 :http://127.0.0.1:9200/shopping/_doc/1
响应结果:

	"_index": "shopping", 
	"_type": "_doc",
	"_id": "1",
	"_version"【版本】: 2,
	"result"【结果】: "updated", # updated表示数据被更新 
	"_shards": 
		"total": 2,
		"successful": 1, 
		"failed": 0
	,
	"_seq_no": 2,
	"_primary_term": 2

  • 修改字段(局部修改):修改数据时,也可以只修改某一给条数据的局部信息,在 Postman 中,向 ES 服务器发 POST 请求 :http://127.0.0.1:9200/shopping/_update/1。
请求体:

	"doc": 
		"title":"小米手机",
		"category":"小米"
	

  • 删除文档:删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。在Postman 中,向 ES 服务器发 DELETE 请求:http://127.0.0.1:9200/shopping/_doc/1。
响应结果:

	"_index": "shopping", 
	"_type": "_doc",
	"_id": "1",
	"_version"【版本】: 4, #对数据的操作,都会更新版本
	"result"【结果】: "deleted", # deleted表示数据被标记为删除 
	"_shards": 
		"total": 2,
		"successful": 1, 
		"failed": 0
	,
	"_seq_no": 4,
	"_primary_term": 2


如果删除一个并不存在的文档的响应结果::

	"_index": "shopping", 
	"_type": "_doc",
	"_id": "1", 
	"_version": 1,
	"result"【结果】: "not_found", # not_found表示未查找到 
	"_shards": 
		"total": 2,
		"successful": 1, 
		"failed": 0
	,
	"_seq_no": 5,
	"_primary_term": 2

  • 条件删除文档:一般删除数据都是根据文档的唯一性标识进行删除,实际操作时,也可以根据条件对多条数据进行删除:向 ES 服务器发 POST请求:http://127.0.0.1:9200/shopping/_delete_by_query
响应结果:

	"took"【耗时】: 175,
	"timed_out"【是否超时】: false,
	"total"【总数】: 2,
	"deleted"【删除数量】: 2,
	"batches": 1,
	"version_conflicts": 0,
	"noops": 0,
	"retries":  
		"bulk": 0, 
		"search": 0
	,
	"throttled_millis": 0,
	"requests_per_second": -1.0, 
	"throttled_until_millis": 0, 
	"failures": []


请求体内容为:

	"query":
		"match":
			"price":4000.00 
		
	

映射操作:

  • 有了索引库,等于有了数据库中的database。接下来就需要建索引库(index)中的映射了,类似于数据库(database)中的表结构(table)。 创建数据库表需要设置字段名称,类型,长度,约束等;索引库也一样,需要知道这个类型下有哪些字段,每个字段有哪些约束信息,这就叫做映射(mapping)。
  • 创建映射:在Postman 中,向 ES 服务器发 PUT 请求:http://127.0.0.1:9200/student/_mapping。
    映射数据说明:
    ①字段名:任意填写,下面指定许多属性,例如:title、subtitle、images、price
    ②type:类型,Elasticsearch 中支持的数据类型非常丰富,说几个关键的:
    <1>String 类型,又分两种:
    1、text:可分词
    2、keyword:不可分词,数据会作为完整字段进行匹配
    <2>Numerical:数值类型,分两类
    1、基本数据类型:long、integer、short、byte、double、float、half_float
    2、浮点数的高精度类型:scaled_float
    <3>Date:日期类型
    <4>Array:数组类型
    <5>Object:对象
    ③index:是否索引,默认为 true,也就是说你不进行任何配置,所有字段都会被索引。
    <1>true:字段会被索引,则可以用来进行搜索
    <2>false:字段不会被索引,不能用来搜索
    ④store:是否将数据进行独立存储,默认为false。原始的文本会存储在_source 里面,默认情况下其他提取出来的字段都不是独立存储的,是从_source 里面提取出来的。当然你也可以独立的存储某个字段,只要设置 “store”: true 即可,获取独立存储的字段要比从_source 中解析快得多,但是也会占用 更多的空间,所以要根据实际业务需求来设置。
    ⑤analyzer:分词器,这里的 ik_max_word 即使用 ik 分词器
请求体:

	"properties":  
		"name":
			"type": "text", 
			"index": true
		,
		"sex":
			"type": "text", 
			"index": false
		,
		"age":
			"type": "long", 
			"index": false
		 
	

  • 查看映射:在 Postman 中,向 ES 服务器发 GET 请求:http://127.0.0.1:9200/student/_mapping
  • 索引映射关联:在 Postman 中,向 ES 服务器发 PUT请求:http://127.0.0.1:9200/student1

高级查询:

  • Elasticsearch 提供了基于 JSON 提供完整的查询 DSL 来定义查询。
  • 查询所有文档:在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search
请求体:

	"query": 
		"match_all":  
	

# "query":这里的  query代表一个查询对象,里面可以有不同的查询属性
# "match_all":查询类型,例如:match_all(代表查询所有),match,term ,range 等等 
# 查询条件:查询条件会根据类型的不同,写法也有差异


服务器响应结果如下:

	"took【查询花费时间,单位毫秒】" : 1116, 
	"timed_out【是否超时】" : false,
	"_shards【分片信息】" :  
		"total【总数】" : 1,
		"successful【成功】" : 1, 
		"skipped【忽略】" : 0, 
		"failed【失败】" : 0
	,
	"hits【搜索命中结果】" : 
		"total"【搜索条件匹配的文档总数】:  
			"value"【总命中计数的值】: 3,
			"relation"【计数规则】: "eq" # eq 表示计数准确,   gte表示计数不准确 
		,
		"max_score【匹配度分值】" : 1.0, 
		"hits【命中结果集合】" : [
		。。。 
		] 
	

  • 匹配查询:match 匹配类型查询,会把查询条件进行分词,然后进行查询,多个词条之间是 or 的关系在Postman 中,向 ES 服务器发 GET 请求:http://127.0.0.1:9200/student/_search
请求体:

	"query":  
		"match": 
			"name":"zhangsan"
		
	

  • 字段匹配查询:multi_match 与 match 类似,不同的是它可以在多个字段中查询。 在 Postman 中,向 ES 服务器发GET 请求 :http://127.0.0.1:9200/student/_search
请求体:

	"query": 
		"multi_match": 
			"query": "zhangsan",
			"fields": ["name","nickname"] 
		
	

  • 关键字精确查询:term 查询,精确的关键词匹配查询,不对查询条件进行分词。 在 Postman 中,向 ES 服务器发 GET 请求:http://127.0.0.1:9200/student/_search
请求体:

	"query":  
		"term": 
			"name": 
				"value": "zhangsan" 
			
		 
	

  • 多关键字精确查询:terms 查询和 term 查询一样,但它允许你指定多值进行匹配。 如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件,类似于 mysql 的 in 在 Postman 中,向ES服务器发 GET 请求 :http://127.0.0.1:9200/student/_search
请求体:

	"query":  
		"terms": 
			"name": ["zhangsan","lisi"] 
		
	

  • 指定查询字段:默认情况下,Elasticsearch 在搜索的结果中,会把文档中保存在_source 的所有字段都返回。 如果我们只想获取其中的部分字段,我们可以添加_source 的过滤 在 Postman 中,向 ES 服务器发 GET 请求:http://127.0.0.1:9200/student/_search
请求体:

	"_source": ["name","nickname"], 
	"query": 
		"terms": 
		"nickname": ["zhangsan"] 
	

  • 过滤字段:我们也可以通过一下字段在Postman中,向ES服务器发GET请求:http://127.0.0.1:9200/student/_search
    ①includes:来指定想要显示的字段
    ②excludes:来指定不想要显示的字段
请求体:

	"_source": 
		"includes": ["name","nickname"] 
	,
	"query":  
		"terms": 
			"nickname": ["zhangsan"] 
		
	

  • 组合查询:bool把各种其它查询通过must(必须 )、must_not(必须不)、should(应该)的方式进行组合在Postman 中,向 ES 服务器发 GET 请求:http://127.0.0.1:9200/student/_search
请求体:

	"query":  
		"bool": 
			"must": [ 
				
					"match": 
						"name": "zhangsan" 
					
				 
			],
			"must_not": [ 
				
					"match":  
						"age": "40"
					 
				
			],
			"should": [ 
				
					"match":  
						"sex": "男"
					 
				
			] 
		
	

  • 范围查询:range 查询找出那些落在指定区间内的数字或者时间。range 查询允许以下字符。在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search
操作符说明
gt>大于>
gte大于等于>=
lt小于<
lte小于等于<=
请求体:

	"query": 
		"range":  
			"age": 
				"gte": 30, 
				"lte": 35
			 
		
	

  • 模糊查询:返回包含与搜索字词相似的字词的文档。
    ①编辑距离是将一个术语转换为另一个术语所需的一个字符更改的次数。这些更改可以包括:
    <1>更改字符(box → fox)
    <2>删除字符(black → lack)
    <3>插入字符(sic → sick)
    <4>转置两个相邻字符(act → cat)
    ②为了找到相似的术语,fuzzy 查询会在指定的编辑距离内创建一组搜索词的所有可能的变体
    或扩展。然后查询返回每个扩展的完全匹配。
    ③通过 fuzziness 修改编辑距离。一般使用默认值 AUTO,根据术语的长度生成编辑距离。
    在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search
请求体:

	"query":  
		"fuzzy": 
			"title": 
				"value": "zhangsan" 
			
		 
	

  • 单字段排序:sort 可以让我们按照不同的字段进行排序,并且通过 order 指定排序的方式。desc 降序,asc 升序。 在Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search
请求体:

	"query":  
		"match": 
			"name":"zhangsan" 
		
	,
	"sort": [
		"age": 
			"order":"desc" 
		
	]

  • 多字段排序:假定我们想要结合使用age 和_score 进行查询,并且匹配的结果首先按照年龄排序,然后 按照相关性得分排序在Postman中,向 ES 服务器发 GET 请求: http://127.0.0.1:9200/student/_search
请求体:

	"query": 
		"match_all":  
	,
	"sort": [ 
		
			"age": 
				"order": "desc" 
			
		, 
		
			"_score":
				"order": "desc" 
			
		 
	]

  • 高亮查询:在进行关键字搜索时,搜索出的内容中的关键字会显示不同的颜色,称之为高亮。 Elasticsearch可以对查询内容中的关键字部分,进行标签和样式(高亮)的设置。
    ①在使用 match 查询的同时,加上一个highlight 属性:
    <1>pre_tags:前置标签
    <2>post_tags:后置标签
    <3>fields:需要高亮的字段
    <4>title:这里声明 title 字段需要高亮,后面可以为这个字段设置特有配置,也可以空
    ②在Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search
请求体:

	"query":  
		"match": 
			"name": "zhangsan" 
		
	,
	"highlight": 
		"pre_tags": "<font color='red'>", 
		"post_tags": "</font>",
		"fields":  
			"name": 
		 
	

  • 分页查询:from:当前页的起始索引,默认从 0 开始。 from = (pageNum - 1) * size 。size:每页显示多少条。在 Postman 中,向 ES 服务器发 GET 请求:http://127.0.0.1:9200/student/_search
请求体:

	"query": 
		"match_all":  
	,
	"sort": [ 
		
			"age": 
				"order": "desc" 
				
		 
	],
	"from": 0, 
	"size": 2

  • 聚合查询:聚合允许使用者对 es 文档进行统计分析,类似与关系型数据库中的 group by,当然还有很 多其他的聚合,例如取最大值、平均值等等。 对某个字段取最大值 max 在 Postman 中,向 ES 服务器发GET 请求:http://127.0.0.1:9200/student/_search
    ①对某个字段取最小值 min
    ②对某个字段取最大值 max
    ③对某个字段求和 sum
    ④对某个字段取平均值 avg
    ⑤对某个字段的值进行去重之后再取总数distinct
    ③stats 聚合,对某个字段一次性返回 count,max,min,avg 和 sum 五个指标
请求体:

	"aggs":
		"max_age":
			"max":"field":"age" 
		
	,
	"size":0

  • 桶聚合查询:桶聚和相当于 sql 中的 group by 语句。terms 聚合,分组统计,在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search

Elasticsearch Java API 操作

Elasticsearch Java API 操作准备:

  • Elasticsearch 软件是由 Java 语言开发的,所以也可以通过 Java API 的方式对 Elasticsearch
    服务进行访问
  • 修改 pom 文件,增加 Maven 依赖关系
<dependencies> 
	<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>
</dependencies>
  • 客户端对象:创建 com.atguigu.es.test.Elasticsearch01_Client 类,代码中创建 Elasticsearch 客户端对象,因为早期版本的客户端对象已经不再推荐使用,且在未来版本中会被删除,所以这里我们采用高级 REST 客户端对象。
    ①注意:9200 端口为 Elasticsearch 的 Web 通信端口,localhost 为启动 ES 服务的主机名
// 创建客户端对象
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")) 
);
...
// 关闭客户端连接 
client.close();
  • ES 服务器正常启动后,可以通过 Java API 客户端对象对 ES 索引进行操作

索引操作:

  • 创建索引:
// 创建索引   - 请求对象
CreateIndexRequest request = new CreateIndexRequest("user"); 
// 发送请求,获取响应
CreateIndexResponse response = client.indices().create(request, 
RequestOptions.DEFAULT);
boolean acknowledged = response.isAcknowledged(); 
// 响应状态
System.out.println("操作状态   = " + acknowledged);
  • 查看索引:
// 查询索引   - 请求对象
GetIndexRequest request = new GetIndexRequest("user"); 
// 发送请求,获取响应
GetIndexResponse response = client.indices().get(request, 
RequestOptions.DEFAULT);
System.out.println("aliases:"+response.getAliases());
System.out.println("mappings:"+response.getMappings());
System.out.println("settings:"+response.getSettings());
  • 删除索引:
// 删除索引   - 请求对象
DeleteIndexRequest request = new DeleteIndexRequest("user"); 
// 发送请求,获取响应
AcknowledgedResponse response = client.indices().delete(request, 
RequestOptions.DEFAULT);
// 操作结果
System.out.println("操作结果   :   " + response.isAcknowledged());

文档操作:

  • 新增文档:
// 新增文档   - 请求对象
IndexRequest request = new IndexRequest(); 
// 设置索引及唯一性标识
request.index("user").id("1001"); 
// 创建数据对象
User user = new User();
user.setName("zhangsan");
user.setAge(30);
user.setSex("男");
ObjectMapper objectMapper = new ObjectMapper();
String productJson = objectMapper.writeValueAsString(user); 
// 添加文档数据,数据格式为  JSON格式
request.source(productJson,XContentType.JSON); 
// 客户端发送请求,获取响应对象
IndexResponse response = client.index(request, RequestOptions.DEFAULT); 
3.打印结果信息
System.out.println("_index:" + response.getIndex()); 
System.out.println("_id:" + response.getId());
2)    修改文档
System.out.println("_result:" + response.getResult());
  • 修改文档:
// 修改文档   - 请求对象
UpdateRequest request = new UpdateRequest(); 
// 配置修改参数
request.index("user").id("1001"); 
// 设置请求体,对数据进行修改
request.doc(XContentType.JSON, "sex", "女"); 
// 客户端发送请求,获取响应对象
UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
System.out.println("_index:" + response.getIndex());
System.out.println("_id:" + response.getId());
System.out.println("_result:" + response.getResult());
  • 查询文档:
//1.创建请求对象
GetRequest request = new GetRequest().index("user").id("1001"); 
//2.客户端发送请求,获取响应对象
GetResponse response = client.get(request, RequestOptions.DEFAULT); 
3.打印结果信息
System.out.println("_index:" + response.getIndex());
System.out.println("_type:" + response.getType());
System.out.println("_id:" + response.getId());
System.out.println("source:" + response.getSourceAsString());
  • 删除文档:
//创建请求对象
DeleteRequest request = new DeleteRequest().index("user").id("1"); 
//客户端发送请求,获取响应对象
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT); 
//打印信息
System.out.println(response.toString());
  • 批量操作:
--------批量新增:--------
//创建批量新增请求对象
BulkRequest request = new BulkRequest(); 
request.add(new
IndexRequest().index("user").id("1001").source(XContentType.JSON, "name", 
"zhangsan"));
request.add(new
IndexRequest().index("user").id("1002").source(XContentType.JSON, "name", 
"lisi"));
request.add(new
IndexRequest().index("user").id("1003").source(XContentType.JSON, "name", 
"wangwu"));
//客户端发送请求,获取响应对象
BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT); 
//打印结果信息
System.out.println("took:" + responses.getTook()); 
System.out.println("items:" + responses.getItems());

--------批量删除:--------
//创建批量删除请求对象
BulkRequest request = new BulkRequest();
request.add(new DeleteRequest().index("user").id("1001"));
request.add(new DeleteRequest().index("user").id("1002"));
request.add(new DeleteRequest().index("user").id("1003")); 
//客户端发送请求,获取响应对象
BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT); 
//打印结果信息
System.out.println("took:" + responses.getTook()); 
System.out.println("items:" + responses.getItems());

高级查询:

  • 请求体查询:
--------查询所有索引数据:--------
// 创建搜索请求对象
SearchRequest request = new SearchRequest(); 
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 
// 查询所有数据
sourceBuilder.query(QueryBuilders.matchAllQuery()); 
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>"); 
for (SearchHit hit : hits) 
//输出每条查询的结果信息
System.out.println(hit.getSourceAsString()); 

System.out.println("<<========");

--------term 查询,查询条件为关键字:--------
// 创建搜索请求对象
SearchRequest request = new SearchRequest(); 
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 
sourceBuilder.query(QueryBuilders.termQuery("age", "30")); 
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>"); 
for (SearchHit hit : hits) 
//输出每条查询的结果信息
System.out.println(hit.getSourceAsString()); 

System.out.println("<<========");

--------分页查询:--------
// 创建搜索请求对象
SearchRequest request = new SearchRequest(); 
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 
sourceBuilder.query(QueryBuilders.matchAllQuery());
// 分页查询
// 当前页其实索引(第一条数据的顺序号),from 
sourceBuilder.from(0);
// 每页显示多少条  size 
sourceBuilder.size(2);
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>"); 
for (SearchHit hit : hits) 
//输出每条查询的结果信息
System.out.println(hit.getSourceAsString()); 

System.out.println("<<========");

--------数据排序:--------
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 
sourceBuilder.query(QueryBuilders.matchAllQuery());
// 排序
sourceBuilder.sort("age", SortOrder.ASC);
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>"); 
for (SearchHit hit : hits) 
//输出每条查询的结果信息
System.out.println(hit.getSourceAsString()); 

System.out.println("<<========");

--------过滤字段:--------
// 创建搜索请求对象
SearchRequest request = new SearchRequest(); 
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 
sourceBuilder.query(QueryBuilders.matchAllQuery());
//查询字段过滤
String[] excludes = ;
String[] includes = "name", "age"; 
sourceBuilder.fetchSource(includes, excludes);
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>"); 
for (SearchHit hit : hits) 
//输出每条查询的结果信息
System.out.println(hit.getSourceAsString()); 

System.out.println("<<========");

--------Bool 查询:--------
// 创建搜索请求对象
SearchRequest request = new SearchRequest();
request.indices("student"); 
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 必须包含
boolQueryBuilder.must(QueryBuilders.matchQuery("age", "30")); 
// 一定不含
boolQueryBuilder.mustNot(QueryBuilders.matchQuery("name", "zhangsan")); 
// 可能包含
boolQueryBuilder.should(QueryBuilders.matchQuery("sex", "男")); 
sourceBuilder.query(boolQueryBuilder);
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>"); 
for (SearchHit hit : hits) 
//输出每条查询的结果信息
System.out.println(hit.getSourceAsString()); 

System.out.println("<<========");

--------范围查询:--------
// 创建搜索请求对象
SearchRequest request = new SearchRequest(); 
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("age"); 
// 大于等于
rangeQuery.gte("30"); 
// 小于等于 
rangeQuery.lte("40");
sourceBuilder.query(rangeQuery); 
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>");
for (SearchHit hit : hits)  
//输出每条查询的结果信息
System.out.println(hit.getSourceAsString()); 

System.out.println("<<========");

--------模糊查询:--------
// 创建搜索请求对象
SearchRequest request = new SearchRequest(); 
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.fuzzyQuery("name","zhangsan").fuzziness(Fu 
zziness.ONE));
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>"); 
for (SearchHit hit : hits) 
//输出每条查询的结果信息
System.out.println(hit.getSourceAsString()); 

System.out.println("<<========");

--------高亮查询:--------
// 高亮查询
SearchRequest request = new SearchRequest().indices("student"); 
//2.创建查询请求体构建器
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 
//构建查询方式:高亮查询
TermsQueryBuilder termsQueryBuilder = 
QueryBuilders.termsQuery("name","zhangsan"); 
//设置查询方式
sourceBuilder.query(termsQueryBuilder);
//构建高亮字段
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("<font color='red'>");//设置标签前缀
highlightBuilder.postTags("</font>");//设置标签后缀
highlightBuilder.field("name");//设置高亮字段 
//设置高亮构建对象
sourceBuilder.highlighter(highlightBuilder); 
//设置请求体
request.source(sourceBuilder); 
//3.客户端发送请求,获取响应对象
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
//4.打印响应结果
SearchHits hits = response.getHits();
System.out.println("took::"+response.getTook());
System.out.println("time_out::"+response.isTimedOut());
System.out.println("total::"+hits.getTotalHits());
System.out.println("max_score::"+hits.getMaxScore());
System.out.println("hits::::>>"); 
for (SearchHit hit : hits) 
String sourceAsString = hit.getSourceAsString(); 
System.out.println(sourceAsString);
//打印高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields(); 
System.out.println(highlightFields);

System.out.println("<<::::");

--------聚合查询:--------
// 最大年龄
SearchRequest request = new SearchRequest().indices("student");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.aggregation(AggregationBuilders.max("maxAge").field("age"));
//设置请求体
request.source(sourceBuilder); 
//3.客户端发送请求,获取响应对象
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
//4.打印响应结果
SearchHits hits = response.getHits(); 
System.out.println(response);

// 分组统计
SearchRequest request = new SearchRequest().indices("student");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.aggregation(AggregationBuilders.terms("age_groupby").field("ag 
e"));
//设置请求体
request.source(sourceBuilder); 
//3.客户端发送请求,获取响应对象
SearchResponse response = client.search(request, RequestOptions.DEFAULT); 
//4.打印响应结果
SearchHits hits = response.getHits(); 
System.out.println(response);

Elasticsearch 环境

单机 & 集群:

  • 单台 Elasticsearch 服务器提供服务,往往都有最大的负载能力,超过这个阈值,服务器性能就会大大降低甚至不可用,所以生产环境中,一般都是运行在指定服务器集群中。
  • 除了负载能力,单点服务器也存在其他问题:
    ①单台机器存储容量有限
    ②单服务器容易出现单点故障,无法实现高可用
    ③单服务的并发处理能力有限
  • 配置服务器集群时,集群中节点数量没有限制,大于等于 2 个节点就可以看做是集群了。一 般出于高性能及高可用方面来考虑集群中节点数量都是3个以上。

集群 Cluster:

  • 一个集群就是由一个或多个服务器节点组织在一起,共同持有整个的数据,并一起提供 索 引 和 搜 索 功 能 。 一 个Elasticsearch 集群有一个唯一的名字标识,这个名字默认就是”elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。

节点 Node: