|

楼主 |
发表于 2007-12-12 10:55:47
|
显示全部楼层
===故事三:记录日志===
===故事三:记录日志===
在故事一种,我们已经讨论了一种记录变更的机制,并且已经证明那不是一个好
办法。
在那之后,首先进入我脑子的一个想法是建立四个数组:
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 " rocessor"(在主线程中),那么为每一个服务线程
维护一个" rocessor"则造成了重复。
- 这种类型的记录缺少变更的顺序信息。
- 没有历史记录。一旦一个记录被客户端读取,它就被清除了,因此没有机会回
滚并从断点重启同步。
% 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')
- ```
复制代码
最近的记录将放在 memlog 中,随着其不断增大,更早的记录将放在 Berkeley
DB 中。后来我发现其实不用这么麻烦,因为完全依赖于 BDB 的内存管理也许来
得更方便。
到目前为止,只有与动作相关的路径名被记录和传输,但是文件的状态其实也
很有用,更进一步,利用 hostid,将有可能是两台主机互为镜像。由于最初在
设计上还存在不足导致了这些缺失,并将在后续版本中加以改进。 |
|