0%

理想情况下,Elasticsearch 应该在服务器上单独运行并使用所有可用资源。为此,你需要配置你的系统,以允许运行 Elasticsearch 的用户访问比默认允许更多的资源。

进入生产之前,必须考虑以下设置:

  • 禁用交换分区
  • 增加文件描述符
  • 确保有足够的虚拟内存
  • 确保足够的线程
  • JVM DNS 缓存设置
  • 临时目录未安装 noexec

开发模式与生产模式

默认情况下,Elasticsearch 假定你正在开发模式下工作。如果以上任何设置的配置都不正确,将在日志文件中写入警告,但是你将能够启动和运行 Elasticsearch 节点。

一旦你配置了像这样的网络设置 network.host,Elasticsearch 就会假设你即将投入生产,并将上述警告升级为异常。这些异常将阻止你的 Elasticsearch 节点启动。这是一项重要的安全措施,以确保不会因服务器配置错误而丢失数据。

配置系统设置

在哪里配置系统设置取决于您用于安装 Elasticsearch 的软件包以及所使用的操作系统。

使用 .zip.tar.gz 软件包时,可以配置系统设置:

当使用 RPM 或 Debian 软件包,大部分系统设置在 系统配置文件。然而,使用 systemd 的系统要求在 systemd配置文件 中指定系统限制。

ulimit

在 Linux 系统上,ulimit 可用于临时更改资源限制。通常需要像 root 切换到将要运行 Elasticsearch 的用户之前那样设置限制。例如,要将打开的文件句柄(ulimit -n)的数量设置为 65536,可以执行以下操作:

1
2
3
sudo su
ulimit -n 65535
su elasticsearch
  1. 切换到 root 用户
  2. 修改打开文件的最大数量
  3. 切换为 elasticsearch 用户以启动 Elasticsearch

新限制仅适用于当前会话。

你可以通过查阅所有当前应用的限制 ulimit -a

/etc/security/limits.conf

在 Linux 系统上,可以通过编辑 /etc/security/limits.conf 文件来为特定用户设置永久限制。要将 elasticsearch 用户打开的最大文件数设置为 65535,请在 limits.conf 文件中添加以下行:

1
elasticsarch - nofile 65535

此更改仅在 elasticsearch 用户下次打开新会话时生效。

Sysconfig 文件

使用 RPM 或 Debian 软件包时,可以在系统配置文件中指定环境变量,该文件位于:

  • RPM: /etc/sysconfig/elasticsearch
  • Debian: /etc/default/elasticsearch

但是,需要通过更改 systemd 指定系统限制。

系统配置

在使用 systemd 的系统上使用 RPM 或 Debian 软件包时,必须通过 systemc 指定系统限制。

systemd 服务文件(/usr/lib/systemd/system/elasticsearch.service)包含默认情况下应用的限制。

要覆盖它们,请添加一个名为 /etc/systemd/system/elasticsearch.service.d/override.conf(或者,你可以运行 sudo systemctl edit elasticsearch 编辑该文件),设置该文件中的所有更改:

1
2
[Service]
LimitMEMLOCK=infinity

一旦修改完,运行下面的命令:

1
sudo systemctl daemon-reload

禁用交换分区

大多数操作系统尝试为文件系统高速缓存使用尽可能多的内存,并急切换出未使用的应用程序内存。这可能会导致部分 JVM 堆设置其可执行页面换出到磁盘上。

交换对性能,节点稳定性非常不利,应不惜一切代价避免交换。它可能会导致垃圾收集持续数分钟而不是毫秒,并且可能导致节点响应缓慢甚至断开与集群的连接。在弹性分布式系统中,让操作系统杀死该节点更为有效。

有三种禁用交换的方法。首选选项是完全禁用交换。如果这不是一个选择,则是否要减少交换性而不是内存锁定取决于你的环境。

禁用所有交换文件

通常,Elasticsearch 是在盒子上运行的唯一服务,其内存使用量由 JVM 选项控制。无需启用交换功能。

在 Linux 系统上,可以通过运行以下命令暂时禁用交换:

1
sudo swapoff -a

这不需要重启 Elasticsearch。

要永久禁用它,你将需要编辑 /etc/fstab 文件并注释掉所有包含单词 swap 的行。

配置 swappiness

Linux 系统上可用的另一个选项是确保 sysctlvm.swappiness 设置为 1。这减少了内核的交换趋势,并且在正常情况下不应导致交换,同时仍然允许整个系统在紧急情况下进行交换。

启用 bootstrap.memory_lock

另一种选择是在 Linux/Unix 系统上使用 mlockall 防止任何 Elasticsearch 内存被换出。

如果 mlockall 尝试分配的内存超过可用内存,则可能导致 JVM 或 Shell 会话退出。

文件描述符

这仅与 Linux 和 macOS 有关,如果在 Windows 上运行 Elasticsearch,则可以安全地忽略它。

Elasticsearch 使用许多文件描述符或文件句柄。文件描述符用尽是灾难性的,很可能导致数据丢失。确保将运行 Elasticsearch 的用户的打开文件描述符数限制增加到 65535 或更高。

虚拟内存

Elasticsearch mmapfs 默认使用目录来存储其索引。默认的操作系统对 mmap 计数的限制可能太低,这可能会导致内存不足异常。

在 Linux 上,你可以通过以下命令来增加限制:

1
sysctl -w vm.max_map_count=262144

要永久设置此值,请更新 /etc/sysctl.conf 里的 vm.max_map_count 设置。要在重启后进行验证,请运行 sysctl vm.max_map_count

RPM 和 Debian 软件包将自动配置此设置。不需要进一步配置。

线程数

Elasticsearch 对不同类型的操作使用许多线程池。能够在需要时创建新线程很重要。确保 Elasticsearch 用户可以创建的线程数至少为 4096。

这可以通过 ulimit -u 4096 在启动 Elasticsearch 之前设置,或者设置 /etc/security/limits.conf 里面的 nproc 为 4096。

尽管 Elasticsearch 需要很少的配置,但在投入生产之前,需要考虑许多设置。

进入生产之前,必须考虑以下设置:

  • 路径设定
  • 集群名称
  • 节点名称
  • 网络主机
  • 发现设置
  • 堆大小
  • 堆转储路径
  • GC 记录
  • 临时目录

path.datapath.logs

如果你使用 .zip.tar.gz 归档,则 datalogs 目录是 $ES_HOME 的子目录。如果这些重要的目录都保留在它们默认的位置,我们很有可能会在升级 Elasticsearch 版本的生活会将其删除。

在生产中使用时,几乎肯定会想要更改 data 和 log 文件夹的位置:

1
2
3
path
logs: /var/log/elasticsearch
data: /var/data/elasticsearch

RPM 和 Debian 发行版已经为 datalogs 使用了自定义路径。

path.data 设置可以设置多个路径,在这种情况下,所有的路径将被用于存储数据(虽然属于单个碎片文件将全部存储在相同的数据路径上):

1
2
3
4
5
path:
data:
- /mnt/elasticsearch_1
- /mnt/elasticsearch_2
- /mnt/elasticsearch_3

cluster.name

cluster.name 节点与集群中的所有其他节点共享节点时,该节点只能加入集群。默认名称为 elasticsearch,但您应将其更改为描述集群用途的适当名称。

1
cluster.name: logging-prod

确保不要在不同的环境中重复使用相同的集群名称,否则可能会导致节点加入了错误的集群。

node.name

Elasticsearch node.name 用作 Elasticsearch 特定实例的人类可读标识符,因此它包含在许多 API 的响应中。它默认为计算机在 Elasticsearch 启动时具有的主机名,但可以在 elasticsearch.yml 按以下方式显式配置:

1
node.name: prod-data-2

network.host

默认情况下,Elasticsearch 仅绑定到环回地址(例如 127.0.0.1[::1])。这足以在服务器上运行单个开发节点。

实际上,可以从 $ES_HOME 单个节点上的相同位置启动多个节点。这对于测试 Elasticsearch 形成集群的能力很有用,但不是建议用于生产的配置。

为了与其他服务器上的节点形成集群,您的节点将需要绑定到非环回地址。尽管网络设置很多,通常你需要配置的是 network.host

1
network.host: 192.168.1.10

network.host 设置也了解一些特殊的值,比如 _local__site__global_ 和诸如 :ip4:ip6 这种修饰符。

一旦提供了自定义的 network.host,Elasticsearch 就会假设你正在从开发模式过渡到生产模式,并将许多系统启动检查从警告升级为异常。

Elasticsearch 具有良好的默认设置,并且只需要很少的配置。可以使用 cluster-update-setting API 在正在运行的集群上更改大多数设置。

配置文件应包含特定于节点的设置(例如 node.name 和路径),或节点为了能够加入集群所需的设置,例如 cluster.namenetwork.host

配置文件位置

Elasticsearch 具有三个配置文件:

  • elasticsearch.yml 用于配置 Elasticsearch
  • jvm.options 用于配置 Elasticsearch JVM 设置
  • log4j.properties 用于配置 Elasticsearch 日志记录

这些文件位于 config 目录中,其默认位置取决于安装是来自自归档发行版(tar.gzzip)还是软件包发行版(Debian 或 RPM 软件包)。

对于存档分发,配置目录位置默认为 $ES_HOME/config。可以通过 ES_PATH_CONF 环境变量来更改 config 目录的位置,如下所示:

1
ES_PATH_CONF=/path/to/my/config ./bin/elasticsearch

或者,您可以通过命令行或外壳配置文件来 export 使用 ES_PATH_CONF 环境变量。

对于软件包分发,config 目录位置默认为 /etc/elasticsearch。config 目录的位置也可以通过 ES_PATH_CONF 环境变量来更改,但是请注意,在你的 shell 中进行设置是不够的。而是,此变量来自 /etc/default/elasticsearch (对于 Debian 软件包) 和 /etc/sysconfig/elasticsearch (对于 RPM 软件包)。您将需要相应地 ES_PATH_CONF=/etc/elasticsearch 在这些文件之一中编辑条目,以更改配置目录位置。

配置文件格式

配置格式为 YAML。这是更改数据和日志目录的路径的示例。

1
2
3
path:
data: /var/lib/elasticsearch
logs: /var/log/elasticsearch

设置也可以按照如下方式展平:

1
2
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch

环境变量替换

${...} 在配置文件中用符号引用的环境变量将替换为环境变量的值。例如:

1
2
node.name: ${HOSTNAME}
network.host: ${ES_NETWORK_HOST}

环境变量的值必须是简单的字符串。使用逗号分隔的字符串,Elasticsearch 将解析为列表。例如,Elasticsearch 将以下字符串拆分为 ${HOSTNAME} 环境变量的值列表:

1
export HOSTNAME="host1, host2"

集群和节点设置类型

可以根据配置方式对集群和节点进行分类:

动态

你可以使用 cluster-update-API 在正在运行的集群上配置和更新动态设置。

你还可以使用来在未启动或关闭的节点上本地配置动态设置 elasticsearch.yml

最好使用 cluster-update-API 设置动态,集群范围内的设置,并且 elasticsearch.yml 仅用于本地配置。使用集群更新 API 可以确保所有节点上的设置都相同。如果您不小心在不同的接点上配置了不同的设置,则可能很难注意到差异。

静态的

只能使用在未启动或关闭的节点上配置静态设置 elasticsearch.yml

必须在集群中的每个相关节点上设置静态设置。

Elasticsearch 聚合使你能够获取有关搜索结果的元信息,并回答诸如 “德克萨斯州有多少账户持有人?” 之类的问题。或 “田纳西州的平均账户余额是多少?” 你可以在一个请求中搜索文档,过滤匹配,并使用汇总分析结果。

例如,以下请求使用 terms 汇总将 bank 索引中的所有账户按状态分组,并按降序返回账户数量最多的十个州:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$params = [
'index' => 'bank',
'body' => [
'size' => 0,
'aggs' => [
'group_by_state' => [
'terms' => [
'field' => 'state.keyword',
],
],
],
],
];
$response = $client->search($params);

响应结果中的 bucketsstate 字段的值,doc_count 是每一个州的账户数量。例如,你可以看到有 27 个账户在 ID(Idaho)。因为请求设置了 size=0,响应仅仅包含了聚合结果。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1000,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"group_by_state": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 743,
"buckets": [
{
"key": "TX",
"doc_count": 30
},
{
"key": "MD",
"doc_count": 28
},
{
"key": "ID",
"doc_count": 27
},
{
"key": "AL",
"doc_count": 25
},
{
"key": "ME",
"doc_count": 25
},
{
"key": "TN",
"doc_count": 25
},
{
"key": "WY",
"doc_count": 25
},
{
"key": "DC",
"doc_count": 24
},
{
"key": "MA",
"doc_count": 24
},
{
"key": "ND",
"doc_count": 24
}
]
}
}
}

你可以组合聚合以构建更复杂的数据汇总。例如,以下请求将一个 avg 聚合嵌套在先前的 group_by_state 聚合中,以计算每个状态的平均账户余额。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$params = [
'index' => 'bank',
'body' => [
'size' => 0,
'aggs' => [
'group_by_state' => [
'terms' => [
'field' => 'state.keyword',
],
'aggs' => [
'average_balance' => [
'avg' => [
'field' => 'balance',
],
],
],
],
],
],
];

$response = $client->search($params);

结果:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
{
"took": 19,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1000,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"group_by_state": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 743,
"buckets": [
{
"key": "TX",
"doc_count": 30,
"average_balance": {
"value": 26073.3
}
},
{
"key": "MD",
"doc_count": 28,
"average_balance": {
"value": 26161.535714285714
}
},
{
"key": "ID",
"doc_count": 27,
"average_balance": {
"value": 24368.777777777777
}
},
{
"key": "AL",
"doc_count": 25,
"average_balance": {
"value": 25739.56
}
},
{
"key": "ME",
"doc_count": 25,
"average_balance": {
"value": 21663
}
},
{
"key": "TN",
"doc_count": 25,
"average_balance": {
"value": 28365.4
}
},
{
"key": "WY",
"doc_count": 25,
"average_balance": {
"value": 21731.52
}
},
{
"key": "DC",
"doc_count": 24,
"average_balance": {
"value": 23180.583333333332
}
},
{
"key": "MA",
"doc_count": 24,
"average_balance": {
"value": 29600.333333333332
}
},
{
"key": "ND",
"doc_count": 24,
"average_balance": {
"value": 26577.333333333332
}
}
]
}
}
}

你可以通过指定 terms 聚合内的顺序来使用嵌套聚合的结果进行排序,而不是按计数进行排序:

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
$params = [
'index' => 'bank',
'body' => [
'size' => 0,
'aggs' => [
'group_by_state' => [
'terms' => [
'field' => 'state.keyword',
'order' => [
'average_balance' => 'desc',
],
],
'aggs' => [
'average_balance' => [
'avg' => [
'field' => 'balance',
],
],
],
],
],
],
];

$response = $client->search($params);

结果:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
{
"took": 9,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1000,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"group_by_state": {
"doc_count_error_upper_bound": -1,
"sum_other_doc_count": 827,
"buckets": [
{
"key": "CO",
"doc_count": 14,
"average_balance": {
"value": 32460.35714285714
}
},
{
"key": "NE",
"doc_count": 16,
"average_balance": {
"value": 32041.5625
}
},
{
"key": "AZ",
"doc_count": 14,
"average_balance": {
"value": 31634.785714285714
}
},
{
"key": "MT",
"doc_count": 17,
"average_balance": {
"value": 31147.41176470588
}
},
{
"key": "VA",
"doc_count": 16,
"average_balance": {
"value": 30600.0625
}
},
{
"key": "GA",
"doc_count": 19,
"average_balance": {
"value": 30089
}
},
{
"key": "MA",
"doc_count": 24,
"average_balance": {
"value": 29600.333333333332
}
},
{
"key": "IL",
"doc_count": 22,
"average_balance": {
"value": 29489.727272727272
}
},
{
"key": "NM",
"doc_count": 14,
"average_balance": {
"value": 28792.64285714286
}
},
{
"key": "LA",
"doc_count": 17,
"average_balance": {
"value": 28791.823529411766
}
}
]
}
}
}

除了这些基本的聚合外,Elasticsearch 还提供了专门的聚合,用于在多个字段上操作并分析特定类型的数据,例如日期,IP 地址和地理数据。您还可以将单个聚合的结果发送到聚合管道中,以便进行进一步分析。

聚合提供的核心分析功能可启用高级功能,例如使用机器学习来检测异常。

不做任何过滤的搜索

一旦将一些数据摄取到 Elasticsearch 索引之后,你就可以通过 /{index}/_search 来发起搜索请求。要访问全套搜索功能,请使用 Elasticsearch Query DSL 在请求正文中指定搜索条件。您可以在请求 URI 中指定搜索的索引的名称。

例如,以下请求检索 bank 按账号排序的索引中的所有文档:

1
2
3
4
5
6
7
GET /bank/_search
{
"query": { "match_all": {} },
"sort": {
{ "account_number": "asc" }
}
}

PHP 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$params = [
'index' => 'bank',
'body' => [
'query' => [
'match_all' => (object)[
],
],
'sort' => [
[
'account_number' => 'asc',
],
],
],
];
$response = $client->search($params);

'match_all' 后面如果是空数组,需要加上 (object) 转换成空对象。

默认情况下,返回值的 hits 字段包含了符合搜索条件的前十条文档:

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
{
"took" : 63,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value": 1000,
"relation": "eq"
},
"max_score" : null,
"hits" : [ {
"_index" : "bank",
"_id" : "0",
"sort": [0],
"_score" : null,
"_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
}, {
"_index" : "bank",
"_id" : "1",
"sort": [1],
"_score" : null,
"_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
}, ...
]
}
}

响应结果也提供了关于搜索请求的一些其他信息:

  • took: 单位为 ms,Elasticsearch 处理这个请求用了多久
  • timed_out: 搜索请求是否超时
  • _shards: 搜索了多少个分片,以及成功、失败或跳过了多少个分片
  • max_score: 找到的最相关文件的分数
  • hits.total.value: 找到了多少个匹配的文档
  • hits.sort: 文档的排序(不按相关性得分排序时)
  • hits._score: 文档的相关性得分(使用时不适用 match_all

每个请求是独立的:Elasticsearch 在请求中不维护任何状态信息。要翻阅搜索结果,请在您的请求中指定 fromsize 参数。

例如,以下请求获取了第 10 到 19 条文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$params = [
'index' => 'bank',
'body' => [
'query' => [
'match_all' => (object)[
],
],
'sort' => [
[
'account_number' => 'asc',
],
],
'from' => 10,
'size' => 10,
],
];

$response = $client->search($params);

根据字段匹配搜索

不精确匹配

既然已经了解了如何提交基本的搜索请求,则可以开始构建更有趣的 match_all 查询。

要在字段中搜索特定术语,可以使用 match 查询。例如,以下请求搜索该 address 字段以查找地址包含 milllane 的客户:

1
2
3
4
5
6
7
8
9
10
11
12
$params = [
'index' => 'bank',
'body' => [
'query' => [
'match' => [
'address' => 'mill lane',
],
],
],
];

$response = $client->search($params);

精确匹配

如果要执行词组搜索而不是匹配单个词,请使用 match_phrase 代替 match。例如,以下请求仅匹配包含短语 mill lane 的地址:

1
2
3
4
5
6
7
8
9
10
11
12
$params = [
'index' => 'bank',
'body' => [
'query' => [
'match_phrase' => [
'address' => 'mill lane',
],
],
],
];

$response = $client->search($params);

要想构造更复杂的查询,可以使用 bool 查询来组合多个查询条件。你可以根据需要(必须匹配),期望(应该匹配)或不期望(必须不匹配)指定条件。

例如,以下请求在 bank 索引中搜索属于 40 岁客户的账户,但不包括住在爱达荷州(ID)的任何人:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$params = [
'index' => 'bank',
'body' => [
'query' => [
'bool' => [
'must' => [
'match' => [
'age' => '40',
],
],
'must_not' => [
[
'match' => [
'state' => 'ID',
],
],
],
],
],
],
];
$response = $client->search($params);

bool 查询中的每个 mustshouldmust_not 元素成为查询子句。文档满足每个 mustshould 的标准的程度有助于文档的相关性得分。分数越高,文档就越符合你的搜索条件。默认情况下,Elasticsearch 返回按这些相关性分数排名的文档。

must_not 子句中的条件被视为过滤器。它影响文件是否包含在结果中,但不会影响文件的评分方式。您还可以显式指定任意过滤器,以基于结构化数据包括或排除文档。

例如,以下请求使用范围过滤器将结果限制为余额在 2000 美元到 30000 美元(含)之间的账户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$params = [
'index' => 'bank',
'body' => [
'query' => [
'bool' => [
'must' => [
'match_all' => [
],
],
'filter' => [
'range' => [
'balance' => [
'gte' => 20000,
'lte' => 30000,
],
],
],
],
],
],
];

$response = $client->search($params);