原文链接:
Loggy使用ES作为其很多核心功能的搜索引擎. 如Jon Gifford在最近的文章中所述, 日志管理系统对搜索引擎有特别需求, 具体如下:
对于大规模的数据索引具有可靠的性能及准实时的表现--在我们场景中,每秒有接近100000条日志记录
同时对于大量的搜索处理能维护较高的性能和效率.
在构建我们的Gen2日志管理服务时, 我们希望能设置最优的参数以最大程序的发挥ElasticSearch的索引和搜索性能. 不幸的时, 很难在一个地方收集到完善的ElasticSearch设置文档. 这篇文章总结了我们学习到的一些知识, 同时可以作为你部署和配置自己的ES应用的一个参考列表.
本文档最新更新于09/2016
1: 提前规划indices, shards和cluster state的增长趋势
在ES中创建indices和shards非常简单, 但需要记住的是每个indiex和shard都有相应的资源开销.如果有太多的indices和shards, 单单是对他们管理上的开销就可能大大降低你的ES集群性能, 甚至极端场景下使服务不可用.
我们发现对管理系统开销影响最大的因素是, 包含了集群中每个索引的mappings
信息.曾经某段时间, 我们单个集群的cluster state信息占用了900MB的空间. 集群虽然还存活着, 但已处于不可用状态.
下面我们通过一些数据来感受下其中都发生了事情...
假设你有一个索引, 其mappings
大小为50K(在我们的集群中, 大约有700个字段). 如果你每小时生成一个索引, 那么一天下来将会有24 x 50K的cluster state数据增量, 大约是1.2MB. 如果你的系统中保存了1年的数据, cluster state信息将达到438MB(8760个indices, 53800个shards). 如果每天一个索引的话, cluster state信息会减少到18.25MB(365个indices, 1825个shards). 可以看到,每小时一个索引会把你置于一个完全不同的处境.
如果你的系统有实时索引数据的需求, 最好先为之做些规划, 并清楚的意识到你的系统中会存储多少cluster state信息以及将包含多少indices和shards. 在部署到生产环境之前, 应该先多做些测试和演练, 避免在凌晨3点因集群不可用而被叫起.
在集群配置方面, 为避免把自己置于险境, 你需要对系统中能容纳的indices和shards数量有全面的掌控.
2: 配置之前,先认识集群拓扑结构
在Loggly, 我们把ES的master节点和data节点分开部署.现在我们要说的不是这些细节, 而是强调在你做出正常的配置之前, 先选择正常的拓扑.
另外, 我们对于索引请求和查询请求也使用了不同的客户端程序. 这样不但可以为data节点减轻负载, 更重要的是我们的执行队列可以先提交给本地客户端, 再由其与集群的其他节点交互.
设置节点为master或data节点由以下两个参数的值来决定:
Master node: node.master:true node.data:falseData node: node.master:false node.data:trueClient node: node.master:false node.data:false
上面的部分比较简单, 下面开始介绍一些需要着重注意的高级属性. 对于大部分系统来说, ES的默认配置已经足够, 但如果你的使用场景正如我们经常看到的日志类系统, 请一定不要错过下面的部分.
3: 设置mlockall是提升性能的最有效手段
Linux把它的物理内存划分为很多块, 称为页
. 而Swapping
则是把内存中的一页复制到磁盘上预定义空间的过程, 以释放更多的内存页, 而磁盘上的这部分空间称为交换区
. 物理内存和交换区空间的总和称为虚拟内存空间.
交换区并不总是有利的. 因为下内存相比, 磁盘总是慢太多. 通常内存的存储速度在纳秒级, 而磁盘却要毫秒级别. 所以磁盘的访问速度比直接访问内存要慢上万倍. 使用的交换空间越多, 系统会越多, 所以要避免交换区的使用.
mlockall
参数允许设置ES节点不换出内存页(只有Linux/Unix系统才有此设置). 在你的yaml文件中, 可通过如下方式设置:
bootstrap.mlockall:true
在5.x版本中, 该参数被bootstrap.memory_lock:true
所代替.
默认mlockall
的值为false, 意味着ES节点的内存被允许换出. 设置该属性值后, 需要重启ES节点才能生效. 可以通过如下访问进行验证设置是否成功:
curl http://localhost:9200/_nodes/process?pretty
同时要注意, 如果设置了mlockall
为true, 要确认通过-DXmx或ES_HEAP_SIZE给你的ES设置了足够的堆空间.
4: 通过discovery.zen控制ES的节点发现管理
Zen discovery
是ES集群中节点发现连接的默认机制, 另外还有一些其他的发现机制, 如Azure, EC2和GCE. Zen discovery
通过discovery.zen.*
的一系列参数进行设置和控制.
在0.x和1.x版本中, 可以设置单播或多播方式, 并且多播是作为ES的默认方式使用的. 如果在这两个版本中想使用单播, 需要设置discovery.zen.ping.multicast.enable
为false.
但自从ES 2.0以来, 单播是Zen discovery
唯一可用的选择.
首先, 你需要通过discovery.zen.ping.unicast.hosts
参数指定一组用于发现和连接节点的hosts. 为了简单, 可以为集群中的每个节点设置相同的值. 我们的集群中使用了所有master节点的地址.
discovery.zen.minimum_master_nodes
参数用于控制最小的合理master节点数, 使得集群中其他节点可被发现和操作. 通常, 在多于2个节点的集群中建议该参数设置的值不小于2. 另外一种计算方式是(master节点数/2 + 1).
Data节点和master节点之前有两种检测方式:
master向所有其他节点发起ping请求, 以确认该节点是否存活
其他的节点向master节点发现ping请求,以确认master是否存活,并决定是否需要开始新一轮的选举
节点检测过程由discovery.zen.fd.ping_timeout
参数设置, 默认值为30s, 决定了一个发起ping请求的节点的响应等待时间. 如果你的集群带宽不足或网络拥堵, 则需要合理的重新调整该参数. 如果网络较差,该参数要相应的设置大一些, 值越大, 节点发现的失败的风险也就越低.
Loggly的discovery.zen参数设置如下:
discovery.zen.df.ping_timeout: 30sdiscovery.zen.mininum_master_nodes: 2discovery.zen.ping.unicast.hosts: ["esmaster01", "esmaster02", "esmaster03"]
就是说30s内需要有节点检测响应返回(通过discovery.zen.df.ping_timeout
设置的).另外,其他节点至少要检测到2个master节点(我们总共有3个master节点), 第三个参数是说我们的单播主机分别为esmaster01, esmaster02和esmaster03.
5: 提防"DELETE_all"
ES的DELETE API
允许你通过一个请求(使用通配符或_all)删除所有的索引数据, 意识到这一点非常重要.如下:
curl -XDELETE 'http://localhost:9200/*/'curl -XDELETE 'http://localhost:9200/_all'
虽然这个功能很强大,却也极为危险,特别是在生产环境. 在我们所有的集群中, 我们都通过action.destructive_requires_name:true
参数禁用了该删除功能.
这个参数由ES 1.0并入, 并替换掉了0.90版本中使用的action.disable_delete_all_indices
参数.
6: 使用Doc Values
在ES2.0及以上版本中, Doc Values
是默认使用的.而在较早的版本中则需要显示指定.Doc Values
通过少量额外的索引和磁盘开销, 提供了比在normal
字段上更高效的排序和聚合操作.本质上, Doc Values
通过把ES转变为列式存储, 进而使得ES大量的分词特性比预想的更高效.
为了深入理解Doc Values
带来的好处, 下面将把Doc Values与normal
字段进行对比.
当在normal字段上进行排序或聚合操作时, 将导致大量的fielddata cache
.因为当字段第一次被缓存时, ES需要为该字段每个可能的取值分配大量的堆空间, 并从每个文档中获取字段值数据. 因为这个过程可能需要从磁盘中读取数据, 因为将会十分耗时.一旦数据缓存完成, 以后再对该字段值的使用都会使用先前缓存的数据, 所以速度也会更快.但当有过多的字段被载入到缓存中时, 就会触发缓存淘汰机制, 一些字段将被汰出, 相应的再次使用被汰出的字段时, 就是又一次的耗时载入过程. 为了高效, 你可能想减少或降低缓存汰出, 这意味着你要减少或限制将被缓存的字段数量.
与之不同, Doc Values
使用基于磁盘的数据结构, 并映射到进程的内存空间, 从而减少对堆空间的占用, 并具有与fielddata cache
相匹敌的性能. 虽然在数据第一次从硬盘读取并载入时依然会有起始开销, 然而这一些都有操作系统的文件系统缓存进行处理, 所以只有你真正需要的数据才会被读入.
简而言之, Doc Values
减少了堆空间使用(相应的降低了GC影响), 并通过文件系统缓存减少读取的数据, 从而提高了整体性能.
7: ES shard分配相关的参数设置
shard分配就是把shard分散到不同节点的过程,可以发生在初始化备份阶段, 副本分配阶段或者节点再平衡过程中.也就是说它发生在任何节点增加或移除的过程中.
参数cluster.routing.allocation.cluster_concurrent_rebalance
决定了在shard再平衡过程中允许并发的shard数.合适的设置由你使用的硬件性能决定, 如集群节点使用的CPU核数, IO性能等.如果参数设置的不合理,将会对ES索引过程的性能带来一定的影响.
cluster.routing.allocation.cluster_concurrent_rebalance:2
ES默认设置的值为2, 也就是说在任何时候最多只允许同时移动2个shards的数据.设置一个不太高的合理数值总是值得的,虽然shard再平衡的并发数受到制约, 但却不会对索引数据造成影响.
另外一个值得一提的参数是cluster.routing.allocation.disk.threshold_enabled
.如果这个参数设置为true, 则在shard分配过程中为shard预留空闲的磁盘空间, 相反如果设置为false,则分配到该节点上的shard数据的未来增加将受到限制--没有足够的空间.
当threshold_enabled设置为true, 有两个相应的指标可以配置:low 和 high.
low 定义了当磁盘使用量超过该指标后,ES将不再为该节点分配shard, 默认值为85%
high 定义了当磁盘使用量超过该指标后,将有shard从该节点中移出, 默认值为90%
这两个参数即可按已使用量的百分比定义(例如,80%是说使用了80%的磁盘,尚有20%空闲), 也可以按最小可用磁盘空间定义(例如,20GB是说该节点尚有20GB空闲空间).
如果你有大量的小shards, ES使用的默认值可能会有些保守. 例如你有1TB的存储空间,而每个shard的大小仅为10GB, 理论上该节点能容纳100个数据shards.如果采用上面的默认设置, 在ES认为节点饱和前, 你最多只能存储80个shards.
所以为了找出一个合理设置, 你需要仔细观察在shard生命周期中的容量变化, 同时为之预留一定的安全空间.在上面的例子中, 假如有5个数据shards, 则需要确认在任何时候都要有50GB的可用空间(不是空闲空间, 是5个shards将占用的总空间).对于1TB的设备, 如果不考虑安全空间, low的值可设置为95%. 然而乘以50%的安全系数, 则需要至少预留75GB的空闲空间, 相应的合理的low值应为92.5%.
8: 合理的恢复设置帮你提升节点重启速度
ES提供了几个属性用于提高集群恢复速度,缩短节点重启时间.合理的取值依赖于你的硬件能力(硬盘和网络速度通常会是瓶颈), 所以能给出的最好的建议就是尝试,尝试,再尝试.
-
控制单个节点并发恢复数据的shard数:
cluster.routing.allocation.node_concurrent_recoveries
shard数据恢复是一个IO密集型操作, 因此需要根据实际情形设置参数.在ES 5.x版本中, 这个参数分拆成两个新的设置:
cluster.routing.allocation.node_concurrent_incoming_recoveriescluster.routing.allocation.node_concurrent_outgoing_recoveries
-
控制单个节点上初始的主shard并发数:
cluster.routing.allocation.node_initial_primaries_recoveries
-
控制一个shard恢复数据时并行打开的数据流个数:
indices.recovery.concurrent_streams
-
与数据流紧密相关的是可用的带宽限制:
indices.recovery.max_bytes_per_sec
实际使用的硬件配置直接决定了以上几个参数的最优值设置.使用SSD硬盘以及高速的(10G)以太网结构和使用机械硬盘以及1G的以太网结构将有巨大的差别.
只有在集群重启的时候,才会使用以上数据恢复相关的参数设置.
9: 合理的线程池设置避免数据丢失
为了提高节点内的线程管理, ES维护了多个线程池.
在Loggly, 我们在使用_bulk
操作来处理索引请求时发现, 设置合适的threadpool.bulk.queue_size
对避免_bulk
重试以及由其可能引起的数据丢失至关重要.
threadpool.bulk.queue_size: 5000
上面的参数用于设置bulk的队列长度, 即当节点的每个shard在处理bulk请求时, 如果无可用线程时, 能为每个shard排队等待的请求数量.这个参数应该与你的bulk请求负载相匹配, 如果设置过小, 而bulk请求较多时, ES会返回一个RemoteTransportException
.
如上所述, bulk请求涉及到的每个shard的数据都在这个队列里, 因此合理的设置是bulk的并发数乘以这些请求相关的shard数. 比如说: 单个bulk请求包含的数据分布在10个shards上, 即便只有一次bulk请求, 你也要有一个至少长度为10的队列设置.话说回来, 如果设置过大
则会消耗更多的JVM堆空间(同时意味你正提交的数据量可能超出你实际能索引的能力), 然而却能减化你的客户端处理逻辑.
要么设置一个相对合适的较大的队列值, 要么在你的代码中合理的处理掉RemoteTransportException
. 如果处理不当, 则会面临数据丢失的风险. 下面的异常模仿的是队列长度为10,而并发发起了超过10个bulk请求的场景:
RemoteTransportException[[][inet[/192.168.76.1:9300]][bulk/shard]]; nested: EsRejectedExecutionException[rejected execution (queue capacity 10) on org.elasticsearch.action.support.replication.TransportShardReplicationOperationAction$AsyncShardOperationAction$1@13fe9be];