目录
一、添加节点
二、节点发现
1. 广播
2. 单播
3. 选举主节点
4. 错误识别
三、删除节点
1. 丢失节点
2. 停用节点
四、升级节点
五、使用_cat API
六、扩展策略
1. 过度分片
2. 将数据切分为索引和分片
3. 最大化吞吐量
七、别名
1. 添加和删除别名
2. 查看别名
3. 使用别名过滤器来屏蔽文档
八、路由
《Elasticsearch In Action》学习笔记。
向ES集群增加节点非常简单,要做的就是启动一个新的ES实例,其它基本是全自动的,新节点会自动被发现,已有数据分片会自动重新平衡分布。具体过程参见“触类旁通Elasticsearch:安装”。
添加节点后,可以执行下面的命令对ES集群进行健康检查:
curl '172.16.1.127:9200/_cluster/health?pretty'
结果返回:
{
"cluster_name" : "ES_cluster",
"status" : "green", # 绿色状态
"timed_out" : false,
"number_of_nodes" : 3,
"number_of_data_nodes" : 3, # 集群中的三个节点均可处理数据
"active_primary_shards" : 23,
"active_shards" : 46, # 所有分片都是激活状态
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 0, # 没有未分配的分片
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 100.0
}
添加节点带来的主要好处是高可用性与性能提升。主分片与其对应的副本分片不在一个节点上。当副本分片是激活状态(缺省状态)时,如果无法找到主分片,ES会自动地将一个对应的副本分片升级为主分片。这样,即使失去了索引主分片所在的节点,仍然可以访问副本分片上的数据。数据分布在多个节点上同样提升了性能,原因是主分片和副本分片都可以处理搜索和获取结果的请求。如果更多的节点加入集群,ES将试图在所有节点上均匀配置分片数量,这样每个新加入的节点都能通过部分数据(以分片的形式)来分担负载。
ES节点使用两种不同的方式来发现另一个节点:广播或单播。ES可以同时使用两者,但默认的配置只使用广播,因为单播需要已知节点的列表来进行连接。
ES启动时,发送广播(multicast)的ping请求到地址224.2.2.4的端口54328,而其它的ES节点使用同样的集群名称(cluster.name),响应这个请求。通过设置elasticsearch.yml中如下的选项(展示了默认值),可以修改或者完全关闭广播发现的若干选项:
discovery.zen.ping.multicast:
group: 224.2.2.4
port: 54328
ttl: 3
address: null # 设置地址为null意味着绑定所有的网络接口
enabled: true
广播发现(multicast discovery)的过程如图1所示。
生产环境的ES集群应该防止节点意外地连接到不属于它们的集群。单播不会将消息发送到全网,而是连接指定列表中的节点。
单播发现(unicast discovery)让ES连接一系列主机,并试图发现更多关于集群的信息。当节点的IP地址不会经常变化,或者ES的生产系统只连接特定的节点而不是整个网络的时候,单播是理想的模式。一个单播配置的例子是在elasticsearch.yml中设置discovery.zen.ping.unicast.hosts: ["10.0.0.3", "10.0.0.4:9300","10.0.0.5[9300-9400]"]。
并非所有的ES集群节点需要出现在单播列表中来发现全部的节点,但是必须为每个节点配置足够的地址,让其认识可用的“口口相传”节点。例如,如果单播列表中的一个节点认识7个集群节点中的3个,而单播列表中的第二个节点认识7个节点中的其它4个,那么该节点执行发现操作后能找到集群中的全部7个节点。单播发现的图形化表示如图2所示。
在发现集群中的全部节点后,ES将进行主节点选取。
一旦集群中的节点发现了彼此,它们会协商谁将成为主节点。主节点负责管理集群的状态,也就是当前的设置和集群中分片、索引以及节点的状态。在主节点被选举出来之后,它会建立内部的ping机制来确保每个节点在集群中保持活跃和健康,这被称为错误识别(fault detection)。ES认为所有的节点都有资格成为主节点,除非某个节点的node.master选项设置为false。当集群只有一个节点时,该节点先等一段时间,如果没有发现集群中的任何其它节点,它就将自己选为主节点。
应设置主节点的最小数量。这个设置并不表示一个ES集群可以拥有多个主节点,实际上它是告诉ES在集群成为健康状态前,集群中有多少节点有资格成为主节点。可以将最小数量设置为集群的总节点数,或者遵循一个常用的规则,将其设置为集群节点数除以2再加上1。将minimum_master_nodes设置为高于1的数量,可以预防集群产生脑裂(split brain)问题。遵守常用规则,3个节点的集群其minimum_master_nodes要设置为2,而对于14个节点的集群,最好将其设置为8。可修改elasticsearch.yml文件中的discovery.zen.minimum_master_nodes,将其设置为符合集群需求的数值。
下面的命令可以查看集群选举了哪个节点作为主节点:
curl '172.16.1.127:9200/_cluster/state/master_node,nodes?pretty'
返回如下:
{
"cluster_name" : "ES_cluster",
"compressed_size_in_bytes" : 18593,
"cluster_uuid" : "T9UbYc0NS3OWw2YF-8TsfA",
"master_node" : "DnLBO3JbQjuLe-bbBxD3Rw", # 当前主节点的ID
"nodes" : {
"JukNH3HkSZCC_O5jAqsDSw" : { # 集群中的第一个节点
"name" : "node125",
"ephemeral_id" : "QCFj_mKjTKO1beBtmMiC9w",
"transport_address" : "172.16.1.125:9300",
"attributes" : {
"ml.machine_memory" : "8203419648",
"ml.max_open_jobs" : "20",
"xpack.installed" : "true",
"ml.enabled" : "true"
}
},
"yO9AEg-BTS20V9BhuEWeuA" : { # 集群中的第二个节点
"name" : "node127",
"ephemeral_id" : "u7WZkfkBSFmGyTymoOyAXg",
"transport_address" : "172.16.1.127:9300",
"attributes" : {
"ml.machine_memory" : "8203485184",
"ml.max_open_jobs" : "20",
"xpack.installed" : "true",
"ml.enabled" : "true"
}
},
"DnLBO3JbQjuLe-bbBxD3Rw" : { # 集群中的第三个节点,为主节点
"name" : "node126",
"ephemeral_id" : "4DgVQJ40QrKt5BFr1mJjRw",
"transport_address" : "172.16.1.126:9300",
"attributes" : {
"ml.machine_memory" : "8203419648",
"ml.max_open_jobs" : "20",
"xpack.installed" : "true",
"ml.enabled" : "true"
}
}
}
}
“脑裂”描述了这样的场景:(通常是在重负载或网络存在问题的情况下)ES集群中一个或多个节点失去了和主节点的通信,开始选举新的主节点,并继续处理请求。这个时候,可能有两个不同的ES集群相互独立地运行着。为了防止这种情况的而发生,需要根据集群节点的数量设置discovery.zen.minimum_master_nodes。将该值设置为节点数除以2并加1是个不错的选择,这意味着如果一个或多个节点失去了和其它节点的通信,它们无法选举新的主节点来形成集群,因为对于它们不能获得所需的节点(可成为主节点的节点)数量(超过一半)。
主节点需要和集群中所有节点通信,以确保一切正常,这称为错误识别(fault discovery)的过程。主节点ping集群中所有其它的节点,而且每个节点也会ping主节点来确认无须选举,如图3所示。
在图3中,每个节点每隔discovery.zen.fd.ping_interval的时间(默认是1秒)发送一个ping请求,等待discovery.zen.fd.ping_timeout的时间(默认是30秒),并尝试最多discovery.zen.fd.ping_retries次(默认是3),然后宣布节点失联。并且在需要的时候进行新的分片路由和主节点选举。如果网络环境有很高的延迟,请确定修改这些值。
如图4所示,如果一个ES集群中的一个节点掉线或停机了,ES所做的第一件事情是自动地将节点node2上的test0和test3副本分片转为主分片。这是由于索引操作会首先更新主分片,所以ES要尽力是索引的主分片正常运作。ES可以选择任一个副本分片并将其转为主分片。
在ES将副本分片转为主分片后,集群会变为黄色状态,这意味着某些副本分片尚未分配到某个节点。ES下一步需要创建更多的副本分片来保持test索引的高可用性。由于所有的主分片现在都是可用的,节点node2上的test0和test3主分片的数据会复制到node3上作为副本分片,而节点node3上的test1主分片的数据会复制到节点node2,如图5所示。
一旦副本分片被重新创建,并用于弥补损失的节点,那么集群将重新回归绿色的状态,全部主分片以及副本分片都分配到了某个节点。在这个时间段内,整个集群都是可用于搜索和索引的,因为实际上没有丢失数据。如果失去的节点多于1个,或者某个没有副本的分片丢失了,那么集群就会变为红色的状态,这意味着某些数据永远地丢失了,需要让集群重连拥有丢失数据的节点,或者对丢失的数据重新建立索引。
就副本的数量而言,有1份副本分片意味着集群可以缺失1个节点而不丢失数据。如果有2个副本分片,可以缺失2个节点而不丢失数据,以此类推。所以要确保选择了合适的副本数量。
当集群进行例行维护时,总是希望关闭某个包含数据的节点,而同时不让集群进入黄色的状态。ES有一种停用节点(decommission)的方式,告诉集群不要再分配任何分片到某个或一组节点上。停用节点的操作会将该节点上的所有数据分片转移到集群中的其它节点。系统通过集群设置的临时修改实现节点的停用:
curl -X PUT "172.16.1.127:9200/_cluster/settings?pretty" -H 'Content-Type: application/json' -d'
{
"transient": { # 临时设置,重启集群后不再有效
"cluster.routing.allocation.exclude._ip": "172.16.1.125"
}
}'
运行这个命令,ES将待停用节点上的全部分片开始转移到集群中的其它节点上。可以使用_nodes端点来确定集群节点的ID,然后查看集群的状态,来了解集群中每个分片目前分配到哪里。
curl -s "172.16.1.127:9200/_nodes?pretty"
curl "172.16.1.127:9200/_cluster/state/routing_table,routing_nodes?pretty"
(1)关于升级的警告
(2)轮流重启
轮流重启(rolling restart)是一种集群重启方式,它是为了在不牺牲数据可用性的前提下,升级一个节点或进行非动态的配置修改。这样可以对ES生产环境的部署进行动态升级。
对于升级而言,多数情况下不希望ES在节点离开集群的情况下开始自动恢复,因为这意味着每个节点都要进行重新均衡。实际上,数据还在那里,节点只是需要重启然后再次加入集群而变为可用。可以通过设置cluster.routing.allocation.enable选项为none做到这一点。滚动升级的过程如下:
curl -X PUT "172.16.1.127:9200/_cluster/settings?pretty" -H 'Content-Type: application/json' -d'
{
"transient": {
"cluster.routing.allocation.enable": "none"
}
}'
关闭即将升级的节点。
curl -X PUT "172.16.1.127:9200/_cluster/settings?pretty" -H 'Content-Type: application/json' -d'
{
"transient": {
"cluster.routing.allocation.enable": "all"
}
}'
对每个需要升级的节点重复整个过程。对于集群中每个升级的节点,都要执行关闭分配和重启分配。如果只在整个升级开始和结束的时候各执行一次,那么升级一个节点的时候,ES不会分配该节点上的分片,一旦升级多个节点集群就可能会变为红色状态。每个节点升级后,重新开启分配选项并等待集群变为绿色状态,这样当进行下一个节点升级的时候,数据就是可分配、可用的。为每个待升级的节点重复这些步骤,直到升级了整个集群。
对于没有副本分片的索引,可以使用停用(decommission)步骤,在关闭节点进行升级前,先转移它上面的全部数据并停用它。
_cat API提供了很有帮助的诊断和调试工具,将数据以更好的可读性打印出来,而不是返回一个巨大的JSON回复。
# 查看集群健康状态
curl -X GET "172.16.1.127:9200/_cat/health?v"
# 查看节点列表
curl -X GET "172.16.1.127:9200/_cat/nodes?v"
# 查看完整的_cat API接口
curl -X GET "172.16.1.127:9200/_cat"
# 查看每个节点的分片
curl -X GET "172.16.1.127:9200/_cat/allocation?v"
# 查看分片的分配情况
curl -X GET "172.16.1.127:9200/_cat/shards?v"
过度分片(over-sharding)是指有意地为索引创建大量分片,用于未来增加节点的过程。因为分片是ES所能移动的最小单位,所以应确保至少拥有和集群节点一样多的主分片数。
另一方面,ES管理每个分片都隐含着额外的开销。这是因为每个分片都是完整的Lucene索引,它需要为索引的每个分段创建一些文件描述符,增加相应的内存开销。如果创建了过多的分片,可能会占用了本来支撑性能的内存,或者触及操作系统文件描述符或内存的极限。
ES的默认设置5个分片适用于大多普通应用,但需要注意,一旦索引被创建,其主分片的数量是不能改变的,只能修改副本分片的数量。
类似于SQL数据库的水平数据分表,例如按每个地区或年月创建索引。使用索引进行规划的另一个方式是别名。别名(alias)就像指向某个索引或一组索引的指针。而且ES中的别名也允许随时修改其所指向的索引。对于数据按语义的方式来切分,这点非常有用。例如“当前”的别名永远可用来指向应该被搜索的数据,而无须修改待搜索索引的名称。此外,索引足够灵活,而且几乎没有额外负载。
ES的别名功能类似于SQL中的视图,只要修改视图定义就可以访问不同的表,而不需要修改表名。
加速索引的一个方法是临时减少集群中副本分片的数量。索引数据时,默认情况下,在数据更新到主分片和所有副本分片之前,请求是不会完成的。所以,在索引阶段将副本分片数量减少到1(甚至是0)是有利的,然后在集中索引阶段结束后将这个数量增加为1或多个。
相反,在搜索的时候,通过加入更多的副本分片,搜索可以更快,这是因为无论是主分片还是副本分片都可以用于搜索。如果搜索请求量太大,集群中的节点很难应付,那么考虑加入节点时,将这些节点的node.data和node.master设置为false。这些节点就可以被用于处理不断涌入的请求,将请求分发到数据节点,收集返回的结果。这些节点只会处理客户端请求的连接,而不会像数据节点那样搜索分片。而另一方面,搜索分片的数据节点则不必处理和客户端之间的连接,只需要搜索分片。
curl -X POST "172.16.1.127:9200/_aliases?pretty" -H 'Content-Type: application/json' -d'
{
"actions": [
{
"add": {
"index": "get-together", # 索引get-together将增加别名gt-alias
"alias": "gt-alias"
}
},
{
"remove": {
"index": "old-get-together", # 删除索引old-get-together的别名gt-alias
"alias": "gt-alias"
}
}
]
}'
curl -X PUT "172.16.1.127:9200/get-together/_aliases/gt-alias?pretty"
curl -X DELETE "172.16.1.127:9200/old-get-together/_aliases/gt-alias?pretty"
curl '172.16.1.127:9200/get-together/_alias?pretty'
curl -X GET '172.16.1.127:9200/get-together/_alias/*?pretty'
curl -X GET '172.16.1.127:9200/_alias/gt-alias?pretty'
curl -X POST "172.16.1.127:9200/_aliases?pretty" -H 'Content-Type: application/json' -d'
{
"actions": [
{
"add": {
"index": "get-together",
"alias": "es-groups",
"filter": {
"term": {
"tags": "elasticsearch"
}
}
}
}
]
}'
curl '172.16.1.127:9200/get-together/_count?pretty' -H 'Content-Type: application/json' -d'
{
"query": {
"match_all": {}
}
}'
curl '172.16.1.127:9200/es-groups/_count?pretty' -H 'Content-Type: application/json' -d'
{
"query": {
"match_all": {}
}
}'
定制路由允许将分享同一个路由值的多篇文档归集到单个分片中,而一旦这些文档放入到同一索引,就可以路由某些查询,让它们可以在索引分片的子集中执行。 挑选拥有足够基数的字段作为路由字段非常重要,这使得数据能够在索引的不同分片中分布。例如下面代码中,将活动的举办城市作为路由值:
curl -X POST "172.16.1.127:9200/get-together/_doc/9?routing=denver&pretty" -H 'Content-Type: application/json' -d'
{
"title":"Denver Knitting"
}'
curl -X POST "172.16.1.127:9200/get-together/_doc/10?routing=denver&pretty" -H 'Content-Type: application/json' -d'
{
"name": "Denver Ruby",
"description": "The Denver Ruby Meetup"
}'
curl -X POST "172.16.1.127:9200/get-together/_doc/11?routing=boulder&pretty" -H 'Content-Type: application/json' -d'
{
"name": "Boulder Ruby",
"description": "Boulderites that use Ruby"
}'
curl -X POST "172.16.1.127:9200/get-together/_doc/12?routing=amsterdam&pretty" -H 'Content-Type: application/json' -d'
{
"name": "Amsterdam Devs that use Ruby",
"description": "Mensen die genieten van het gebruik van Ruby"
}'
在查询时指定路由值:
curl -X POST "172.16.1.127:9200/get-together/_search?routing=boulder,amsterdam&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"name": "ruby"
}
}
}'
使用_search_shards API来决定搜索在哪里执行:
curl -X GET "172.16.1.127:9200/get-together/_search_shards?pretty"
curl -X GET "172.16.1.127:9200/get-together/_search_shards?pretty&routing=denver"
配置路由:
curl -X PUT "172.16.1.127:9200/routed-events?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"event": {
"_routing": {
"required": true # 指定添加文档时必须手动提供路由值
},
"properties": {
"name": {
"type": "text"
}
}
}
}
}'
当试图索引一篇没有路由值的文档时会报错:
curl -X POST "172.16.1.127:9200/routed-events/event/1?pretty" -H 'Content-Type: application/json' -d'
{
"name":"my event"
}'
返回的错误信息如下:
{
"error" : {
"root_cause" : [
{
"type" : "routing_missing_exception",
"reason" : "routing is required for [routed-events]/[event]/[1]",
"index_uuid" : "_na_",
"index" : "routed-events"
}
],
"type" : "routing_missing_exception",
"reason" : "routing is required for [routed-events]/[event]/[1]",
"index_uuid" : "_na_",
"index" : "routed-events"
},
"status" : 400
}
结合路由和别名:
curl -X POST "172.16.1.127:9200/_aliases?pretty" -H 'Content-Type: application/json' -d'
{
"actions": [
{
"add": {
"index": "get-together",
"alias": "denver-events",
"filter": {
"term": {
"name": "denver"
}
},
"routing": "denver"
}
}
]
}'
curl -X POST "172.16.1.127:9200/denver-events/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match_all": {}
}
}'
假设别名指向一个单独的索引,那么它们可以和路由一起使用,在查询或索引的时候自动地使用路由值。