转自:http://stor.51cto.com/art/201803/567763.htm
Ceph作为一款开源的分布式存储软件,可以利用X86服务器自身的本地存储资源,创建一个或多个存储资源池,并基于资源池对用户提供统一存储服务,包括块存储、对象存储、文件存储,满足企业对存储高可靠性、高性能、高可扩展性方面的需求,并越来越受到企业的青睐。
Ceph作为一款开源的分布式存储软件,可以利用X86服务器自身的本地存储资源,创建一个或多个存储资源池,并基于资源池对用户提供统一存储服务,包括块存储、对象存储、文件存储,满足企业对存储高可靠性、高性能、高可扩展性方面的需求,并越来越受到企业的青睐。经过大量的生产实践证明,Ceph的设计理念先进,功能全面,使用灵活,富有弹性。不过,Ceph的这些优点对企业来说也是一把双刃剑,驾驭的好可以很好地服务于企业,倘若功力不够,没有摸清Ceph的脾性,有时也会制造不小的麻烦,下面我要给大家分享的正是这样的一个案例。
A公司通过部署Ceph对象存储集群,对外提供云存储服务,提供SDK帮助客户快速实现对图片、视频、apk安装包等非结构化数据的云化管理。在业务正式上线前,曾经对Ceph做过充分的功能测试、异常测试和性能测试。
集群规模不是很大,使用的是社区0.80版本,总共有30台服务器,每台服务器配置32GB内存,10块4T的SATA盘和1块160G的Intel S3700 SSD盘。300块SATA盘组成一个数据资源池(缺省配置情况下就是名称为.rgw.buckets的pool),存放对象数据;30块SSD盘组成一个元数据资源池(缺省配置情况下就是名称为.rgw.buckets.index的pool),存放对象元数据。有过Ceph对象存储运维部署经验的朋友都知道,这样的配置也算是国际惯例,因为Ceph对象存储支持多租户,多个用户在往同一个bucket(用户的一个逻辑空间)中PUT对象的时候,会向bucket索引对象中写入对象的元数据,由于是共享同一个bucket索引对象,访问时需要对这个索引对象加锁,将bucket索引对象存放到高性能盘SSD组成的资源池中,减少每一次索引对象访问的时间,提升IO性能,提高对象存储的整体并发量。
系统上线后,客户数据开始源源不断地存入到Ceph对象存储集群,在前面的三个月中,一切运行正常。期间也出现过SATA磁盘故障,依靠Ceph自身的故障检测、修复机制轻松搞定,运维的兄弟感觉很轻松。进入到5月份,运维兄弟偶尔抱怨说SSD盘对应的OSD有时会变的很慢,导致业务卡顿,遇到这种情况他们简单有效的办法就是重新启动这个OSD,又能恢复正常。大概这种现象零星发生过几次,运维兄弟询问是不是我们在SSD的使用上有什么不对。我们分析后觉得SSD盘的应用没有什么特别的地方,除了将磁盘调度算法修改成deadline,这个已经修改过了,也就没有太在意这个事情。
5月28日晚上21:30,运维兄弟手机上接到系统告警,少部分文件写入失败,马上登陆系统检查,发现是因为一台服务器上的SSD盘对应的OSD读写缓慢引起的。按照之前的经验,此类情况重启OSD进程后就能恢复正常,毫不犹豫地重新启动该OSD进程,等待系统恢复正常。但是这一次,SSD的OSD进程启动过程非常缓慢,并引发同台服务器上的SATA盘OSD进程卡顿,心跳丢失,一段时间后,又发现其它服务器上开始出现SSD盘OSD进程卡顿缓慢。继续重启其它服务器上SSD盘对应的OSD进程,出现了类似情况,这样反复多次重启SSD盘OSD进程后,起不来的SSD盘OSD进程越来越多。运维兄弟立即将此情况反馈给技术研发部门,要求火速前往支援。
到办公室后,根据运维兄弟的反馈,我们登上服务器,试着启动几个SSD盘对应的OSD进程,反复观察比对进程的启动过程:
1、 用top命令发现这个OSD进程启动后就开始疯狂分配内存,高达20GB甚至有时达到30GB;有时因系统内存耗尽,开始使用swap交换分区;有时即使最后进程被成功拉起,但OSD任然占用高达10GB的内存。
2、 查看OSD的日志,发现进入FileJournal::_open阶段后就停止了日志输出。经过很长时间(30分钟以上)后才输出进入load_pg阶段;进入load_pg阶段后,再次经历漫长的等待,虽然load_pg完成,但进程仍然自杀退出。
3、 在上述漫长启动过程中,用pstack查看进程调用栈信息,FileJournal::_open阶段看到的调用栈是在做OSD日志回放,事务处理中是执行levelDB的记录删除;在load_pg阶段看到的调用栈信息是在利用levelDB的日志修复levelDB文件。
4、 有时候一个SSD盘OSD进程启动成功,运行一段时间后会导致另外的SSD盘OSD进程异常死掉。
从这些现象来看,都是跟levelDB有关系,内存大量分配是不是跟这个有关系呢?进一步查看levelDB相关的代码后发现,在一个事务处理中使用levelDB迭代器,迭代器访问记录过程中会不断分配内存,直到迭代器使用完才会释放全部内存。从这一点上看,如果迭代器访问的记录数非常大,就会在迭代过程中分配大量的内存。根据这一点,我们查看bucket中的对象数,发现有几个bucket中的对象数量达到了2000万、3000万、5000万,而且这几个大的bucket索引对象存储位置刚好就是出现问题的那几个SSD盘OSD。内存大量消耗的原因应该是找到了,这是一个重大突破,此时已是30日21:00,这两天已经有用户开始电话投诉,兄弟们都倍感“鸭梨山大”。已经持续奋战近48小时,兄弟们眼睛都红肿了,必须停下来休息,否则会有兄弟倒在黎明前。
31日8:30,兄弟们再次投入战斗。
还有一个问题,就是有些OSD在经历漫长启动过程,最终在load_pg完成后仍然自杀退出。通过走读ceph代码,确认是有些线程因长时间没有被调度(可能是因levelDB的线程长时间占用了CPU导致)而超时自杀所致。在ceph的配置中有一个filestore_op_thread_suicide_timeout参数,通过测试验证,将这个参数设置成一个很大的值,可以避免这种自杀。又看到了一点点曙光,时钟指向12:30。
有些进程起来后,仍然会占用高达10GB的内存,这个问题不解决,即使SSD盘OSD拉起来了,同台服务器上的其它SATA盘OSD运行因内存不足都要受到影响。兄弟们再接再厉啊,这是黎明前的黑暗,一定要挺过去。有人查资料,有人看代码,终于在14:30从ceph资料文档查到一个强制释放内存的命令:ceph tell osd.* heap release,可以在进程启动后执行此命令释放OSD进程占用的过多内存。大家都格外兴奋,立即测试验证,果然有效。
一个SSD盘OSD起来后运行一会导致其它SSD盘OSD进程退出,综合上面的分析定位,这主要是因为发生数据迁移,有数据迁出的OSD,在数据迁出后会删除相关记录信息,触发levelDB删除对象元数据记录,一旦遇到一个超大的bucket索引对象,levelDB使用迭代器遍历对象的元数据记录,就会导致过度内存消耗,从而导致服务器上的OSD进程异常。
根据上述分析,经过近2个小时的反复讨论论证,我们制定了如下应急措施:
1、 给集群设置noout标志,不允许做PG迁移,因为一旦出现PG迁移,有PG迁出的OSD,就会在PG迁出后删除PG中的对象数据,触发levelDB删除对象元数据记录,遇到PG中有一个超大的bucket索引对象就会因迭代器遍历元数据记录而消耗大量内存。
2、 为了能救活SSD对应的OSD,尽快恢复系统,在启动SSD对应的OSD进程时,附加启动参数filestore_op_thread_suicide_timeout,设置一个很大的值。由于故障OSD拉起时,LevelDB的一系列处理会抢占CPU,导致线程调度阻塞,在Ceph中有线程死锁检测机制,超过这个参数配置的时间线程仍然没有被调度,就判定为线程死锁。为了避免因线程死锁导致将进程自杀,需要设置这个参数。
3、 在目前内存有限的情况下,异常的OSD启动会使用swap交换分区,为了加快OSD进程启动,将swap分区调整到SSD盘上。
4、 启动一个定时任务,定时执行命令ceph tell osd.* heap release,强制释放OSD占用的内存。
5、 SSD对应的OSD出现问题的时候,按如下步骤处理:
a) 先将该服务器上的所有OSD进程都停掉,以腾出全部内存。
b) 然后启动OSD进程,并携带filestore_op_thread_suicide_timeout参数,给一个很大的值,如72000。
c) 观察OSD的启动过程,一旦load_pgs执行完毕,可以立即手动执行ceph tell osd.N heap release命令,将其占用的内存强制释放。
d) 观察集群状态,当所有PG的状态都恢复正常时,再将其他SATA盘对应的OSD进程启动起来。
按照上述步骤,我们从17:30开始逐个恢复OSD进程,在恢复过程中,那几个超大bucket索引对象在做backfilling的时候需要较长时间,在此期间访问这个bucket的请求都被阻塞,导致应用业务请求出现超时,这也是单bucket存储大量对象带来的负面影响。
5月31日23:00,终于恢复了全部OSD进程,从故障到系统全部成功恢复,我们惊心动魄奋战了72小时,大家相视而笑,兴奋过度,再接再厉,一起讨论制定彻底解决此问题的方案:
1、 扩大服务器内存到64GB。
2、 对新建bucket,限制存储对象的最大数量。
3、 Ceph 0.94版本经过充分测试后,升级到0.94版本,解决单bucket索引对象过大问题。
4、 优化Ceph对levelDB迭代器的使用,在一个大的事务中,通过分段迭代,一个迭代器在完成一定数量的记录遍历后,记录其当前迭代位置,将其释放,再重新创建一个新的迭代器,从上次迭代的位置开始继续遍历,如此可以控制迭代器的内存使用量。
前事不忘后事之师,汲取经验教训,我们总结如下几点:
1、 系统上线前必须经过充分的测试
A公司的系统上线前,虽然对ceph做了充分的功能、性能、异常测试,但却没有大量数据的压力测试,如果之前单bucket灌入了几千万对象测试,也许就能提前发现这个隐患。
2、 运维过程中的每一个异常都要及时引起重视
此案例中,在问题爆发前一段时间,运维部门已经有反馈SSD异常的问题,可惜没有引起我们重视,倘若当时就深入分析,也许可以找到问题根由,提前制定规避措施。
3、 摸清ceph的脾性
任何软件产品都有相应的规格限制,ceph也不例外。如果能提前深入了解ceph架构及其实现原理,了解单bucket过度存放大量对象所带来的负面影响,提前规划,也不会出现本案例中遇到的问题。RGW对配额的支持非常全面,包括用户级别的、bucket级别的,都可以配置单个bucket允许存放的最大对象数量。
4、 时刻跟踪社区最新进展
在Ceph的0.94版本中,已经支持了bucket索引对象的shard功能,一个bucket索引对象可以分成多个shard对象存储,可有效缓解单bucket索引对象过大问题。