深入浅出Flashcache(一)

Cache is king.

在计算机系统中,cache的魔爪无处不在。CPU中有L1,L2,甚至L3 cache;Linux有pagecache,MySQL有buffer cache/query cache;IO系统中Raid卡/磁盘也有cache;在大型互联网系统中,数据库前面一般也都有一层memcache。Cache是容量与性能之前取平衡的结果,以更低的成本,获得更高的收益,是系统设计时应该遵循的原则。

传统机械硬盘几十年来,容量不断翻倍的增长,相比较而言,性能的增长就慢的像蜗牛了。对于依赖IO性能的应用,典型的如数据库,一直在等待新的技术来拯救。在此之前,身躯庞大的高端存储,动辄重达几吨。相比于存储里带的硬盘来说,价格贵得离谱,而存储的附加价值,在于io在大量硬盘之间的均衡分布,以及IO链路的多路容灾,以及部分固件层面的优化和数据保护等。

Flash disk(SSD/FusionIO等)的出现,改变了这一切。Flash disk将硬盘从机械产品变成了电气产品,功耗更小,性能更好,时延更优,看起来传统硬盘已经不堪一击,数据库欢欣鼓舞,新的革命似乎将一夕成功。但新东西也有它致命的缺陷,价格和经过时间检验的稳定性。

所以Facebook的Mohan Srinivasan在2010年开源了Flashcache,将Flash disk做为普通硬盘的cache,这个思路,目前一些尝试也在raid卡硬件层面做尝试,例如LSI的CacheCade Pro,不过之前版本新浪的童鞋测试过似乎性能没有想象的好。Flashcache在淘宝一些核心数据库中已经在线运行了大半年,经过调优后的表现稳定。Flashcache利用了Linux的device mapping机制,将Flash disk和普通硬盘的块设备做了一层映射,在OS中变现为一块普通的磁盘,使用简单,是一个值得推荐的方案。Flashcache最初的实现是write backup机制cache,后来又加入了write through和write around机制:

  • write backup: 先写入到cahce,然后cache中的脏块会由后台定期刷到持久存储。
  • write through: 同步写入到cache和持久存储。
  • write around: 只写入到持久存储。

在详细的介绍Flashcache之前,需要先了解一下Linux的block device和device mapper相关的知识。

1. Block Device
块设备最初主要是依据传统硬盘等IO操作较慢的设备而设计的,所以Linux中为块设备的IO操作提供了cache层,所以基于块设备的请求一般是buffer io,当然后来由于数据库等自己有cache机制的应用,os/fs层面的cache就成了多余,所以出现了绕过os/fs层cache的direct io。

块设备在设备确定层和kernel之间,为Kernel提供了统一的IO操作接口,同时隐藏了不同硬件设备的细节。当有多个并发IO请求到块设备时,请求的顺序会影响IO的性能,因为普通的机械硬盘需要移动机械臂,所以kernel一般会对IO做排序等调度后再发送到块设备层。IO调度算法是一种电梯算法(elevator algorithm),目前主要有cfq/deadline/anticipatory/noop,其中cfq是Linux的默认策略;anticipatory在新的内核中已经放弃;deadline在大部分OLTP数据库应用中更具优势,IO的响应时间更稳定些;noop只对IO请求进行简单的合并,其他不干涉,在FusionIO等IO性能很好的设备上,noop反而更具优势,所以FusionIO的驱动默认使用了noop。关于IO Scheduler,后文会有更详细的解释。

块设备在用户空间是一种特殊的文件类型,由(major,minor)来标识,major区分disk,minor区分partition。Linux中一般把设备文件放在/dev目录。实际上你完全可以将块设备文件创建到其他地方,只要(major,minor)唯一确定,块设备文件最后访问的起始同一个块设备。

$ls -l /dev/sda1
brw-rw—- 1 root disk 8, 1 2011-12-03 01:00 /dev/sda1

$sudo mknod /opt/sda1 b 8 1

$ls -l /opt/sda1
brw-r–r– 1 root root 8, 1 2011-12-03 11:54 /opt/sda1

由于块设备处于文件系统和物理设备驱动之间,在这一层做一些工作可以对所有IO产生影响,因此很多优秀的产品都在这一层做文章,除了Flashcache,还有一个比较著名的就是DRBD(DRBD已经进入2.6.33内核)。
Read more of this post

HBase运维碎碎念

最近开始看HBase,幸运的是,现在HBase社区已经非常的活跃,网络上可以找到大量的参考资料。但对于大集群的运维经验,还有待积累。上周在团队内部简单分享了一下这段时间的读书总结,现在把PPT放出来。

这个PPT只是个读书笔记,可能有些理解有误的地方,如果发现了,请一定要留下评论。

关于HBase的一些零碎事

随着Facebook使用HBase来构建实时消息系统,基于Hadoop的面向列存储的HBase持续升温。

目前稳定版本的HBase0.90.2只能基于Hadoop0.20.x系列版本,暂不支持最新的0.21.x。而且官方版本的Hadoop0.20.2(或者0.203.0)缺少一个重要的特性,HDFS不支持sync模式的持久,这样HBase就有较大的丢失数据的风险。要在生产环境使用HBase,有两个选择,一是使用Cloudera的CDH3版本,Cloudera就类似MySQL的Percona,对官方版本的Hadoop做了很多改进工作,而且经典的《Hadoop:The Definitive Guide》一书的作者Tom White就是Cloudera的一员,这也和《High performance MySQL》一书的作者主要来是Percona一样。另外一种选择,就是自行编译Hadoop branch-0.20-append源码分支,这里有详细的说明

对于HBase这种类似BigTable的系统,其优化之一是消除了磁盘的随机写。付出的代价是将最新的数据保存在内存表中,对内存有较大的需求。如果内存表的数量较多,则每个内存表就会在较小的时候刷到磁盘,导致磁盘文件多而且小。范围读取数据的时候就会跨多个数据文件甚至多个节点。为提升读性能,系统都会设计有compaction操作。另外为了防止某些情况下数据文件过大(hbase.hregion.max.filesize,默认256M,太大的数据文件在compaction等操作是对内存的消耗更大),HBase也设计了split操作。Compaction和Split操作,对于在线应用的响应时间都容易造成波动,他们的策略需要根据应用的特性进行调整。建议在业务低峰期手工调整。

HBase的regionserver宕机超过一定时间后,HMaster会将其所管理的region重新分布到其他存活的regionserver,由于数据和日志都持久在HDFS中,因此该操作不会导致数据丢失。但是重新分配的region需要根据日志恢复原regionserver中的内存表,这会导致宕机的region在这段时间内无法对外提供服务。而一旦重分布,宕机的节点起来后就相当于一个新的regionserver加入集群,为了平衡,需要再次将某些region分布到该server。 因此这个超时建议根据情况进行调整,一般情况下,宕机重启后即可恢复,如果重启需要10分钟,region重分布加恢复的时间要超过5分钟,那么还不如等节点重启。Region Server的内存表memstore如何在节点间做到更高的可用,是HBase的一个较大的挑战。Oceanbase也是采用内存表保持最新的更新数据,和HBase不同的是,Oceanbase使用的是集中的UpdateServer,只需要全力做好UpdateServer的容灾切换即可对业务连续性做到最小影响。分布还是集中,哪些功能分布,哪些功能集中,各自取不同平衡,是目前大部分分布式数据库或者存储的一个主要区别。当然,像Cassandra这种全分布的,架构上看起来很完美,实际应用起来反而问题更多。

对于java应用,线上运维最大的挑战之一就是heap内存管理。GC的不同方式,以及使用内存表和cache对内存的消耗,可能导致局部阻塞应用或者stop the world全局阻塞或者OOM。因此HBase的很多参数设置都是针对这两种情况。HBase使用了较新的CMS GC(-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode)。

默认触发GC的时机是当年老代内存达到90%的时候,这个百分比由 -XX:CMSInitiatingOccupancyFraction=N 这个参数来设置。concurrent mode failed发生在这样一个场景:
当年老代内存达到90%的时候,CMS开始进行并发垃圾收集,于此同时,新生代还在迅速不断地晋升对象到年老代。当年老代CMS还未完成并发标记时,年老代满了,悲剧就发生了。CMS因为没内存可用不得不暂停mark,并触发一次全jvm的stop the world(挂起所有线程),然后采用单线程拷贝方式清理所有垃圾对象。这个过程会非常漫长。为了避免出现concurrent mode failed,我们应该让GC在未到90%时,就触发。

通过设置 -XX:CMSInitiatingOccupancyFraction=N

这个百分比, 可以简单的这么计算。如果你的 hfile.block.cache.size 和 hbase.regionserver.global.memstore.upperLimit 加起来有60%(默认),那么你可以设置 70-80,一般高10%左右差不多。

(以上CMS GC的说明引自HBase性能调优

目前关于HBase的书不多,《Hadoop: The Definitive Guide》第二版有一章,另外最权威的要算官方的这本电子书了。

这篇是最近看HBase过程中的一些零碎的东西,记录于此备忘。

白话MongoDB(三)

通过源代码编译安装好MongoDB之后,接下来需要配置运行。在MongoDB的安装目录,有几个子目录,bin下面是可执行文件,包括

  • mongod:数据库服务端,类似mysqld,每个实例启动一个进程,可以fork为Daemon运行
  • mongo:客户端命令行工具,类似sqlplus/mysql,其实也是一个js解释器,支持js语法
  • mongodump/mongorestore:将数据导入为bson格式的文件/将bson文件恢复为数据库,类似xtracbackup
  • mongoexport/mongoimport:将collection导出为json/csv格式数据/将数据导入数据库,类似mysqldump/mysqlimport
  • bsondump:将bson格式的文件转储为json格式的数据
  • mongos:分片路由,如果使用了sharding功能,则应用程序连接的是mongos而不是mongod
  • mongofiles:GridFS管理工具
  • mongostat:实时监控工具

最简单的,通过执行mongod即可以启动MongoDB数据库服务,mongod支持很多的参数,但都有默认值,其中最重要的是需要指定数据文件路径,或者确保默认的/data/db存在并且有访问权限,否则启动后会自动关闭服务。Ok,那也就是说,只要确保dbpath就可以启动MongoDB服务了:

$ ./mongod --dbpath /tmp
Fri Apr  1 00:34:46 [initandlisten] MongoDB starting : pid=31978 port=27017 dbpath=/tmp 32-bit 

** NOTE: when using MongoDB 32 bit, you are limited to about 2 gigabytes of data
**       see http://blog.mongodb.org/post/137788967/32-bit-limitations
**       with --dur, the limit is lower

Fri Apr  1 00:34:46 [initandlisten] db version v1.8.0, pdfile version 4.5
Fri Apr  1 00:34:46 [initandlisten] git version: 9c28b1d608df0ed6ebe791f63682370082da41c0
Fri Apr  1 00:34:46 [initandlisten] build sys info: Linux ning 2.6.36-ningoo #1 SMP 
Wed Nov 17 21:45:13 CST 2010 i686 BOOST_LIB_VERSION=1_42
Fri Apr  1 00:34:46 [initandlisten] waiting for connections on port 27017
Fri Apr  1 00:34:46 [websvr] web admin interface listening on port 28017

mongod的主要参数有:

dbpath: 数据文件存放路径,每个数据库会在其中创建一个子目录。用于防止同一个实例多次运行的mongod.lock也保存在此目录中。
logpath:错误日志文件
logappend: 错误日志采用追加模式(默认是覆写模式)
bind_ip: 对外服务的绑定ip,一般设置为空,及绑定在本机所有可用ip上,如有需要可以单独指定
port: 对外服务端口。Web管理端口在这个port的基础上+1000
fork: 以后台Daemon形式运行服务
journal:开启日志功能,通过保存操作日志来降低单机故障的恢复时间,在1.8版本后正式加入,取代在1.7.5版本中的dur参数。
syncdelay: 执行sync的间隔,单位为秒。
directoryperdb: 每个db存放在单独的目录中,建议设置该参数。
maxConns: 最大连接数
repairpath: 执行repair时的临时目录。在如果没有开启journal,异常宕机后重启,必须执行repair操作。

在源代码中,mongod的参数分为一般参数,windows参数,replication参数,replica set参数,以及隐含参数。上面列举的都是一般参数。如果要配置replication,replica set等,还需要设置对应的参数,这里先不展开,后续会有专门的文章来讲述。执行mongo –help可以看到对大多数参数的解释。但有一些隐含参数,则只能通过看代码来获得(见db.cpp po::options_description hidden_options(“Hidden options”);),隐含参数一般要么是还在开发中,要么是准备废弃,因此在生产环境中不建议使用。

可能你已经注意到,mongod的参数中,没有设置内存大小相关的参数,是的,mongodb使用os mmap机制来缓存数据文件数据,自身目前不提供缓存机制。这样好处是代码简单,mmap在数据量不超过内存时效率很高。但是数据量超过系统可用内存后,则写入的性能可能不太稳定,容易出现大起大落,不过在最新的1.8版本中,这个情况相对以前的版本已经有了一定程度的改善,具体请参考realzyy的测试

这么多参数,全面写在命令行中则容易杂乱而不好管理。因此,mongod也和mysqld一样支持将参数写入到一个配置文本文件中,然后通过config参数来引用此配置文件:

./mongod --config /etc/mongo.cnf 

至此,已经成功的运行了一个单机的mongodb实例。

无觅相关文章插件,快速提升流量