線上Spark處理Bzip2引出Hadoop Bzip2線程安全問題

我們的Hadoop生產環境有兩個版本,其中一個是1.0.3,爲了支持日誌壓縮和split,我們添加了hadoop-1.2中關於Bzip2壓縮的feature. 一切運行良好。

爲了滿足公司對迭代計算的需求(複雜HiveSQL,廣告推薦算法,機器學習 etc), 我們構建了自己的Spark集羣,最初是Standalone Mode,版本spark-0.9.1,支持Shark。

上線後,問題接踵而來,最爲致命的是,shark在處理Hadooop bzip2文件時計算結果通常會有偏差,有時差的特別離譜(比如,用shark統計1個5kw行的日誌,結果只有

3kw行).

顯然shark+hive+spark+hadoop的某個環節出了bug。第一次面對這麼複雜的系統,着實頭疼。

於是,開始蠻幹,部署shark+hive+spark+hadoop開發環境,debug,查看出問題的環節。(這個過程中把Spark-core的源碼也縷了一遍),始終沒有發現什麼問題。

後來,參加了Spark技術大會,和同行交流的過程中,幡然悔悟: Spark的task是線程級併發的,而Hadoop MR的task是進程級併發的,那麼,會不會是Bzip2存在線程安全問題呢?

回來後,查看Bzip2Codec相關的代碼,終於發現了問題所在。(話說,凌晨3點,沒有eclipse,用vim 改的), 迫不及待的重新編譯Hadoop和Spark,測試,發現處理Bzip2結果OK了!


     CBzip2InputStream 有一個flag變量private static boolean skipDecompression ,默認值是false. (表示是否要跳出解壓縮?)

      這個變量在CBZip2InputStream的 public static long numberOfBytesTillNextMarker方法裏被置爲true. 在CBZip2InputStream.read方法裏可能被置爲false;

     看來,它是一個流(inputStream)讀取過程中經常用到的flag變量.

假設,現在同一個進程內,有兩個線程都要做Bzip2流的讀取工作,它們共用一個static類型的標誌位skipDecompression. 顯然會出問題的。

解決辦法:將其skipDecompression改成非static類型,修改調用skipDecompression的靜態方法爲非static類型。

(PS:    Bzip2Codec.createInputStream 方法用於將給定的流InputStream轉換成CBzip2InputStream.  是Bizp2文件讀取時的必經方法。該方法裏調用了CBZip2InputStream.numberOfBytesTillNextMarker靜態方法,這裏導致了多線程讀取BZip2流時skipDecompresion標誌位混亂)

由於向社區提交path需要漫長的過程,暫時沒有提交社區。具體的patch如下,如有同行遇到同類問題,請借鑑.

Index: src/core/org/apache/hadoop/io/compress/bzip2/CBZip2InputStream.java
===================================================================
--- src/core/org/apache/hadoop/io/compress/bzip2/CBZip2InputStream.java	(版本 510)
+++ src/core/org/apache/hadoop/io/compress/bzip2/CBZip2InputStream.java	(版本 525)
@@ -129,7 +129,9 @@
   private int computedBlockCRC, computedCombinedCRC;
 
   private boolean skipResult = false;// used by skipToNextMarker
-  private static boolean skipDecompression = false;
+  //modified by jicheng.song
+  //private static boolean skipDecompression = false;
+  private  boolean skipDecompression = false;
 
   // Variables used by setup* methods exclusively
 
@@ -315,13 +317,18 @@
  * @throws IOException
    *
    */
-  public static long numberOfBytesTillNextMarker(final InputStream in) throws IOException{
-    CBZip2InputStream.skipDecompression = true;
-    CBZip2InputStream anObject = null;
+  //modified by jicheng.song
+  //public static long numberOfBytesTillNextMarker(final InputStream in) throws IOException{
+  public  long numberOfBytesTillNextMarker(final InputStream in) throws IOException{
+    this.skipDecompression = true;
+    //
+    this.in = new BufferedInputStream(in, 1024 * 9);// >1 MB buffer
+    this.readMode = readMode;
+    //CBZip2InputStream anObject = null;
 
-    anObject = new CBZip2InputStream(in, READ_MODE.BYBLOCK);
+    //anObject = new CBZip2InputStream(in, READ_MODE.BYBLOCK);
 
-    return anObject.getProcessedByteCount();
+    return this.getProcessedByteCount();
   }
 
   public CBZip2InputStream(final InputStream in) throws IOException {
@@ -395,7 +402,9 @@
 
     if(skipDecompression){
       changeStateToProcessABlock();
-      CBZip2InputStream.skipDecompression = false;
+      //modified by jicheng.song
+      //CBZip2InputStream.skipDecompression = false;
+      this.skipDecompression = false;
     }
 
     final int hi = offs + len;
Index: src/core/org/apache/hadoop/io/compress/BZip2Codec.java
===================================================================
--- src/core/org/apache/hadoop/io/compress/BZip2Codec.java	(版本 510)
+++ src/core/org/apache/hadoop/io/compress/BZip2Codec.java	(版本 525)
@@ -148,8 +148,11 @@
     // BZip2 start of block markers are of 6 bytes.  But the very first block
     // also has "BZh9", making it 10 bytes.  This is the common case.  But at
     // time stream might start without a leading BZ.
+      CBZip2InputStream tmpInput = new CBZip2InputStream(seekableIn, readMode); 
     final long FIRST_BZIP2_BLOCK_MARKER_POSITION =
-      CBZip2InputStream.numberOfBytesTillNextMarker(seekableIn);
+      //modified by jicheng.song
+      //CBZip2InputStream.numberOfBytesTillNextMarker(seekableIn);
+      tmpInput.numberOfBytesTillNextMarker(seekableIn);
     long adjStart = Math.max(0L, start - FIRST_BZIP2_BLOCK_MARKER_POSITION);
 
     ((Seekable)seekableIn).seek(adjStart);


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章