Editlog的接口规范设计文档(HDFS-1580:Add interface for generic Write Ahead Logging mechanism)

来源:互联网 发布:skycc营销软件系统 编辑:程序博客网 时间:2024/06/05 04:55
转帖请注明本空间地址:http://blog.csdn.net/chenpingbupt 
原文请参:
https://issues.apache.org/jira/browse/HDFS-1580
https://issues.apache.org/jira/secure/attachment/12481883/EditlogInterface.3.pdf
https://issues.apache.org/jira/secure/attachment/12474635/generic_wal_iface.pdf
译文如下:
本文提出一个NN使用editlog的接口设计和规范。
1、设计目标
1、本接口应该和多种底层的editlog存储系统相适应,如本地文件系统,NFS,Bookkeeper等
2、本设计应该在现有的hdfs代码上很容易的实现
3、本设计应该很容易在需要HA的实现时,进行扩展。
2、术语定义:
Journal - 指的是一系列的Transactions,每个Transaction有一个连续增长的整数作为txid与之关联
StorageDevice - 指的是存储journals的设备,如nfs,bookkeeper等等。一个StrorageDevice可能具有内置的冗余保证,也能支持存储多个应用的journal文件,所有一个每个journal需要一个joural id来标识
JournalSet - 一般来说应用系统都会将journal并行的写入到多个存储设备中,为了消除存储系统的不可靠,或者为了进行share,而写入到nfs系统中。这个并行的journal就称为JournalSet,JournalSet扩展了Journal的属性(OO角度)
3、关键用例
典型的系统中,NN会将Journal并行写入到多个StorageDevices,如本地文件系统和nfs、bookkeeper等。以下的用例虽然只提到一个Journal,但是可以将其想象为一个JournalSet,类似的,如果提到一个StorageDevices,在一个StrorageDevice集下亦是适用。
1、NameNode Format:NN在格式化sd的时候将对journal进行初始化并且给点一个journalId,如果之前有一个存在的journal已经具有了该journalid,那么format过程将抛出一个异常,除非设置了强制进行format。
2、NameNode startup:
a、NN给sd一个指定的JournalID来打开该Journal,如果sd没有为这个JournalID进行初始化,那么将抛出异常
b、NN从一个特定的txid开始读取Journal,一般是从最后一个checkpointed image的最后一条TransactionID开始读取
c、NN获取一个Journal-output-stream开始写新的Transactions,一个Journal只能有一个writer来写入,否则抛出异常
3、NN定期对Journal进行roll,一般是由于checkpoint发起的
4、NN删除过期的Transaction,如果NN认为当前已经不需要了,如:checkpoint已经完成
5、CheckpointNode读取一个Journal的Transaction进行Checkpoint的时候,PNN也可以并行的写入到该Journal中
6、StandbyNN读取一个Journal来保持warm的时候,PNN同时也可以对该Journal进行写入
7、当一个StandbyNN准备变成ActiveNN时,会通知该sd去fence其他writer,如果sd不支持fence,那么抛出异常。
Journal抽象设计考虑点
需要将segment这个概念暴露出来吗?原本考虑每个segment都具有相同的布局,能够从Transaction中获取segment的信息,所以在Transaction Record中不需要存储这个segment。另外roll这个方法也没有暴露出来,Journal可以被看作是单一的Transaction序列,从这个角度来看我们也不需要暴露segment的概念
JournalSet和Journal:这两者的关系与不同在哪里?前者继承了后者,前者是后者的同一种抽象的另一种实现。
4、接口规范提议:
interface Journal {
     Journal (URI u, boolean format, boolean force) throws AlreadyExistsException, IOException;
     EditLogOutputStream getOutputStream() throws MultipleWriterException, IOException;
     //This indicates that transactions up to txnId are safe to be deleted.
     void purgeTransactions (long txnId);
     EditLogInputStream getInputStream (long sinceTxnId) throws IOException;
     long getNumberOfTransactions(long sinceTxnId) throws IOException;
}
Interface EditLogOutputStream {
     void write(long txnId, byte[] txn) throws IOException;
     void setVersion(long version);
     void mark();
     close();
     //Alternative to this method is to sync upon every write and let write return asynchronously
     //so that fsnamesystem lock and handle thread is not held while writing and sync‐ing.
     void sync() throws IOException;
}
abstract class EditLogInputStream extends InputStream{
     void readNext();
     long getTxnId();
     long getVersion();
     byte[] getTxn();
     close();
}
5、讨论:
1、由Journal的Address和ID组成其URI,NN应该为其生成唯一的JournalID
2、Journal可以被构建,格式化,强制格式化。格式化意味着创建一个新的JournalID,强制格式化意味着删除老的。
3、如果一个stream已经被open了,再来进行getOutputStream()应该抛出异常
4、getInputStream应该返回一个从特定txid开始inputStream
5、purgeTransation判断能否安全的删除到所有到指定txid的Transaction
6、EditLogOutputStream.setVersion()应该保证任何的后续Transaction必须使用这个新的version
7、EditLogInputStream.readNext()应该读向下一个Transaction,后续的getTxnId或者getVersion都应该返回这个Transaction的相关属性。
readNext()会将底层的流指向下一个Transaction的开始位置,所以如果应用调用getTxn()可以直接读取到这个Transaction而避免buffer-copy。出于这个原因,EditLogInputStream也实现InputStream去直接读取底层流
8、EditLogInputStream.mark()表示这个输入流最多可以读到mark调用的前一个Transaction。这方法和Checkpoint中的roll方法类似。
9、getNumberOfTransactions(sinceTxnId)返回Journal中可以提供的从sinceTxnId开始的Transaction数量
10、roll方法可以去掉,因为mark完全可以取代
11、当某一checkpointer通知NN开始进行checkpoint,NN将在outputstream上进行mark。那样,所有到mark为止的Transaction都会被checkpointer读取到。
12、新增sync方法,因为在写入editlog的thread一把都是在对namespace进行write,持有写锁,异步的写入可以使得thread尽早释放写锁。
6、Error secnarios
1、当一个outputStream由于权限,空间不足,网络等问题不能创建时,getOutputStream会抛出异常,然后进行重试或者中止
2、如果EditLogOutputStream.write()调用不能完成时,需要抛出异常。这时NN可以将该stream关闭,然后将该Journal加入到bad Journal列表。后续可以尝试将该Journal进行restore,如果成功,可以在该stream上再次进行getOutputStream()。这个情况下,这个Journal会丢失一些Transactions,因此无法提供老的Transactions。另一方面对于client来说,write的异常使得client要么进行重试写入该Transaction,但是不能跳过这个而写入下一个Transaction。
3、一个打开的Stream必须要保证Transaction是顺序的并连续的。但如果是一个新打开的Stream,到时可以不接着上一个Transaction的txid连续的。但是必须要记录这个流不能提供老的Transactions
4、如果Journal失效一段时间然后在写入的时候,就会出现Transaction空洞。那么这时候调用getInputStream(long sinceTxnId)正好是空洞中的Transaction时,将抛出异常。这就意味这Journal必须记录自己能提供的Transactions
5、另外getNumberOfTransactions(long sinceTxnId)在sinceTxnId之后发现空洞,也会抛出异常
6、readNext如果不能完整的读取一个Transaction,也抛出异常,这是客户端可以:
a、抛弃这个Journal
b、关闭这个inputstream,从下一个Txit打开另一个inputstream
7、如果Client检查到Transaction损坏时,要么抛弃这个Journal,要么关闭这个inputStream,从下一个txit打开下一个inputstream
7、JournalSet
JournalSet和Journal具有一致的接口,如前所述JournalSet将一系列的Journals在内部并行操作,对外部而言,就像只有一个Journal,所以它的接口和Journal是一致的。接口如下:
public class JournalSet implements Journal {
     JournalSet(Configuration conf, URI u, boolean format, boolean force) ;
     //List of journals
     private List<Journal> listOfJournals;
}
JournalSet的构造函数需要从conf中获取Journal的配置,并将其放入到listOfJournals列表中管理。
写入时,JournalSet会将写入流分流的写入到各个Journals内去
读取时,JournalSet会选择一个它认为最好的Journal进行读取
getNumberOfTransactions返回所有流能读到的最大值
purgeTransactions会作用到所有的底层流中