NFS宽限期及其在nfs-ganesha中的实现(基于版本V3-stable)

412 阅读6分钟

宽限期的理解

  1. 背景:在nfsv4协议之后,客户端与服务端的交互都是有状态的,当nfs服务端发生重启,客户端上已经持有的一些状态(打开的文件,委托,文件锁,客户端租约等等),希望在服务端重启后能够得到恢复,避免状态被其他客户端抢占而导致冲突;如果客户端状态丢失,可能会导致一些问题,例如文件锁丢失可能会导致数据一致性问题;委托丢失可能会导致性能下降;租约丢失可能会导致客户端无法维持锁或委托;打开的文件状态丢失可能会导致性能下降等等
  2. 基本原理:ganesha服务端在启动后的一段时间内,只允许原客户端去恢复之前持有的一些状态,而不允许客户端去执行新的状态变更操作(如新打开文件,新加文件锁),这段时间称为宽限期(grace period);在宽限期结束后,才允许新的状态变更操作

实现

  1. 在ganesha中,采用了一个全局静态变量grace_status来表明服务端是否处于宽限期
    • 第一位为1(GRACE_STATUS_ACTIVE)表明在宽限期
    • 第二位为1(GRACE_STATUS_CHANGE_REQ)表明处于“需要状态变化期”
    • 第三位开始到高位记录的是grace状态的引用计数,每次加减引用的步长为4(GRACE_STATUS_REF_INCREMENT),即第3位到高位不为0则表明引用计数不为0(GRACE_STATUS_COUNT_MASK)
  2. 网关在两种情况下会尝试启动宽限期状态(nfs_start_grace):
    • 网关刚开始启动时会将grace_status置为1
    • 通过dbus管理命令(网关集群模式)
  3. nfs_start_grace的实现逻辑
    nfs_start_grace
    {
        //记录开始宽限期起始时间
        ret = clock_gettime(CLOCK_MONOTONIC, &current_grace);
        
        /*
        1. 判断当前状态已经是GRACE_STATUS_ACTIVE,跳过后续步骤
        2. 判断当前状态引用数不为0,将状态位“需要状态变化期”置为1;否则尝试将状态位“宽限期”位置1
        3. 判断grace_status跟之前相比是否有变化,若无变化则退出,否则重复1,2,3逻辑,1,2,3步操作均为原子操作
        4. 以上操作做完后,有三种可能:
            - 当前状态已经是宽限期状态,跳出后续操作
            - 若之前状态不是宽限期状态,且状态引用不为0(此种情况一般是网关运行过程中执行了启动grace操作,比如手工执行,或者网关集群联动),则宽限期启动失败,退出;
            - 若是系统刚启动,则实际上状态引用必为0(只有与客户端有交互才有可能加引用),因此之前状态不是宽限期状态,此时“宽限期”状态设置成功,可以继续后续操作
        */
        
        // 加载重启前保存的clientids,用于后续的客户端状态恢复
        nfs4_recovery_load_clids(NULL);
    }
    
  4. 在宽限期是如何做状态回收的
    • 网关通过持久化目录加载重启前建连并完成协商的clientid,并放入全局clid_list列表(nfs4_recovery_load_clids)
    • 对于与服务端建连协商成功的客户端clientid(nfs4_op_setclientid_confirm),且判断在全局clid_list列表中,说明是恢复的clientid,标记clientid为允许回收(cid_allow_reclaim = true)
    • 在ganesha中,对于状态操作(打开文件,委托,加锁,设置属性等等)都需要进行网关是否处于宽限期的判断,来决定是否允许操作继续:
      • 当前网关处于宽限期,只允许状态回收(relaim,或者说恢复)操作,不允许新的状态操作
      • 当前网关不处于宽限期,不再允许状态回收操作,只允许新的状态操作
  5. 以open操作流程为例:
nfs4_op_open
{
    ...
    res_OPEN4->status = open4_validate_claim(data, claim, clientid, &grace_ref);
    ...
    
    if (grace_ref)
	nfs_put_grace_status();
}
// 在nfs4_op_open中调用open4_validate_claim判断是否允许claim
open4_validate_claim
{
    bool want_grace = false;
    /* 这里是判断是否底层自身就支持grace,如果支持则不需要ganesha网关这一层去支持,目前实现中默认都不支持,因此take_ref为true */
    bool take_ref = !op_ctx->fsal_export->exp_ops.fs_supports(
		op_ctx->fsal_export, fso_grace_method);
    switch (claim)
    {
        case CLAIM_NULL:
            // CLAIM_NULL表明是新open,若是v4.1及以上且判断relaim未完成,则置NFS4ERR_GRACE即不允许新打开文件
        case CLAIM_PREVIOUS:
            // CLAIM_PREVIOUS表明是回收之前open的fh,若不是恢复的clientid,或是v4.1及以上且判断relaim已完成,则置NFS4ERR_NO_GRACE,即对于当前客户端宽限期已结束不允许在回收;否则需继续判断是否是在宽限期:want_grace = true;
        ...
    }
    
    // 看当前grace状态是否与want_grace一致,不一致则返回false
    if (take_ref)
    {
	if (nfs_get_grace_status(want_grace))
	{
            *grace_ref = true;
	}
	else
	{
            status = want_grace ? NFS4ERR_NO_GRACE : NFS4ERR_GRACE;
	}
    }
    else
    {
	*grace_ref = false;
    }
}

nfs_get_grace_status和nfs_put_grace_status:
1. 相当于是给当前的grace状态加引用,引用加成功则返回true,引用加失败则返回false
2. 加引用成功需要两个条件:
 - 期望的grace状态(want_grace)与当前网关实际的grace状态(grace_status&1)相同
 - 当前网关不处于grace状态变更时期
3. 当加引用成功后,grace状态的引用数不为零,则不允许网关变更grace状态
4. 因此在open操作中,当加grace状态引用成功后,执行完open后续操作,会调用nfs_put_grace_status对grace状态减引用

综合以上分析:

  • 对于新open,若当前在宽限期,会返回NFS4ERR_GRACE,否则允许继续open操作
  • 对于reclaim open,若在宽限期,允许继续open操作,否则返回NFS4ERR_NO_GRACE
  1. 宽限期的结束,reaper后台线程中循环调用reaper_run,最后进入nfs_try_lift_grace
nfs_try_lift_grace
{
    ...
    timeout = current_grace;  // 宽限期起始时间
    timeout.tv_sec += nfs_param.nfsv4_param.grace_period; // 宽限期阈值(90s)
    // 判断超过阈值则应结束宽限期(in_grace = false)
    in_grace = gsh_time_cmp(&timeout, &now) > 0;
    
    if (!in_grace) {
        /*
        1. 判断当前状态已经不是宽限期状态(GRACE_STATUS_ACTIVE),直接退出
        2. 将状态位“需要状态变化期”置为1(GRACE_STATUS_CHANGE_REQ,这样在客户端与服务端状态交互操作中均不会再加引用计数);
        3. 判断grace_status跟之前相比是否有变化,若无变化则退出,否则重复1,2,3逻辑,1,2,3步操作均为原子操作
        4. 以上操作做完后,有两种可能:
            - 当前状态已经不是宽限期状态,跳出后续操作
            - 已经将状态位“需要状态变化期”置为1(GRACE_STATUS_CHANGE_REQ)
        */
        
        // 判断grace_status引用计数为0则可以关闭宽限期
        nfs_lift_grace_locked
            // fs_clean_old_recov_dir:将fs_clean_old_recov_dir中的备份的记录清空
            - nfs_end_grace
            //关闭宽限期状态
            - cur = __sync_and_and_fetch(&grace_status,
			~(GRACE_STATUS_ACTIVE|GRACE_STATUS_CHANGE_REQ));
            
    }
    
}

后记

  1. nfs-ganesha作为通用nfs网关,可以对接各种存储后端,在一致性和故障恢复等等需要考虑与底层实现的联动,例如nfs-ganesha+cephfs的方案,cephfs其实有caps机制,当一个cephfs的客户端(nfs-ganesha+libcephfs)挂掉,客户端与mds的session断开,有一个session_timeout(默认300s),在这段时间内授予给原客户端的caps能保证不会授予给其他客户端,因此我理解fsal_ceph是支持grace的