|
补丁 6282725 hostname/hostid should be stored in the label 在导入池时引入了主机 ID 检测。
如果上次访问这个池的是另一个系统,那么这个导入操作将被拒绝(当然可以通过 '-f' 标志重写)。
这对于那些使用他们自己的群集(也就是所谓“穷人群集”)的人来说尤其重要。人们需要的是:
1)clientA 创建池(使用共享存储器)
2)clientA 重启/崩溃
3)clientB 强制导入这个池
4)clientA 启动完成
5)clientA 通过 /etc/zfs/zpool.cache 自动导入池
在这里,clientA 和 clientB 导入了同样的池,并且两者都可以对它写入——但是 ZFS 本身
不支持多个写入器,因此这两个客户端将很快破坏这个池,因为它们看到的池的状态不同。
既然我们在标签中储存了主机 ID,并且验证了导入池的系统是最后一个访问该池的系统,
那么上面提到的穷人群集的破坏则无法再发生。下面是一个使用 iSCSI 上的共享存储器的例子。
在这个例子当中,clientA 是 'fsh-weakfish',clientB 是 'fsh-mullet'。
首先,让我们在 clientA 上创建一个池(假设两个客户端都已经在 iSCSI 上设置完毕):
fsh-weakfish# zpool create i c2t01000003BAAAE84F00002A0045F86E49d0
fsh-weakfish# zpool status
pool: i
state: ONLINE
scrub: none requested
config:
NAME STATE READ WRITE CKSUM
i ONLINE 0 0 0
c2t01000003BAAAE84F00002A0045F86E49d0 ONLINE 0 0 0
errors: No known data errors
fsh-weakfish# zfs create i/wombat
fsh-weakfish# zfs create i/hulio
fsh-weakfish# zfs list
NAME USED AVAIL REFER MOUNTPOINT
i 154K 9.78G 19K /i
i/hulio 18K 9.78G 18K /i/hulio
i/wombat 18K 9.78G 18K /i/wombat
fsh-weakfish#
注意对 clientB 报告中的增强信息 'zpool import':
fsh-mullet# zpool import
pool: i
id: 8574825092618243264
state: ONLINE
status: The pool was last accessed by another system.
action: The pool can be imported using its name or numeric identifier and
the '-f' flag.
see: http://www.sun.com/msg/ZFS-8000-EY
config:
i ONLINE
c2t01000003BAAAE84F00002A0045F86E49d0 ONLINE
fsh-mullet# zpool import i
cannot import 'i': pool may be in use from other system, it was last accessed by
fsh-weakfish (hostid: 0x4ab08c2) on Tue Apr 10 09:33:07 2007
use '-f' to import anyway
fsh-mullet#
我们希望在 clientA 关机之前不要强制导入这个池。因此在重启 clientA (fsh-weakfish) 之后,
在 clientB (fsh-mullet) 上强制导入:
fsh-weakfish# reboot
....
fsh-mullet# zpool import -f i
fsh-mullet# zpool status
pool: i
state: ONLINE
scrub: none requested
config:
NAME STATE READ WRITE CKSUM
i ONLINE 0 0 0
c2t01000003BAAAE84F00002A0045F86E49d0 ONLINE 0 0 0
errors: No known data errors
fsh-mullet#
clientA 完成启动之后,我们通过 syslog 来看一下这个消息:
WARNING: pool 'i' could not be loaded as it was last accessed by another system
(host: fsh-mullet hostid: 0x8373b35b). See: http://www.sun.com/msg/ZFS-8000-EY
然后仔细检查,确认池 'i' 事实上没有被加载:
fsh-weakfish# zpool list
no pools available
fsh-weakfish#
之后核实一下,这个池从 clientB 的角度来看还没有被破坏,我们看到:
fsh-mullet# zpool scrub i
fsh-mullet# zpool status
pool: i
state: ONLINE
scrub: scrub completed with 0 errors on Tue Apr 10 10:28:03 2007
config:
NAME STATE READ WRITE CKSUM
i ONLINE 0 0 0
c2t01000003BAAAE84F00002A0045F86E49d0 ONLINE 0 0 0
errors: No known data errors
fsh-mullet# zfs list
NAME USED AVAIL REFER MOUNTPOINT
i 156K 9.78G 21K /i
i/hulio 18K 9.78G 18K /i/hulio
i/wombat 18K 9.78G 18K /i/wombat
fsh-mullet#
很好,永别了,穷人群集的破坏。
我想指出的一个细节是,您需要对强制导入一个池的*时间*多加注意。例如,
如果您在重启 clientA *之前*在 clientB 上强制导入一个池,那么破坏仍然会发生。这是因为
命令 reboot(1M) 会完全占用系统,这意味着它将取消所有文件系统的挂载,而取消
文件系统的挂载将对池写入一些数据。
您可以使用 zdb(1M) 查看标签上的最新信息:
fsh-mullet# zdb -l /dev/dsk/c2t01000003BAAAE84F00002A0045F86E49d0s0
--------------------------------------------
LABEL 0
--------------------------------------------
version=6
name='i'
state=0
txg=665
pool_guid=8574825092618243264
hostid=2205397851
hostname='fsh-mullet'
top_guid=5676430250453749577
guid=5676430250453749577
vdev_tree
type='disk'
id=0
guid=5676430250453749577
path='/dev/dsk/c2t01000003BAAAE84F00002A0045F86E49d0s0'
devid='id1,ssd@x01000003baaae84f00002a0045f86e49/a'
whole_disk=1
metaslab_array=14
metaslab_shift=26
ashift=9
asize=10724048896
DTL=30
--------------------------------------------
LABEL 1
--------------------------------------------
...
(2008-05-21 20:36:55.0/2008-05-21 19:24:24.0)
Permalink
Trackback: http://blogs.sun.com/erickustarz/zh/entry/%E7%BB%93%E6%9D%9F_%E7%A9%B7%E4%BA%BA%E7%BE%A4%E9%9B%86_%E7%9A%84%E7%A0%B4%E5%9D%8F
NFSv4 的大文件句柄 —— NFSv2 的末日
通过 Solaris NFS 服务器创建的文件句柄看起来是什么样子的呢?如果我们看一看 fhandle_t 结构, 就可以看到它的布局:
struct svcfh {
fsid_t fh_fsid; /* filesystem id */
ushort_t fh_len; /* file number length */
char fh_data[NFS_FHMAXDATA]; /* and data */
ushort_t fh_xlen; /* export file number length */
char fh_xdata[NFS_FHMAXDATA]; /* and data */
};
typedef struct svcfh fhandle_t;
其中 fh_len 表示 fh_data 中有效字节的长度,同样,fh_xlen 是 fh_xdata 的长度。注意,NFS_FHMAXDATA 以前是这样定义的:
#define NFS_FHMAXDATA ((NFS_FHSIZE - sizeof (struct fhsize) + 8) / 2)
为了避免混淆,我移除了 fhsize,并将它缩短为:
#define NFS_FHMAXDATA 10
好了, 但是 fh_data 是从哪里来的呢?它是本地文件系统的 FID (通过 VOP_FID)。fh_data 表示文件句柄的真实文件,fh_xdata 表示导出的文件/目录。所以对于 NFSv2 和
NFSv3, 文件句柄基本上都是这种形式:
fsid + file FID + exported FID
NFSv4 也几乎完全一样,不同之处是在结尾处添加了两个字段。您可以在 nfs_fh4_fmt_t 中看到它的基本布局:
struct nfs_fh4_fmt {
fhandle_ext_t fh4_i;
uint32_t fh4_flag;
uint32_t fh4_volatile_id;
};
fh4_flag 用于从“常规的”文件中区分指定的属性,fh4_volatile_id 目前仅仅用做测试目的——当然是为了测试各种可变的文件句柄。因为 Solaris 没有本地文件系统,也没有稳定的文件句柄,所以我们并不十分需要使用 fh4_volatile_id。
好,让我们回到 NFS_FHMAXDATA 中那个不可思议的“10”,那里到底发生了什么? 将这些字段加起来, 您得到: 8(fsid) + 2(len) + 10(data) + 2(xlen) + 10(xdata) = 32 字节。NFSv2 的协议限制是什么?——查一下 "FHSIZE" 就知道了。目前 Solaris 服务器将文件句柄限定为 10 字节的 FID 只是为了与 NFSv2 兼容。注意,为了正常运行,特意将这个限制带到本地文件系统中。检验 UFS 的
ufid:
/*
* This overlays the fid structure (see vfs.h)
*
* LP64 note: we use int32_t instead of ino_t since UFS does not use
* inode numbers larger than 32-bits and ufid's are passed to NFS
* which expects them to not grow in size beyond 10 bytes (12 including
* the length).
*/
struct ufid {
ushort_t ufid_len;
ushort_t ufid_flags;
int32_t ufid_ino;
int32_t ufid_gen;
};
注意,NFSv3 的协议限制是 64 字节,NFSv4 的协议限制是 128 字节。所以这两种文件系统理论上可以发布大文件句柄,但是它们对现有数据还不能这样做,这主要有两个原因: 首先还没有这样的需求,更重要的是,在做出任何改动之前,文件句柄必须在通信上保持一致。如果这个问题得不到解决,那么当更长的文件句柄被引入时,所有有活动挂载点的客户端将得到一个 STALE 错误。想象一下,一个服务器在 NFSV3 上为一个文件发放一个 32 字节的文件句柄,然后这个服务器被升级,现在可以发放 64 字节的文件句柄——即使所有多出的 32 字节都被设为 0,那也是一个不同的句柄,客户端将视其为一个 STALE 引用。这种情况下,使用强制取消挂载或者重启客户端可以解决这个问题。但是,为了一个简单的(也应该是无害的)服务器升级,而强制让所有活动的客户端执行一些手动的管理操作似乎有点可笑。
所以我的博客标题应该是:如何让文件句柄变得更大——这与我上文几乎是矛盾的。要注意的关键一点是,那些从没有通过 NFS 提供的文件也绝不会有一个为它们生成的文件句柄,所以它们可以是协议允许的任意长度,我们不用担心 STALE 文件句柄问题。
如果您不熟悉 ZFS 的 .zfs/snapshot,很快会有一个关于它的博客文章。通常它会在根目录的 "main" 文件系统下放置一个 .zfs 文件,所有创建的快照将放到 .zfs/snapshot 下的命名空间中。示例如下:
fsh-mullet# zfs snapshot bw_hog@monday
fsh-mullet# zfs snapshot bw_hog@tuesday
fsh-mullet# ls -a /bw_hog
. .. .zfs aces.txt is.txt zfs.txt
fsh-mullet# ls -a /bw_hog/.zfs/snapshot
. .. monday tuesday
fsh-mullet# ls -a /bw_hog/.zfs/snapshot/monday
. .. aces.txt is.txt zfs.txt
fsh-mullet#
通过对 .zfs/snapshot 的介绍,我们现在左右为难 —— 要么只使可以做“镜像挂载(mirror mounts)”的 NFS 客户端访问 .zfs 目录,要么为 .zfs 下的文件增加 ZFS 的 fid。“镜像挂载”将允许我们执行技术上正确的解决方案:为 "main" 文件系统及其所有快照设置一个唯一的 FSID。这就需要 NFS 客户端跨越服务器挂载点。后一种选择是,为 "main" 文件系统及其所有快照设置一个 FSID。这意味着 "main" 文件系统下相同的文件及其任意一个快照看起来是一样的 —— 所以 NFS 上 "cp" 这样的命令不会喜欢它。
“镜像挂载”是我们的专有词汇,它是为了让客户端跨越服务器文件系统的边界 —— 正如 FSID (文件系统标识符)规定的那样。这在 NFSv4 中是完全合法的(请参见 rfc 3530 中的以下章节:"7.7 Mount Point Crossing" 和 "5.11.7. mounted_on_fileid")。NFSv3 并不是真正允许这个功能(请参见这里 "3.3.3 Procedure 3: LOOKUP - Lookup filename")。尽管需要一些技巧,我还是确信它能够实现(也许通过自动挂载程序)。
镜像挂载的问题是,还没有人曾经真正实现过它们。所以如果我们执行技术上更加正确的解决方案,为 "main" 的本地文件系统及其全部快照设置一个唯一的 FSID,只有 Solaris Update 2(?)NFSv4 客户端能够在初始传递的 ZFS 上访问 .zfs 文件。这看起来很傻。
如果我们能够在这个唯一的 FSID 上稍做让步,那么所有现存的 NFS 客户端都可以访问 .zfs 文件。这看起来更有吸引力。哦,等一下,有一个小问题。我们希望至少 "main" 文件系统中文件的文件句柄与快照的文件句柄是不一样的,这样可以保证 NFS 不至于完全混乱。一个小问题是,我们现在发放的文件句柄是 32 字节的 NFSv2 协议限制(见上文)下的最高值。如果我们向文件句柄中添加任何特殊位(如一个快照标识符),那么 v2 就无法处理了。
您知道吗?糟糕的 v2。说真的,这真是个老古董,早该到一边去了。既然快照标识符并不需要被添加到 "main" 文件系统中,non-.zfs 快照文件的 FID 将保留同样的大小,使之能在 NFSv2 的限制之内。 这样我们就可以在 NFSv2 上访问 ZFS,只是会被拒绝。这就是 ZFS 的好处。
fsh-weakfish# mount -o vers=2 fsh-mullet:/bw_hog /mnt
fsh-weakfish# ls /mnt/.zfs/snapshot/
monday tuesday
fsh-weakfish# ls /mnt/.zfs/snapshot/monday
/mnt/.zfs/snapshot/monday: Object is remote
fsh-weakfish#
那么 v3 和v4 的情况如何?既然 v4 是 Solaris 所默认的,并且它的代码相对简单,那么现在我改变 v4 来处理大文件句柄。NFSv3 很快就会推出。我们除了通过 fhandle4_t 将它扩展了一点点之外,它的结构基本上与 fhandle_t 相同。
/*
* This is the in-memory structure for an NFSv4 extended filehandle.
*/
typedef struct {
fsid_t fhx_fsid; /* filesystem id */
ushort_t fhx_len; /* file number length */
char fhx_data[NFS_FH4MAXDATA]; /* and data */
ushort_t fhx_xlen; /* export file number length */
char fhx_xdata[NFS_FH4MAXDATA]; /* and data */
} fhandle4_t;
因此,唯一的区别是 FID 最多可以有 26 字节而不是 10 字节。那么,为什么是 26?NFSv3 的协议限制是 64 字节。如果我们需要为 NFSv4 提供比 64 字节更大的文件句柄,也很容易修改 —— 只要创建一个适用于更大 FID 的新结构,并且应用到 NFSv4。为什么将来做这个修改比过去更容易呢?为了使 NFSv4 文件句柄向后兼容,我需要做的部分工作是:当文件句柄确实是 XDR'd 时,我们需要对它们进行解析,这样过去以 10 字节 FID(基于 fhandle_t 结构)发放的文件句柄将在 10 字节 FID 的基础上继续发放,但与此同时,也允许返回比 10 字节更大的 FID的VOP_FID()(如 .zfs)。因此,根据本地文件系统的需求,NFSv4 将返回不同长度的文件句柄。
查看老代码 xdr_nfs_resop4(了解到文件句柄作为一个连续字节集是安全的),只需执行以下操作:
case OP_GETFH:
if (!xdr_int(xdrs,
(int32_t *)&objp->nfs_resop4_u.opgetfh.status))
return (FALSE);
if (objp->nfs_resop4_u.opgetfh.status != NFS4_OK)
return (TRUE);
return (xdr_bytes(xdrs,
(char **)&objp->nfs_resop4_u.opgetfh.object.nfs_fh4_val,
(uint_t *)&objp->nfs_resop4_u.opgetfh.object.nfs_fh4_len,
NFS4_FHSIZE));
现在,我们并不是简单地做一个 xdr_bytes,而是使用了 fhandle_ext_t 模板并且内部永远留有 26 字节 FID 的空间。但是对于 OTW,我们跳过了一些字节,这取决于 fhx_len 和 fhx_xlen,请参见 xdr_encode_nfs_fh4。
2005 年关于文件句柄的内容,这些就足够了。
(2008-05-21 20:37:29.0/2008-05-21 18:24:23.0)
Permalink
Trackback: http://blogs.sun.com/erickustarz/zh/entry/nfsv4_%E7%9A%84%E5%A4%A7%E6%96%87%E4%BB%B6%E5%8F%A5%E6%9F%84_nfsv2_%E7%9A%84%E6%9C%AB%E6%97%A5
|