[原创][涅磐系列]文件系统近实时镜像工具及其高可用应用
时间:2007-10-15
来源:互联网
https://sourceforge.net/projects/crablfs/
用 http 好像快一些:
http://sourceforge.net/projects/crablfs
相关的文档可以在:
http://crablfs.sourceforge.net/
找到。其中与文件系统实时镜像有关的部分在:
http://crablfs.sourceforge.net/#ru_data_man
这个项目是另一个项目的一个子项目,其最终目标,我想是做一个易管理的基于 LFS 的发行版,所以另外还有两个子项目。其中之一是我以前在论坛上发过的一个“基于用户的包管理系统”:
http://www.linuxsir.org/bbs/showthread.php?t=277172
旨在为从源代码编译的软件包提供一种方便安全的包管理方式,例如它提供交互模式,在交互模式下可以按照安装 LFS 的方式一步步进行,这样保留了学习的过程;而在交互模式下输入的安装指令可以自动记录,并在非交互模式下自动运行,保留这些模板文件,就得到了一个你自己的定制发现版,并且是和硬件平台无关的(最终目标如此,当然中间还是会有些小困难,不过应该不难克服)。
而这个项目,是为了提供一些方便系统管理的工具集,比如备份等。在这里,我要讨论的就是一种备份方式:实时镜像加轮转。这里考虑的主要是软硬件资源比较有限的情况(穷呀),是一种比较经济的解决思路,如果你有钱,使用商业的阵列和软件也许更好。
当然,所有这些工具集,包括前面的包管理器,其实当然也可以用在其他任何发行版上面,比如我就在自己的 RHEL4 的环境中应用了这些工具。
所提供的文档都是英文的,在代码中的注释和内部文档也是英文的,因为在一开始编写的时候,要在不同的机器上工作,我自己的 LFS 系统没有加 GBK 支持,老是出乱码。而且中文编码本来就是个老大难的问题了,我自己也有些不同的想法,所以索性写英文的,在翻译中文文档,并利用 gettext 处理内部文档,一方面也提高下自己的英文水平。
由于比较忙碌,我也只能挤时间来做这个事情,所以只能一点一点的写。最后形成的文档,则会加入到
http://crablfs.sourceforge.net/
由于本人水平和经验有限,难免纰漏和错误,欢迎多多指教。
谢谢。
作者: Chowroc 发布时间: 2007-10-15
只好等有相关中文说明的再看看了。
作者: youbest 发布时间: 2007-10-15
备份总是系统管理中最重要的一件事情,目前看来,无论是商业的还是开源的,
都有许多解决方案。
也许是由于我个人的肤浅吧,目前我没有使用过这样的商用软件,只知道不会
便宜,而且就我使用商业软件的经验来说,总觉得难以找到真正合适的,并且
我也不希望被绑定在某种昂贵的硬件设施上。
那么来看一看文件系统备份的问题。目前来说,对数据库的备份只能采用不同
的方法,并且已经有一些比较好的方案,所以这里不予考虑。
似乎编写一个脚本定时的运行``tar``看上去不错,但是这些归档文件将很快失去
易管理性,因为无法很容易的分别出在归档中加入了什么,同时,针对文件系统
的不同部分,可能会生成不同的归档文件。
“简单 tar”的另一个问题是不利于实现增量和差分备份,尤其是 tar 无法找出
被删除的文件,这将导致在恢复时文件系统里塞满了过时的废品(也许还有一些
敏感的东西)。这个问题可以利用``find``来得到一个低效率的解决方案,但是
还是有问题:
最重要的是:你仍然将被一个拥有多于上百万的文件/目录的真实的生产系统卡住
,因为仅仅是压缩/解压这些归档文件都是非常非常慢的(也比较消耗系统资源)...
事实上,仅仅是遍历一次它的文件系统都要耗费大量时间。在这种情况下,恢复
时的效率和易用性都没有被很好的考虑。
因此,利用现代高速、巨大并且便宜的磁盘,一些同步的机制被应用于备份和
恢复,例如``rsync``, ``amanda`` ... 同时利用 Linux 的 hard link 能力,
你可以为每一个同步点保留一个完全备份,但实际上却只是一个增量变化(可以
认为这时一种象快照的方式,通常利用轮转 rotate 实现)。
有一个 ruby 程序``pdumpfs``同时利用了 rsync 和 rotate(``pdumpfs-rsync``)
,我在 FreeBSD 上用过一段时间。
利用这种工具,我们可以使用前数小时之内的数据来急救一个崩溃的系统,例如,
如果备份系统如下:
```
+----------+ +----------+
| worker | --- rsync ---> | rescue |
+----------+ +----------+
那么就可以使用 "rescue" 主机来代替 "worker"主机,通常只需要在 "rescue"
主机上做一些符号链接就可以了。
一种更经济的拓扑结构是这样的:
```
+----------+
| worker | --- rsync -----------
+----------+ |
...... |
|
+----------+ |
| worker | --- rsync -----------
+----------+ |
V
+----------+ +----------+
| worker | --- rsync ---> | backup |
+----------+ +----------+
| |
[take_over] |
| |
V |
+----------+ |
| rescue | <------------------ NFS
+----------+
利用这种模式,你可以得到一个缓冲器,因此可以得到更多的时间来处理和恢复
崩溃的系统。很显然,这种模式也非常经济,因为你可以仅仅使用两台主机就
备份和救护(高可用)多于三台的主机(多对一)。
这里之所以使用 NFS 是因为直接利用拷贝来恢复将会是一个及其消耗时间和资源
的方式,因此是不可行的。例如,一个真实的在线生产系统,使用 SCSI 磁盘,
100 MBit 网卡和网线,50GB 的数据/大约 200 万文件/目录(其中目录数量为 10
万左右),将需要超过两个小时才能把所有文件拷贝回来(可以建立一个
SNMP/Cacti 来观察这个过程)。
尽管 SCSI 接口是
``target0:0:0: FAST-80 WIDE SCSI 160.0 MB/s DT (12.5 ns, offset 62)``
(dmesg 输出),这里 MB/s 是 MBps(MBits/s),所以 3 MBytes/s 实际上是在获取
(拷贝)这些大量的小文件时可以预期的上限,而 30 MBits/s 则是可预期的网络
带宽的上限,在这种情况下,实际的瓶颈在磁盘 I/O 上。
如果传输一个有数 GBytes 的大文件,情况则完全不同:磁盘 I/O 可以超过
11 MBytes/s 而网络带宽是 98 MBits/s,所以这时网络成为了瓶颈,此时磁盘和
网络的性能都可以认为被充分利用了。
我们必须考虑前面那种情况,因为那是在生成系统中我们通常遇到的情况,计算
一下时间:
```
sh$ echo "50 * 1024 / 3 / 60 / 60" | bc -l
4.74074074074074074074
```
这意味着从备份中恢复需要超过 5 小时的时间。即便将磁盘 I/O 提高一倍:
```
sh$ echo "50 * 1024 / 6 / 60 / 60" | bc -l
2.37037037037037037037
```
仍然要花费超过 2 个小时。这完全不能被接受。
所以关键是:**尽可能消除或减少在救护时的拷贝和遍历文件系统的操作**
在小文件和大文件直接巨大的差别源自于文件系统自身的原理和结构。以 Ext3fs
为例,其物理磁盘被分割成很多组(groups),因此元数据(metadata)以及目录和
文件散布在磁盘的各个部分,这对于随机存储是有利的,但在遍历这样的操作中,
内核不得不取得所有的元数据,因此需要耗费大量的时间在磁盘寻道上。而大
文件则通常在磁盘上有连续的数据块。
也许你会考虑使用 RAID,不过我看,如果考虑到写速率,那么 RAID10 也只能
预期两倍的速度,则仍然需要超过一个小时的时间。这还只是考虑拷贝的时间,
你可能还需要一些其他的操作,例如改变文件的属主和权限,这同样需要递归的
遍历。也许还有其他一些辅助操作 ...
目前我还不是太清楚 **Reiserfs**,但我不期望 5 倍以上的提示,即使利用
RAID,我想加上相关操作,一个小时总是需要的。
然而即使使用上面提到的救护方式,你仍然会损失自上一次 rsync 运行后的所有
数据,因为:
rsync 机制是不完美的(当然这个世界上也没有什么是完美的),因为 rsync 不是
实时的。每一次调用 rsync,它必须取得一个需要传输的文件的列表,而要获取
这个列表,它需要对源端的文件系统做一次遍历,比较每一个源文件和目标文件
的时间戳并执行相应操作。随着文件系统越来越大,越来越多的时间和资源将被
耗费在同步上,你必须增加两次运行的间隔,否则系统可能会进入下旋状态,
但是你本意上应该是要减小这个间隔,因为更大的时间间隔意味着损失更多的
文件。在一个关键的服务上,这样的损失可能无法接受。
rsync 另外有一个 "vanished" 问题,在得到列表和进行实际文件传输之间,源
文件系统可能改变,一些目录/文件可能被创建、更改,或者被删除,前者不是
一个很重要的问题,但后者可能是。例如 pdumpfs-rsync 可能会在这样的
"vanished" 的地方终止,留下余下的文件没有被同步。不过现在的 rsync 应该
已经足够健壮可以简单的忽略这些警告。
无论你使用哪种机制,tar/find 或 rsync, ...,仍然有一个挑战:通常情况下
你可能只想备份文件系统的一个部分,同时排出其中的一些部分,例如有 web
站点的 php 程序产生的大量的 cache 文件,...; 这些部分的识别应该易于管理
,最好是在你第一次指定之后能够有计算机自动完成。
到目前为止,针对文件系统的实时镜像,我还没有找到一个开源的解决方案,
所以我自己写了一个,希望对你们有用。(事实上,就在昨天(**9 月 18 日**,
2007),我发现了几个,例如商业的 PeerFs,以及开源的 DRBD,目前我还不是
非常了解它们,只有一些概念,我会找个时间学习和实验它们,看看有没有更好
的解决方案)。
必须说明的是,这个 C/S 应用程序,我称之为 ``mirrord/fs_mirror``,在
``cutils`` 这个包里面,并不是一个完全绝对的实时备份工具,它只是"近实时"
,也就是说,在一般的情况下(正常的系统负载和磁盘 I/O,以及足够的网络带宽
),在运行 mirrord agent 的 server 主机和运行 fs_mirror 的备份客户端之间
,会有一个一般少于 1s 的延迟,而其消耗的系统资源基本上可以忽略。详细的
细节在 "[Design #ru_data_man_design]" 一节。
很明显,到目前为止,mirrord/fs_mirror **只适用于拥有小文件的文件系统**,
对于频繁变动的大文件例如日志文件和数据库,已经有许多其他的应用了。
作为总结,看一下备份和恢复包含的几个方面的问题:
+ 通常有两种类型的备份:镜像或者说热备份,以及周期性的备份。
为了使恢复过程尽可能快,镜像是必要的。其另一个作用是应付硬件故障(尽管
RAID 可以处理磁盘故障,但并不能处理 CPU/内存/电源/... 故障等,而为所有
这些设备都配备冗余又过于昂贵)。
但是镜像不能解决:误操作(可能有上千个用户的上千个子目录的数据需要备份,
也许某一天,某个用户告诉你他/她想要回滚到几天前的状态)、病毒或入侵。
如果其中之一确实发生了,那么你只能依赖于周期性的备份档案。
所以备份应该同时包括这两个方面,而这两个方面都可以用 mirrord/fs_mirror
来实现。
+ 为了更快的恢复,文件的拷贝和目录遍历都应该尽可能限制,所以最好的方法
是直接用救护主机替换崩溃的主机,或者从备份主机中导出相应的目录和文件
系统以缓冲压力,因此可以使用在上面提到的 rsync 相同的拓扑结构,细节在
后续章节中讨论(如果你只关系如何使用 mirrord/fs_mirror,可以直接跳到
"[Usage Manual #ru_data_man_usage]" 一节)。
+ 可管理性非常重要,你可能希望只备份整个文件系统的若干部分,区别它们(
例如与平台相关和平台无关的)。在被包含的部分,仍然可能希望排除一些用处
不大的内容。mirrord/fs_mirror 通过``fs_info``模块提供了这个功能。
作者: Chowroc 发布时间: 2007-10-15
谢谢。
作者: Chowroc 发布时间: 2007-10-15
然后,看了一下没太看明白,和快照有什么区别?
作者: youbest 发布时间: 2007-10-15
作者: Chowroc 发布时间: 2007-10-15
下午就看到了这文章的,当时w3m字符下好像无法回帖。晚上才把桌面装上。
作者: tfkdmwmqtr 发布时间: 2007-10-16
的基本使用可能更好。不过为了更好的理解,先粗略地解释一下其基本原理。
mirrord/fs_mirror 利用了最近 Linux 内核(从 2.6.12 开始)提供的一种新特性,
称之为 inotify,它可以监控文件系统发生的变化,并向上层调用它的应用程序
抛出事件,这些应用捕捉到这些事件以后,可以查出是那些文件发生了那些变化,并
进行相应的操作,因为避免了遍历和比较,又是由内核来实现的,所以是一个很轻量
级的解决办法。
mirrord/fs_mirror 就是利用这种特性,在两台主机之间做文件系统的镜像,
这种镜像因此可以非常接近实时状态,并且也不需要消耗太大的系统资源,
因此为备份和高可用提供了一种途径。
一些关于 inotify 的文档:
[Kernel Korner - Intro to inotify #http://www.linuxjournal.com/article/8478]
[Monitor Linux file system events with inotify #http://www-128.ibm.com/developerwork...-inotify.html]
[Lightweight filesystem notifications #http://blog.printf.net/articles/2006...notifications]
Linux 内核里的源代码:
```
./include/linux/inotify.h
./Documentation/filesystems/inotify.txt
./fs/inotify.c
```
目前 mirrord/fs_mirror 利用了一个 pyinotify 项目,它是在 inotify 上包裹
了一个 Python 层作为模块。
http://pyinotify.sourceforge.net/
作者: Chowroc 发布时间: 2007-10-16
===安装===
包含 mirrord/fs_mirror 的包是``cutils``,是``ulfs``的一个子项目。uLFS
意思是"**Your** own customed and easily managable **LFS**(Linux From
Scratch) distrubution",它包含一些工具以达到这个目的,例如
[User Based Package Management(包管理系统) #ru_package_man],
Configuration Sharing Management(配置共享管理),以及这个文件系统备份和
近实时镜像(也许在将来我可以实现一个完全实时的应用)。
在需要备份的服务器上安装 mirrord,此时需要 Python 2.4 或更高的版本,
以及 pyinotify-0.7.1:
```
sh$ tar xfz Python-2.5.1.tar.gz
sh$ cd Python-2.5.1
sh$ ./configure --prefix=/usr
sh$ make
sh# make install
sh$ tar xfj pyinotify-0.7.1.tar.bz2
sh$ cd pyinotify-0.7.1
sh$ python setup.py build
sh# python setup.py install
```
在备份主机客户端运行 fs_mirror,只需要 Python 即可。
然后要安装 caxes-0.1.2,这也是``ulfs``的另一个子项目,可以在``cutils``
相同的下载页面取得,它包括我定义的若干辅助的数据结构,例如 Python Tree
```
sh$ tar xfz caxes-0.1.2.tar.gz
sh$ cd caxes-0.1.2
sh$ python setup.py build
sh# python setup.py install
```
最后,安装 cutils-0.1.1:
```
sh$ tar xfz cutils-0.1.1.tar.gz
sh$ cd cutils-0.1.1
sh$ python setup.py build
sh# python setup.py install --install-scripts=/usr/local/bin
```
===简单使用===
参考 [The Second Story #ru_data_man_design_second] 中的图,为备份或镜像
一个文件系统,首先需要告诉备份/镜像程序要备份些什么。可以运行``fs_info``
来做这件事:
```
server# fs_info -a t:/var/www/html system
```
这会将"/var/www/html"加入到备份/镜像包含列表中,并分配一个对文件系统
部分进行标识的标识名"system",这个标识在你第一次运行这个命令的时候创建
,你也可以使用其他名字,例如"www", "web", "work" 等...
只有最上层的目录是必需的,所以:
```
server# fs_info -a t:/var/www/html \
-a t:/var/www/html/include system
```
与前面那个命令的效果完全一样("/var/www/html")。
为了将一些目录/文件排除在备份/镜像之外,使用"x:"标志:
```
server# fs_info -a t:/var/www/html \
-a x:/var/www/html/cache system
```
这会将"/var/www/html/cache"加入到排除列表当中。
如果有一个列表,可以直接将其加入:
```
server# fs_info -a tL:/tmp/included_01 \
-a tL:/tmp/included_02 \
-a xL:/tmp/excluded_01 system
```
fs_info 实际的效果就是向 $datadir/$identity/.{t,x}_files 中追加条目,
所以你也可以直接编辑这两个文件,或者使用 shell 脚本重定向功能:
```
server# find /var -maxdepth 1 >$datadir/$identity/.t_file
```
不过 fs_info 可以为你做若干有效性检查的操作,例如判断文件/目录是否存在
等。无论如何,选择权在你手里。
$datadir 默认是"/var/fs_info",你可以通过编辑
"/etc/python/fs_info_config.py"来调整它为"/var/mirrord",以适应 mirrord
的需要,因为 mirrord 的默认值就是"/var/mirrord",这个值也可以通过编辑
"/etc/python/mirrord_config.py"来调整。
这些配置文件都直接使用"Python Tree"定义的数据结构,在前面提到的``caxes``
包里面。
$identity 默认为"system",如前所述。
这些配置也可以自命令行改变,例如:
```
server# fs_info -o datadir=/var/mirrord -a t:/var/www/html
```
**注意**,inotify 的默认内核参数值太小:
```
16384 /proc/sys/fs/inotify/max_queued_events
8192 /proc/sys/fs/inotify/max_user_watches
```
"max_user_watches"的大小取决于要备份的这部分文件系统包含多少目录,可以
用:
```
sh# find $path -type d | wc -l
```
来统计一下,并保证这个值大于统计结果。
"max_queued_events"意味着 inotify 管理的队列的最大长度,文件系统变化越
频繁,这个值就应该越大。如果你在日志中看到""** Event Queue Overflow **"
这样的消息,这就意味着"max_queued_events"太小,并且自此之后对于文件系统
的监控都是不准确的,你应该在调整参数后重启"mirrord"。
这些设置之后,就可以启动 mirrord 来监控和记录源文件系统的变化了:
```
server# mirrord
# OR verbose:
server# mirrord -v
```
你可以自配置文件"/etc/python/mirrord_config.py"或命令行来调整 mirrord
的行为,例如 wmLog 的长度(o.length, 此值越大,就会保留越多的记录,这
可以为长时间停止的 fs_mirror 客户端从断点重启提供更大的方便,但同时也
意味着在读取和变更 wmLog 时需要更多的资源),绑定的接口和端口,以及
socket 的超时,或需要镜像的 identity 标识集。
mirrord 启动之后,等待一段时间(取决于硬件、文件系统类型以及需要
备份/镜像的目录/文件数量),当"boot init finished"的消息被打印的时候,就
可以切换到客户端并启动 fs_mirror 了。
后续的版本将会消除这个等待阶段,使你可以在启动 mirrord 之后立即启动
fs_mirror。
在启动 fs_mirror 之前,必须保证客户端能够与 mirrord 通信并进行普通文件
的传输(只需要普通文件传输),因此必须在防火墙上允许 2123 端口(或选择另
一个端口),以及你选择用来传输普通文件的端口 -- 就象在
[The Second Story #ru_data_man_design_second] 和
[The Fifth Story: Monitor #ru_data_man_design_monitor]
中所描述的那样,mirrord/fs_mirror 的设计支持很多文件传输机制,例如 FTP,
NFS, SMB/CIFS 等等... 不过目前来说,我只实现了两种:REPORT 和 FTP。
"REPORT" 仅仅打印由 fs_mirror 接收到的 wmLog 中的动作,这仍然是一个很
有用的特性,在后面讨论。
"FTP" 同步机制将创建目录,删除或移动(递归)目录/文件,这些都可以在本地
完成,而只有对普通文件的写操作需要通过 FTP 协议进行实际的文件传输操作,
这种方式降低了在服务器上的资源消耗。注意整个文件都会被传输,而不是仅仅
被变更的部分,所以这**不是**一个字节级别的传输,因此不适用于大的并且
变化频繁的文件,或者说,如果大小和变化频度的乘积过大,或"size/seconds"
的值太小(这里 seconds 表示自第一次传输以来所经过的间隔),都不适合传输。
未来也许会在客户端做一个计算以判断是否应该传输。
这些同步机制定义在 cutils.fs_sync 模块中。
在需要镜像的源文件系统的主机上创建一个明文"mirror"或"backup"的用户是
一个好主意,这个用户应该拥有这部分文件系统的读权限,这可以利用 Linux
的 ACL 功能来实现,并使这个用户能够通过 FTP 访问,也许你想进一步使用
FTP SSL。到目前为止,认证机制还没有实现。
编辑 fs_mirror 的配置文件"/etc/fs_mirror_config.py"以获取关于配置的跟多
信息。
此时,启动 fs_mirror:
```
client# fs_mirror
# OR verbose:
client# fs_mirror -v
```
"fs_mirror --help"将提供命令行的更多信息。
作为一个例子,你可能会使用如下的参数:
```
client# fs_mirror -v -o host=roc \
-o fssync.user=root \
-o fssync.port=2121 \
--password-from=/root/.fs_mirror/secrets:roc \
--datadir=/data/fs_mirror/roc
```
现在,镜像的目录/文件将被放在 /data/fs_mirror/$hostname 中,选项"-o"
(--option) 与 /etc/python/fs_mirror_config.py 中 o.* 拥有相同的含义,
如前所述。这里,hostname 和两个文件同步的参数被改变了,当然要保证
hostname 可以被解析。
如果 verbose 模式打开了的话,在屏幕上可能会有这样的消息:
```
Daemon PID 7094
Connecting to the Mirrord: ('roc', 2123) ...
Conected.
Server reply 'OK' for 'START'
Server reply 'OK' for 'SN:b5e0ce480c925184dbdb6f23a62ddc6d,872302'
Received next serial message: 'SERIAL:862303'
WRITE FILE '/var/Counter/data/5597.dat', PREFIX '/data/fs_mirror/roc'
Received next serial message: 'SERIAL:862306'
CREATE DIR '/var/www/html/sample231.com/syssite', PREFIX 'data/fs_mirror/roc'
WRITE FILE '/var/www/html/sample231.com/sysstie/rec.php', PREFIX '/data/fs_mirror/roc'
DELETE DIR/FILE '/var/www/html/sample231.com/sysstie/rec.php.old', PREFIX '/data/fs_mirror/roc'
...
```
有一个普通文件"$datadir/sn"用来记录 session 和 serial number:
```
sh# cat /data/fs_mirror/roc/sn
b5e0ce480c925184dbdb6f23a62ddc6d,872302
```
这个文件在 fs_mirror 需要从断点重启的时候被使用。它使用一个文件锁
(fcntl.LOCK_EX)来避免镜像两个文件系统到一个目录,或同时镜像一个文件系统
两次(运行两个同样的 fs_mirror 实例)。
在[概述 #ru_data_man_overview]一节说过,轮转机制是必需的。目前
fs_mirror 还没有内置的轮转实现,这只能在以后来实现它,但是可以自己写
一个脚本做这个事情。在 SVN 的 repository 里面的"prototypes"子目录里有
两个例子:prototypes/fs_mirror_rotate.py 和 prototypes/mirror_rotate。
这两个脚本如下:
prototypes/mirror_rotate:
```
hosts="p01 2121
p02 2121
p03 2121"
cd `dirname $0`
# for host in hosts; do
echo "$hosts" | while read host port; do
pid=`ps aux | grep "fs_mirror.*$host" | grep -v 'grep' | awk '{print $2}'`
if [ $? -eq 0 ]; then
if [ -n "$pid" ]; then kill $pid; fi
&& ./fs_mirror_rotate.py $host
&& /usr/local/bin/fs_mirror -v
-o host=$host
-o fssync.user=root
-o fssync.port=$port
--passwd-from=/root/.mirror/secrets:$host
--datadir=/data/fs_mirror/$host/
fi
&& cat /data/fs_mirror/$host/www/prima/usermap
| awk -F, '{printf("%s %s %s %s %s\n", $1, $2, $3, $4, $5)}'
| while read perm uid user gid site; do
chown $uid.$gid /data/fs_mirror/$host/www/users/$site -R
# gid always be ftpd
done
done
prototypes/fs_mirror_rotate.py:
```
# -*- encoding: utf-8 -*
import os,sys,shutil
import time
import datetime
mirror = "/data/fs_mirror"
backdir = "/data/hosts"
try:
host = sys.argv[1]
except IndexError:
print >> sys.stderr, "Lack of a host identity"
sys.exit(1)
day = datetime.date(*time.localtime()[:3])
new = os.path.normpath("%s/%s" % (mirror, host))
NL = len(new)
old = os.path.normpath("%s/%s/%s" % (backdir, host, str(day)))
OL = len(old)
shutil.move(new, old)
for root, dirs, files in os.walk(old):
d_new = os.path.normpath("%s/%s" % (new, root[OL:]))
os.mkdir(d_new)
# status = os.lstat(root)
# perm = status[0]
# os.chmod(d_new, perm)
# uid = status[4]
# gid = status[5]
# os.chown(d_new, uid, gid)
try:
os.chmod(d_new, 0755)
except OSError:
pass
# print "CREATE DIR '%s'" % d_new
for fname in files:
f_new = os.path.normpath("%s/%s" % (d_new, fname))
f_old = os.path.normpath("%s/%s" % (root, fname))
os.link(f_old, f_new)
# Hard link will reserve the permission and ownership of a file
try:
os.chmod(f_new, 0644)
except OSError:
pass
# print "HARD LINK '%s' -> '%s'" % (f_old, f_new)
interval = datetime.timedelta(days=14)
overdue_day = day - interval
overdue_dir = os.path.normpath("%s/%s/%s" % (backdir, host, str(overdue_day)))
try:
shutil.rmtree(overdue_dir)
except OSError, (errno, strerr):
if errno == 2:
print strerr
else:
raise
示例归档:
```
[root@stor p01]# ls -l
total 144
drwxr-xr-x+ 6 root root 4096 Oct 2 03:10 2007-10-03
drwxr-xr-x+ 6 root root 4096 Oct 3 03:10 2007-10-04
drwxr-xr-x+ 6 root root 4096 Oct 4 03:10 2007-10-05
drwxr-xr-x+ 6 root root 4096 Oct 5 03:10 2007-10-06
drwxr-xr-x+ 6 root root 4096 Oct 6 03:10 2007-10-07
drwxr-xr-x+ 6 root root 4096 Oct 7 03:10 2007-10-08
drwxr-xr-x+ 6 root root 4096 Oct 8 03:10 2007-10-09
drwxr-xr-x+ 6 root root 4096 Oct 9 03:10 2007-10-10
drwxr-xr-x+ 6 root root 4096 Oct 10 03:10 2007-10-11
drwxr-xr-x+ 6 root root 4096 Oct 11 03:10 2007-10-12
drwxr-xr-x+ 6 root root 4096 Oct 12 03:10 2007-10-13
drwxr-xr-x+ 6 root root 4096 Oct 13 03:10 2007-10-14
drwxr-xr-x+ 6 root root 4096 Oct 14 03:10 2007-10-15
drwxr-xr-x+ 6 root root 4096 Oct 15 03:10 2007-10-16
[root@stor p01]# ls 2007-10-16/etc/httpd/conf.d -l
total 112
-rw-r--r-- 9 root root 58 Oct 8 13:41 bw_mod.conf
-rw-r--r-- 9 root root 187 Oct 8 13:41 mod_caucho.conf
-rw-r--r-- 9 root root 2965 Oct 8 13:41 site.conf
-rw-r--r-- 9 root root 10919 Oct 8 13:41 ssl.conf
-rw-r--r-- 9 root root 85876 Oct 8 13:41 virtual.conf
```
硬链接数是 8,这意味着上次启动 mirrord 实在 2007-10-08。
注意 fs_sync 会在传输一个普通文件之前先做 unlink,以避免破坏轮转的备份
,既然轮转脚本实际做的就是简单的创建目录和普通文件硬链接。
到目前为止,只有变更动作(CREATE, FWRITE, DELETE, MOVE)及其相应的路径名
被传输,所以如果目录/文件有属主/权限的需求,当前的 mirrord/fs_mirror
自身不能解决,要知道改变属主和权限涉及到遍历操作,因此比较消耗资源。
所以我在上面改变轮转脚本令每天其改变一次属主和权限,这当然不是一个很好
的办法,特别是在没有统一认证中心的情况下。这种更强的功能将在一个未来的
版本中实现。
使用一个认证中心是比较合理的,例如 NIS, LDAP 或 Kerberos,因为保持权限
和属主的一致是很有必要的。
**我必须指出客户端的 fs_mirror 到目前还没有彻底测试**
**这也是我时间有限,经验不足的缘故**
**因此你可能需要多花一些时间做调整**
作者: Chowroc 发布时间: 2007-10-16
作者: d00m3d 发布时间: 2007-10-17
作者: d00m3d
网站页面超长,看来得花点时间消化才行
|
作者: Chowroc 发布时间: 2007-10-17
在[概述 #ru_data_man_overview]一节关于 rsync 的部分的示例中,我已经讨论
过为高可用进行备份的问题。使用 mirrord/fs_mirror,只需要将"rsync"替换为
"mirrord",就得到了这样的拓扑:
```
+----------+
| worker | -[mirrord] -----------
+----------+ |
...... |
|
+----------+ |
| worker | -[mirrord] -----------
+----------+ |
V
[fs_mirror]
|
+----------+ +----------+
| worker | -[mirrord] ---> | backup |
+----------+ +----------+
| |
[take_over] |
| |
V |
+----------+ |
| rescue | <------------------- NFS
+----------+
这种多对一的备份是比较经济的。如果一个工作主机故障,你可以使用救护主机
替换它,同时利用一些高可用的手段,例如
[The High Availability Linux Project #http://www.linux-ha.org/]
``heartbeat``项目。
为了使在备份主机上的"fs_mirror"能够镜像若干工作主机上的文件系统,应该
使用如下方法调用 fs_mirror:
```
client# fs_mirror -v -o host=worker01 -o fssync.user=mirror -o fssync.port=21 \
--password-from=/root/.fs_mirror/secrets:worker01 --datadir=/data/fs_mirror/worker01/
client# fs_mirror -v -o host=worker02 -o fssync.user=mirror -o fssync.port=21 \
--password-from=/root/.fs_mirror/secrets:worker02 --datadir=/data/fs_mirror/worker02/
```
保证主机名可以被解析,以及用户"mirror"存在与服务器上,并拥有整个需要
镜像的文件系统的读取权限(推荐使用 Linux ACL)。
如前所述,使用这种方法,你可以吧不同主机的文件系统镜像到不同的子目录中
,然后创建若干正确的符号链接,以利于接管。
作者: Chowroc 发布时间: 2007-10-17
mirrord/fs_mirror 一个预料之外的用处是作为 IDS(Intrusion Detection
System 入侵监测系统)来运行,此时使用 fs_sync 的"REPORT"机制,这可以在
某种程度上被看作是实时的``tripware``。在服务器端,将那些不会频繁变动的
文件系统部分置于 mirrord(inotify) 的监控之下,例如所有的可执行文件,
以及配置文件,在客户端,以 "REPORT" 模式运行 fs_mirror,则在服务器上的
所有重要的变更都能够及时的被报告。
在 客户端上为这种 IDS 应用实现一个 GUI 看上去不错,这样我就可以在我的
PC 机上跑了,例如公司的 Windows 系统。
目前来说,这个问题还没有深入考虑,一些比较重要的特性例如 SSL 支持还
没有加入,不过这种使用方式是可以肯定的。
作者: Chowroc 发布时间: 2007-10-17
DRBD 也许可以工作在 2.4 内核,不过好像镜像磁盘不能挂载。具体我没有试过。
对了,我想你是从 python.cn 的邮件列表里面看到这个项目的吧,我想把邮件转过去,因为别人也可能会遇到类似的问题,行吗?
On 10/19/07, 俊杰蔡 <[email protected]> wrote:
> 您好:
> 我目前在笔记本上测试,系统为ubuntu,安装libdb4.4,mirrord_config.py中配置如下:
> 1 import tree
> 2 options = tree.Tree('mirrord')
> 3 o = options
> 我使用sudo mirrord运行,依然会出现如上错误。
>
> 另外,我想问下,由于内核版本原因,我们目前的内核没有到2.6, 所以没有inotify特性,所以如果我需要移植,就要找其它的技术来代替,你对此方面有何建议呢?
>
> 谢谢
>
>
> 在07-10-18,Roc Zhou < [email protected]> 写道:
>
> > 您好:
> > 这个问题应该是 Berkeley DB 抛出的异常,您先看一下您系统中 BDB 是否安装了(应该是叫做 db4 的一个包),它的版本是什么。
> >
> > 另外,您给我看一下 /etc/python/mirrord_config.py 配置是怎样的,您设置的 o.datadir 是否有写权限?
> >
> > 当然现在 mirrord/fs_mirror 还有许多局限性需要去完善,这些局限性在文档中我也说明了。对于上百台服务器,您也可以看看其他的项目,比如 GFS;关于镜像,还有象 BRDB 这样的项目,我在文档里面也提及了的,因为 mirrord/fs_mirror 不适合频繁变动的大文件。
> >
> > 这个项目是开源项目,使用了 GPL v2.0,您当然可以自由扩展,只要遵循 GPL 就可以了。当然我也很高兴您能告诉我做了那些改进。
> >
> > 谢谢。
> >
> >
> >
> > On 10/18/07, 俊杰蔡 < [email protected] > wrote:
> > > 您好:
> > >
> > > 我对uLFS中的备份功能比较感兴趣,目前我所从事的工作是管理上百台服务器,其中备份策略比较简单,虽然也用到一些同步工具和自己写的一些脚本,但是没有一个完整的备份架构。所以花了点时间看了下你备份的原理,很清晰。不过由于我们备份的特殊性,不能直接使用。我想问下是否可以在你的项目上作些扩展?
> > > 另外,我下载并安装了工具,但无法正常运行:报错如下
> > > cjj@cjj-laptop:/var$ mirrord
> > > Traceback (most recent call last):
> > > File "/usr/local/bin/mirrord", line 172, in <module>
> > > md = mirrord.Mirrord(_identities, _datadir, _loglen)
> > > File "/usr/lib/python2.5/site-packages/cutils/mirrord.py", line 1009, in __init__
> > > self.shared.wdirs = Snapdb(path=path)
> > > File "/usr/lib/python2.5/site-packages/cutils/fs_snap.py", line 394, in __init__
> > > self.dbfile = bsddb.btopen(path, 'n')
> > > File "/usr/lib/python2.5/bsddb/__init__.py", line 323, in btopen
> > > d.open(file, db.DB_BTREE, flags, mode)
> > > bsddb.db.DBNoSuchFileError: (2, 'No such file or directory')
> > > Daemon PID 6870
> > > cjj@cjj-laptop:/var$
> > >
> > > 由于python使用时间不是太长,所以请教一下这个问题。
> > >
> > >
> > > 谢谢
> > >
> >
>
>
作者: Chowroc 发布时间: 2007-10-22
如果遇到 ImportError 这样的异常,通常是说明没有安装 BDB,也就是 db4 这个包。而上面的报错是因为 mirrord 不会自动创建目录,这个目录是由 fs_info 脚本创建的,而我没有对这个异常进行捕捉并给出正确的说明,因为之前我都会先运行 fs_info 的 :P
作者: Chowroc 发布时间: 2007-10-22
在初始化阶段完成之后,进入实时同步状态。我在 3 台主机上都部署了的,而且都运行了将近半个月,其中有一台主机有一天不知道怎么回事,我发现 fs_mirror 从 mirrord 接受到了空记录。正常的情况下,应该得到这样的内容:
"CREATE:/var/www/html"
"FWRITE:/var/www/html/index.php"
"DELETE:/var/www/html/temp"
"MOVE':('/var/www/html/aa', '/var/www/html/bb')"
...
但不应该有空记录。这导致 fs_mirror 进入了死循环。
我让 fs_mirror 从断点重启,但仍然还是如此,而且 DEBUG 发现每次出问题都在同一个 serial 号(我使用 Berkeley DB 作为日志记录,serial 号作为其 key),所以我怀疑问题出在 BDB,但不知道怎么测试以确定问题。
我首先尝试在 python 中直接打开原来的数据库:
>>> x = bsddb.btopen("/var/mirrord/wmlog"
>>> len(x)
623748
>>> x["6854"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.5/bsddb/__init__.py", line 223, in __getitem__
return _DeadlockWrap(lambda: self.db[key]) # self.db[key]
File "/usr/lib/python2.5/bsddb/dbutils.py", line 62, in DeadlockWrap
return function(*_args, **_kwargs)
File "/usr/lib/python2.5/bsddb/__init__.py", line 223, in <lambda>
return _DeadlockWrap(lambda: self.db[key]) # self.db[key]
KeyError: '6854'
>>> x[str(6854)]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.5/bsddb/__init__.py", line 223, in __getitem__
return _DeadlockWrap(lambda: self.db[key]) # self.db[key]
File "/usr/lib/python2.5/bsddb/dbutils.py", line 62, in DeadlockWrap
return function(*_args, **_kwargs)
File "/usr/lib/python2.5/bsddb/__init__.py", line 223, in <lambda>
return _DeadlockWrap(lambda: self.db[key]) # self.db[key]
KeyError: '6854'
>>> x.first()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.5/bsddb/__init__.py", line 278, in first
rv = _DeadlockWrap(self.dbc.first)
File "/usr/lib/python2.5/bsddb/dbutils.py", line 62, in DeadlockWrap
return function(*_args, **_kwargs)
_bsddb.DBNotFoundError: (-30990, 'DB_NOTFOUND: No matching key/data pair found')
然后,我尝试把数据库拷贝和移动出来,然后打开新的数据库文件:
>>> x = bsddb.btopen("/tmp/wmlog")
>>> len(x)
0
为什么把数据文件拷贝出来以后会有这种问题呢?尤其是 len() 的结果为 0?
我只好重新运行 mirrord,重建 BDB 数据文件,到现在没有再遇到这种问题。
不知道这个偶然性的问题是怎么回事?有哪位对 BDB 比较了解的能够赐教一二吗?
作者: Chowroc 发布时间: 2007-10-28
作者: d00m3d 发布时间: 2007-10-28
不过可以先放在一边。之前讲的是如何使用。下面我说一说设计上的一些思路。
作者: Chowroc 发布时间: 2007-10-31
===inotify===
为了实现镜像,应用程序必须知道自上一次传输文件变更后到现在的这个间隔
之间,又有哪些内容发生了变化,而 rsync 需要遍历这个文件系统来比较时间戳
,而只是一个比较消耗资源且不可能实时的过程。
而 mirrord/fs_mirror 能够避免遍历和比较,这通过最近的 Linux 内核(自
2.6.12 起)所提供的一个新功能:inotify 来实现。
inotify 可以监控指定目录的所有变更,这就是说,当你创建、删除、移动、
改变目录/文件,或改变在这些目录中的普通文件的内容时(非递归),inotify
将抛出事件,而应用程序可以捕捉和排队这些事件,找出导致这些事件的文件
的名字,并进行相应的操作(这里 mirrord 将记录这个动作及其相应的文件名,
并告知 fs_mirror 传输这些记录,fs_mirror 此时做相应的创建目录、删除、
移动或传输文件等操作)。
尽管 inotify 不提供递归功能,应用程序却可以递归地将目录加入到 watches
列表中,这些 watches 表明了这些目录需要被 inotify 监控,这些标识在内核
中只是一些数字描述符,所以不占多少内存,并且这个功能有内核提供,因此是
轻量级的。
关于 inotify 的细节,可以在 Internet 上找到一些介绍:
[Kernel Korner - Intro to inotify #http://www.linuxjournal.com/article/8478]
[Monitor Linux file system events with inotify #http://www-128.ibm.com/developer ... rary/l-inotify.html]
[Lightweight filesystem notifications #http://blog.printf.net/articles/ ... ystem-notifications]
或者阅读 Linux 内核中关于 inotify 的源代码:
```
./include/linux/inotify.h
./Documentation/filesystems/inotify.txt
./fs/inotify.c
```
在这个项目,mirrord/fs_mirror 使用了一个 Python 包``pyinotify``:
http://pyinotify.sourceforge.net/
作者: Chowroc 发布时间: 2007-10-31
最简单的一种思路来自于我的一个同事,那就是利用硬链接来记录这些文件系统
的变化,通过 NFS 导出这些包含硬链接的目录给远程主机,同时在备份主机上
运行的守护进程定期的将这些导出记录移动到它自己的本地目录,象这样:
```
使用等宽字体显示:
[inotify] ----> [mirrord] worker host rescue/backup host
| |(invoke)
| |
__original-fs__ == HardLink ==> __records__ == MoveOnNFS ==> [fs_mirror]
尽管这看起来简单,但这种模式是有问题的。有几个问题是无法通过这种“简单
硬链接”来解决的:
+ 首先无法知道哪些内容被“删除”了,因此 fs_mirror 无法同步这些“删除”
操作,“移动”同理因为“移动”隐含着某种“删除”。
事实上,在 inotify 中,"MOVE"和"DELETE"的行为是不同的(在后面的章节中
讨论),而简单的硬链接无法区分这些差异。
你可能会建议建立一个"DELETE"目录并只记录"DELETE"操作?但这导致应用模型
过于复杂,并且:
+ 操作的顺序是很重要的(显然不可能在创建一个文件之前就删除它,或者在其
上层目录都不存在的情况下写一个文件,...),而“简单硬链接”无法记录这些
操作的顺序。
+ fs_mirror 必须通过 NFS 从记录目录中移出目录/文件,否则记录目录会变得
越来越大,这将导致性能的下降。
+ 这里,由 fs_mirror 进行的移动操作是有问题的,因为,它不是一个原子操作
。显然,"move"是由"copy"(事实上这里必须通过 NFS 进行 copy,因此比简单的
copy 还要复杂一些)和随后的"remove"组成,因此不可能在一个系统调用中完成
,即使是"copy"也可能要调用好几次甚至更多的 read/write 系统调用,这些都
可能导致记录的丢失。
用一个例子来说明:
```
使用等宽字体显示:
A |
| V
WRITE_CLOSE (mv)
procedure:
| |
| |<-- COPY_COMPLETED
| |
|<-- WRITE_CLOSE |
| |
| |<-- REMOVE
WRITE_CLOSE 是一个在文件写操作发生时产生的事件,如果你已经阅读 inotify
的文档,这不难理解。当这个事件产生的时候,inotify 处理器进行相应的“硬
链接”操作,从 file1 到 file2,假设这个 file2 已经由上一次 WRITE_CLOSE
产生,因此这一次 WRITE_CLOSE 应该忽略"OSError: file exists"这样的异常,
这样在“拷贝”的时候,只有上一次变更的内容被传输,而“删除”操作只是
unlink 刚刚创建的(可能只有几毫秒)这个硬链接文件 file2,但并没有同步上次
变更的内容,因此这些内容被丢失了。如果这个文件直到系统崩溃的时候都没有
再变化过,那么它的这个变更就永远丢失了。一个文件不是什么大问题,但是
没有任何保证只有少数文件会被这种机制影响。而我们知道,即使“拷贝”本身
也不是原子的,因此这会导致目标文件系统出现大量的“漏洞”。
+ 可能会有某种需要,将一个文件系统镜像到好几台客户主机上,或者说“多路
镜像”。而简单硬链接无法达到这个目的,因为在 fs_mirror "moved" 硬链接
文件之后,记录就等于被“抹除”了。
+ 这看上去更像是一种脚本的解决办法,而不是一个产品级别的解决方案,既然
backup 是一个关键应用,最好是使用经过严格测试的产品,而不是几个简单的
脚本。
+ 简单硬链接不够灵活,因为你必须将所有文件都放在同一个文件系统中,因此
意味着你必须调整每一个主机。事实上,在每台工作主机上都设置 NFS 有些烦人
,而且这种“推”模式不够安全——你不得不通过 NFS 导出关键数据。
+ 最后,既然你需要在物理硬盘上建立结构,这可能会导致性能下降。
% EOL
因此应该使用一个更好的策略来金路这些文件系统的变更。既然现在 mirrord
使用 pyinotify,我建议你看一看它的文档。
作者: Chowroc 发布时间: 2007-10-31
作者: hongfeng 发布时间: 2007-11-02
现在,一个从"pyinotify.ProcessEvent"继承的类的实例可以被用来执行与事件
相应的操作,例如,有一个方法:
``def process_IN_CLOSE_WRITE(self, event):``
这个 inotify "IN_CLOSE_WRITE" 事件将在一个循环中被这个方法执行,例如:
```
while not self.Terminate: ... if wmNotifier.check_events(wmTimeout): wmNotifier.read_events() ... wmNotifier.process_events() ...wmNotifier.process_events()
这里 wmNotifier 是 "pyinotify.Notifier" 的一个实例(如果你不太清楚,请
参阅 pyinotify 的文档),当 wmNotifier.process_events() 被调用的时候,它
运行一个从"ProcessEvent"继承的类的实例的 process_* 方法,"IN_DELETE"事件
对应于"process_IN_DELETE()","IN_WRITE_CLOSE"对应于
"process_IN_WRITE_CLOSE",如前所述。
因此,要做的就是编写这样的一个实例及其 process_* 方法,我们可以将这个
继承的类命名为 "Processor"。
并不是所有由 inotify 产生的事件都是重要的,对于文件系统镜像来说,只有
几个事件需要被考虑:
IN_CREATE (with IN_ISDIR for only directory)
IN_WRITE_CLOSE (only for regular file)
IN_DELETE
IN_MOVED_FROM
IN_MOVED_TO
__另一个也很有用的事件是"IN_ATTRIB",但是目前这个版本还没有应用__
__因为一开始设计 mirrord 的时候想法还不是特别成熟__
那么这个 Processor 应该做什么?或者说 process_* 应该是怎样的?让我们先
看看这个问题:什么需要被监控。
如果你已经读过 inotify 和 pyinotify 的文档,你知道只有那些被 inotify
监控了的目录/文件才会产生事件,而将这些目录/文件加入监控则是你的应用
程序的事情。因此你必须通过调用 inotify 的 inotify_add_watch() 或
pyinotify 的 WatchManager.add_watch() 来什么要将那些内容加入监控。
这个操作涉及到前面已经提到过的一个可管理性问题。我们需要知道的第一个
规则就是:只有目录需要被加入监视。因为任何其子目录/子文件产生的变更都会
导致 inotify 事件产生,而你可以通过 pyinotify 的"Event"实例来获得监视
目录的路径和相应的子目录/子文件的名字。实际上,pyinotify 就是只监控目录
。
前面提到过递归的问题,这就是说,当你将一个目录增加到 inotify 的监视中的
时候,只有在这个目录下第一层的子目录和子文件会产生 inotify 事件,例如,
如果"/var/www/html"被监控,则"/var/www/html/index.php"和
"/var/www/html/include"的变更可以被捕捉,但
"/var/www/html/include/config.php"或"/var/www/html/include/etc"却不行。
这意味着如果使用``mkdir -p /var/www/html/include/etc``创建目录而
"/var/www/html"被监控,只有"/var/www/html/include"将会导致 inotify 产生
"IN_CREATE|IN_ISDIR"事件。同样的规则适用于 Python 的``os.makedirs``,
以及任何递归的拷贝/移动操作!
所以将所有需要的目录加入到监控室这个程序(mirrord)的事情。这将导致对文件
系统的某些部分进行遍历操作,而这是比较消耗资源的,不过这个遍历只需要做
一次,而在这个名之为"boot init"的阶段之后,资源可以被释放而 mirrord 将
以 daemon 方式运行。
尽管 pyinotify.WatchManager 有一个"rec"参数来调用 add_watch(),但我不
打算使用它,因为:
+ 它没有排除目录的功能,例如,一些"cache"目录,或保护变化频繁的大文件的
目录。
+ 这个"rec"是静态的,这意味着当目录/文件已经被删除或移走后,inotify 的
watches 不会进行相应的调整。事实上,这种情况主要发生在"IN_MOVED_FROM"
事件时,你可以看看在 svn 源代码中的"ulfs/cutils/trunk"目录下的的原型
脚本"prototypes/mirrord_inotify_delete.py"。
有必要避免这些监控"尸体"留在系统中,这可能导致 mirrord 进程变得太臃肿,
同时因为当前的 pyinotify 使用内存来存储 watches,这也会导致内存的浪费。
% EOL
这里,一个``fs_info.py``模块被用来进行包含/排除操作。这可以看作是一个
文件系统标识系统,可以使用它的包含/排除配置来将文件系统切割成若干部分
,并且使用它提供的方法来得到符合配置的所有目录/文件的一个完整列表。
例如,你可以将一些目录/文件加入到一个文件系统标识中,通过如下方法:
```
sh# fs_info -a t:/var/named \
-a t:/var/www/html \
-a x:/var/www/html/cache \
-a x:/var/www/html/log website
```
这里``fs_info``是一个命令行的接口,它会根据传递的选项调用相应的
fs_info.py 模块的方法,"-a"意思是"append",而 t/x 与 tar 的 "-T/-X"选项
意思相近,或者说,"include/exclude"。
标识名是"website",这意味着文件系统标识"website"包含若干目录及其子目录/
子文件,而当你调用 mirrord 时,你可以直接将这个 identity 作为参数传递给
``mirrord.py``模块的``Mirrord``构造,这样这个实例将可以调用
FsInfo.find() 来得到所有子目录/子文件的完整列表,同时排除包含在这个标识
里面的排除目录。
fs_info.FsInfo 会建立一个"/var/fs_info/website"目录,并且记录这些保护(t)
目录到 /var/fs_info/website/.t_files,记录排除(x)目录到
/var/fs_info/website/.x_files。你可以调整位置,通过修改 fs_info 的配置:
``options.datadir = "/var/mirrord``,配置在 /etc/python/fs_info_config.py。
所以最后我们得到这样一个图:
```
使用等宽字体显示: (1)---> [mirrord] ---(2)---> [fs_mirror] ---(3) / \ [fs_info] /==============(4)===============> [fs_sync] / / \ +--------------+ +---------------+ | original | | mirrored | | file system | | file system | +--------------+ +---------------+ (1) tell it what to monitor for mirror (2) tell it what are modified (3) tell it what to transport (4) the actual regular files been transporting
通过第(2)步,fs_mirror 知道有哪些内容发生了变化,并调用 fs_sync 来执行
相应的操作:目录创建、目录/文件删除和移动可以在客户端上直接完成,因此
不需要消耗服务器上的其他资源;但普通文件内容的变更无法镜像除非将文件
拷贝过来。这个拷贝操作可以通过任何网络传输协议和工具来完成,例如 FTP,
NFS, SMB/CIFS, SSH 或 rsync ...,因此 fs_sync 可以有多种实现。
更进一步的关于这个同步机制的说明可以在
[The Fifth Story: Monitor #ru_data_man_design_monitor]
找到。
在解释了"fs_info"和(1)之后,回到前面的问题:"Processor"(为避免混淆,
我们现在将其称为"Monitor")应该做什么?这是"mirrord"和(2)的功能的一部分。
更详细的关于 fs_info 的细节,请阅读代码和内部文档。
从前面的图形和故事一,我们知道 mirrord 必须:
+ 收集所有的路径名到 inotify watches,这些文件名可以通过
fs_info.FsInfo.find() 方法得到,在 mirrord.Mirrord 的"boot init"阶段。
+ 维护文件系统快照(snapshot)。这是另一个有用的特性,是一个当前文件系统
所有路径名的集合(包含若干相关的其他信息)。有几个理由说明为什么要使用
snapshot,将在后面谈到。
+ 创建一个 Monitor 实例,在 inotify 产生有关事件的时候来执行相应的
process_* 操作,包括在删除/移动或递归拷贝/移动的时候调整 watches,以及
记录文件系统的变更。
+ 将这些变更记录传递给 fs_mirror (2),但是只在 fs_mirror 要求进行的时候
才传输内容,因为这种"拉"的模式更安全也更灵活(如果使用"推"的模式,则当不
能 push 的时候,mirrord 其他方面的工作会受到影响,除非 mirrord 做得更加
复杂)。所以 mirrord 应该是一个服务器端的**(agent),而 fs_mirror 是
客户端。
通过 socket 来传输这些记录是比较合适的,因此这导致要在 agent 和 client
之间设计一个合理的协议。
+ 能够为若干客户端服务,以形成"多点镜像(multi-mirror)",即若干客户端
可以并发的镜像一个文件系统,或者更进一步(在未来某个版本中),并发同步
文件系统的不同部分。
+ 调度若干线程或进程以完成不同的事情。至少有两个线程存在:main thread
和 schedule thread。
主线程在一个无限循环中运行,通过"**Monitor**"来检查、读取和处理 inotify
事件,当没有任何事情发生的时候,阻塞周期为若干秒(默认为 4s),否则相应的
操作会立即调用。
既然 mirrord 需要通过 socket 来为 fs_mirror 服务,那么至少需要有一个
线程监听在一个端口上等待连接和请求,而主线程已经被 Monitor 占据来处理
inotify 事件,所以一个调度线程就变得有必要了(另一个解决办法是使用
select/poll)。它被称为"schedule thread",可以支持"multi-mirror",因为
当一个运行 fs_mirror 的主机请求同步的时候,一个新的"服务线程"会被启动
并服务于这个客户端。
服务线程(server thread)主要处理与协议相关的事情。
+ 在线程之间,主要是在主线程和服务线程之间,共享一些变量,例如变更记录
和文件系统快照。
作者: Chowroc 发布时间: 2007-12-03
在故事一种,我们已经讨论了一种记录变更的机制,并且已经证明那不是一个好
办法。
在那之后,首先进入我脑子的一个想法是建立四个数组:
CREATE (IN_CREATE|IN_ISDIR only for directories),
FWRITE (only for regular files writing),
DELETE (IN_DELETE),
MOVE (IN_MOVED_FROM and IN_MOVED_TO),
并将任何变更有关的路径名不断追加到这几个相应的数组中。
所以,一开始我想设计如何在每一个服务线程中去管理这些数组,即每次一个
服务线程被启动,它就为自己创建这些数组,因为每一个客户端的状态都是不同
的。
要维护这些线程相关的数组,就要求每一个线程都实现一个自己的 inotify
Processor 实例,或者说,一个"Counter",来追加变更记录,并在客户端已经
读取之后清除这些记录。
但是这种设计仍然是有很大问题的:
- 既然已经有一个 inotify "Processor"(在主线程中),那么为每一个服务线程
维护一个"Processor"则造成了重复。
- 这种类型的记录缺少变更的顺序信息。
- 没有历史记录。一旦一个记录被客户端读取,它就被清除了,因此没有机会回
滚并从断点重启同步。
% EOL
如果参考一下 MySQL replication,那么就可以发现正确的解决办法了。所需要
的就是建立一个 **Log** 来记录所有类型的变更相应的路径名。让我们将其命名
为"wmLog"(Watch Modify Log)吧。
这个 wmLog 应该被主线程和服务线程共享,在主线程中的"Monitor"将最新的
变更追加到 wmLog 的尾部,同时服务线程可以读取 wmLog 并利用一个指针来
标识读取位置,这样一个服务线程就知道哪些内容已经处理过了。
那么这个 wmLog 应该象什么样子呢?既然顺序很重要,那么一个列表
(list/sequence)就相当直接,但是一个哈希表则更灵活和清楚。如果需要更改
wmLog,例如,删除过时的记录(比如一个月以前的记录)以避免 wmLog 太长了,
哈希表就会非常有用;并且利用类似哈希表的接口,可以比较方便的切换到其他
的日志机制(例如,Berkeley DB,后面将讨论)。
为了使哈希有序,其键值是序列数字。每次一个 inotify 事件发生和处理之后,
序列号就递增 1,因此 wmLog 看起来就像是 Python 中的 dictionary 结构那样
:
``` { 1 : ('CREATE', '/var/www/html'), 2 : ('CREATE', '/var/www/html/include'), 3 : ('FWRITE', '/var/www/html/index.php'), 4 : ('DELETE', '/var/www/html.old'), 5 : ('MOVE', ('/var/www/html', '/var/www/html.new')), ... } ```
的序列号都不一样,这反映出不同的客户端不同的读取进度。
这种机制使得客户端从断点重启同步成为可能,例如,当主线程已经将序列号
递增到 10234 时,一个服务线程可能仅仅督导 9867 位置,这时这个客户端可能
因为某种原因终止了,而主线程的序列号则继续递增。下次重启这个客户端的
时候,它可以告诉服务线程从 9867 这一点开始传输 wmLog,否则客户端不得不
要求一个全新的同步,那将迫使服务线程重新发送整个文件系统快照的全部内容
,而这个重发的过程是比较消耗资源的(既然你可以使用 Python 的 generator,
这个过程不会消耗太多内存,但仍然需要消耗 CPU 和网络带宽),并且接下来的
普通文件的传输将更消耗资源。
但是不像 MySQL replication,到目前为止还没有实现一种机制使 mirrord 可以
从断点重启。因为在两次 mirrord 启动之间,文件系统可能发生了没有被监控的
变化,所以最简单的方法就是重建整个快照,并重置 wmLog 为 0,这当然也将
导致客户端重做初始同步,因为必须保证源文件系统和镜像文件系统的一致性。
mirrord 通过要求客户端提供一个利用时间产生的 MD5 session 给服务线程来
实现这个目标。
未来的一个版本将会通过在启动时比较现在的文件系统和上次终止时的快照来
改进这种启动策略,以“计算”出在两次启动之间的变更,这可以使的启动更
平滑。
wmLog 是哈希表,所以 Python 的 dictionary 相当直接,但它常驻内存,而
Log 一定是会越来越大的。如果我们假设每一条记录的长度是 30 bytes,而变更
的频率是 1/s,那么每天它需要消耗"30 * 86400 / 1024 / 1024 = 2.47 MBytes"
内存,因为可以控制 wmLog 的长度,所以也可以控制内存使用的上限。如果源
文件系统不是太大,或者变化不是太频繁,将记录放在内存的 wmLog 中看上去
还是不错的。
相反的,使用一个 hash like 的数据库也很有道理,例如 Berkeley DB。仅仅
只需要创建一个简单的类 fs_wmlog.WmLog 来包裹对 Berkeley DB 的操作,因为
wmLog 只接受整数而 BDB 只接受字符串,这种方式保持了一致性。
最初我是一内存 dict,但是生产线上我必须处理超过百万的目录/文件的文件
系统,为简单起见,这个版本完全使用 BDB,但在未来我将考虑将两者都实现。
第一个实现是将它们都联合到一个统一的 API,我的意思是将有 dict 和
Berkeley DB 两个 wmLog,象这样:
``` class WmLog: def __init__(self, path): self.memlog = {} self.dbdlog = bsddb.btopen(path, 'n') ```
DB 中。后来我发现其实不用这么麻烦,因为完全依赖于 BDB 的内存管理也许来
得更方便。
到目前为止,只有与动作相关的路径名被记录和传输,但是文件的状态其实也
很有用,更进一步,利用 hostid,将有可能是两台主机互为镜像。由于最初在
设计上还存在不足导致了这些缺失,并将在后续版本中加以改进。
作者: Chowroc 发布时间: 2007-12-12
http://www.yourlfs.org/ru_data_man_zh_CN.html
作者: Chowroc 发布时间: 2007-12-25
BTW,好奇一問,何以網站不用 unicode 編碼?
作者: d00m3d 发布时间: 2007-12-25
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28