Ericson's blog Time is limited, To be a better better man

Hadoop safemode详解

Hadoop在NameNode启动时会先进入Safemode安全模式。在安全模式下,只允许对元数据的访问操作。默认情况下,NameNode在重启时,DataNode需要向NameNode发送块的信息,NameNode只有获取到整个文件系统中99.9%(这可以配置)的块满足最小副本才会自动退出安全模式。百分比可以通过dfs.namenode.safemode.threshold-pct来设置,这个值小于0表示无须等待直接退出安全模式;这个值大于1表示永远处于安全模式。

Hadoop safemode主要从4个配置中读取,然后决定是不是安全模式。配置如下:

  • dfs.namenode.replication.min:这个配置表示块满足的最小的备份数
  • dfs.namenode.safemode.threshold-pct:这个配置表示共需有多少百分比的块满足上面备份要求,才能退出安全模式,如果这个百分比大于1,则会永久处于安全模式
  • dfs.namenode.safemode.min.datanodes:表示在NameNode退出安全模式之前活着的datanode的数量,当这个数量大于集群datanode数量时,表示永久位于安全模式
  • dfs.namenode.safemode.extension:这个配置表示在满足第二个条件时,NameNode还需要处于安全模式的时间,单位是秒

Hadoop safemode的主要命令:

  • 进入:hadoop dfsadmin -safemode enter
  • 获取:hadoop dfsadmin -safemode get
  • 离开:hadoop dfsadmin -safemode leave
实际遇到问题:当NameNode所在节点的资源不够,即使使用命令离开安全模式,也会自动进入安全模式,所以配置Hadoop时要注意这个问题。

Hadoop源码编译

下面针对Hadoop2.5.0源码讲解源码编译:

编译前的需求:

  • Linux System
  • JDK 1.6+
  • Maven 3.0+
  • Findbugs 1.3.9(if running findbugs)
  • ProtocolBuffer 2.5.0
  • CMake 2.6+(if compiling native code)
  • Zlib devel(if compiling native code)
  • openssl devel(if compiling native hadoop-pipes)
  • Internet connection for first build(to fetch all Maven and Hadoop dependencies)

Maven main modules: hadoop(Main Hadoop Project)

  • hadoop-project(Parent POM for all Hadoop Maven modules)
  • hadoop-project-dist(Parent POM for modules that generate distributions)
  • hadoop-annotations(Generates the Hadoop doclet used to generated the Javadocs)
  • hadoop-assembiles(Maven assembiles used by the different modules)
  • hadoop-common-project(Hadoop Common)
  • hadoop-hdfs-project(Hadoop HDFS)
  • hadoop-mapreduce-project(Hadoop MapReduce)
  • hadoop-tools(Hadoop tools like Streaming, Distcp, etc)
  • hadoop-dist(Hadoop distributions assembler)

Maven build goals:

  • Clean: mvn clean
  • Compile: mvn compile [-Pnative]
  • Run tests: mvn test [-Pnative]
  • Create JAR: mvn package
  • Run findbugs: mvn compile fingbugs:findbugs
  • Run checkstyle: mvn compile checkstyle:checkstyle
  • Install JAR in M2 cache: mvn install
  • Deploy JAR to Maven repo: mvn deploy
  • Run clover: mvn test -Pclover [-DcloverLicenseLocation=${use.name}/.clover.license]
  • Run Rat: mvn apache-rat:check
  • Build javadocs: mvn javadoc:javadoc
  • Build distribution: mvn package [-Pdist][-Pdocs][-Psrc][-Pnative][-Dtar]
  • Change Hadoop version: mvn version:set -DnewVersion=NEWVERSION

Build options:

  • Use -Pnative to compile/bundle native code
  • Use -Pdocs to generate & bundle the documentation in the distribution(using -Pdist)
  • Use -Psrc to create a project source TAR.GZ
  • Use -Dtar to create a TAR with the distribution(using -Pdist)

模式总结

模式分类:
创建型模式:

  • 抽象工厂模式:提供一个创建一系列或相关依赖对象的接口,而无需指定它们具体的类
  • 建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
  • 工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂模式使一个类的实例化延迟到其子类
  • 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
  • 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点

创建型模式隐藏了这些类的实例是如何被创建和放在一起,整个系统关于这些对象所知道的是由抽象类所定义的接口。这样,创建型模式在创建了什么、谁创建它、它是怎么被创建的,以及何时创建这些方面提供了很大的灵活性。内聚性描述的是一个例程内部组成部分之间相互联系的紧密程度。而耦合性描述的是一个例程与其他例程之间联系的紧密程度。软件开发的目标应该是创建这样的例程:内部完整,也就是高内聚,而与其他例程之间的联系则是小巧、直接、可见、灵活的,这就是松耦合。

结构型模式:

  • 适配器模式:将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
  • 桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化
  • 组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性
  • 装饰者模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更加灵活
  • 外观模式:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
  • 享元模式:为运用共享技术有效地支持大量细粒度的对象
  • 代理模式:为其他对象提供一种代理以控制对这个对象的访问

行为型模式:

  • 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
  • 模板方法模式:定义一个操作的算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
  • 命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,可以对请求排队或记录请求日志,以及支持可撤销的操作
  • 状态模式:允许一个对象在其内部状态改变时改变它的行为,让对象看起来似乎修改它的类
  • 职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
  • 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
  • 中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
  • 访问者模式:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
  • 策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化
  • 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
  • 迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示

访问者模式

访问者模式,表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新元素。访问者模式结构图如下所示: visitor 访问者模式的目的是要把处理从数据结构分离出来,访问者模式的优点是增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。访问者模式的缺点其实也就是使增加新的数据结构变得困难。

Visitor类,为该对象结构中ConcreteElement的每一个类声明一个Visit操作。

abstract class Visitor{
	public abstract void VisitConcreteElementA(ConcreteElementA concreteElementA);
	public abstract void VisitConcreteElementB(ConcreteElementB concreteElementB);
}

ConcreteVisitor1和ConcreteVisitor2类,具体访问者,实现每个由Visitor声明的操作。每个操作实现算法的一部分,而该算法片断乃是对应于结构中对象的类。

class ConcreteVisitor1 extends Visitor{
	public void VisitConcreteElementA(ConcreteElementA concreteElementA){
		System.out.println("A访问");
	}
	public void VisitConcreteElementB(ConcreteElementB concreteElementB){
		System.out.println("B访问");
	}
}
class ConcreteVisitor2 extends Visitor{
	//同上
}

Element类,定义一个Accept操作,它以一个访问者为参数:

abstract class Element{
	public abstract void Accept(Visitor visitor);
}

ConcreteElementA和ConcreteElementB类,具体元素,实现Accept操作:

class ConcreteElementA extends Element{
	public void Accept(Visitor visitor){
		visitor.VisitConcreteElementA(this);
	}
	public void OperationA(){}
}
class ConcreteElementB extends Element{
	public void Accept(Visitor visitor){
		visitor.VisitConcreteElementB(this);
	}
}

ObjectStructure类,能枚举它的元素,可以提供一个高层接口以允许访问者访问它的元素。

class ObjectStructure{
	private List<Element> elements=new ArrayList<Element>();
	private void Attach(Element element){
		elements.add(element);
	}
	private void Detach(Element element){
		elements.remove(element);
	}
	public void Accept(Visitor visitor){
		for(Element e:elements){
			e.Accept(visitor);
		}
	}
}

客户端代码:

static void main(String[] args){
	ObjectStructure o=new ObjectStructure();
	o.Attach(new ConcreteElementA());
	o.Attach(new ConcreteElementB());
	ConcreteVisitor1 v1=new ConcreteVisitor1();
	ConcreteVisitor2 v2=new ConcreteVisitor2();
	o.Accept(v1);
	o.Accept(v2);
}

访问者模式优点:

  • 符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,适用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展
  • 扩展性良好:元素类可以通过接受不同的访问者实现对不同操作的扩展

访问者模式适用场景:
假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者去。

假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去。

但是,访问者模式并不是那么完美,它也有着致命的缺陷:增加新的元素类比较困难。通过访问者模式代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。所以访问者模式比较适用于对已有功能的重构,比如说,一个项目的基本功能已经确定下来,元素类的数据已经基本确定下来不会变了,会变的只有这些元素内的相关操作,这时候,我们可以使用访问者模式对原有的代码进行重构一遍,这样一来,就可以在不修改各个元素类的情况下,对原有功能进行修改。

解释器模式

解释器模式,给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。解释器模式需要解决的是,如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。解释器模式结构图如下所示: interpreter AbstractExpression(抽象表达式),声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。

abstract class AbstractExpression{
    public abstract void Interpret(Context context);
}

TerminalExpression(终结符表达式),实现与文法中的终结符相关联的解释操作。实现抽象表达式中所要求的接口,主要是一个interpret方法。文法中每一个终结符都有一个具体终结表达式与之相对应。

class TerminalExpression extends AbstractExpression{
    public void Interpret(Context context){
        System.out.println("终端解释器");
    }
}

NonterminalExpression(非终结符表达式),为文法中的非终结符实现解释操作。对文法中每一条规则R1,R2 …. Rn都需要一个具体的非终结符表达式类。通过实现抽象表达式的interpret方法实现解释操作。解释操作以递归方式调用上面所提到的代表R1,R2…..Rn中各个符号的实例变量。

class NonterminalExpression extends AbstractExpression{
    public void Interpret(Context context){
        System.out.println("非终端解释器");
    }
}

Context,包含解释器之外的一些全局信息。

class Context{
    private String input;
    private String output;
}

客户端代码,构建表示该文法定义的语言中一个特定句子的抽象语法树,调用解释操作:

static void main(String[] args){
    Context context=new Context();
    List<AbstractExpression> list=new ArrayList<AbstractExpression>();
    list.add(new TerminalExpression());
    list.add(new NonterminalExpression());
    list.add(new TerminalExpression());
    list.add(new TerminalExpression());
    for(AbstractExpression expre:list){
        expre.Interpret(context);
    }
}

当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。建议当文法非常复杂时,使用其他技术如语法分析程序或编译器生成器来处理。

解释器模式优缺点:
优点:

  • 可扩展性比较好,灵活
  • 增加了新的解释器表达式的方式
  • 易于实现文法

缺点:

  • 执行效率比较低,可利用场景比较少
  • 对于复杂的文法比较难以维护

解释器模式适用场景:

  • 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
  • 一些重复出现的问题可以用一种简单的语言来进行表达
  • 文法较为简单

解释器案例:计算器就是一个解释器,把输入的表达式字符串按文法解释为最后的值。

解释器模式总结:

  • 在解释器模式中由于语法由很多类表示的,所以可扩展性强
  • 虽然解释器的可扩展性强,但是如果语法规则的数目太大的时候,该模式可能就会变得异常复杂,所以解释器模式适用于文法较为简单的。
  • 解释器模式可以处理脚本语言和编程语言。常用于解决某一特定类型的问题频繁发生情况。