同事在用Streaming方式处理日志链接的时候出现了中文乱码的问题,该日志的文件名格式为:/tmp/searchweblog.2012-09-13.bz,且其mapper和reducer的python代码如下:
#!/usr/bin/env python
import sys
for line in sys.stdin:
print line.strip()
解决该问题的第一步,认为是python在处理中文字符时需要转码,因为Hadoop的默认编码是UTF-8编码,是支持中文的,且通过hadoop
fs –cat
/tmp/searchweblog.2012-09-13.bz|bunzip2|more显示的中文并不乱码。改python脚本,代码如下:
#!/usr/bin/env python
import sys
import unicodedata
for line in sys.stdin:
line =
unicode(line, "utf-8")
line.strip().encode("utf-8")
在仔细观察其结果,发现并不仅仅是中文乱码,而且e文部分也是乱码,而且极像是通过vi打开的那种压缩文件,故推测应该是Hadoop没有依据对该压缩文件进行处理,而当着文本文件直接读取。一般情况下,如果是常用格式的压缩文件,Hadoop是能够自动识别处理的,但是这里很显然bz是手误导致文件名格式错误,并没有按照常用的格式进行命名处理。
解决方法:
1)将其文件名改为:/tmp/searchweblog.2012-09-13.bz2,在用第一个python代码用streaming的方式执行,执行结果不乱码。
《中国青年》"},{"id":"4001","t":"univ"}]&s=0
《中国青年》"},{"id":"4001","t":"univ"}]&s=5
《大学生杂志》《人民画报》
《大学生杂志》《人民画报》
《生活》
《生活》
搜人/视频/日志/照
马明鸽
2)尝试深究Hadoop源码,通过参数设置来指定相应的解压类来解析文件:
Hadoop默认输入文件格式为TextInputFormat类,而且通过实例化LineRecordReader类来按行读取,在看LineRecordReader函数的initialize函数,代码如下:
compressionCodecs = new CompressionCodecFactory(job);
codec = compressionCodecs.getCodec(file);
// open the file and seek to the start of the split
final FileSystem fs = file.getFileSystem(job);
fileIn = fs.open(file);
if (isCompressedInput()) {
//如果codec!=null则通过解压机制来读取。因此要读getCodec函数了。
decompressor = CodecPool.getDecompressor(codec);
if (codec instanceof SplittableCompressionCodec) {
final SplitCompressionInputStream cIn =
((SplittableCompressionCodec)codec).createInputStream(
fileIn, decompressor, start, end,
SplittableCompressionCodec.READ_MODE.BYBLOCK);
in = new LineReader(cIn, job);
start = cIn.getAdjustedStart();
end = cIn.getAdjustedEnd();
filePosition = cIn;
} else {
in = new LineReader(codec.createInputStream(fileIn, decompressor),
job);
filePosition = fileIn;
}
} else {
fileIn.seek(start);
in = new LineReader(fileIn, job);
filePosition = fileIn;
}
类CompressionCodecFactory的getCodec函数如下:
public CompressionCodec getCodec(Path file) {
CompressionCodec result = null;
if (codecs
!= null) {
String filename = file.getName();
String reversedFilename = new
StringBuilder(filename).reverse().toString();
SortedMapsubMap =
codecs.headMap(reversedFilename);
if (!subMap.isEmpty()) {
String potentialSuffix = subMap.lastKey();
if (reversedFilename.startsWith(potentialSuffix)) {
result = codecs.get(potentialSuffix);
}
}
}
return
result;
}
很显然这是依据各个压缩方式默认的扩展名来识别压缩格式的,其默认的压缩格式配置是:
io.compression.codecs:org.apache.hadoop.io.compress.DefaultCodec,org.apache.hadoop.io.compress.GzipCodec,org.apache.hadoop.io.compress.BZip2Codec
期间并没有发现强制按照某种解压方式解压的设置,故(2)中方法并不适合。
总结,Hadoop识别文件的压缩格式是通过扩展名来识别的,如果扩展名命名不规范,会采用默认直接按照文本行来读取,而不通过压缩方式进行解压读取,从而出现乱码。