[分布式 文件系统] 分布式文件系统Nutch和coda-存储备份-ChinaUnix.net

最近在看存储相关的知识,这篇文章不错,贴过来保存

分布式文件系统

http://www.trucy.org/blog/fanghong/archives/000874.html

先占个位,如果结合nutch和gfs的作个记录也许更好,看是否有空了。明天陪lazet检查身体,应该没有什么问题,期望如此。

最近知识科学的内容看得杂乱,不好,期望尽快理一些头绪,也期望可以指教的多多指教,可以尽快进入自己期望的内容。

参考资料:

http://wiki.apache.org/nutch/NutchDistributedFileSystem

http://labs.google.com/papers/gfs-sosp2003.pdf

2005-03-22 18:45:21

Nutch分布式文件系统

  NDFS:在一系列机器上存储庞大的面向流的文件,包含多机的存储冗余和负载均衡。

  文件以块为单位存储在NDFS的离散机器上,提供一个传统的input/output流接口用于文件读写。块的查找以及数据在网络上传输等细节由 NDFS自动完成,对用户是透明的。而且NDFS能很好地处理用于存储的机器序列,能方便地添加和删除一台机器。当某台机器不可用时,NDFS自动的保证 文件的可用性。只要网上的机器序列能提供足够的存储空间,就要保证NDFS文件系统的正常运。

  NDFS是建立在普通磁盘上的,不需要RAID控制器或者其它的磁盘阵列解决方案。

语法

1). 文件只能写一次,写完之后,就变成只读了(但是可以被删除)

2). 文件是面向流的,只能在文件末尾加字节流,而且只能读写指针只能递增。

3). 文件没有存储访问的控制

所以,所有对NDFS的访问都是通过验证的客户代码。没有提供API供其它程序访问。因此Nutch就是NDFS的模拟用户。

3.系统设计

NDFS包含两种类型的机器:NameNodes和DataNodes: NameNodes维护名字空间;而DataNodes存储数据块。NDFS中包含一个NamdNode,而包含任意多的DataNodes,每个 DataNodes都配置与唯一的NameNode通信。

1)NameNode: 负责存储整个名字空间和文件系统的布局。是一个关键点,不能down掉。但是做的工作不多,因此不是一个负载的瓶颈。

维护一张保存在磁盘上的表: filename-0->BlockID_A,BlockID_B…BlockID_X,etc.;filename就是一字符串,BolockID是唯一的标识符。每个filename有任意个blocks。

2)DataNode:负责存储数据。一个块应该在多个DataNode中有备份;而一个DataNode对于一个块最多只包含一个备份。

维护一张表:BlockID_X->array of bytes..

3)合作:DataNode在启动后,就主动与NameNode通信,将本地的Block信息告知NameNode。NameNode据此可以构造一颗 树,描述如何找到NDFS中的Blocks。这颗树是实时更新的。DataNode会定期发送信息给NameNode,以证明自己的存在,当 NameNode收不到该信息时,就会认为DataNode已经down了。

4)文件的读写过程:例如Client要读取foo.txt,则有以下过程。

a.Client通过网络联系NameNode,提交filename:”foo.txt”

b.Client收到从NameNode来的回复,包含:组成”foo.txt”的文件块和每个块存在的DataNode序列。

c.Client依次读取每个文件块。对于一个文件块,Client从它的DataNode序列中得到合适的DataNode,

然后发送请求给DataNode,由DataNode将数据传输给Client

4.系统的可用性

NDFS的可用性取决于Blocks的冗余度,即应该在多少个DataNode保持同一Block的备份。对于有条件的话可以设置3个备份和2个最低备份 (DESIRED_REPLICATION and MIN_REPLICATION constants in fs.FSNamesystem)。当一个块的低于MIN_REPLICATION,NameNode就会指导DataNode做新的备份。

5.net.nutch.fs包的一些文件介绍

1)NDFS.java:包含两个main函数,一个是关于NameNode的,一个是关于DataNode的

2)FSNamesystem.java:维护名字空间,包含了NameNode的功能,比如如何寻找Blocks,可用的DataNode序列

3)FSDirectory.java:被FSNamesystem调用,用于维护名字空间的状态。记录NameNode的所有状态和变化,当NameNode崩溃时,可以根据这个日志来恢复。

4)FSDataset.java: 用于DataNode,维护Block序列等

5)Block.java and DatanodeInfo: 用于维护Block信息

6)FSResults.java and FSParam.java: 用于在网络上传送参数等

7)FSConstants.java:包含一些常数,用于参数调整等。

NDFSClient.java:用于读写数据

9)TestClient.java:包含一个main函数,提供一些命令用于对NDFS的存取访问

6.简单的例子

1)创建NameNode:

Machine A:java net.nutch.fs.NDFS$NameNode 9000 namedir

2)创建DataNode:

Machine B:java net.nutch.fs.NDFS$DataNode datadir1 machineB 8000 machineA:9000

Machine C:java net.nutch.fs.NDFS$DataNode datadir2 machineC 8000 machineA:9000

运行1,2步后,则得到了一个NDFS,包含一个NameNode和两个DataNode。(可以在同一台机器的不同目录下安装NDFS)

3)client端的文件访问:

创建文件:java net.nutch.fs.TestClient machineA:9000 CREATE foo.txt

读取文件:java net.nutch.fs.TestClient machineA:9000 GET foo.txt

重命名文件:java net.nutch.fs.TestClient machineA:9000 RENAME foo.txt bar.txt

再读取文件:java net.nutch.fs.TestClient machineA:9000 GET bar.txt

删除文件:java net.nutch.fs.TestClient machineA:9000 DELETE bar.txt

========================

coda分布式文件系统

简介

coda分布式文件系统是由卡耐基·梅隆大学开发的一个实验中的分布式文件系统。Coda在移动计算处理上具有很多别的系统没有的先进的特性,很多的开发者为此做出了很大的贡献。他具有以下特性:

* 离线状态下移动客户端仍可操作

o 保持离线状态客户端的数据一致性

o 带宽自适应

* 错误恢复

o 服务器之间的读写复制

o 解决服务器之间的冲突

o 处理服务器之间的网络故障

o 处理断开的客户端

* 性能和可靠性

o 客户端可高性能、持久的保存文件、目录以及属性。

o 回写式缓存

* 安全性

o Kerberos方式身份验证

o 访问控制列表

* 对共享的完美诠释

* 可以免费获取源代码

Fig 1: Coda 的标志 (原图例作者:Gaich Muramatsu)

你可能会对某些专业术语感到困惑,下面我们先把可能遇到的术语加以介绍。

分布式文件系统

一个分布的文件系统将文件存储在一个或更多的叫做服务器的计算机上,并且可以让客户机像访问普通文件一样访问。使用文件服务器有很多的好处。文件可 以尽可能广泛的被能访问服务器的计算机使用,在同一个地点存储并且共享某文件比把这个文件分散到许多客户机上单独存储要好。对于保证信息的安全而作的备份 处理相对更加容易,因为只有存储文件的服务器需要做备份。服务器可以提供很大的存储空间,如果每个客户端都单独准备一个这样大的空间的成本会很大,显然是 不现实的。当某一部分人需要共享文档的时候,分布式文件系统的有效性就体现出来了。更进一步的说,共享应用软件也是一个很好的选择。共享之后系统的管理将 会变得相对简单。

设计一个好的分布式文件系统需要处理很多的问题。由于网络本身的瓶颈和服务器的超负荷工作,在网络中传送大量的文件通常是效率很低并且有延迟。数据 的安全性也是另外的一个需要特别注意的方面,我们必须确认访问数据的客户端确实是经过我们认证的,并且数据在传送过程中没有泄漏。另外还有两个和故障处理 有关的方面。通常情况下客户端相对网络连接来说出故障的可能性更小。网络的故障会造成客户端不再使用的假象。同样,服务器的故障会令人更加不悦,因为它会 造成所有的客户端无法访问急需的信息。Coda作为一个分布式文件系统的研究原型将会一一处理上面提到的问题。

Fig. 2 服务器的安全控制 (原图例作者: Gaich Muramatsu)

Coda 最初是在Mach 2.6上实现的,并且最近已经移植到了Linux、NetBSD、FreeBSD。 Michael Callahan 已经将coda的一部分移植到了Windows 95上,我们正在研究将Coda移植到Windows NT上的可行性。现在我们主要在为coda向不同系统移植做努力。同时我们加入了一部分的新特性(例如:回写缓存、基本存储单元)并且重新编写了coda 代码的一部分。很多使用者通过互联网给我们发送了大量有用的回馈信息。在未来,coda很可能会成为一个受到大家欢迎的、广泛使用的、可免费获取的分布式 文件系统。

Coda客户端

假设我们在一个Linux工作站上运行coda客户端,输入mount后我们将会看到一种类型为”coda”的文件系统被挂在 /coda 。这个目录下就是服务器向客户端提供的文件,所有的客户端看到的都是相同的文件名、相同的空间。客户端连接的是”coda”,并不是连接到了某个单独的服 务器,这一切都是对客户不可见的。这与挂载单独一个服务器上的网络文件系统是有很大的区别的。常见的Windows下的文件系统(Novell和微软的 CIFS)以及Macintosh使用的Appleshare 文件系统都是按照单一的卷来挂载。当然,全局共享空间并不是coda发明的。Coda的前身-Andrew文件系统(AFS)首先提出了存储所有的文件在 /afs 。类似的,OSF中的DFS/DCE分布式文件系统也是将所有的文件挂载在同一个目录下。微软新开发的分布式文件系统(DFS)支持所有的服务器共享同样 的一个文件树,就像是unix下auto-mount守护进程和yellow pages的组合。为什么说单一的挂载点更好?这主要是因为所有的客户端可以被相同的配置,所有的用户永远都是看到相同的文件树。对于客户端数量非常多的 情况下,使用相同的配置是很重要的一点。假设使用NFS,客户端需要更新服务器的列表并且在/etc/fstab中存入相应的挂载点。使用coda的话, 客户端只需要知道coda的根目录是在/coda。当添加新的服务器或者添加新的共享目录时客户端会从/coda的目录树中自动获得信息。

了能理解在服务器连接在网络上的时候coda如何运作,我们首先分析一个简单的文件操作。假设我们通过输入”cat /coda/tmp/foo”来显示一个coda文件的内容。首先,cat程序将要进行一些和文件有关的系统调用。系统调用是程序要求内核进行某种服务的 操作。比如,当打开文件的时候,内核首先会去找i结点然后返回一个指向该文件的句柄给调用的程序。i结点中保存了可以让内核获取的如何调用文件的信息。文 件句柄是为需要的程序准备的。外部的调用进入系统内核的虚拟文件系统(VFS),他发现调用的是在/coda下面的一个文件后就通过内核中coda文件系 统的模块来处理。Coda是最低的最基本的文件系统模块,他保存最近的从虚拟文件系统中得到的回复,将请求送到Coda缓存管理系统。这个缓存管理系统被 命名为Venus。Venus首先检查客户端磁盘缓存中是否有tmp/foo,如果缓存没有命中,她将会连接服务器以便获取tmp/foo。当文件定位 后,Venus将向内核报告,并且最终从系统调用返回到应用程序。下图就是刚才提到的过程的图示。

Fig 3. Client/Venus/Vice

这张图表展示的就是应用程序通过系统调用来要求内核提供相应服务的过程。内核将请求送到Venus, Venus读取设备文件/dev/cfs0来获知请求。Venus通过读取cache、发送请求到服务器,或者按照离线模式来处理这些请求。离线模式是指 无法连接到存放该文件的服务器的情况。一般情况是指使用没有和原有网络连接的笔记本,或者网络连接出现了问题才会进入离线模式。当服务器出现问题的时候也 会进入离线模式。

当内核第一次将外部请求送到Venus的时候,Venus将通过远程程序调用(RPC)方式从服务器端下载整个文件,然后将文件作为一个文件容器存 放在缓存中(目前是/usr/coda/venus.cache/)。从这时起,文件就作为了一个本地的普通文件,所有的读写操作都不会涉及到 Venus,只会涉及到本地文件系统(比如在Linux下可能是ext2文件系统)。Coda的读写操作的速度是由本地文件系统响应操作的速度决定的。如 果文件再一次被打开,文件不会从服务器中再次传送,而是使用本地保存的那一份。目录文件(需要注意的是目录也只是一个文件而已)以及属性(比如属主、权 限、大小)都会被Venus暂存。Venus允许本地缓存中有需要的文件的时候不连接服务器对文件进行操作。当文件被修改并且关闭后, Venus将向服务器发送一个修改之后的新文件。其他的修改文件系统的行为,比如建立目录、删除文件或目录、建立或删除连接(或符号连接)同样也会向服务 器发送。

我们可以发现coda的缓存系统保存了客户需要的所有的信息,并且只在需要更新文件系统的时候才与服务器进行联系。研究表明,对文件的只读操作要比 修改操作多很多。因此我们主要研究的是如何减少客户和服务器之间的通信。主动缓存已经在AFS和DFS上实现,但是大多数的其他的系统没有做这样的工作。 在下面我们会看到coda如何保持文件的一致性,但首先我们先来讲将如何支持离线操作。

通过缓存来处理离线操作

coda 对离线操作处理的支持实际上使它变成了一个可以自动适应网络故障的文件系统。在80年代,卡耐基·梅隆大学的校园里使用AFS来连接约1000个客户端。 在这样大的规模下,网络和服务器的故障几乎是每天都会出现的。当大量的移动客户(比如笔记本)出现后,coda这样支持网络暂时失效的分布式文件系统被及 时的发明出来了。

在前面的一段中,我们已经讲到coda缓存所有的访问需要的信息。当做出对文件系统的更新后,需要有消息传送到服务器端。在通常的有网络连接的模式 下,这样的更新消息是同步传送给服务器的,也就是说当客户端作了更新的时候服务器端同时也作更新。如果服务器暂时不可用,或者服务器和客户端之间的网络连 接出现了故障,这种更新操作会报告一个超时错误并且会失败。在网络连接失效的情况下,有些操作是不可能完成的。比如试图从服务器上获取本地缓存中没有的文 件。在这样的情况下,必须向调用的程序报告错误。然而,大多数的超时错误可以按照下面提到的办法处理。

为了支持离线操作或者暂时的网络故障,Venus不会向用户报告更新超时错误。Venus会发现服务器出现了暂时不可用的问题,并且把这些更新在客 户端暂时作记录。在离线状态下,所有的更新都被记录在客户端修改日志(CML, client modification log)中。这个日志会经常地被写入到磁盘中。当coda切换到离线模式的时候,用户不会发觉任何的区别。当与服务器重新建立连接后,Venus将重建 CML。他将向服务器重新提交文件系统的更改,以便让服务器端的文件也保持最新。CML是经过优化处理的。他会自动取消类似建立一个文件然后又删除这样的 操作。

除上面提到的几点,另外还有两个与离线操作有关的问题。第一,离线操作是一个临时储存文件的概念。当缓存没有命中并且Venus无法连接到服务器 时,它需要尽可能的使关键文件保持最新,这就需要他不断的去要求服务器发送来最新的文件。这些关键文件就存放在用户的临时储存数据库中(这个数据库通过跟 踪用户的访问自动建立)。我们把更新临时储存文件的工作叫做”hoard walk”。在实际操作中,我们的笔记本储存了大量的系统软件,比如X11视窗系统的二进制文件和库,或者Wabi以及Microsoft Office。这些文件都像本地文件一样,储存的应用程序可以很好的运行。

Fig 4: 临时文件被”粘”在缓存中 (原图作者: Gaich Muramatsu)

第二个问题是在恢复连接之后重新处理修改过的文件时会遇到的。假如有多个客户端对同一个文件作了修改,并且都传送到了服务器,那么就会出现冲突。我 们把这样的冲突叫做局部/全局冲突(local/global)或者叫做客户端/服务器冲突(client/server)。这样的冲突有时候可以通过专 门的解决方法来自动修复(比如一个客户端向日历文件中写入周一的安排另一个客户端插入了一个周三的安排。这样的情况就是可以解决的冲突)。当然,有些时候 冲突是不能自动解决的,这就需要受人工的干预来进行处理。

假设我们在一个周五带着装满了源代码的笔记本离开了办公室去出差。在处理完堆积如山的工作之后,隔周的周一(也就是10天后)我们回到了办公室。当笔记本重新连接后,系统就会自动更新这些代码。这就是我们说所的移动办公。

Fig. 5 错误恢复的方法

卷,服务器,服务器间的复制

大多数的网络文件系统是在服务器上的一个可以被远程调用的本地文件结构。这种可以被远程客户端挂载的文件系统,在windows上称作网络共享 (network share),在unix上称作网络文件系统(network file system)。大多数这样的系统都是远程挂载一个已经在分布的服务器上的挂载好的卷。这样的系统关心的是服务器的分区、目录以及相应的共享。Coda和 AFS文件系统从本质上与这些是不一样的。

Coda服务器上的文件并不是存放在通常的文件系统上的。对于这些文件的组织如下所示。Coda 服务器以及工作站的分区作为对文件服务器可用的部分。这些分区将存放按照卷的方式来管理的文件。每一个卷带有一个和文件系统一样的目录结构,也就是说一个 卷的根目录以及根目录下的目录树。相对于分区来说,卷要小,但是相对于单一个目录或者单一的文件的逻辑单位来说,卷要大。举例来说,某个用户的home目 录可能就是一个单独的coda卷,coda相关的资源就存放在这个卷里面。通常情况下一个单独的服务器可能包含数百个卷,每个卷的平均大小可能是10兆。 从系统管理员的角度来看,卷是一个易于管理的很灵活的普通的文件数据。

Coda将卷、目录、访问控制列表以及文件的属性保存在一个raw分区中。通过一种基于日志的可恢复的虚拟内存系统 (log based recoverable virtual memory package, RVM)来实现快速并且确保一致性的访问。只有文件的数据保存在服务器的分区上。通过RVM可以实现对事务的支持,也就是说当服务器崩溃后,不用花费太多 的力气就可以恢复整个系统。

卷带有一个名称和他自己的编号,卷可以被挂载到/coda下的任何一个位置。比如要使一个叫做u.braam的卷挂载到 /coda/usr/braam,只需要执行”cfs makemount u.bramm /coda/usr/braam”。Coda不允许挂载点是一个已经存在的目录。他会把建立一个新的目录作为挂载的过程之一。这样做避免了将unix文件 系统挂载到已经存在的目录下。这个操作和Macintosh以及Windows的网络驱动器及共享很类似。但是最明显的区别是挂载点对于客户端是不可见 的。他看起来只是一个/coda下的很普通的目录。系统中有一个单独的卷具有作为根卷的权限,这个卷在启动的时候就被挂载。

Coda通过三组32位的整数来标示一个文件。这组整数被称为Fid。Fid包含卷编号(VolumeID)、V结点编号(VnodeID)以及一 个全局唯一标识符 (Uniquifier)。通过卷编号来确定文件存储的卷。V结点编号就是文件的i结点编号。全局唯一标识符来是为保持文件一致性处理准备的。Fid在 Coda服务器集群中是唯一的。

在coda服务器之间是有读写复制的。也就是说一组服务器向客户端发送文件数据,对文件的更新也会在这组服务器中执行。这样做的好处是提高了数据的 可用性,假如一个服务器出现了问题,其他的服务器会自动接替,客户端不会受到任何影响。卷可以存放在一组服务器中,我们把这样的一组服务器叫做VSG (Volume Storage Group)。

这些复制的卷的卷编号也是被复制的。复制后的卷编号将本地的卷结合在一起成为VSG。

VSG是一个保存复制后卷的服务器列表。

每个服务器的本地卷定义了一个分区和本地的卷编号来保存文件和元数据。

当Venus 要访问服务器上的对象,他首先需要找到卷信息以便确定保存该文件的卷。这个卷信息包含服务器列表和存放该文件的服务器上的卷编号。在VSG中的服务器之间 关于文件的通信是读一次写多次的操作,即读取VSG中某一个服务器上的文件然后向所有的可用的VSG成员传播更新的消息。我们把可用的VSG成员叫做 AVSG(Available VSG members)。Coda可以使用多点传送RPC来向很多服务器提交更新操作,这样做不会对整体的性能有太大的影响。

前面讲到的必须首先获得卷信息有时候会给人带来误解。一旦获取卷信息之后,接下来的文件访问因为路径遍历的减少而受益。这是因为卷的根相对通常的挂 载了很大的目录来说要近很多。(译者注:前文中提到每个卷都有自己根以及目录树,而卷可能会挂载在很深的目录中。直接从相应卷的根来找文件显然要比从 /coda找文件要快。)

服务器之间的复制就像是离线操作一样有两点需要介绍:决定是否复制以及如何修复错误。某些在VSG中的服务器可能因为服务器或者网络的故障和其他的 服务器隔离开了。在这样的情况下AVSG保存的对象一定会比VSG少。更新无法传递到所有的服务器上,只能传送到AVSG,从而会导致全局(服务器与服务 器之间)冲突。

Fig 6: AVSG vs. VSG (原图例作者: Gaich Muramatsu)

在获取某个对象或者某个对象的属性之前,Venus首先从所有可用的服务器上获取该对象的版本戳。如果她发现某些服务器没有这个文件的最新版,她就 会启动一个处理进程来自动解决不一致的情况。如果无法解决,就需要用户手动来进行修复。这个进程由客户端发起,然后完全由服务器来执行。

服务期间的复制和解决不一致的情况看起来是不可思议的操作。我们的服务器经常出现磁盘故障。为了修复服务器所需做的工作只是替换新的磁盘然后告诉coda:去解决他吧。系统会自动向磁盘写入从别的服务器上获取的最新的数据。

Coda的应用

Coda 一直在卡耐基·梅隆大学使用。约有数十个客户端使用它来做coda的开发工作,并且它作为一个完全的可以离线使用文件系统来运行特定的应用程序。下面的两 点可以说明coda的特性非常的成功。我们购买了一些Wabi以及相关的一些Windows软件的授权。Wabi可以让用户执行微软的 Powerpoint。我们把Wabi、Windows 3.1还有微软的Office程序存放在Coda上,并且与客户端进行共享。当然客户端的存放自己参数的.ini文件是每个用户单独使用的,大多数的库和 应用程序是共享的。通过临时暂存的文件我们仍然可以在离线的笔记本上进行展示。这种操作在会议上是经常遇到的。

我们已经使用这个系统很多年了,从来没有发生过丢失用户数据的现象。有些时候服务器上的磁盘报废了,但当所有的卷都被复制后,我们将一个空的磁盘放 入到服务器中并且让保持一致性的系统来自动更新数据。所需要做的工作实际上只是在放入新磁盘后在受到影响的目录树下运行”ls -lR”。服务器会发现缺少的文件,负责保持一致性的进程会将文件从正常的服务器上传送到新修复好的服务器。

有很多的应用程序都会因为Coda而受益。

FTP 的镜像站点可以是一个Coda客户端。我们用拥有众多镜像站点的ftp.redhat.com来做一个例子。每个镜像站点都是使用一个Perl脚本来遍历 Redhat的目录树以便知道是否有任何更新并且获取更新的文件,不论在镜像上是否需要这样。假如Redhat使用coda来作为他们的ftp空间,镜像 站点相当于coda客户端,只是Redhat自己有写入的权限。当Redhat更新文件的时候,Coda服务器会自动告知镜像站点有文件改变了。当某人试 图从镜像站点下载更新过的文件的时候,镜像站点才会从服务器上下载最新的文件。

WWW服务器也可以作为coda客户端。很多ISP因为 WWW服务器短缺而很头痛。对于单一的http服务器来说,访问量实在是太大了。已经证明使用NFS来共享文件会造成效率上面的问题,手动的在某几个特定 的服务器之间复制文件是经常需要做的操作。Coda可以解决这样的问题。每个服务器作为一个coda客户端,将数据保存在自己的缓存中。访问的速度是由本 地磁盘的速度来决定的。ISP可以离线来更改他们网页的信息,我们也为移动客户端做了一个很好用的应用程序。

网络计算机可以通过使用coda作为缓存来大大的提高性能。当服务器可用的时候,网络计算机会自动去更新。大多数的操作是没有网络流量的,即使重新启动。

我们现在要做的工作主要是提高Coda的质量。Coda的轮廓已经通过研究大体勾划出来了。回写式缓存将加入到Coda中以便能更高速的运转。离线 操作是一种很特殊的回写缓存操作。我们正在利用离线操作的代码来实现在线时的回写缓存。Kerberos的支持已经加入。网络协议对Coda的支持使这个 很容易做到。我们希望在未来能做到让Coda客户端同时连接多个Coda服务器集群,并且把Coda移植到尽可能多的操作系统上。

获取Coda

Coda可以通过 ftp.coda.cs.cmu.edu 来下载。 在这个服务器上你可以找到为 Linux 准备的 RPM 包以及 tar 打包的源代码。 Linux 2.2及以后的内核可以支持 Coda。 在我们的 www 站点 www.coda.cs.cmu.edu 上你可以找到更多的资源,比如邮件列表、手册以及研究论文等等。

来源URL:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=869554