Group
bfs 的核心是镜像(而非 EC,私有云有钱任性),考虑故障隔离时,方便运维也方便代码的做法是,按照机器大粒度来做故障隔离,ZooKeeper 的设计:
把机器做一次分组,比如机架 A 的 store1 和机架 B 的 store2,成为一个分组 group1,那么 store1 下面所有的 volume 任何一个出现故障,整个 group 都是只读的,这时候通知故障转移,依赖 ZooKeeper 的 directory 就会使用其他的 group。
所以有个大前提,group 最好多几个,避免挂一个分组就没可用的资源了。这就是牺牲了一定的可用性来完成架构的简单化,如果按照 volume 级别来做高可用,代码复杂度高挺多的,有兴趣的可以思考下这个问题。
目前 OPS 模块还是 Python 脚本来实现,当时考虑的方案是,按照目前所有资源,比如机架分布、存储分布、随机性、副本等 N 个参数,根据 ops 平台输入计算一个合理的分配完成一个分组,这块目前在 TODO,这样当一批机器入库资产以后,可以根据自由选择套餐分配可用的 bfs 资源组。 Volume
做好完分组以后,就是分配卷(volume)了,由 ops 平台触发,调用各自分组需要分配资源的 store 机器,创建 volume,id 是通过 ops 平台指定的,这里代码简单的自增分配了 volume id,然后创建好另外一个 ZooKeeper 节点信息,方便后面的 pitchfork 和 directory 获取统计信息写入。
[attach]5466[/attach]
其中处理了多少写请求,写延迟,剩余空间都是 pitchfork 来定期维护更新到 ZooKeeper,方便 directory 根据规则来调度到不同的 store 集合。这个结构和上文 store 部门提供的 ZooKeeper 结构区别在于,store 关注的是 store 本身有哪些 volume,而这个关注的是 volume 有哪些 store。比如 store-1 可能有 volume1,2,3,而 volume1 可能需要 store-1 和 store-2 两组机器来构成一个卷,所以 volume id 对于 store 单个节点是唯一的,但是对于不同的 store,volume id 如果有重复,表示他们两个其实是一组。 心跳模块 pitchfork
pitchfork 启动时,会根据整个 ZooKeeper rack 进行枚举,找到所有的 store 的集合,然后根据当前 pitchfork 的节点数量,进行任务划分。这样当 store 非常多时,任务就在各个节点之间进行了任务拆分,这个特质和 kafka 的 consumer group 实现几乎是一致的。看看 ZooKeeper 的节点:
[attach]5467[/attach]
使用 ZooKeeper 的顺序和临时节点,当 pitchfork 启动时候,创建一个临时、顺序的节点,如果 pitchfork 挂掉,节点会消失,因此只需要把 pitchfork 监听 pitchfork,watch 这个 parent 就可以获取整个 pitchfork 节点的新增和删除,然后重新进行分配。
把所有 store 排序以后,除以所有可用的节点数量来进行任务分配,这样即使是分布在不同机器的 pitchfork 都可以得到一直计算结果,进行任务分配。
任务分配完毕以后,内部会并行进行 Probe 探针,定期检查 store 下所有 volume 的健康状况。很巧妙的使用了 Golang map 每次 range 具有随机性的特点进行探测。当然 store 本身内部的 IO 错误,会直接设置 error 标志位,这样探测也会失败,当探测失败,就会更新整个 store 的状态为只读或者不可用,所以故障是机器级别的。
同样使用 Golang goroutine 并行异步的同步 store 中每个 volume 的内部统计,比如 IOPS、delay、freespace 等,方便 directory 调度。 目录模块 directory
目录服务根据上文的 Volume ZK meta,就可以非常方便的获取到各种进行然后进行打分。
目前规则比较简单粗暴,先获取所有 group,然后根据 group下的所有 store,然后根据所有 store 下的 volume,给 group 计算一个总分,根据总分对 group 进行权重分配,然后 rand 命中一个 group,就是它来负责写操作 了。
如果某个 group 有任意一个 store 只读或者故障,就放弃这个 group,最终调度单元就是 group 为粒度了。group 选中以后,再 rand 具体 store中的某个 volume,就开始写了(volume 级别没做那么细了,直接用的随机分布),因此副本数其实是由 ops 平台设置 group 决定的。
[attach]5468[/attach]
高可用
因为有 pitchfork 模块来更新 ZooKeeper 通知 group 和 volume 的信息,方便重新进行打分或者调度或者故障转移,所以整个高可用都是基于 directory 的调度来实现的,对 store 代码几乎是没有任何侵入的。
这样设计的好处是,可以非常方便更新 directory 代码升级策略,或者修复 bug,而不是 store 节点之间做复制,因为 store 重启的成本很大,而且存储引擎层面的东西最好最少的修改,而是单纯当作存储来用。
从 ops 的副本策略来看,属于 CAP 中的 CP 系统,牺牲了可用性。因为当副本数过多的情况下,响应会变慢,因为要求所有 volume下的 store 全部写入成功才会返回的,所以是强一致的。任意一个 store 挂掉以后,因为可以提供只读服务,所以网络分区的问题也可以得到解决。