好久没有更新博客以及技术文档了,关于 strom 集群配置这一块,我写到使用 zk 进行配置服务的设计,众位有兴趣的道友可以去尝试下。
不过接触到分布式集群这一块之后,我发现集群配置成型的组件还是比较多的,比如比较经典的 puppt 集群管理配置,都是可以使用到的。我只是提供一个可行方案,多熟练使用 zk 是有好处的。
今天的主题是分布式集群方面的,实时处理就打住了。
关于集群存储顺带着提一些其他的方面的东西。
这篇文章的重点是分布式文件系统的共享层高可用 CTDB 实现讲解,而重点中的重点是 CTDB 的优化。
集群存储考量点
关于分布式集群存储这一块,其实考量点就那么几个,说都都是通用的。
系统性能
性能是比较重要的一方面,这点我就不多说了,总体来说开源的分布式文件系统都需要经过一定的优化,而且性能针对的文件大小性能要求也不一样。
总体来说,内核态的分布式文件系统比用户态分布式文件系统的性能会好上不少。测试过不少 DFS 的,目前为止 Lustre 还是不可超越的存在,包括几种商用的 DFS 。
集群的规模
分布式文件系统能够支持的集群规模也是一个很重要的考量点。支持的规模影响了它的使用环境。
如果是小规模的私有云之类的,通用存储中 mfs 之类的就很容易,但是集群的数量一多就支撑不下去。就目前通用的 DFS 来说, lustre/glusterfs 对规模的支撑性还是不错的,而 moosefs 就有点小差距了。
数据高可用
集群的高可用即集群在非常规的情况下能够正常提供存储服务,通常分为几个层次的高可用,从下到上依次是存储磁盘级的高可用,服务器级别的高可用,以及共享层的高可用。
磁盘的高可用即损坏部分磁盘不影响集群的正常使用,通常使用 raid10 、 raid01 或者 raid5 等 raid 方式来保护磁盘数据,但是整个服务器节点宕机则 raid 就没有用处,这就涉及到了分布式文件系统层的高可用。
即损坏一个节点不会影响系统的正常使用,就目前来看开源的这些分布式文件系统都使用数据副本的方式来提供文件系统层的高可用,例如 HDFS , MFS 、 GlusterFS 等 (Lustre 不提供数据冗余保护 ) ,都提供多副本的数据冗余保护。
最后一个就是共享层的高可用。说到共享层的高可用,这就得说到通用集群存储的使用方式,通常 DFS 会提供一个统一命名空间给用户,当然,这也是分布式文件系统的设计最根本的目的,将零散的存储集合起来。但通常最后都是通过共享协议的方式将这个存储提供给用户,例如 SMB 、 NFS 或者 FTP 之类。若提供全局统一存储空间的节点对外提供标准共享,但恰巧这个节点宕机了,这就会导致共享中断,这就涉及到了共享层的高可用。这正是这次的主题。
集群高可用 CTDB
共享层高可用
回到 2.3 的问题,在共享节点宕机的情况下如何保证共享连接不中断,或者说数据不中断呢?
通常的设计是提供全局命名空间的节点不直接对外提供共享,或者更确切地说不使用实际 IP 对外提供共享。通常几个节点对外提供一组虚拟的 IP ,而虚拟的 IP 与这些实际的节点 IP 有着相应的映射关系。与用户更紧密的虚拟 IP ,用户连接共享时是通过连接虚拟 IP ,他们并不关心实际节点是哪一个。
当某个实际节点宕机之后,对应的虚拟 IP 会转移到其他的实际节点上去,这一过程对于用户来说是透明的,不会影响共享的使用。
而 CTDB 正是承担了这一项工作。
安装
关于 CTDB 在集群中承担的角色,我这就不多说了,上面基本上已经描述的很清楚了。其实关于 CTDB 的安装我也不打算说太多,因为网上有不少安装流程,而且官网上也有详细的安装过程。
给出两个出处:
(1) 官网的《 Configuring CTDB 》:
(2) 贵哥的《基于开源软件构建高性能集群 NAS 系统》:
// 里头包含了 CTDB 的搭建过程,大家可以看一看,很有参考价值。
安装过程虽然不再提供,但有些注意事项还是需要说一说:
(1) 首先 CTDB 安装过程中一个很重要的环节就是,为 CTDB 的配置文件提供一个各节点能够共同访问的目录。因为 CTDB 的部分配置文件是需要各个节点共同维护的例如 ctdb 以及 node 文件,还有就是共享的一些临时状态锁文件等等。
(2) 其他诸如 smb 、 nfs 、 ftp 的配置文件放入其中虽然说不是必须的,但是有利于配置文件的维护,这就是为何在其他节点做软连接的形式给共享配置。
(3) 此外 public_addresses 这个配置文件不是必须共享的,根据官网的配置样式可以知道,它可以根据每个节点的网口情况,自己维护一份单独的 public_addresses 文件,这样的话就比较灵活。当然若集群规模足够大,服务器统一,为了方便管理,最好还是将该文件作为共享配置文件统一管理。
(4) CTDB 作为管理共享以及对外提供虚拟 IP 连接的组件,其虽然管理 SMB 、 NFS 以及 FTP 等的启停,监控共享服务的状态,但其内部并不集成这个共享,只是说将这些共享统一管理,所以服务的单独重启之类的操作不会影响。但是若想单独停止某一个共享服务则无法做到,因为 CTDB 会定时的对各个服务状态进行检测,若检测到服务异常,它会进行服务的重启操作。而这检测机制则将是下面的讲述重点。
优化
存在现象
因为个人实践是与 GlusterFS 搭配使用,所以个人实践发现的问题是以 GlusterFS 作为底层分布式文件系统产生的,在其他分布式文件系统上可能会出现该问题。
使用 CTDB 作为共享层高可用组件,底层分布式文件系统使用当前比较流行的 GlusterFS 文件系统,并通过 NFS 对外进行提供共享,在长时间的写文件操作,并且是多并发的情况下,会经常出现文件写中断,并且在系统压力越大的时候该情况出现的频率越频繁。
在实际使用中,文件的写中断的后果是非常严重的。
工作机制
经过追踪,发现导致文件中断写操作的原因是 NFS 服务进行了重启操作,再进一步追踪会发现导致 NFS 经常重启的正是 CTDB 。
CTDB 启动的时候对底层 NFS 、 SMB 服务起着监管的作用,其中很重要的一项就是定时的检测 NFS 、 SMB 的状态是否正常,若不正常它会在非人为干扰的情况下进行共享服务的重启操作,这就是为何 NFS 为何会经常重启的原因。
那为何 CTDB 会检测到 NFS 状态异常呢?根据实际追踪会发现,事实上 NFS 一直都是正常运行。那为何还是会出现 NFS 重启呢?
这就涉及到了 CTDB 对于 NFS 的状态检测机制了。
CTDB 对 NFS 的状态检测机制:
先来看一段 shell 脚本代码。
######################################################
# check that a rpc server is registered with portmap
# and responding to requests
# usage: ctdb_check_rpc SERVICE_NAME VERSION
######################################################
ctdb_check_rpc ()
{
progname="$1"
version="$2"
if ! ctdb_check_rpc_out=$(rpcinfo -u localhost $progname $version 2>&1) ; then
ctdb_check_rpc_out="ERROR: $progname failed RPC check:
$ctdb_check_rpc_out"
echo "$ctdb_check_rpc_out"
return 1
fi
}
// 相信大家看代码注释也可以看出来这段代码是干什么的,它正是 CTDB 监控服务是否在线的一个函数,它检测不止 nfs 这一项,只是在检测 nfs 时出现了问题而已。
如上所示,他在检测 nfs 服务时,是通过 rpc 服务进行检测的, rpc 服务会对检测的服务进行发包探测,若在时间窗口内回返一个不可连接、或者是超时包时,它会认为检测服务是处于异常状态的。
而如上存在的现象就是因为在 CTDB 默认的 20 秒左右心跳检测中,对于 NFS 的 rpc 探测导致了超时,所以 CTDB 认为 NFS 是非正常状态的。但是并不是说 CTDB 一检测到 rpc 探测超时就执行 NFS 重启操作,这样的话误操作的可能性会很大,这就涉及到了它的另一个机制。
CTDB 启动时会维护一个服务状态文件,例如 /var/lib/ctdb/failcount/nfs 文件,它记录了 NFS 服务 连续 检测超时或者失败的次数。
若 ctdb_check_rpc 函数检测到 NFS 正常,调用 ctdb_counter_init 函数对 nfs 的失败计数器 ( 记录文件 ) 进行清零,若不正常则调用 ctdb_counter_incr 函数对计数器加一,这样的话计数器中维护的就是 NFS 的连续失败次数。当达到了一定的设置上限之后, CTDB 就会调用 NFS 重启函数,进行重启操作。
真正对计数器进行检测是否到达重启值的函数是 ctdb_check_counter ,该函数会对预警设置文件 /etc/ctdb/nfs-rpc-checks.d/20.nfsd.check 进行检测,默认的数值是 2 ,即连续检测到两次 NFS 的 rpc 探测失败后就执行 NFS 重启操作,重启后对计数器清零。
ctdb_check_counter () {
_msg="${1:-error}" # "error" - anything else is silent on fail
_op="${2:--ge}" # an integer operator supported by test
_limit="${3:-${service_fail_limit}}"
shift 3
_ctdb_counter_common "$1"
# unary counting!
_size=$(stat -c "%s" "$_counter_file" 2>/dev/null || echo 0)
if [ $_size $_op $_limit ] ; then
if [ "$_msg" = "error" ] ; then
echo "ERROR: $_limit consecutive failures for $_service_name, marking node unhealthy"
exit 1
else
return 1
fi
fi
}
以上就是整个 CTDB 在整个事件处理的过程,但还有一个问题,那就是为何 rpc 探测 nfs 会经常出现响应超时的情况,这就涉及到了系统的负载, GlusterFS 本身 系统延时 就高,当高并发时,系统压力大, rpc 探测就会经常出现响应超时的情况。
优化方案
优化方案,还不如说是解决方案,能解决实际问题的方案。如 3.3.2 已经知道了 CTDB 的操作机制,那么针对它的实现有如下两种方案。
(1) 降低 NFS 重启几率,即尽量的使连续出现 NFS 响应超时的次数减少。
具体的方案为修改计数器判定上限,即 /etc/ctdb/nfs-rpc-checks.d/20.nfsd.check 文件,将上限增大,这样出现连续响应失败的可能性就降低了。就个人实践经验来说,目测修改到 4~5 左右就能避免这种情况的发生。
但这种方案只是治标不治本,虽然避免了 NFS 意外重启,但也降低了系统对故障检测的灵敏性,因为若 NFS 服务真的出现了故障需要重启,它需要四次或者说五次的心跳检测才会让 NFS 重启,这就耽误了时间,在实际生产中是非常致命的。
好吧,既然不满意,咱来点猛的吧,换个姿势再来一次。
(2) 修改 NFS 状态判定方式:
从 3.3.2 的 ctdb_check_rpc 函数中可以看到 rpcinfo -u localhost $progname $version 语句是判断 NFS 服务是否处于正常状态的根本所在,也是引起判断失误的根本所在。在 GlusterFS 系统读写压力大的时候,个人进行过手动的 rpc 探测,有时候一分多钟都返回不了结果,并且最终返回的结果还是响应超时,总之就是非正常结果。
既然不好用,为何要用它?从根本入手,直接将这种判定方式修改掉。判断 NFS 是否在线不一定只依赖 RPC 探测,可以使用 service nfs status 检测返回结果,或者更深一步分别检测 NFS 的子进程,例如 rpc.svcgssd 、 rpc.mountd 、 nfsd 以及 rpc.rquotad 四个子进程。
当然,只要能够判断 NFS 服务在线即可,不一定说一定要用上述这种方法,只要有更好的,更准确的都可以。这种修改优化的方式,更加准确的判断出 NFS 是否真正的出现故障,才能在最短的时间内进行 NFS 重启操作。
补充
除了上面的一些特点,本人还将 CTDB 与其他一些分布式文件系统搭配测试,发现了一些比较特殊的情况。
(1)CTDB不支持用户态的分布式文件系统的文件读写高可用。
共享层高可用包括两个方面,一个是在服务节点宕机时,已经连接的共享是否会断开,是否需要重新连接;两一方面是正在执行的文件写过程是否会中断。
CTDB 在与 GlusterFS 以及 MFS 等用户态的文件系统搭配使用时,是不支持文件层的高可用的,但是与内核态的分布式文件系统,例如 Lustre 搭配使用时,则不会出现文件写中断的现象。这可能是用户态的文件系统在文件写过程中,在 fuse 层保存了文件句柄之类的相关信息,宕机之后信息丢失,导致了文件写中断。当然,这只是猜测,需要进一步验证。