github 利用ssh进行操作

      公司太操蛋,所有https劫持,github连不上,什么都提交不了。检查了一下,可以用ssh进行操作,记录一下。

      首先,查看一下github的说明,加入ssh key所有需要的东西, 链接为 https://help.github.com/articles/using-ssh-over-the-https-port/ 。

      第二,如果一切顺利,通过ssh操作,从自己的仓库中找到ssh地址,然后就可以了,比如 git push git@github.com:jiangyu/scalaLearn.git master

因为被扫端口导致JobTracker挂起问题

       最近一段时间我们的Hbase集群所用的JobTracker(hadoop基线版本1.0.2)天天夜里挂起,停止服务,无法回应任何TaskTracker心跳,进而无法调度任务。以下是心跳汇报报错:

                                                 Jt1

      可以看出最后出错在fairscheduler调度任务阶段,有pool name是null,导致了最后排序报NPE错误。        

      最后查找这个NULL值得来源,通过jira发现社区已经发现了这一个问题,jira地址是https://issues.apache.org/jira/browse/MAPREDUCE-4195。        简单的说一下这个问题,第一步是有人通过脚本或者工具(不是通过页面链接)调用了jobqueue_details.jsp页面,错误发生在下面代码

  String queueName = request.getParameter("queueName");
  TaskScheduler scheduler = tracker.getTaskScheduler();
  Collection jobs = scheduler.getJobs(queueName);

queueName传入的肯定是null,然后调用FairScheduler的getJobs,此时queueName是null。FairScheduler会调用PoolManager的getPool方法: 

  public synchronized Pool getPool(String name) {
    Pool pool = pools.get(name);
    if (pool == null) {
      pool = new Pool(scheduler, name);
      pool.setSchedulingMode(defaultSchedulingMode);
      pools.put(name, pool);
    }
    return pool;
  }

      通过这个方法,就产生了名称是NULL的queueName。此后就跟前文所说,调度器挂起,JT无法调度任何服务。解决方法:参见jira。

      另外,提一下我们产生这个问题的原因,是由于Sina曾经被人攻陷内网,安全组有两台服务器不停的扫内部服务器端口,hadoop bug就这样被触发了。

 

Hadoop trunk maven编译问题

 从Hadoop trunk git库中pull下来代码后,需要进行编译,但是编译过程中报以下错误:

[ERROR] Plugin org.apache.hadoop:hadoop-maven-plugins:3.0.0-SNAPSHOT or one of its dependencies could not be resolved: Could not find artifact org.apache.hadoop:hadoop-maven-plugins:jar:3.0.0-SNAPSHOT -> [Help 1]

       解决方法很简单:

       $ cd hadoop-maven-plugins

       $ mvn install

Java GZIPInputStream OutOfMemoryError

       记录一个集群中用户提交任务失败的情况,用户的任务一直没有问题,但是某天的日志过来后,在集群中报错,报的是GZipInputStream 的OutOfMemory问题。刚开始认为是数据过大,将内存设置大一些就好了,但是设置内存不管作用,在单机给他的日志做测试的时候依然报错,错误堆栈信息为:

2014-11-10 09:52:54,743 FATAL [main] org.apache.hadoop.mapred.YarnChild: Error running child : java.lang.OutOfMemoryError

        at java.util.zip.Inflater.inflateBytes(Native Method)

        at java.util.zip.Inflater.inflate(Inflater.java:238)

        at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:135)

        at java.util.zip.GZIPInputStream.read(GZIPInputStream.java:90)

        at com.sina.suda.mr.util.BinaryDecoder.unGZip(BinaryDecoder.java:21)

        at com.sina.suda.mr.util.BinaryDecoder.decodePostStr(BinaryDecoder.java:67)

       根据堆栈查看了一下jdk source code的路径,发现最后的native 代码报错。Google该问题,发现时jdk6的问题,由于日志传输过程中gzip文件corrrupt掉了,这样导致了OutOfMemory。解决方法比较暴力,在外层catch OutOfMemoryError,然后返回一个空结果。

ThreadLocal造成Hadoop权限混乱问题

一、问题现象

       10月底进行了一次中心机迁移,迁移完成后发现大量文件的权限不正确,导致了用户任务大量出问题。为暂时解决问题,我们对所有的目录增加读和执行权限(a+rx),对所有文件增加了读(a+r)权限,以保证用户能够运行任务。

P1

         如上图所示,用户文件被加入了Acl功能,而管理员并没有为该用户开通任何Acl功能。

P2

        查询Acl后,发现用户加入了一个完全无关的Acl。可以确定是Hadoop的元数据出了问题,导致了权限的混乱。

二、bug 查找

       第二天取消了大部分目录的ACL,同时又设置了一些ACL。在查找问题的过程中发现从新中心机拿出FSImage并重新加载,取消的ACL也有部分设置了新的ACL,可是从新的中心机内存中通过hdfs dfs -ls 查询后权限是完全正常的。初步怀疑是SNN merge Edit和FSImage出问题或者FSImage EditLog写入的时候出问题,最复杂的就是JournalNode内部出问题(最不可能的一种情况)。

       首先查看了一下SNN的merge代码路径,与ANN的savenamespace 路径是完全一致的。写了一个简单的测试程序,从ANN大量随机的建立目录(包含ACL和不含ACL)并记录其EditLog日志,同时从SNN不断的读取journalnode中的Edits,并记录,对比两边Edit日志,最后发现两边的Edit日志是完全一样的,这就排除了是SNN后者Journalnode出的问题。进而去查看记录的EditLog日志,发现在ANN中的Editlog是存在问题的,一些本没有设置ACL的目录被加入了ACL记录,说明这是在ANN log Editlog的时候出现的问题。查看mkdir的代码路径,代码段一直是在sync中的,所以肯定不是由竞争导致的数据不一致,继续查找,在logMkdir部分发现了这段代码,

  public void logMkDir(String path, INode newNode) {

    PermissionStatus permissions = newNode.getPermissionStatus();

    MkdirOp op = MkdirOp.getInstance(cache.get())

      .setInodeId(newNode.getId())

      .setPath(path)

      .setTimestamp(newNode.getModificationTime())

      .setPermissionStatus(permissions);

 

    AclFeature f = newNode.getAclFeature();

    if (f != null) {

      op.setAclEntries(AclStorage.readINodeLogicalAcl(newNode));

    }

    logEdit(op);

  }

           其中cache是一个ThreadLocal的变量,属于线程私有变量,这就存在了一个非常严重的问题,假设之前这个线程mkdir继承了一个ACL,那么就会走到op.setAclEntries,op是从cache中获得的,这次mkdir没有任何问题。下面这个线程又logMkdir,这次newNode是一个普通的INode,不带ACL属性,那么就不会设置任何AclEntries,但是cache中的MkdirOp是存在AclEntries属性的,就是上次设置的属性,这样logEdit以后这个dir的permission就混乱了,不是自己的属性了。导致了重启后属性出现的问题。

       这个bug我已经反馈给了社区,jira号是https://issues.apache.org/jira/browse/HDFS-7385 

 

三、解决方案

          patch非常简单,对于没有AclEntries的op,设置null即可。对于已经存在问题的NameNode,从ANN saveNamespace,获得一个正确的FSImage,SNN先升级代码,读取正确的Image,启动,切换为ANN,再把原来的ANN升级。

Java ThreadLocal介绍

       记录一下ThreadLocal的一些用法,以及由此引出的一个Hadoop bug。首先说一下ThreadLocal,通常在多线程编程方面我们更多使用的是Lock,Synchronized等等,ThreadLocal使用并不是很多。当年我入职淘宝时候其中的一道笔试题考查的就是ThreadLocal。 根据JDK中的描述,ThreadLocal提供了Thread本地变量。相对于ThreadLocal,其他变量能够被所有线程访问并且改写,需要通过同步机制来进行协调。而ThreadLocal是每个线程内部的一个私有变量,线程有个隐式的引用,线程之间互不干扰。简单的说就是ThreadLocal是通过空间换时间,每个线程对应了一个ThreadLocal的私有变量,只有这个线程自己使用,它的好处是不用关注线程之间的相互干扰,不需要加锁等,这样在大并发情况下是相当划算的,因为synchronized操作毕竟是相当损失性能的。

       但是,使用ThreadLocal的时候要注意ThreadLocal变量是线程独有的,如果通过ThreadLocal需改一些状态改变,一定要注意不要将这个线程之前的状态加入到这次操作中,导致错误。比如,下面的列子,我有10个线程,每个线程有一个Meta的ThreadLocal变量,我的本意是10个线程去设置状态,每个线程做1000次操作,其中有一次是Exception的操作,其它都是正确的操作。下面为代码:

       Meta类:

package com.dj;
 
 
public class Meta {
 
  private int number;
  private STATE state;
 
  public Meta() {
    number = 0;
    state = STATE.NORMAL;
  }
 
  private Meta(int number, STATE state) {
    this.number = number;
    this.state = state;
  }
 
  public int getNumber() {
    return number;
  }
 
  public void setNumber(int number) {
    this.number = number;
  }
 
  public STATE getState() {
    return state;
  }
 
  public void setState(STATE state) {
    this.state = state;
  }
 
  public static Meta spawn(Meta meta) {
    return new Meta(meta.number,meta.state);
  }
 
}

       STATE类:

package com.dj;
 
public enum STATE {
  NORMAL, EXCEPTION
};

       Process类。

package com.dj;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
 
public class Process {
  public static final AtomicInteger GENERATOR = new AtomicInteger(0);
  public static final int TIMES =  1000;
  public static final int THREADS = 10;
  public static final int EXCEPTION_NUM = 10;
  public ArrayList<Meta> list = new ArrayList<Meta>();
 
 
  ThreadLocal<Meta> t = new ThreadLocal<Meta>() {
    protected Meta initialValue() {
      return new Meta();
    };
  };
 
  synchronized void process(STATE state) {
    t.get().setNumber(GENERATOR.getAndAdd(1));
    if(state==STATE.EXCEPTION) {
      t.get().setState(state);
    }
    list.add(Meta.spawn(t.get()));
  }
 
 
  public int stat() {
    Collections.sort(list, new Comparator<Meta>() {
 
      @Override
      public int compare(Meta o1, Meta o2) {
        return o1.getNumber() < o2.getNumber() ? -1 : 1; 
      }
 
    });
 
    int exception = 0;
    for(Meta meta: list) {
      if(meta.getState() == STATE.EXCEPTION)
        exception++;
    }
 
 
    System.out.println("Exception number should be " +
    EXCEPTION_NUM+" but the real number is "+exception);
    return exception;
  }
 
  public static void main(String[] args) {
    final Semaphore locks = new Semaphore(THREADS);
    Thread[] threads = new Thread[THREADS];
    final Process process = new Process();
    final AtomicInteger exSize = new AtomicInteger(0);
    for(Thread t : threads) {
      t = new Thread(new Runnable() {
 
        @Override
        public void run() {
          try {
            locks.acquire();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          Random r = new Random();
          int setException = r.nextInt(TIMES);
          exSize.addAndGet(TIMES-setException);
          for(int i=0 ; i<TIMES; i++) {
            if(i == setException)
              process.process(STATE.EXCEPTION);
            else 
              process.process(STATE.NORMAL);
          }
          locks.release();
        }
      });
      t.start();
    }
    int realNumber = 0;
    while(true) {
      if(locks.availablePermits() == THREADS) {
        realNumber = process.stat();
        break;
      }
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        break;
      }
    }
    System.out.println(realNumber+"  "+exSize);
  }
}

       运行时结果为:

Exception number should be 10 
but the real number is 4943 4943 4943

       这也很好解释,由于我们设置Exception操作之后没有重新设置状态,导致了异常状态有上千次。这其实也是近期我发现的Hadoop一个比较严重Bug的一个模拟。下一篇会讨论一下Hadoop的这个问题。

JAVA对象内存layout

       迟到的文章,4年前就有所研究,但是总是用的时候现看,补上这篇blog,方便自己查询记忆。 通常情况上来说,Java程序员不需要了解对象在内存中的情况,这是一件非常幸福的事情,不需要malloc,free等等,更不需要管垃圾回收。但是,在一些情况下,比如内存占用非常大的应用(像hadoop namenode),就需要对内存占用十分敏感,比如我之前的blog曾经计算过,如果大规模设置ACLs后对hadoop集群内存的压力。 这种情况就需要仔细的计算java类的内存使用情况,一点点的内存节省,当面临成千万的对象的时候效果就会很明显。

       JAVA对象内存计算分为shallow size和deep size。shallow size就是这个对象自己占用的内存空间,包括有基本数据类型占用的内存,其它reference对象的reference 占用的内存。和deep size就是这个除了这个对象自己占用的内存空间,再加上reference对象的占用内存综合。我更感兴趣的是deep size。 一下所有计算都是按照64 bit进行的,32 bit请自行查询。默认情况下java6都是开启-XX:+UseCompressedOops,会对object 的header计算有影响。

        64 bit object的header压缩的占用12byte,不压缩的占用16byte,包括有对象类型的指针,锁信息,以及hash等等。 基本的类型内存占用如下: mem1

       对象的reference64位不压缩的占用8 bytes, 开启压缩占用4 bytes。

       内存layout规则如下:

       1、对象在内存中都是8字节对齐的,即内存计算后不能被8整除,要补充到8字节对齐

       2、基本数据类型也需要对齐,根据类型占用字节数对齐,比如long、double8字节对齐, int,float 4字节对齐

       3、在多数情况下,由于对齐的原因,JVM会按照类型占用的字节多少排列顺序,占用多的排在前面,占用少的排在后面,对象的reference排在最后。比如:

class MyClass {
   byte a;
   int c;
   boolean d; 
   long e;
   Object f; 
}

       如果按照定义的顺序,那么内存使用如下,总共有14 bytes的padding。

[HEADER: 16 bytes] 16 
[a: 1 byte ] 17
[padding: 3 bytes] 20 
[c: 4 bytes] 24 
[d: 1 byte ] 25 
[padding: 7 bytes] 32 
[e: 8 bytes] 40 
[f: 4 bytes] 44 
[padding: 4 bytes] 48

       如果按照大小排列,也就是2中的规则排列,少使用了6 bytes。

[HEADER: 16 bytes] 16 
[e: 8 bytes] 24 
[c: 4 bytes] 28 
[a: 1 byte ] 29 
[d: 1 byte ] 30 
[padding: 2 bytes] 32 
[f: 4 bytes] 36 
[padding: 4 bytes] 40

       4、class field之间不会混在一起,如果class B extends class A,那么父类所有field先去排列,接下来是子类,同时子类起始位置需要对齐4 byte。

       5、对于数组来说,header除了上面说的以外,还需要加入8 bytes,标识array 长度

DataNode BlockReport bug解决(二)

三、bug复现

       测试环境生成一个5M的测试文件,文件路径为/test/missdisk/part-m-0000,fsck查看了一下,该文件只有一个block,blockid为blk_1074399823_659259,该block的三个副本分别分布在5.42、5.26和5.27上面。

为了重现问题,选择5.26和5.27停掉DN,同时在5.42上面将将存放块blk_1074399823的盘data6下线。

       使用命令 echo “scsi remove-single-device 00 02 06 00″ > /proc/scsi/scsi

       

       查看5.42的DN日志,日志显示FsDatasetImpl移除data6盘,同时从FsDatasetImpl的数据结构中移除data6上面的所有副本信息,5.42会errorReport 将data6盘掉盘信息汇报给NN,NN记录下了。但是NN并没有将该盘上面的块信息从NN的数据结构blockmap中移除,由于5.26和5.27的DN停掉了,所以这个时候NN会从5.42上面仅存的副本(实际上已经坏了)repicate到其他两个节点上面去。当从5.42拷贝数据副本到其他节点时,实际上DN认为该副本其实已经损坏,并将块损坏了不能拷贝到其他节点上报给NN,最终副本拷贝失败。等待一段时间之后执行fsck,发现nn显示42节点副本是好的,但是get文件时报错。50070页面显示没有missing block,但是在Live Nodes页面对应的DN又有Failed Volumes。该问题成功重现。


四、解决方案

https://issues.apache.org/jira/browse/HDFS-7208?jql=project%20%3D%20HDFS%20AND%20text%20~%20errorReport

社区提出了解决这个问题的四种方案:

1、DN通过DatanodeProtocol.reportBadBlocks将坏盘中对应的数据块信息通知NN,让NN从数据结构中移除这些块信息。这种方式比较慢,因为每次只能上报一个block。

2、DN通过 DatanodeProtocol.errorReport将坏盘的storageId告诉给NN,从而让NN移除storageId下面所要的块信息,这种方式需要修改DatanodeProtocol.errorReport接口,增加storageId参数,同时DN端代码需要大量调整,比较麻烦。 

3、DN通过blockReport的方式告诉NN有坏盘,并从NN中移除坏盘中得数据块信息,由于blockReport每6小时才会执行一次block的全量汇报,这种方式会有很大的延迟。

4、DN通过心跳的方式告诉NN有坏盘,并从NN中移除坏盘中得数据块信息,社区提供的patch就是基于这种方式。这种方式不用只用修改NN端的代码。