背景
过年前,寂寞哥给我三台机器,说搞个新的openTSDB集群。机器硬件是8核16G内存、3个146G磁盘做数据盘。
我说这太抠了,寂寞哥说之前的TSDB集群运行了两年,4台同样配置的机器,目前hdfs才用了40%,所以前期先用着这三台机器,不够再加。
于是我只好默默地搭好了CDH5、openTSDB(2.1版本,请注意此版本号)、bosun,并在20台左右的机器上部署了scollector用来测试,然后将dfs.replication
改为了2,一切正常。
过完年回来后,开始批量在主要业务机器上部署scollector,大概增加到了160台左右。运行了一个星期后,正在添加各种grafana dashboard的时候,就开始发现各种异常了。
这篇文章主要是总结了处理openTSDB各种状况的过程。需要注意的是,这是个持续的过程,中间修改了非常多的参数,有些问题是做了某项修改后,有很明显的改善,而有些问题的解决其实回头看的时候并不能知道究竟是哪些修改解决了问题,也还没有时间重新去修改验证。因此,本文并不能作为一份解决问题的FAQ文档,纯当本人处理问题的记录罢了。
打开文件数
首先是发现了无法打开master:4242
页面了,查看了openTSDB的日志,很显眼的too many open file
,于是检查了ulimit
,还是默认的1024,这肯定不够用的。需要修改系统的资源限制:
首先是/etc/security/limits.conf
# 添加
* soft nofile 102400
* hard nofile 102400
然后是/etc/profile
# 文件末尾添加
ulimit -HSn 102400
重新登录,重启openTSDB,然后检查进程的打开文件数:
# cat /proc/$(ps -ef | /bin/grep 'opentsd[b]' | awk '{print $2}')/limits | /bin/grep 'open files'
Max open files 102400 102400 files
修改已生效,打开文件数的问题解决了。
内核参数
接着没过1小时,就出现了查询非常久都出不来的情况,于是就注意观察openTSDB的打开文件数,然而只有1500左右,怀疑可能是并发数和backlog的问题,检查了连接数以及当前的内核参数,发现此时连接数并没有超过内核设置。不过本着尽量排除影响的原则,还是改大了这两个内核参数:
net.ipv4.tcp_max_syn_backlog = 16384
net.core.somaxconn = 65535
没有什么新的线索的情况下,只好又重启了TSDB。重启后又恢复正常了。于是用watch命令持续盯着openTSDB的连接数,一边用grafana每30秒刷新页面。发现正常情况时,连接数大概是900以下,而出现问题时(grafana刷不出来图时),连接数突然就上升到1200以上,然后1分钟内蹦到了2900。赶紧使用netstat看下是什么状态的连接数上涨了,发现是TIME-WAIT。检查net.ipv4.tcp_tw_reuse
已经是1了,然后这天由于有别的事情要忙,就暂时没再看了。
regionserver java堆栈大小
第二天早上的时候发现又是grafana刷新不出图,后来上CDH管理页面才发现是其中有一个regionserver(tsdb3)挂了:经大数据组大神提醒,多半是GC导致regionserver挂了。查看果然有个小时级别的GC:查看了HBase Regionserver java堆栈大小,原来才设置为2.44G。请教了大神,说是因为我部署的这个CDH集群,除了HBase之外还有其他的其实对于openTSDB没有用的Hive、Hue、Oozie、Sqoop2等,所以CDH会根据角色的情况分配java堆栈大小,但是我们完全可以将这些没有用到的服务关掉,手动将HBase Regionserver java堆栈大小设置为物理内存的一半,也就是8G。
改完配置重启了HBase,貌似一切又正常了。而且,由于有更重要的问题要处理,因此就先暂时放一放了。
HBase表压缩
那件更重要的事情,就是磁盘空间用得太快了。才160台机器一星期的数据,磁盘空间就才90%下降到80%,我一开始觉得不可能,之前的旧集群,用了两年才用了40%,新的怎么一星期就用了10%呢,这么下去难道只能用10个星期?后来看了下CDH的图,每秒datanode写入都有1M左右,146G的硬盘前途堪忧啊!原来是旧的tcollector是自己写的收集脚本,一台机器收集的metric平均才30来个,而scollector不算自己写的外部脚本,本身就提供了上百个metric,再加上自己写的外部收集脚本,这么下来两者的数据根本就不是一个数量级了。
想起之前在初始化TSDB的HBase的时候,没有采用压缩:
env COMPRESSION=NONE HBASE_HOME=/usr/lib/hbase /usr/share/opentsdb/tools/create_table.sh
不知道现在还能不能开启表压缩,赶紧去请教大神。大神甩给我一个文档:
# hbase shell # 查看表是否启用压缩 # describe "tsdb" # 暂停对外服务,将指定的HBase表disable # disable "tsdb" # 更改压缩类型 # alter 'tsdb', NAME => 't', COMPRESSION => 'gz' # 重新启用表 # enable "tsdb" # 确认是否开启了压缩 # describe "tsdb" # 使压缩在全站生效 # major_compact "tsdb" # disable "tsdb" # 更改压缩类型 # alter 'tsdb', NAME => 't', COMPRESSION => 'NONE' # 重新启用表 # enable "tsdb" # 确认是否开启了压缩 # describe "tsdb" # 使压缩在全站生效 # major_compact "tsdb"
需要注意的是,千万不要在表繁忙期间执行大合并操作
我说,用gz
压缩是不是对性能影响大啊,不是很多地方都在用snappy
吗?大神解释说,gz
是压缩比最高的,只对CPU资源有所损耗;按你这个集群的情况,CPU还有负载,都还是比较闲的,加上磁盘资源又这么紧张,最好还是用gz
吧,如果有影响,再改为snappy
呗。
我想也是,于是就开启了表压缩,磁盘空间问题解决了:从上图可以很明显地看到开启压缩前后的对比。
HBase memstore flush and store files compaction
解决完压缩问题后,回头来再看openTSDB查询偶尔没有及时响应的问题。通过查看bosun的Errors页面,经常会出现net/http: request canceled
的报错,这是因为我设置了bosun每分钟检查一次某些metric作为报警的来源,这些报错是因为读取数据时候超时了,那么应该重点看下openTSDB为何反应慢。看了下TSDB的日志,并没有发现什么异常,于是把目光集中在了HBase上。
Google了一下tsdb hbase performance
,发现了这篇文章,里面的这张图(以下简称为cycle图)总结得很好:
那么HBase反应慢,应该是这两种情况:
- 1->8->9
- 1->2->3->4->5
可以看到memstore flush
很明显在这张图中处于一个核心地位,那么减少memstore flush
是否可以改善呢?于是将hbase.hregion.memstore.flush.size从默认的128M改大为1G。
CDH中的hbase.hregion.memstore.flush.size作用解释如下:如memstore大小超过此值(字节数),Memstore将刷新到磁盘。通过运行由hbase.server.thread.wakefrequency指定的频率的线程检查此值。
但是这样修改后有个很严重的副作用:GC时间更长了(箭头指向为修改前后的比较):后来就把这个参数恢复为128M这个默认值了。
至于compaction方面的参数,看着解释貌似不好修改,于是就没有改了:
HBase GC 调优
再来看下GC的时间规律,发现都是集中在一个小时的0分左右。查看了scollector自带的hbase.region.gc.CollectionTime
这个指标值,确实在一个小时的0分左右就有一个GC的高峰:(红线为ParNew,而蓝线为ConcurrentMarkSweep)大神一看,说每小时开始的时候集群网络IO会飙高,问我TSDB这时候在干什么。于是我在发生问题的时候用iftop -NP
检查网络IO,发现基本上都是60020和50010端口之间的流量:看不出来openTSDB在做什么,之前用的TSDB都不用怎么改配置直接用默认值的。
大神沉思下,说我还有办法可以优化下GC时间,优化调整的目的应该是削平GC的波峰,让整个系统的正常服务时间最大化。 大神在HBase RegionServer 的 Java 配置选项
加上以下参数:
-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/data/logs/gc.log
接着修改了以下HBase参数:
- HBase Server 线程唤醒步骤 (hbase.server.thread.wakefrequency):该值决定了Hbase Memstore刷新的检测频率,该值默认值为10s,在数据高峰时,每秒写入的数据达到20M左右,调整该值到5s,来帮助尽量使得MemStore的值不超过128M。
- RegionServer 中所有 Memstore 的最大大小 (hbase.regionserver.global.memstore.upperLimit):该值是小数形式的百分比,默认为0.4,该值与Hfile缓存块大小的总和不能超过0.8,不然会造成HBase启动失败,其目的是为了在Memstore占用的内存达到Java堆栈的该百分比时强制执行刷新数据到磁盘的操作,这里我们将Memstore的百分比设置为0.5,目的是为了尽量避免强制刷新。还有一个最小大小的值(hbase.regionserver.global.memstore.lowerLimit)为0.38,表示要刷新到0.38才结束刷新,未做修改,后续可以调整。
- HFile 块缓存大小 (hfile.block.cache.size): 该值默认值为0.4,调整为0表示Hbase的磁盘写入不使用内存缓存,测试发现调整为0后性能有一定的退化,尤其是在数据刷新操作的过程中消耗的时间有所上升,这里我们把该值调整为0.3(因为hbase.regionserver.global.memstore.upperLimit已改为了0.5)。
- HStore 阻塞存储文件 (hbase.hstore.blockingStoreFiles):该值默认为10,如在任意 HStore 中有超过此数量的 HStoreFiles,则会阻止对此 HRegion 的更新,直到完成压缩或直到超过为 'hbase.hstore.blockingWaitTime' 指定的值。将该值改大到100,就是为了减少cycle图中的第9步。
然后根据GC打印日志的分析,还修改了HBase RegionServer的Java配置选项:
默认情况下Hbase在新代中采用的GC方式是UseParNewGC,在老代中采用的GC方式为UseConcMarkSweepGC,这两种GC方法都支持多线程的方法,CMS的GC耗费的时间比新代中的GC长,同时如果内存占满还会触发Full GC,我们的优化方向是让GC尽量在新代中进行,通过GC日志发现新代的内存大小只有600M,而总的Java堆栈大小为8G,官方的推荐是新代内存占用为总堆栈的3/8,于是在这里增加参数-Xmn3000m,来扩大新代的大小。
经过大神的一番调优后,可以明显看到GC时间明显下降了:
文件系统读写优化
考虑到是写入数据太多,查看系统的磁盘所有写所消耗的时间确实都比较高,尝试了以下优化方案:(以sdb为例)
- ext4挂载参数:
mount -o remount,noatime,nodelalloc,barrier=0 /dev/sdb1
- 调度电梯算法改为deadline
echo "deadline" > /sys/block/sdb/queue/scheduler
- 开启文件系统的预读缓存
blockdev --setra 32768 /dev/sdb
经过一番修改后,linux.disk.msec_write
(Total number of ms spent by all writes) 这个指标值大幅下降,但不清楚是GC调优还是文件系统读写优化的效果,但根据经验,ext4性能相比ext3是有所回退的,这么改动应该是有效果的,但barrier=0
这个挂载参数需谨慎使用。
增加机器/hotspotting
虽然GC时间下降了,但是还是在挺高的数值上,而且bosun的Errors还是偶尔出现,为此我增加了bosun的Errors监控指标,通过bosun的api,专门监控bosun出现request canceled
的情况:可以看到出现此类错误的时候,基本都是在每个小时的0分附近。
反反复复试过了很多HBase调优之后,依然有偶尔出现request canceled
的问题,且每到整点左右时,用grafana查询也是要1到2分钟才将一个dashboard刷新完,就是说1/30的时间是不可服务的。
既然之前搜到的那篇文章中有提到最好的方式是增加机器了,刚好旧的TSDB集群刚好也压缩过了,用两个regionserver就可以了,于是将剩下的第三台机器加到了新的TSDB集群中来。
新的regioserver加进来后,发现情况还是不是太好,依旧是每到一小时的0分附近就是各种slow response。查看了master:60010
页面,各regionserver之间的request分配并不平均,通常是新加入的regionserver只是旧的两台的30%不到,这可能会导致大量的写入集中在两台旧的regionserver上,因而造成slow response。所以先尝试手动平衡下region。
比如从master:60010
上看数据,发现这个region的request比较多,因此决定将它手动迁移到新的tsdb4(tsdb4.domain.com,60020,1457011734982)
tsdb,x00x00xF5VxA6x9AxE0x00x00x01x00x00xDAx00x00x13x00x00cx00x00x15x00x00xDE,1454242184814.29987604fab49d4fd4a0313c6cf3b1b6.
操作如下:
# hbase shell
move "29987604fab49d4fd4a0313c6cf3b1b6" "tsdb4.domain.com,60020,1457011734982"
balance_switch false
记得关闭balance,否则过5分钟,被移动的region又自动回来了
可以看到修改完后,3个regionserver的writeRequestCount比之前平均多了:
增加内存
每到整点就是各种slow response的问题依然存在。没办法之下只好给regionserver增加内存进行测试了。将3台regionserver的内存都增加到了32G,然后将java堆栈大小改为16G。
其他优化
另外看到bosun的Errors里还出现了10000 RPCs waiting on ... to come back online
,怀疑可能是处理程序计数不足,于是调大了这两个参数:
最终解?TSDB compaction
各种方法都试过的情况下,还是出现这个“每到整点左右就是各种slow response的问题”。
既然系统、hadoop、HBase方面都调整过了,最后只能找openTSDB和bosun下手了。看到scollector已经提供了相当多的tsd指标值,于是增加了一个TSDB的grafana dashboard把所有的相关指标值都显示出来,看看每个小时0分的时候是否有些蛛丝马迹可循。
最明显的一个指标值就是tsd.hbase.rpcs
,每到整点的时候才出现这些delete
、get
操作,平时都是0,那么100%可以确定这些操作是有关联的:
直接上Google搜tsdb delete
,结果都是问怎么从TSDB中删除数据的。
回过头来再看其他的指标值,发现tsd.compaction.count
也是在整点的时候特别高:需要注意的是TSDB的compaction和HBase的compaction其实不是同一个概念。
然后用tsdb compaction
作为关键词一搜,出来了官网的文档,仔细看下这一段:
是不是很符合我们的情况,确实是compaction在前而delete在后。原来是TSDB在每小时整点的时候将上个小时的数据读出来(get
),然后compact成一个row,写入(put
)到HBase,然后删除(delete
)原始数据,所以我们看整点时的RPC请求,这些操作都会出现个凸起。
接着在这个链接(貌似都是小集群容易出现这个问题)里看到开发者的讨论:
于是看下openTSDB的配置文档,果然发现了相关配置:都是在openTSDB-2.2才新增的配置,赶紧升级了openTSDB。
那么将tsdb的compaction给关闭了有什么副作用呢?这个链接中开发者解释了有两点坏处:
- 磁盘使用率会增加5到10倍;
- 读操作会变慢。
这两点显然都是不可取的,而采用修改compaction间隔等效果并不明显,最后采用了tsd.storage.enable_appends = true
的方式,终于将此问题解决了:
看下修改前后的性能比较:GC时间下降明显。
memstore变化平缓多了。
slow put 也不再出现了。
在整点的时候经常出现的request cancle
不再出现了,整点时使用grafana也不会出现转圈圈到timeout的情况了。
不过值得注意的是,磁盘容量也有所下降(bosun图的时间是UTC时区):
经验总结
整理信息的时候偶然Google发现了这篇2014年的文章,其实是一样的问题,这次虽然经历了很多波折,但是也是一个难得的学习机会。虽然作为运维,我们能接触到大量优秀的开源产品,但有多少是能仔细看完一个开源产品的文档的?一个软件,不同的情景下面就有不同的表现,不能一概而论。直到遇到坑才去填,明显是本末倒置的行为,以后要戒掉这种浮躁的心态,同一份配置不要以为之前没有问题就能一直用。