在前面的文章( ES vs Solr)中我们提到, ES构建了Loggly的很多核心功能. 在把这项通用搜索技术用于我们的日志管理系统, 并为超过5000多客户提供准实时服务的过程中, 我们在技术上成长颇多. 按照我们对开源社区的尊重, 在此希望能把我们所学到的知识回馈到社区.
本文将探讨对ES扩展过程中的性能有深远影响的关键概念: cluster state
.
ES的一个突出优点是其无模型规则约束. 起初, 你可以把一个包含任意字段数目的文档添加到索引中, 而并不需要提前在ES中定义这些字段的类型. 能把这些字段添加到索引并随后进行搜索是件让人兴奋的事情. 但这些字段的名称, 类型以及它们被添加到的索引信息则会自动添加到ES的 cluster state
信息中(假设你使用了默认的 动态mapping设置. 当然像ES的其他配置一样, 你可以调整或禁用该行为).
当然,这里还有些秘密: 如果你向一个已存在的字段(例如integer)中试图添加另外一种类型(例如string)的字段值时, ES将会失败. 同一个索引中的相同字段不能同时具有两种不同类型. 这称为 mapping冲突. ES的处理取决于涉及的具体类型. 例如:
如果向一个字符串类型中添加整数值, 则会进行强制类型转换
如果向一个整数类型中添加字符串值, 则会遇到异常, ES拒绝接收该文档.
所以关注下你的ES响应,尤其在使用bulk做批量索引时.
像其他倒排索引一样, 当你搜索数据时, ES需要知道这些数据的元信息及其存储位置. 当节点接收到查询请求, 首先要做的就是你要查询的对象在哪些分片上, 然后判断这个索引上有哪些字段以及它们的类型.(你不能在一个字符串类型的字段上进行数字范围查询). 而这些信息都记录在cluster state之中.
顾名思义, cluster state是全局性信息, 包含了整个群集中所有分片的元信息(规则, 位置, 大小等信息), 并保持每个每节的信息同步.
在一个包含众多节点的集群中, ES是如何做到信息同步的呢? 原来ES的cluster state信息是由master节点维护的, 当它收到data节点的状态更新变化后, 就把这些信息依次广播到其他节点, 仅此而已.
请记住: cluster state是你的群集中每个节点上的每个索引包含的每个分片的所有字段信息. 如果你有大量的字段, 例如把ES作为大量易变且无固定规则文档的存储, 那么cluster state将会变得庞大. 在Loggly服务中恰恰如此. 因为客户发送给我们的文档格式随意, 包含任意数据的唯一字段, 并且数量跨度较大. 我们每秒处理数十万次请求, 所以Loggly的cluster state可达数百兆大小.
也许警告声已在你脑海中响起: "你是说当有任何变化时, ES会广播数百兆的数据到不同节点?". 也许事实并不像你想像的那么糟糕. 在cluster state管理方面, ES已经做了几个优化:
从ES2.0以来, 只有变化的cluster state信息才会被广播. 相比以前的版本, 这带来了巨大的性能提升.
在ES节点前传递信息之前, ES对cluster state做了效果显明的压缩.
ES在合并cluster state更新以及批量处理上相当明智, 特别是最近的ES版本中.(在后面的文章 Pending Tasks会介绍前期版本中未做这些优化时的内部处理)
即便如此, 经常关注你的cluster state也是很有必要的. 另外, 我们发现在规模化运行ES集群早期, 索引大量数据之前要做的第一件事, 就是 为ES的cluster state数据量设置上限, 以避免超出ES处理能力而导致集群故障. 关于cluster state更严重的问题--以及在Loggly, 我们是如何解决cluster state问题的--将会在以后的文章出介绍, 希望能对你有所帮助.
下面是一个具有两个节点的集群, 其中包含了只有一条文档的一个索引的cluster state信息. 当然这与我们真实场景中的数据有很大区别, 因为在我们的集群中有很多机器和大量的索引节点以及超出你想象的mapping数量. 但这个例子已经足够让你了解cluster state所包含的信息了.
{
"cluster_name" : "elasticsearch",
"version" : 11,
"master_node" : "-mq1SRuuQoeEq-3S8SdHqw",
"blocks" : { },
"nodes" : {
"sIh5gQcFThCcz3SO6txvvQ" : {
"name" : "Max",
"transport_address" : "inet[/162.245.23.194:9301]",
"attributes" : { }
},
"-mq1SRuuQoeEq-3S8SdHqw" : {
"name" : "Llyron",
"transport_address" : "inet[/162.245.23.194:9300]",
"attributes" : { }
}
},
"metadata" : {
"templates" : { },
"indices" : {
"blog" : {
"state" : "open",
"settings" : {
"index" : {
"uuid" : "UQMz5vbXSBqFU_8U3u4gYQ",
"number_of_replicas" : "1",
"number_of_shards" : "5",
"version" : {
"created" : "1030099"
}
}
},
"mappings" : {
"user" : {
"properties" : {
"name" : {
"type" : "string"
}
}
}
},
"aliases" : [ ]
}
}
},
"routing_table" : {
"indices" : {
"blog" : {
"shards" : {
"4" : [ {
"state" : "STARTED",
"primary" : true,
"node" : "sIh5gQcFThCcz3SO6txvvQ",
"relocating_node" : null,
"shard" : 4,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : false,
"node" : "-mq1SRuuQoeEq-3S8SdHqw",
"relocating_node" : null,
"shard" : 4,
"index" : "blog"
} ],
"0" : [ {
"state" : "STARTED",
"primary" : true,
"node" : "sIh5gQcFThCcz3SO6txvvQ",
"relocating_node" : null,
"shard" : 0,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : false,
"node" : "-mq1SRuuQoeEq-3S8SdHqw",
"relocating_node" : null,
"shard" : 0,
"index" : "blog"
} ],
"3" : [ {
"state" : "STARTED",
"primary" : false,
"node" : "sIh5gQcFThCcz3SO6txvvQ",
"relocating_node" : null,
"shard" : 3,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : true,
"node" : "-mq1SRuuQoeEq-3S8SdHqw",
"relocating_node" : null,
"shard" : 3,
"index" : "blog"
} ],
"1" : [ {
"state" : "STARTED",
"primary" : false,
"node" : "sIh5gQcFThCcz3SO6txvvQ",
"relocating_node" : null,
"shard" : 1,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : true,
"node" : "-mq1SRuuQoeEq-3S8SdHqw",
"relocating_node" : null,
"shard" : 1,
"index" : "blog"
} ],
"2" : [ {
"state" : "STARTED",
"primary" : true,
"node" : "sIh5gQcFThCcz3SO6txvvQ",
"relocating_node" : null,
"shard" : 2,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : false,
"node" : "-mq1SRuuQoeEq-3S8SdHqw",
"relocating_node" : null,
"shard" : 2,
"index" : "blog"
} ]
}
}
}
},
"routing_nodes" : {
"unassigned" : [ ],
"nodes" : {
"sIh5gQcFThCcz3SO6txvvQ" : [ {
"state" : "STARTED",
"primary" : true,
"node" : "sIh5gQcFThCcz3SO6txvvQ",
"relocating_node" : null,
"shard" : 4,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : true,
"node" : "sIh5gQcFThCcz3SO6txvvQ",
"relocating_node" : null,
"shard" : 0,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : false,
"node" : "sIh5gQcFThCcz3SO6txvvQ",
"relocating_node" : null,
"shard" : 3,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : false,
"node" : "sIh5gQcFThCcz3SO6txvvQ",
"relocating_node" : null,
"shard" : 1,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : true,
"node" : "sIh5gQcFThCcz3SO6txvvQ",
"relocating_node" : null,
"shard" : 2,
"index" : "blog"
} ],
"-mq1SRuuQoeEq-3S8SdHqw" : [ {
"state" : "STARTED",
"primary" : false,
"node" : "-mq1SRuuQoeEq-3S8SdHqw",
"relocating_node" : null,
"shard" : 4,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : false,
"node" : "-mq1SRuuQoeEq-3S8SdHqw",
"relocating_node" : null,
"shard" : 0,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : true,
"node" : "-mq1SRuuQoeEq-3S8SdHqw",
"relocating_node" : null,
"shard" : 3,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : true,
"node" : "-mq1SRuuQoeEq-3S8SdHqw",
"relocating_node" : null,
"shard" : 1,
"index" : "blog"
}, {
"state" : "STARTED",
"primary" : false,
"node" : "-mq1SRuuQoeEq-3S8SdHqw",
"relocating_node" : null,
"shard" : 2,
"index" : "blog"
} ]
}
},
"allocations" : [ ]
}