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 http://blog.yunnotes.net/index.php/tfs-erasure-code/ 之后, 数据块的索引会冗余存储一份到校验块上, 这个设计导致发生大量的 ErasureCode 编组后, 按 4 倍保留的索引的空间不够用了. 解决方案
针对新上线的 DS, 扩大 RESERVE_RATIO, 为索引保留更多的存储空间
对于已经上线的 DS, 移除一部分未用的 block, 并修改程序确保这部分 block 以后也不会被分配使用
主备 NS 共享存储 ErasureCode 编组时, Nameserver(NS)在后台选取一批访问较少的 block 进行编组, 并把编组信息写到 http://code.taobao.org/p/tair/src/ 里. 以 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, 此时一旦发生主备切换就会有问题.
block A 参与编组, 主, 备 NS 都看到 A 已编组
主 NS 将 block A 所在的编组解除掉, block 处于未编组状态, 主看到 A 未编组, 但备还看到 A 已编组
主挂掉, 备接管; A 汇报到新的主时, 编组的状态就发生冲突
解决方案是主 NS 将解组的信息以日志的形式写入 tair, 备周期性的扫描解组的日志, 将解组的信息应用到内存, 此时主备看到的 block 状态就是一 致的; 其实这个方案也不能完全解决问题, 因为主备 NS 看到的数据始终存在一定时间的不一致, 而方案是否可行关键看这个不一致窗口的长度是否在能接受的范围 内. Jerasure 多线程并发 参与 EraureCode 编组的 block 丢失时, 如果文件被访问到了, 会产生实时的退化读. 线上的退化读发生的概率比预期要少很多, 主要是我们对 参与编组的 block 进行了限制, 必须一个月没有被访问过才编码, 而 TFS 里大部分的访问都集中在最近写入的文件上, 所以编码过的 block 很少发生退化 读. 最近观察到一个现象, 在退化读文件时, DS 可能发生 coredump, 从 core 文件定位到问题是 Jerasure 库里发生数组越界访问的问题; 同 时发现有另外一个线程也在做退化读, 研究了下 Jerasure 的代码发现, 创建编解码矩阵的过程是不能多线程并发的, 因为其公用全局变量 galois_mult_tables/galois_div_tables 等. 目前集群内编组的配置基本都是 4+2, 后面可能增大数据块个数, 但由于 Jerasure 这个多线程的限制, 我们要想支持编解码的并发, 只能有两种选择, 目前还在评估中.
编组的配置不修改, 一直为 4+2, 这样编解码矩阵只用创建一次, 以后每次编解码都复用
多线程的编解码过程加锁互斥进行, 不要同时创建编解码矩阵
恢复数据错误 最近某个机房搬迁, 这次采用直接搬机器的方式(类似事情去年也发生过, 当时采用迁移数据的方式, 把数据先同步到另一个机房); 将物理机搬到新的机 房, 原以为会比用工具迁移数据来得轻松, 结果机器搬运到新机房后, 一个集群坏了好几台机器(系统盘故障, 电源故障等), 导致发生大量的 ErasureCode 恢复和解组, 也正是由于这次大规模的恢复, 发现在恢复读数据时, 没有检查返回值(非常低级的错误), 导致的结果是恢复的数据有可能 是错误的(比如读数据超时或达到流量阈值). 紧急修复 bug 上线之后, 接下来就要为 bug 擦屁股了, 要对线上所有的数据进行一次全量的检查, 找出有问题的数据, 重新从备份的集群同步; 通过这个 bug, 以后需要重点关注的问题.
写代码及 review 代码上的疏忽
测试时对失败场景的模拟
数据校验是存储的根基, 数据有问题不可怕, 不能发现问题才可怕
未及时扩容 上周末线上一个备集群容量使用到 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 上), 折腾了两个半天才搞定. 处理完后感慨万千
线上容量使用到 90% 就有告警的, 结果却一直没扩容(开发运维都需要反思)
是时候考虑将扩容自动化了, 根据历史容量增长趋势, 自动从 buffer 里选取一批机器上线
来源: http://it.taocms.org/05/3610.htm