ELK 性能(4) — 大规模 Elasticsearch 集群性能的最佳实践
介绍
集群规模
集群数:6
整体集群规模:
300 Elasticsearch 实例
141 物理服务器
4200 CPU 核心
38TB RAM
1.5 Pb 存储
索引日志:
内容
开场白
健康提示
将 Elasticsearch 集群的名称 “elasticsearch” 进行重命名。当网络内有两个以上的集群时,就会发现这样做所带来的好处。
为了防止误删除,设置参数
action.destructive_requires_name=true
始终使用 SSD 。这并不是可选的。
需要至少 10G 的带宽。
采用监护人制度,开发并发布自己的版本。
扩展
扩展 Elasticsearch 集群
影响到 Elasticsearch 集群的因素
影响到集群内存的因素
段内存(segment memory):~4b RAM/文档 = ~4Gb/10亿行日志
字段数据内存(field data memory):几乎与段内存相当
过滤器缓存(filter cache):~1/4 到 1/2 的段内存,取决于搜索的内容
剩下的所有(50% 的系统内存)用作操作系统文件的缓存
无法获得足够的内存
影响到集群I/O的因素
SSD 持续写速率
计算片恢复的速度(假设一个节点失败):
片大小(Shard>
(每个节点上分片的数量 * 片大小)/ (磁盘写速度 / 节点分片的数量)
例如:30Gb 分片,每个节点 2 个分片,250Mbps 的写速度:
(2 * 30Gb)/ 125Mbps = 8 mintues
恢复弹性所能忍受的时间
可以忍受失去多少节点
一台服务器多个节点会增加恢复所需的时间
影响到网络的因素
10G 至少
10 分钟恢复 vs 50+ 分钟恢复
1G 瓶颈:网络上线
10G 瓶颈:磁盘速度
扩展 Logstash 集群
扩展 Logstash 的 CPU
规则 1:买所能承受的尽可能快的 CPU 核心
规则 2:参见第一条
更多的过滤 = 更多的 CPU
监控
Marvel
自研
易用
需要花时间开发
数据存入 ES
与自己的系统集成
很多分析度量
没有集成
重复造轮子
成本高
免费
监控 Elasticsearch
度量在多个地方都有暴露:
_cat API
包括了大多数度量,易读
_stats API,_nodes API
涵盖所有,JSON格式,易于解析
发送到 Graphite
创建 dashboards
监控系统
SSD 性能
监控 Logstash 报管道阻塞的频率,并找出原因
动态的磁盘空间阀值
((服务器的数量 - 失败的数量)/ 服务器的数量)- 15%
100 服务器
最多允许 6 个失败
磁盘空间预警的阀值 =((100 - 6)/ 100)- 15%
磁盘空间预警的阀值 = 79%
根据集群增加与移除节点的数量配置并管理系统
额外的 15% 是用来提供申请并准备更多节点的时间
扩展 Logstash
影响 Logstash 性能的因素
日志行的长度
Grok 模式的复杂度 - 正则表达式非常慢
插件的使用
GC
超线程
重复测量
将日志以 JSON 格式输出并没有带来很大的好处,除非不使用 grok,kv 等。Logstash 还是需要将字符串转换成为 ruby 的 hash
GC 垃圾回收
缺省配置通常是可以的
确保记录了 GC 的图
Ruby 会很容易的创建很多对象:在做伸缩扩展时需要监控 GC
在写插件时需要时刻记住 GC
不好的:1_000_000.times { "This is a string" }
user
time
0.130000
好用法:foo = 'This is a string'; 1_000_000.times { foo }
user
time
0.060000
插件性能基准
如何建立基准
度量某些过滤器
度量更多的过滤器
计算每个过滤器的成本
社区提供的过滤器只是在大多数情况下适用
在测评时执行至少 5 分钟的时间,使用大数据集
建立基准的吞吐量:Python,StatsD,Graphite
Logstash 简单配置,10m 行 apache 日志,没有过滤:
input { file {
path => "/var/log/httpd/access.log"
start_position => "beginning"
}
}
output {
stdout { codec => "dots" }
}
Python 脚本将 Logstash 输出到 statsd :
sudo pip install statsd
#!/usr/bin/env python
import statsd, sys
c = statsd.StatsClient('localhost', 8125)
while True:
sys.stdin.read(1)
c.incr('logstash.testing.throughput', rate=0.001)
为什么我们不用 statsd 输出插件?它会降低输出的速度!
放在一起
logstash -f logstash.conf | pv -W | python throughput.py

插件性能 Grok
增加一个简单的 Grok
grok { match => [ "message", "%{ETSY_APACHE_ACCESS}" ] }
在只有一个 worker 时,性能下降 80%
增加 worker 的数量,吞吐量仍然下降了 33%:65k/s -> 42k/s
-w <num_cpu_cores>
插件性能 kv
加一个 kv 过滤器
kv { field_split => "&" source => "qs" target => "foo" }
吞吐量基本不变,有 10% 的下降(40k/s)
吞吐量变化较大主要因为 GC 的压力
kv 很慢,以下是一个用来查询字符串的 splitkv 插件
kvarray = text.split(@field_split).map { |afield| pairs = afield.split(@value_split)
if pairs[0].nil? || !(pairs[0] =~ /^[0-9]/).nil? || pairs[1].nil? ||
(pairs[0].length < @min_key_length && !@preserve_keys.include?(pairs[0]))
next
end
if !@trimkey.nil?
# 2 if's are faster (0.26s) than gsub (0.33s)
#pairs[0] = pairs[0].slice(1..-1) if pairs[0].start_with?(@trimkey)
#pairs[0].chop! if pairs[0].end_with?(@trimkey)
# BUT! in-place tr is 6% faster than 2 if's (0.52s vs 0.55s)
pairs[0].tr!(@trimkey, '') if pairs[0].start_with?(@trimkey)
end
if !@trimval.nil?
pairs[1].tr!(@trimval, '') if pairs[1].start_with?(@trimval)
end
pairs
}
kvarray.delete_if { |x| x == nil }
return Hash[kvarray]
splitkv 之前的 CPU 占用率是 100% ,之后的占用率是 33% 。
Elasticsearch 的输出
Logstash 的输出设置直接影响了 Logstash 所在机器的 CPU
将 flush_size 从 500 改到 5000 ,或更多
将>
增加输出线程 workers
结果受日志行的影响
当使用缺省的 500 flush_size 时,Logstash 集群的峰值会达到 50% ,处理能力在每秒 ~40k 日志行。将这个值改到 10k 时,同时增加>
Pipeline 管道性能
Logstash 2.3 之前
…/vendor/…/lib/logstash/pipeline.rb
SizedQueue.new(20)
->>
Logstash 2.3 之后
—pipeline-batch-size=500
最好在调优最后改变这个参数。管道的性能受输出插件性能的影响。
测试配置变更
增加上下文
发现管道的延迟
mutate { add_field => [ "index_time", "%{+YYYY-MM-dd HH:mm:ss Z}" ]
}
logstash 服务器处理日志行
mutate { add_field => [ "logstash_host", "<%= node[:fqdn] %>" ]
}
对日志行进行哈希,实现重放
hashid 插件可以避免重复行
~10% 下降
服务器上的配置
describe package('logstash'), :if => os[:family] == 'redhat' do
it { should be_installed }
end
describe command('chef-client') do
its(:exit_status) { should eq 0 }
end
describe command('logstash -t -f ls.conf.test') do
its(:exit_status) { should eq 0 }
end
describe command('logstash -f ls.conf.test') do
its(:stdout) { should_not match(/parse_fail/) }
end
describe command('restart logstash') do
its(:exit_status) { should eq 0 }
end
describe command('sleep 15') do
its(:exit_status) { should eq 0 }
end
describe service('logstash'),
:if => os[:family] == 'redhat' do
it { should be_enabled }
it { should be_running }
end
describe port(5555) do
it { should be_listening }
end
Input
input { generator {
lines => [ '<Apache access log>' ]
count => 1
type => "access_log"
}
generator {
lines => [ '<Application log>' ]
count => 1
type => "app_log"
}
}
Filter
filter { if [type] == "access_log" {
grok {
match => [ "message", "%{APACHE_ACCESS}" ]
tag_on_failure => [ "parse_fail_access_log" ]
}
}
if [type] == "app_log" {
grok {
match => [ "message", "%{APACHE_INFO}" ]
tag_on_failure => [ "parse_fail_app_log" ]
}
}
}
Output
output { stdout {
codec => json_lines
}
}
小结
更快的 CPU
CPU 核心数 > CPU 时钟速度
增加管道的大小
更多内存
18Gb+ 防止频繁 GC
横向扩展
为日志行添加上下文
编写自己的插件
对所有的东西进行性能评测
扩展 Elasticsearch
默认基准
Logstash 输出: 默认选项 + 4 workers
Elasticsearch: 默认选项 + 1 shard, no replicas
影响索引的因素
日志行的长度与分析,默认映射
doc_values - 必须
使用更多的 CPU 时间
索引时使用更多的磁盘空间,磁盘 I/O
有助于降低内存的使用
如果发现 fielddata 使用过多内存,定位占用最多的,然后将它们移到 doc_values
为恢复保留足够的带宽
CPU
分析
映射
默认映射会创建大量 .raw 字段
doc_values
合并
恢复
内存
索引的缓冲
GC
段(segment)数量和未优化的索引
网络
影响内存的因素
以 32Gb 堆为例的分布情况:
Field data: 10%
Filter cache: 10%
Index buffer: 500Mb
Segment cache (~4 bytes per doc):
每个节点可存储的文档数
32Gb - ( 32G / 10 ) - ( 32G / 10 ) - 500Mb = ~25Gb (段内存)
25Gb / 4b = 6.7bn 个文档(所有片的总和)
10bn docs / day, 200 shards = 50m docs/shard
1 daily shard per node: 6.7bn / 50m / 1 = 134 days
5 daily shards per node: 6.7bn / 50m / 5 = 26 days
Doc Values
Doc values 可以降低内存开销
Doc values 会消耗 CPU 和存储
部分字段使用 doc_values:
1.7G Aug 11 18:42 logstash-2015.08.07/7/index/_1i4v_Lucene410_0.dvd
所有字段使用 doc_values:
106G Aug 13 20:33 logstash-2015.08.12/38/index/_2a9p_Lucene410_0.dvd
不要盲目地为所有字段开启 doc_values
找到使用最频繁的字段,然后将它们转换成 Doc Values
curl -s 'http://localhost:9200/_cat/fielddata?v' | less -S
示例
total
request_uri
_size
owner
ip_address
117.1mb
11.2mb
28.4mb
8.6mb
4.3mb
96.3mb
7.7mb
19.7mb
9.1mb
4.4mb
93.7mb
7mb
18.4mb
8.8mb
4.1mb
139.1mb
11.2mb
27.7mb
13.5mb
6.6mb
96.8mb
7.8mb
19.1mb
8.8mb
4.4mb
145.9mb
11.5mb
28.6mb
13.4mb
6.7mb
95mb
7mb
18.9mb
8.7mb
5.3mb
122mb
11.8mb
28.4mb
8.9mb
5.7mb
97.7mb
6.8mb
19.2mb
8.9mb
4.8mb
88.9mb
7.6mb
18.2mb
8.4mb
4.6mb
96.5mb
7.7mb
18.3mb
8.8mb
4.7mb
147.4mb
11.6mb
27.9mb
13.2mb
8.8mb
146.7mb
10mb
28.7mb
13.6mb
7.2mb
内存小结
实例使用 128Gb 或 256Gb RAM
根据硬件配置优化 RAM
Haswell/Skylake Xeon CPUs 有 4 个内存通道
Elasticsearch 多个实例
为每个实例分配自己的名称 node.name
CPU
CPU 密集型操作
写压力
CPU 核心数受并发的索引操作影响
核心数 优于 CPU 频率值
基准
为什么这么慢?
[logstash-2016.06.15][0] stop throttling indexing: numMergesInFlight=4, maxNumMerges=5
合并
第一步:将分片数从 1 提升到 5
第二步:禁用 merge throttling(ES < 2.0)
index.store.throttle.type: none
拆分 Hosts
当 CPU 接近最大时,需要加入更多节点
在不同 Hosts 上运行 Elasticsearch 以及 Logstash
吞吐量有 50% 的提升:13k/s -> 19k/s
超线程 Hyperthreading
超线程可以提升 20% 的性能
CPU 治理
~15-30% 的性能提升。
# echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
存储
磁盘 I/O
建议
更多的建议
好的 SSD 非常重要
廉价 SSD 会大大降低性能
不要使用多个数据路径,使用 RAID 0
大量的 translog 写磁盘操作会是瓶颈
如果有大量段合并,但是 CPU 和 磁盘 I/O 还有空闲:
可以尝试提升值
index.merge.scheduler.max_thread_count
降低间隔(Durability)
index.translog.durability: async
Translog fsync() 值为 5s ,足够
集群的恢复会吃掉大量磁盘 I/O
需要在恢复前后调整相应的参数
indices.recovery.max_bytes_per_sec: 300mb
cluster.routing.allocation.cluster_concurrent_rebalance: 24
cluster.routing.allocation.node_concurrent_recoveries: 2
任何的持续 I/O 等待都意味着存在一个次优状态
SSD 的选择
基准
降低间隔后,基本仍然维持在 ~20-25k,但更平滑
为什么提升很小?Merging
$ curl -s 'http://localhost:9200/_nodes/hot_threads?threads=10' | grep % 73.6% (367.8ms out of 500ms) 'elasticsearch[es][bulk][T#25]'
66.8% (334.1ms out of 500ms) 'elasticsearch[es][[logstash][1]: Lucene Merge Thread #139]'
66.3% (331.6ms out of 500ms) 'elasticsearch[es][[logstash][3]: Lucene Merge Thread #183]'
66.1% (330.7ms out of 500ms) 'elasticsearch[es][[logstash][1]: Lucene Merge Thread #140]'
66.1% (330.4ms out of 500ms) 'elasticsearch[es][[logstash][4]: Lucene Merge Thread #158]'
62.9% (314.7ms out of 500ms) 'elasticsearch[es][[logstash][3]: Lucene Merge Thread #189]'
62.4% (312.2ms out of 500ms) 'elasticsearch[es][[logstash][2]: Lucene Merge Thread #160]'
61.8% (309.2ms out of 500ms) 'elasticsearch[es][[logstash][1]: Lucene Merge Thread #115]'
57.6% (287.7ms out of 500ms) 'elasticsearch[es][[logstash][0]: Lucene Merge Thread #155]'
55.6% (277.9ms out of 500ms) 'elasticsearch[es][[logstash][2]: Lucene Merge Thread #161]'
分层存储
将更多访问的索引放在更多的服务器上,并分配更多的内存以及更快的 CPU
将 “冷” 索引独立存储(SSD下仍然需要这么做)
设置 index.codec: best_compression
移动索引,重新优化
构建 elasticsearch-curator 可以让事情变得简单
为什么默认的配置 Merging 如此多?
$ curl 'http://localhost:9200/_template/logstash?pretty'
看到了吗?
"string_fields" : { "mapping" : {
"index" : "analyzed", // <--- see?
"omit_norms" : true,
"type" : "string",
"fields" : {
"raw" : {
"ignore_above" : 256, // <--- see?
"index" : "not_analyzed", // <--- see?
"type" : "string" // <--- see?
}
}
},
"match_mapping_type" : "string",
"match" : "*"
}
使用自定义映射
"string_fields" : { "mapping" : {
"index" : "not_analyzed",
"omit_norms" : true,
"type" : "string"
},
"match_mapping_type" : "string",
"match" : "*"
}
有那么一点帮助
索引的性能
增加 bulk 线程池可以控制索引的爆发
但同时也要注意,这会隐藏性能的问题
增加索引的缓冲
增加刷新的时间,1s 到 5s
将索引请求发送到多个 hosts
增加 worker 直到没有明显的性能提升为止
num_cpu / 2
增加 flush_size 知道没有明显的性能提升为止
10,000
磁盘 I/O 性能
索引协议
Transport 仍然是性能最好的,但是 HTTP 已经非常接近了
Node 基本上不会使用
自定义映射模板
默认模板为每个字段额外生成 not_analyzed.raw 字段
分析每个字段会占用 CPU
额外的字段会吃掉更多磁盘空间
动态字段和 Hungarian 标记
使用开启了动态字段的自定义映射模板,但是将它们设置为 non_analyzed 剔除 .raw 字段,除非真的需要它。
这可以将 Elasticsearch 集群的 CPU 的使用率从 28% 降到 15%
消息的复杂度也十分相关
加 20k 的新行与平均 1.5k 的索引速率
截断
ruby { code => "if event['message'].length > 10240 then
event['message'] = event['message'].slice!(0,10240)
end"
}
让 Logstash 做更多的事情
索引的大小
按索引来调优分片
num_shards = (num_nodes - failed_node_limit) / (number_of_replicas + 1)
50 个节点,并允许最多 4 个节点失败,replication 为 1x:
num_shards = (50 - 4) / (1 + 1) = 23
如果分片大于 25Gb ,需要相应增加分片数
调优 indices.memory.index_buffer_size
index_buffer_size = num_active_shards * 500Mb
其中“active_shards”:指任何 5 分钟内更新的分片
调试 refresh_interval
默认 1s - 过于频繁
增加到 5s
更高的值会导致磁盘抖动
目标:将磁盘里的缓冲尽可能的移储
例如:Samsung SM863 SSDs
DRAM buffer: 1Gb
Flush speed: 500Mb/sec
参考
参考来源:
2016.6 ELK: Moose-ively scaling your log system
结束
运维网声明
1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网 享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com