安装NTP服务保证hbase集群机器时间时刻同步,最少不要大于30秒(hbase机器间时差默认值),因为hbase的表里默认列timestamp都需要用到机器时间,而作为分布式列式数据库,机器间时间统一很重要。
3.1.4 交换区
建议将 /proc/sys/vm/swappiness 设置为最大值 10或者0。默认值为 60。使用 sysctl 命令在运行时更改该设置并编辑 /etc/sysctl.conf,以在重启后保存该设置。
#vi /etc/sysctl.conf
vm.swappiness = 10
3.1.5 禁用透明大页面压缩
默认启用透明大页面压缩,可能会导致重大性能问题。请运行“echo never > /sys/kernel/mm/transparent_hugepage/defrag”以禁用此设置,然后将同一命令添加到 /etc/rc.local 等初始脚本中,以便在系统重启时予以设置。
#vi /etc/rc.local
echo never > /sys/kernel/mm/transparent_hugepage/defrag
3.2 网络
网络设备最低选择千兆网卡,最好万兆网卡;
性能好的交换机;
跨机房多机架部署Hadoop集群;
双电源确保断电故障。
3.3 java
3.3.1 JDK版本
JDK版本,首先要看hadoop对JDK版本要求,在hadoop2.7.1里要求最少JDK1.6+;
hbase里没明确说明,至少也是JDK1.6+;
目前多半是建议JDK1.8;
3.3.2 JVM参数调整
hbase基于HDFS之上,所以首先得优化HDFS内存,而HDFS里namenode节点内存直接决定你HDFS里最多文件个数,datanode里内存也相应要调整,最后是优化GC,在hadoop-env.sh里配置HADOOP_NAMENODE_OPTS和HADOOP_DATANODE_OPTS的内存和GC如下:
export HADOOP_NAMENODE_OPTS="-Xmx5g -Xms5g -Xmn256M -XX:SurvivorRatio=1 -XX:PermSize=128M -XX:MaxPermSize=128M -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false -XX:MaxTenuringThreshold=15 -XX:+CMSParallelRemarkEnabled -XX:+UseFastAccessorMethods -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -Xloggc:/home/hadoop/hadoop-2.7.1/logs/gc-hadoop-namenode.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M"
export HADOOP_DATANODE_OPTS="-Xmx5g -Xms5g -Xmn256M -XX:SurvivorRatio=1 -XX:PermSize=128M -XX:MaxPermSize=128M -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false -XX:MaxTenuringThreshold=15 -XX:+CMSParallelRemarkEnabled -XX:+UseFastAccessorMethods -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -Xloggc:/home/hadoop/hadoop-2.7.1/logs/gc-hadoop-datanode.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M"
3.4 hadoop优化
3.4.1 hadoop的HADOOP_NAMENODE_OPTS和HADOOP_DATANODE_OPTS优化
上面3.3.2里已经涉及
3.4.2 hadoop的HA
hbase是基于HDFS的,所以hadoop的尽量做hadoop HA部署,保证两个namenode(一个active 一个standby),保证HDFS故障后自动切换,高可用
3.4.3 hadoop的HDFS并发处理能力
hbase其实是HDFS的客户端,hbase的数据最终要落地到HDFS,所以并发处理HDFS的能力必须提升,下面的配置你必须优化:
hadoop的core-site.xml的io.file.buffer.size
hadoop的core-site.xml的io.compression.codecs
hadoop的hdfs-site.xml的dfs.namenode.handler.count
hadoop的hdfs-site.xml的dfs.datanode.handler.count
hadoop的hdfs-site.xml的dfs.datanode.max.transfer.threads
hadoop的hdfs-site.xml的dfs.datanode.balance.bandwidthPerSec
io.file.buffer.size
131072
io.compression.codecs
org.apache.hadoop.io.compress.SnappyCodec
dfs.namenode.handler.count
600
The number of server threads for the namenode.
dfs.datanode.handler.count
600
dfs.datanode.max.transfer.threads
409600
dfs.datanode.balance.bandwidthPerSec
52428800
3.5 zookeeper
zookeeper至少3台,其次最好奇数台,奇数是由zookeeper的少数服从多数的选举机制决定;
zookeeper.session.timeout默认为180秒,太长,修改zookeeper.session.timeout之前请首先一定要优化hbase的GC配置后才改此项值,hbase团队设置180秒是为了防止hbase初级使用者在不优化hbase GC的情况下,频繁因为GC导致hbase节点与zookeeper之间超时才设置的180秒,所以对于熟练者你改此值之前请确保你已经修改hbase GC。
3.6 hbase优化
3.6.1 hbase客户端优化
hbase客户端的源码我在另一篇博客源码解读--(1)hbase客户端源代码中进行介绍,了解源码只是为了让你能清醒的去优化hbase客户端。hbase客户端优化关键项目如下:
hbase客户端里传入hbase.client.write.buffer(默认2MB),加到客户端提交的缓存大小;
hbase客户端提交采用批量提交,批量提交的List的size计算公式=hbase.client.write.buffer*2/Put大小,Put大小可通过put.heapSize()获取,以hbase.client.write.buffer=2097152,put.heapSize()=1320举例,最佳的批量提交记录大小=2*2097152/1320=3177;
hbase客户端尽量采用多线程并发写
hbase客户端所在机器性能要好,不然速度上不去
下面是我当时在调研hbase时候做过的压测记录:
操作hbase你只需在maven里引入如下依赖项:
org.apache.hbase
hbase-client
1.2.1
建议的客户端操作代码如下:
Configuration configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.property.clientPort", "2181");
configuration.set("hbase.client.write.buffer", "2097152");
configuration.set("hbase.zookeeper.quorum","192.168.199.31,192.168.199.32,192.168.199.33,192.168.199.34,192.168.199.35");
Connection connection = ConnectionFactory.createConnection(configuration);
Table table = connection.getTable(TableName.valueOf("tableName"));
try {
// Use the table as needed, for a single operation and a single thread
// construct List putLists
List putLists = new ArrayList();
for(int count=0;count<100000;count++){
Put put = new Put(rowkey.getBytes());
put.addImmutable("columnFamily1".getBytes(), "columnName1".getBytes(), "columnValue1".getBytes());
put.addImmutable("columnFamily1".getBytes(), "columnName2".getBytes(), "columnValue2".getBytes());
put.addImmutable("columnFamily1".getBytes(), "columnName3".getBytes(), "columnValue3".getBytes());
put.setDurability(Durability.SKIP_WAL);
putLists.add(put);
//3177不是我杜撰的,是2*hbase.client.write.buffer/put.heapSize()计算出来的
if(putLists.size()>=3177-1){
//达到最佳大小值了,马上提交一把
table.put(putLists);
putLists.clear();
}
}
//剩下的未提交数据,最后做一次提交
table.put(putLists)
} finally {
table.close();
connection.close();
}
3.6.2 hbase服务端优化
3.6.2.1 hbase服务端源代码对于内存的分配规律
1)Hbase内存分配=memstore(写)+blockcache(读)+other(其他)
2)Memstore占内存百分比(写)+blockcache占内存百分比(读)<=0.8
3)Memstore有两个临界点,第一个临界点是hbase.regionserver.global.memstore.size.lower.limit,默认=0.95,达到这个点,会选择当前region里memstore最大那个flushing;第二个临界点hbase.regionserver.global.memstore.size,默认=0.4,达到这个点,所有region做flushing;
4)Blockcache通过hfile.block.cache.size设置,默认=0.4
3.6.2.2 hbase内存配置多大合适
经验公式如下:
hbase.hregion.memstore.flush.size*单机hbase的region个数/hbase.regionserver.global.memstore.size/hbase.regionserver.global.memstore.size.lower.limit
举例如下:我的hbase要求写入快,读取速度在写入速度之后考虑,那么我把内存尽可能多的给到写,所以我调整hbase.regionserver.global.memstore.size=0.6,hbase.regionserver.global.memstore.size.lower.limit=0.6,hfile.block.cache.size=0.1,这样0.6+0.1<0.8首先没有违背hbase的大原则,hbase.hregion.memstore.flush.size=128MB保持不变毕竟HDFS的block刚好也是128MB,我预估每个机器最后单节点上负载hbase100个区,那么我hbase节点的内存要配置的最大值为128MB*100/0.6/0.6=35555MB=35GB,所以修改hbase-env.sh里如下配置:
export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS –Xmx35g –Xms35g –Xmn2g -XX:SurvivorRatio=1 -XX:PermSize=128M -XX:MaxPermSize=128M -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false -XX:MaxTenuringThreshold=15 -XX:+CMSParallelRemarkEnabled -XX:+UseFastAccessorMethods -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -Xloggc:/home/hadoop/hbase-1.2.1/logs/gc-hbase-regionserver.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M"
3.6.2.3 hbase 服务端GC优化
配置详见3.6.2.2
3.6.2.4 hbase的split和预分区
hbase从设计那一刻起就要尽最大可能规避hbase的split操作,split的意思是hbase单区文件大小过大,需要拆分为两个文件,而避免hbase不做split的最好的办法就是提前预分区,一个预分区建表语句如下:
disable 'habsetest'
drop 'habsetest'
n_splits = 108
create 'hbasetest', {NAME => 'info', TTL=>'15552000', COMPRESSION => 'SNAPPY'}, {SPLITS => (1..n_splits).map {|i| "#{i*999/n_splits}"}}
最好的预分区是做到今后都不会发生split操作,那么预分区多少呢?这里有个逐步计算方法:
首先你要知道你这个hbasetest数据表数据保留多久,比如保留半年,也即180天;
第二步,你得观察每天数据量,例如每天hbasetest的数据量会产生10GB的数据;
第三步,你得知道你配置的单个region文件的大小,比如hbase.hregion.max.filesize=53687091200,意思是单个region最大50GB;
第四步,开始计算,180天的数据量=10GB*180*HADOOP备份数3=5400GB,这些数据占用分区数=108,
第五步,开始建表时候你就知道你必须在设计时候就得建立108个预分区同时设置数据只保留15552000秒之内的数据,也即保留180天内的数据,这样,只要你的估算准确,永远不会进行split操作,就算做,也只是少数一两个区split做而已,基本不影响hbase读写性能。
3.6.2.5 hbase的compact
hbase的memstore会不断刷小文件,而compact会不断合并小文件和清理过期数据和标记删除的数据,compact又分major compact和minor compact,我们要尽量关闭major compact变成手动在空闲期让它做major compact,
hbase.hregion.majorcompaction
0
禁止majorcompaction,这里虽然禁止了,但是还是得做,是通过linux定时任务在空闲时间执行
在hbase空闲期通过设置linux 的crontab定时任务来做major compact
cd /opt/hbase-1.2.1/bin
./hbase shell
major_compact 'hbasetest'
quit
3.6.2.6 合理设计rowkey
rowkey一定要设计合理,关于rowkey,你要理解如下:
hbase对于rowkey的处理是把rowkey按照ASCII码字典序来处理的,意思是ASCII对应的顺序字符的二进制顺序来处理,例如0-9字符的Byte值
hbase会按照这种ASCII字典序把rowkey和每个区的start rowkey和end rowkey对比,就知道该把这条记录写到哪个区
所以,rowkey的设计一定要尽量使得记录随机化离散化,不然会导致数据倾斜