jvm file.encoding 屬性引發的 MapReduce/HBase 亂碼問題

http://cache.baiducontent.com/c?m=9d78d513d99b05f04fede52a56488071182497634bc0d1652888c215c53f07145321a3e52878564291d27d141cb20c19afe736056e507ceec894df0f9cac925f7ed578290b6ddf164e8642f39c5b768171ca01adf858fabbf73393afc5d3a815098c0c5b&p=9e759a45d7c15bbc1cb38c2d021485&newp=8b2a97568e8011a058ec962452488b231610db2151d3d5136c8cd0&user=baidu&fm=sc&query=jvm+file%2Eencoding&qid=&p1=3html

 

 

一、問題:

最近在往 HBase 寫中文的時候,發現 hbase 查出來的數據會有部分中文亂碼了,而部分中文又是正常的,按理來講,通常的亂碼問題要麼全亂,要麼不亂。考慮到出現中文的地方都是來源於 hdfs 上的一個配置文件,而這個配置文件能夠肯定是 utf-8 編碼的,那排除了原始文件致使的亂碼,想一想 MR 代碼裏也沒有轉碼的邏輯,也排除了代碼的問題,那就只有一種可能:Hadoop 集羣的系統環境是異構的,這裏面可能涉及到 linux 、java 的環境變量、配置的問題。java

 

二、排查:

(1)打印了整個集羣的�0�2echo $LANG、echo $LC_ALL 等linux系統變量,發現都是一致的,排除了 os 環境的問題。linux

(2)剩下的重點放在了 java 環境上,在代碼里加上以下兩句,打印每條記錄的 ip 和 jvm 編碼,而後看看亂碼的記錄是那臺機器產生的,而且當時 jvm child 的編碼狀況:apache

java.net.InetAddress test = java.net.InetAddress.getByName("localhost");
put.add(Bytes.toBytes("cf"), Bytes.toBytes("ip"), Bytes.toBytes(test.getLocalHost().getHostAddress()));
put.add(Bytes.toBytes("cf"), Bytes.toBytes("ec"), Bytes.toBytes(System.getProperty("file.encoding")));

同時也直接�0�2
System.out.println 出相應的中文字段,看是寫進 hbase 以前仍是以後亂掉的。 緩存

跑了一份測試數據後,發現 hbase 裏的 ip、jvm 編碼是沒有規律的,而後查看�0�2syso 打印的 log 發現,在寫 hbase 以前已經就已經亂碼了,而後想一想 hbase 裏的數據亂碼之因此沒有規律是由於 map 後要 shuffle、reduce 才能到 hbase。PS:sysout自己的編碼是根據file.encoding來的。 多線程

而後再次把 ip、jvm編碼 統計代碼放到 map 階段輸出,果然發現了規律,集羣中有兩臺機器的 jvm 編碼不一致,不是 utf-8 的: 併發

到這裏咱們能夠知道緣由了:因爲集羣中兩臺機器的 jvm 參數(file.encoding)不一致致使了部分中文結果的亂碼。 jvm

 

三、解決方案:

知道緣由了,那就看如何解決了,目的就是要改變 file.encoding 的值�0�2。 函數

 

(1)永久方案:

因爲這個參數是 jvm 的啓動參數,運行時不可被更改(你能夠理解爲這個參數是個全局參數,並且被緩存了,若是一旦運行時更改了, 可能會形成整個 jvm 裏面的程序奔潰),你只能修改系統的charset, 或者jvm的啓動參數里加上 -Dfile.encoding=」UTF-8″ 來指定,你運行時�0�2setProperty(「file.encoding」,」ISO-8859-1″); 這樣是沒用的,so,永久的解決辦法是:啥時候把這兩臺機器offline 改編碼後再online,而後再手動執行下 data balance。 oop

 

(2)臨時工方案:

不想這麼大動干戈,想要臨時解決方案,也行,那就須要在我們本身的業務代碼裏繞開 jvm 提供的默認�0�2file.encoding 編碼,本身指定編碼:

BufferedReader in = new BufferedReader(new FileReader(path.toString()));
換成:
BufferedReader in = new BufferedReader((new InputStreamReader(new FileInputStream(path.toString()),"utf-8")));

上面一句是我以前亂碼的代碼,若是你沒有指定讀取編碼,那麼 jvm 會使用本身的 file.encoding,這樣就會形成在某些機器上讀取文件就亂掉了。下面一句是本身指定編碼,這樣繞開了 jvm 的默認編碼,與 jvm 今後形同陌路~�0�2

PS:FileReader 貌似沒有提供指定編碼的構造方法,因此換成了下面的類。

 

(3)疑問:

爲何以前一直都沒亂碼,而此次讀文件卻亂碼了呢?

那是由於 hbase 的 Bytes、map 的�0�2fileinputformat key/value、mapreduce 的�0�2context.write 默認都是本身硬編碼了 utf-8,作到了 和 jvm 編碼無關,因此不會遇到上述問題。

 

四、深刻理解 jvm 的�0�2-Dfile.encoding 參數

上面說了這麼多,可能有同窗仍是不大明白:jvm 的這參數有毛用啊?爲毛以前都沒聽過這玩意呢?

恩,沒聽過正常,以前我也沒聽過哈~

 

(1)從源碼開始追蹤

在JDK 1.6.0_20的src.zip文件中,查找包含file.encoding字眼的文件.
共找到4個, 分別是:
(a)先上重頭戲 java.nio.Charset類:

public static Charset defaultCharset() {
		if (defaultCharset == null) {
			synchronized (Charset.class) {
				java.security.PrivilegedAction pa = new GetPropertyAction("file.encoding");
				String csn = (String) AccessController.doPrivileged(pa);
				Charset cs = lookup(csn);
				if (cs != null)
					defaultCharset = cs;
				else
					defaultCharset = forName("UTF-8");
			}
		}
		return defaultCharset;
	}

在java中,若是沒有指定charset的時候,好比new String(byte[] bytes), 都會調用Charset.defaultCharset()的方法,咱們能夠清楚的看到defaultCharset是隻能被初始化一次,這裏仍是有點小問題的,在多線程併發調用的時候仍是會初始話屢次,固然後面都是從cache(lookup的函數)裏讀出來的,問題也不大。
當咱們在改變System.getProperties裏的file.encoding 的時候,defaultCharset已經被初始化過了,因此不會在調用初始化的代碼。
jvm 啓動的時候,load class, 最後調用main函數以前,defaultCharset已經初始化好,而不少函數裏都掉用了這個方法象String.getBytes, 還有 InputStreamReader, InputStreamWriter 都是調用了 Charset.defaultCharset()的方法。

(b)java.net.URLEncoder的靜態構造方法,�0�2
影響到的方法 java.net.URLEncoder.encode(String)

恩,這裏也須要注意,以前已經有同窗掉坑裏去了,請使用:encode(String s, String enc) 方法,此法無側漏,一覺睡到大天亮~

(c)com.sun.org.apache.xml.internal.serializer.Encoding的getMimeEncoding方法(209行起)

(d)最後一個javax.print.DocFlavor類的靜態構造方法

能夠看到,系統變量file.encoding影響到
1. Charset.defaultCharset() Java環境中最關鍵的編碼設置
2. URLEncoder.encode(String) Web環境中最常遇到的編碼使用
3. com.sun.org.apache.xml.internal.serializer.Encoding 影響對無編碼設置的xml文件的讀取
4. javax.print.DocFlavor 影響打印的編碼

 

(2)Java’s file.encoding property on Windows platform

This property is used for the default encoding in Java, all readers and writers would default to use this property. 「file.encoding」 is set to the default locale of Windows operationg system since Java 1.4.2. System.getProperty(「file.encoding」) can be used to access this property. Code such as System.setProperty(「file.encoding」, 「UTF-8」) can be used to change this property. However, the default encoding can not be changed dynamically even this property can be changed. So the conclusion is that the default encoding can’t be changed after JVM starts. 「java -Dfile.encoding=UTF-8」 can be used to set the default encoding when starting a JVM. I have searched for this option Java official documentation. But I can’t find it.

 

五、Refer:

系統變量file.encoding對Java的運行影響有多大?

http://www.blogjava.net/ivanwan/archive/2011/01/31/343810.html

Setting the default Java character encoding?

http://stackoverflow.com/questions/361975/setting-the-default-java-character-encoding

相關文章
相關標籤/搜索