@HUST张友东 work@taobao zyd_com@126.com
分类: 服务器与存储
2014-07-01 21:18:06
作者:zyd_com@126.com
博客:ydzhang.blog.chinaunix.net | blog.yunnotes.net
微博:
TFS支持ErasureCode的版本上线数月,参与编码的数据量也不断在增加,因为规模效应,最近也暴露除了不少问题。
索引预留空间问题
TFS每个Dataserver(DS)进程管理一块磁盘,DS上线前,需要对磁盘进行format,format的主要工作是创建一批固定大小的文件出来,这些文件对应TFS里的block(通常64M),block里存储多个小文件,每个block对应一个索引文件,存储文件在block内的offset、size信息,用于快速定位文件。
索引文件占用的存储空间并不固定,其决定于block里存储多少个文件,所以在format时,必须为索引文件预留一定大小的磁盘空间,TFS的策略是,根据用户配置的“平均文件大小”来计算出一个保留值block_size / avg_file_size * index_size * RESERVE_RATIO,然后把剩余的空间都用于分配block文件。
之前RESERVE_RATIO一直设置为4,也就是说,即使集群平均文件大小偏离配置值4倍,保留的空间也是足够的。但开启ErasureCode之后,数据块的索引会冗余存储一份到校验块上,这个设计导致发生大量的ErasureCode编组后,按4倍保留的索引的空间不够用了。
解决方案
1. 针对新上线的DS,扩大RESERVE_RATIO,为索引保留更多的存储空间
2. 对于已经上线的DS,移除一部分未用的block,并修改程序确保这部分block以后也不会被分配使用
主备NS共享存储
ErasureCode编组时,Nameserver(NS)在后台选取一批访问较少的block进行编组,并把编组信息写到里。以4+2为例,NS选取了block D1、D2、D3、D4,编码出校验block P1、P2,编组完后,NS就会在tair里添加一条记录如下:
group_id = 1 // 编组id在运行过程中不断增长
data_num = 4 // 数据块个数
check_num = 2 // 校验块个数
algorithm = "caucy reed solomon" // 编码算法
block_list = [D1, D2, D3, D4, P1, P2] // block列表
主NS添加记录到tair,备NS会周期性(间隔很短)的扫描tair,看是否有新增加的编组,如果有则加载到内存;由于编组的id是递增的,备NS每次从上次扫描的位置接着读取,成本很低。
在没有发生解组的情况下,上面的方式运行很正常,但有编组被解除掉时(编组内超过2个block丢失),由于这个信息没有及时同步到备NS,此时一旦发生主备切换就会有问题。
1. block A参与编组,主、备NS都看到A已编组
2. 主NS将block A所在的编组解除掉,block处于未编组状态,主看到A未编组,但备还看到A已编组
3. 主挂掉,备接管;A汇报到新的主时,编组的状态就发生冲突
解决方案是主NS将解组的信息以日志的形式写入tair,备周期性的扫描解组的日志,将解组的信息应用到内存,此时主备看到的block状态就是一致的;其实这个方案也不能完全解决问题,因为主备NS看到的数据始终存在一定时间的不一致,而方案是否可行关键看这个不一致窗口的长度是否在能接受的范围内。
Jerasure多线程并发
参与EraureCode编组的block丢失时,如果文件被访问到了,会产生实时的退化读。线上的退化读发生的概率比预期要少很多,主要是我们对参与编组的block进行了限制,必须一个月没有被访问过才编码,而TFS里大部分的访问都集中在最近写入的文件上,所以编码过的block很少发生退化读。
最近观察到一个现象,在退化读文件时,DS可能发生coredump,从core文件定位到问题是Jerasure库里发生数组越界访问的问题;同时发现有另外一个线程也在做退化读,研究了下Jerasure的代码发现,创建编解码矩阵的过程是不能多线程并发的,因为其公用全局变量galois_mult_tables/galois_div_tables等。
解决方法
在创建编码矩阵时加锁,避免多线程并发
恢复数据错误
最近某个机房搬迁,这次采用直接搬机器的方式(类似事情去年也发生过,当时采用迁移数据的方式,把数据先同步到另一个机房);将物理机搬到新的机房,原以为会比用工具迁移数据来得轻松,结果机器搬运到新机房后,一个集群坏了好几台机器(系统盘故障、电源故障等),导致发生大量的ErasureCode恢复和解组,也正是由于这次大规模的恢复,发现在恢复读数据时,没有检查返回值(非常低级的错误),导致的结果是恢复的数据有可能是错误的(比如读数据超时或达到流量阈值)。
紧急修复bug上线之后,接下来就要为bug擦屁股了,要对线上所有的数据进行一次全量的检查,找出有问题的数据,重新从备份的集群同步;通过这个bug,以后需要重点关注的问题。
1. 写代码及review代码上的疏忽 2. 测试时对失败场景的模拟 3. 数据校验是存储的根基,数据有问题不可怕,不能发现问题才可怕
未及时扩容
上周末线上一个备集群容量使用到96%了(这个问题跟ErasureCode没关系),而NS配置的集群写入阈值就是96%,导致大部分容量超过(大于或等于)96%的DS无法写入数据了,此时刚好有8个DS(后来统计到的)是新上线的,导致所有从主集群同步过来的数据都写到这8个DS了,使得这个8个DS的压力非常大;由于只有8个DS可写,主集群的同步队列也堆积了很多文件,并一直持续。
发现这个问题后,迅速修改了NS的容量阈值,调整到98%,恢复正常的写服务,并让PE同学扩容。扩容后,写服务虽然正常了,但主集群的DS上堆积的未同步文件仍然是往上述8个DS同步(因为文件所属的block是确定的,而这些block都至少有一个副本在8个DS上)。
此时为了让集群快速均衡,只能人工介入,将8个DS逐个下线,通过工具将这些DS上的block重新从主集群同步一份(重新同步会将block随机分配到所有可用的DS上),折腾了两个半天才搞定。处理完后感慨万千
1. 线上容量使用到90%就有告警的,结果却一直没扩容(开发运维都需要反思)
2. 是时候考虑将扩容自动化了,根据历史容量增长趋势,自动从buffer里选取一批机器上线