Elasticsearch 集群的健康状态、路由和索引别名(十三)

这篇分析es集群的健康状态、路由和索引别名问题:
 
集群的健康 API 接口提供了一个方便但略有粗糙的概览,包括集群、索引和 分片的整体健康状况。这通常是发现和诊断集群中常见问题的第一步。 curl -XGET http://xxxxxx:9200/_cluster/health?pretty 从这个答复的表明信息,我们可以推断出很多关于集群整体健康状态的信息。
 
Elasticsearch 集群的健康状态、路由和索引别名(十三)
 
我们一般比较关注的是: "cluster_name" : 集群名称
"status" :
正常情况下, Elasticsearch 集群健康状态分为三种: green 最健康得状态,说明所有的分片包括备份都可用 ; 这种情况
Elasticsearch 集群所有的主分片和副本分片都已分配 , Elasticsearch 集群是 100% 可用的。
yellow 基本的分片可用,但是备份不可用(或者是没有备份) ; 这种情况 Elasticsearch 集群所有的主分片已经分片了,但至少还有一个副本是缺失的。不 会有数据丢失,所以搜索结果依然是完整的。不过,你的高可用性在某种程度上 被弱化。如果 更多的 分片消失,你就会丢数据了。把 yellow 想象成一个需要 及时调查的警告。 red 部分的分片可用,表明分片有一部分损坏。此时执行查询部分数据仍然 可以查到,遇到这种情况,还是赶快解决比较好; 这种情况 Elasticsearch 集群至 少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索 只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。
"timed_out" :
是否有超时
"number_of_nodes" :
集群中的节点数量
"number_of_data_nodes" :
数据节点数
"active_primary_shards" :
指出你集群中的主分片数量
"active_shards" :
所有索引的 _ 所有 _ 分片的汇总值,即包括副本分片。
"relocating_shards" :
大于 0 表示 Elasticsearch 正在集群内移动数据的分片 , 来提升负载均衡和故 障转移。这通常发生在添加新节点、重启失效的节点或者删除节点的时候,因此 出现了这种临时的现象。
"initializing_shards" :
当用户刚刚创建一个新的索引或者重启一个节点的时候,这个数值会大于 0
"unassigned_shards" :
这个值大于 0 的最常见原因是有尚未分配的副本分片。在开发环境中,这个 问题很普遍,因为单节点的集群其索引默认有 5 个分片和 1 个副本分片。这种情 况下,由于无多余节点来分配副本分片,因此还有 5 个未分配的副本分片。
"active_shards_percent_as_number" :
集群分片的可用性百分比,如果为 0 则表示不可用。集群健康 API 提供了更多的细粒度的操作,允许用户进一步地诊断问题。在 这个例子中,可以通过添加 level 参数,深入了解哪些索引受到了分片未配置的 影响。
比如:
http://xxxxxx:9200/_cluster/health?level=indices&pretty=true
除此之外与集群相关的 API 还有:
查看集群健康状态接口 (_cluster/health)
查看集群状况接口 (_cluster/state)
查看集群统计信息接口 (_cluster/stats)
查看集群挂起的任务接口 (_cluster/pending_tasks)
集群重新路由操作 (_cluster/reroute)
更新集群设置 (_cluster/settings)
节点状态 (_nodes/stats)
节点信息 (_nodes)
节点的热线程 (_nodes/hot_threads)
关闭节点 (_nodes/_master/_shutdown)
使用 _cat API
有的时候可读性很重要,我们就需要更方便的 _cat API 接口。这个 _cat API 提供了很有帮助的诊断和调试工具,将数据以更好的可读性打印出来。 _cat API 有很多特性,它们对于调试集群的各个方面都是很有帮助的。你可 以运行 curl 'localhost:9200/_cat ' 来查看所支持的 _ cat API 接口的完整清单。 allocation-展示分配到每个节点的分片数量。
count- 统计整个集群或索引中文档的数量。
如: GET _cat/count/employees?v
health-- 展示集群的健康状态。
indices- 展示现有索引的信息。
master--- 显示目前被选为主节点的节点。
nodes--- 显示集群中所有节点的不同信息。
recovery-- 显示集群中正在进行的分片恢复状态。
shards- 展示集群中分片的数量、大小和名字。
plugins--- 展示已安装插件的信息。
路由
我们已经知道了文档是如何以通过分片形式来定位的。这个过程被称为路由
( routing ) 文档。为了让你更好地回忆,这里重温一下: 当 Elasticsearch 散列文 档的 ID 时就会发生文档的路由,来决定文档应该索引到哪个分片中,这可以由 你指定也可以让 Elasticsearch 生成。 但是这个路由完全可以我们手动指定。 索引的时候,Elasticsearch 也允许你手动地指定文档的路由,使用父子关系 实际上就是这种操作,因为子文档必须要和父文档在同一个分片。
为什么使用路由
假设你有一个 100 个分片的索引。当一个请求在集群上执行时会发生什么 呢?
1. 这个搜索的请求会被发送到一个节点
2. 接收到这个请求的节点,将这个查询转到这个索引的每个分片上(可能 是主分片,也可能是副本分片)
3. 每个分片执行这个搜索查询并返回结果
4. 结果在通道节点上合并、排序并返回给用户 因为默认情况下,Elasticsearch 使用文档的 ID (类似于关系数据库中的自增 ID),如果插入数据量比较大,文档会平均的分布于所有的分片上,这导致了 Elasticsearch 不能确定文档的位置, 所以它必须将这个请求转到所有的 N 个分片上去执行 这种操作会给集群带 来负担,增大了网络的开销; 那么如何确定请求在哪个分片上执行呢?Elasticsearch 提供了一个 API 接口, 告诉我们一个搜索请求在哪些节点和分片上执行。
_search_shards API
比如我们创建一个两分片的索引:
put open-soft-shard
{
"settings":{
"index.number_of_shards":2
     }
}
并放入数据:
put /open-soft-shard/_doc/1
{
"name": "Apache Hadoop",
"lang": "Java",
"corp": "Apache",
"stars":200
} put /open-soft-shard/_doc/2
{
"name": ["Apache Activemq","Activemq Artemis"],
"lang": "Java",
"corp": "Apache",
"stars":[500,200]
}
put /open-soft-shard/_doc/3
{
"name": ["Apache Kafka"],
"lang": "Java",
"corp": "Apache",
"stars":[500,400]
}
put /open-soft-shard/_doc/object
{
"name": ["Apache ShardingSphere"],
"lang": "Java",
"corp": "JingDong",
"stars":400,
"address":{
"city":"BeiJing",
"country":" 亦庄 "
     }
}
使用搜索分片 ( search shards ) API 接口来查看请求将在哪些分片上执行。 GET /open-soft-shard/_search_shards?pretty
可以看到,会搜索全部的两个分片。 当我们 GET /open-soft-shard/_search_shards?pretty&routing=1 即使在索引中只有两个分片, 当指定路由值 1 的时候,只有分片 shard 0 会被 搜索。对于搜索需要查找的数据,有效地切除了一半的数据量! 所以当处理拥有大量分片的索引时,路由会很有价值,当然对于 Elasticsearch 的常规使用它并不是必需的。
配置路由
路由也可以不使用文档的 ID ,而是定制的数值进行散列。通过指定 ULR 中 的 routing 查询参数,系统将使用这个值进行散列,而不是 ID
PUT /employees/_doc/2?routing=rountkey
{
………
}
}
在这个例子中, rountkey 这个由我们自己输入的值决定文档属于哪个分片的 散列值,而不是文档的 ID 2 。 由上可知,自定义路由的方式非常简单,只需要在插入数据的时候指定路由 的 key 即可。虽然使用简单,但有细节需要注意。我们来看看:
1 、先创建一个名为 study_route 的索引,该索引有 2 shard 0 个副本
PUT study_route/
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
}
}
2 :查看 shard
GET _cat/shards/study_route?v
3 :插入第 1 条数据
PUT study_route/_doc/a?refresh
{
"data": "A"
}
4 :查看 shard
GET _cat/shards/study_route?v
Elasticsearch 集群的健康状态、路由和索引别名(十三)
 
5 :插入第 2 条数据
PUT study_route/_doc/b?refresh
{
"data": "B"
}
6 :查看 shard
GET _cat/shards/study_route?v
Elasticsearch 集群的健康状态、路由和索引别名(十三)
 
7 :查看此时索引里面的数据
GET study_route/_search
这个例子比较简单,先创建了一个拥有 2 shard 0 个副本(为了方便观 察)的索引 study_route 。创建完之后查看两个 shard 的信息,此时 shard 为空, 里面没有任何文档(docs 列为 0 )。接着我们插入了两条数据,每次插完之后, 都检查 shard 的变化。通过对比可以发现 docid=a 的第一条数据写入了 0 shard , docid=b 的第二条数据写入了 1 shard 。 接着,我们指定 routing ,看看有什么变化。
8 :插入第 3 条数据
PUT study_route/_doc/c?routing=key1&refresh
{
"data": "C"
}
9 :查看 shard
GET _cat/shards/study_route?v
Elasticsearch 集群的健康状态、路由和索引别名(十三)
10 :查看索引数据
GET study_route/_search
我们又插入了 1 docid=c 的新数据,但这次我们指定了路由,路由的值 是一个字符串"key1". 通过查看 shard 信息,能看出这条数据路由到了 0 shard 。 也就是说用"key1" 做路由时,文档会写入到 0 shard 。 接着我们使用该路由再插入两条数据,但这两条数据的 docid 分别为之前 使用过的 "a" "b" ,最终结果会是什么样?
11 :插入 docid=a 的数据,并指定 routing=key1
PUT study_route/_doc/a?routing=key1&refresh {
"data": "A with routing key1"
}
 
Elasticsearch 集群的健康状态、路由和索引别名(十三)
es 的返回信息表明文档 a updated
12 :查看 shard
GET _cat/shards/study_route?v
Elasticsearch 集群的健康状态、路由和索引别名(十三)
 
13 :查询索引
GET study_route/_search
 
Elasticsearch 集群的健康状态、路由和索引别名(十三)
 
14 :插入 docid=b 的数据,使用 key1 作为路由字段的值
PUT study_route/_doc/b?routing=key1&refresh
{
"data": "B with routing key1"
}
 
Elasticsearch 集群的健康状态、路由和索引别名(十三)
es 返回的信息变成了 created
15 :查看 shard 信息
GET _cat/shards/study_route?v
 
Elasticsearch 集群的健康状态、路由和索引别名(十三)
 
16 :查询索引内容
GET study_route/_search
Elasticsearch 集群的健康状态、路由和索引别名(十三)
两个 id b 的文档,其中一个比另一个多了一个字段 "_routing" 和步骤 11 插入 docid=a 的那条数据相比,这次这个有些不同,我们来分析 一下。步骤 11 中插入 docid=a 时, es 返回的是 updated ,也就是更新了步骤 2 中插入的 docid a 的数据,步骤 12 13 中查询的结果也能看出,并没有新增 数据,route_test 中还是只有 3 条数据。 而步骤 14 插入 docid=b 的数据时, es 返回的是 created ,也就是新增了一 条数据,而不是 updated 原来 docid b 的数据,步骤 15 16 的确也能看出多 了一条数据,现在有 4 条数据。而且从步骤 16 查询的结果来看,有两条 docid 为 b 的数据,但一个有 routing ,一个没有。而且也能分析出有 routing 的在 0 号 shard 上面,没有的那个在 1 shard 上。 这个就是我们自定义 routing 后会导致的一个问题: docid 不再全局唯一。 ES shard 的实质是 Lucene 的索引,所以其实每个 shard 都是一个功能完善的倒排索 引。ES 能保证 docid 全局唯一是采用 docid 作为了路由,所以同样的 docid 肯定 会路由到同一个 shard 上面,如果出现 docid 重复,就会 update 或者抛异常,从 而保证了集群内 docid 唯一标识一个 doc 。但如果我们换用其它值做 routing ,那 这个就保证不了了,如果用户还需要 docid 的全局唯一性,那只能自己保证了。 因为 docid 不再全局唯一,所以 doc 的增删改查 API 就可能产生问题,比如下面
的查询: GET study_route/_doc/b GET study_route/_doc/b?routing=key1 上面两个查询,虽然指定的 docid 都是 b ,但返回的结果是不一样的。所以, 如果自定义了 routing 字段的话,一般 doc 的增删改查接口都要加上 routing 参数 以保证一致性。 为此,ES 在 mapping 中提供了一个选项,可以强制检查 doc 的增删改查接 口是否加了 routing 参数,如果没有加,就会报错。设置方式如下:
PUT < 索引名 >/
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
},
"mappings": {
"_routing": {
"required": true
// 设置为 true ,则强制检查; false 则不检查,默认为 false
             }
      }
}
比如:
PUT study_route1/
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
},
"mappings": {
"_routing": {
"required": true
           }
       }
}
// 写入一条数据
PUT study_route1/_doc/b?routing=key1 {
"data": "b with routing"
}
// 以下的增删改查都会抱错
GET study_route1/_doc/b
PUT study_route1/_doc/b
{
"data": "B"
}
DELETE study_route1/_doc/b
// 错误信息
 
Elasticsearch 集群的健康状态、路由和索引别名(十三)
前面我们说过很多时候自定义路由是为了减少查询时扫描 shard 的个数,从 而提高查询效率。默认查询接口会搜索所有的 shard ,但也可以指定 routing 字段, 这样就只会查询 routing 计算出来的 shard ,提高查询速度。 使用方式也非常简单,只需在查询语句上面指定 routing 即可,允许指定多 个:
-- 查询所有分区
GET study_route/_search
{
"query": {
"match": {
"data": "b"
     } }
}
-- 查询指定分区
GET study_route/_search?routing=key1
{
"query": {
"match": {
"data": "b"
       }
    }
}
 
索引别名
别名,有点类似数据库的视图,别名一般都会和一些过滤条件相结合,可以 做到即使是同一个索引上,让不同人看到不同的数据。别名的访问接口是_alias
创建索引添加别名
PUT /index_alias
{
"aliases": {
"index1": {
"filter":
{
"term":{
"name":"Apache"
           }
        }
    }
  }
}
为已有索引添加别名
put /open-soft/_alias/open-soft-alias
{
"filter": {
"match":{
"lang":"Java8"
     }
  }
}
查询指定索引的别名
get /open-soft/_alias
删除别名
delete /open-soft/_alias/open-soft-alias
查询别名
get _cat/aliases
get 索引名 /_alias ,看索引所有的别名,如: GET /open-soft/_alias
get 别名,看别名关联索引的详情,如: GET open-soft-alias
通过别名查询
别名的使用和索引差不多,比如:
GET open-soft-alias/_search
_aliases 接口
同时 _aliases 接口可以做批量操作,比如通过 _aliases 接口将一个别名关联多
个索引:
POST /_aliases
{
"actions": [
{
"add": {
"index": "my_index_1",
"alias": "my_index_alias"
        }
    },
{
"add": {
"index": "my_index_2",
"alias": "my_index_alias" }
        }
    ]
}
或者对于同一个 index ,我们给不同人看到不同的数据,如 my_index 有个字 段是 team team 字段记录了该数据是那个 team 的。 team 之间的数据是不可见 的。
POST /_aliases
{
"actions": [
{
"add": {
"index": "my_index",
"alias": "my_index_teamA_alias",
"filter":{
"term":{
"team":"teamA"
            }
       }
     }
  },
{
"add": {
"index": "my_index",
"alias": "my_index_teamB_alias",
"filter":{
"term":{
"team":"teamB"
          }
       }
     }
   },
{
"add": {
"index": "my_index",
"alias": "my_index_team_alias" }
        }
    ]
}
只要有可能,尽量使用别名,推荐为 Elasticsearch 的每个索引都使用别名, 因为在未来重建索引的时候,别名会赋予你更多的灵活性。假设一开始创建的索 引只有一个主分片,之后你又决定为索引扩容。如果为原有的索引使用的是别名, 现在你可以修改别名让其指向额外创建的新索引,而无须修改被搜索的索引之名 称( 假设一开始你就为搜索使用了别名 ) 。 另一个有用的特性是,在不同的索引中创建窗口。比如,如果为数据创建了 每日索引,你可能期望一个滑动窗口涵盖过去一周的数据,别名就称为 last-7-days 。 然后,每天创建新的每日索引时,你可以将其加入别名,同时停用或者删除第 8 天前的旧索引。 别名还能提供另一个特性,那就是路由。不过,在谈论使用别名进行路由之 前,我们先来讲述一下通常情况下路由是如何使用的。
结合路由和别名
在之前的章节中,我们已经了解到别名是索引之上的抽象,非常强大和灵活。 假设别名指向一个单独的索引,那么它们也可以和路由一起使用,在查询或索引 的时候自动地使用路由值。如:
POST /_aliases
{
"actions": [
{
"add": {
"index": "open-soft",
"alias": "my_index_alias",
"filter":{"match":{"lang":"Java"}},
"routing":"AKey"
       }
},
{
"add": {
"index": "open-soft",
"alias": "my_index_alias2",
"filter":{"match":{"lang":"Java"}},
"routing":"BKey"
        } }
    ]
}
PUT /open-soft/_alias/open-soft-alias2
{
"filter":
{
"match":{
"lang":"Java"
}
},
"routing":"CKey"
}
_rollover 接口
_rollover 接口用于根据一系列条件将别名指向一个新的索引,这些条件包括 存续时间、文档数量和存储容量等。这与日志文件使用的文件滚动类似,文件滚 动是通过不断创建新文件并滚动旧文件来保证日志文件不会过于庞大,而_rollover 接口则是通过不断将别名指向新的索引以保证索引容量不会过大。但这 种别名滚动并不会自动完成,需要主动调用_rollover 接口。 别名滚动的条件可通过 conditions 参数设置,包括 max_age max_docs 和 max_size 等三个子参数。例如,创建一个索引 logs- 1 并分配别名 logs, 然后调用 logs 别名的 _rollover 接口设置别名滚动条件,如 :
PUT /logs-1
{
"aliases": {
"logs": {}
     }
}
POST /logs/_rollover
{
"conditions": {
"max_age": "14d",
"max_docs": 10000,
"max_size": "4gb" }
}
在示例中, logs 别名指向 logs-1 索引,最大存活周期为 14 天,最大文档数 量 10000 条,最大存储容量 4GB 。因为 logs-1 索引刚刚创建,存活时间、文档数 量和存储容量都不满足条件,所以使用示例的请求不会对 logs 别名产生任何影 响。这通过请求返回的结果也可以看到:
Elasticsearch 集群的健康状态、路由和索引别名(十三)
 
从返回结果的 conditions 属性来看,三个条件匹配的结果都是 false, 所以不 会触发索引滚动。如果想体验别名滚动的效果,可以将 max_age 设置为 1s 再调 用上面的请求。之后通过“GET _cat/indices" 接口就会发现有新的索引 logs-000002 产生,再分别查看这两个索引就会发现, logs-1 的别名已经被清空, 而 logs-000002 的别名中则已经添加了 logs 。新索引的命名规则在原索引名称数 字的基础上加 1 ,并且将数值长度补 0 凑足 6 位。 所以使用_rollover 接口时,要求索引名称必须以数字结尾,数字与前缀之间 使用连接线“- ”连接。 由于_rollover 接口在滚动新索引时,会将别名与原索引的关联取消,所以通 过别名再想查找已经编入索引的文档就不可能了。为了保证原文档可检索,可以 通过别名 is_ write_index 参数保留索引与别名的关系。当使用 is_ write_index 参 数设置了哪一个索引为写索引时,_rollover 接口滚动别名指向索引时将不会取消别名与原索引之间的关系。它会将原索引的 is_ write_index 参数设置为 false, 并 将新索引的 is_ write_index 参数设置为 true 。 例如在创建 logs-4 时添加参数所示 :
PUT /logs-4
{
"aliases": {
"logs4": {
"is_write_index": true
        }
    }
}
再执行示例中的请求时
POST /logs4/_rollover {
"conditions": {
"max_age": "1s",
"max_docs": 10000,
"max_size": "4gb"
     }
}
GET _cat/aliases?v
 
Elasticsearch 集群的健康状态、路由和索引别名(十三)
 
会发现 logs-4 is _write_ index 参数被设置为 false, 而新生成索引 logs-000005 的 is_ write_ index 参数则为 true 。在两者的别名列表中都包含有 logs,可以继续通过 logs 别名对原索引进行查询。
 
es集群分析到此结束,下一篇我们分析kibana 的详细使用,敬请期待。