MapReduce实践
一、用python开发MapReduce
1.WordCount程序
- 文件名:run.sh
# 文件名:run.sh
#hadoop安装目录下的bin/hadoop是解析器。
HADOOP_CMD="/usr/local/src/hadoop-2.6.1/bin/hadoop"
#用Python开发,必须通过hadoop-streaming的方式提交,需要引入hadoop-streaming.jar。
STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.1/share/hadoop/tools/lib/hadoop-streaming-2.6.1.jar"
#INPUT_FILE_PATH_1和OUTPUT_PATH都是在hdfs上,输入文件要上传至/1.data。
INPUT_FILE_PATH_1="/1.data"
OUTPUT_PATH="/output"
$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH
#Step 1.
#通过Streaming的方式来提交作业
$HADOOP_CMD jar $STREAM_JAR_PATH \
-input $INPUT_FILE_PATH_1 \
-output $OUTPUT_PATH \
-mapper "python map_new.py" \
# -mapper "python map_new.py mapper_func IPLIB" \
-reducer "python red_new.py" \
# 或 -reducer "cat" \
#代码在本地提交,集群的多个节点是没有代码的。通过配置【-file 文件】把本地代码分发到各个执行MapReduce的节点上。
-file ./map_new.py \
#-file加不加引号都可以,但是-mapper和-reducer必须加引号。
-file "./red_new.py"
- 文件名:1.data,把它上传到hdfs的/1.data。
- 目前通过非java开发mapreduce的输入数据通常是以TextFile结构,优点是可读性很好,缺点是数据不压缩,浪费空间磁盘开销大。
- HDFS存储支持文件格式还有很多,SequenceFile格式按照<k,v>存储 ,支持把一些小的文件统一打包到大的SequenceFile,可以对小文件高效存储处理,支持压缩,既可以支持明文方式读取也可以通过压缩算法进行压缩。java开发通常是SequenceFile格式(默认支持),python开发通常是TextFile格式。
- SequenceFile数据通常是二进制的,查看文件用命令hadoop fs -text /xxx,而cat不能看SequenceFile文件,所以使用text功能更好,既可以查看明文也可以查看加密文件。
Is not until you fly that you fall ,
when the sun come again ,
you are unstopable .
when the dreams come again ,
you fight .
- 文件名:map_new.py
# encoding: utf-8
#文件名:map_new.py
import sys
for line in sys.stdin:
ss=line.strip().split(' ')
for word in ss:
print '\t'.join([ word.strip(),'1' ])
- 文件名:red_new.py
# encoding: utf-8
#文件名:red_new.py
import sys
cur_word=None
sum=0
for line in sys.stdin:
ss=line.strip().split('\t')
if len(ss)!=2:
continue
word,cnt=ss
if cur_word==None:
cur_word=word
if cur_word!=word:
print '\t'.join([cur_word,str(sum)])
cur_word=word
sum=0
sum+=int(cnt)
print '\t'.join([cur_word,str(sum)])
运行命令:bash run.sh或sh run.sh
提前手动测试(sort -k1表示按第一列字符串排序,模拟了shuffle过程):
cat 1.data | python map_new.py | sort -k1 | python red_new.py( > result.local)
若出现” SyntaxError:Non-ASCII character ‘\xe6’ in file red_new.py on line 1, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details “,
那么map_new.py 文件头加上# encoding: utf-8。此时执行bash run.sh成功。
如果还是没用,就把没用的注释删除。
2.白名单过滤进行WordCount
首先需要将The_Man_of_Property.txt上传到输入路径INPUT_FILE_PATH_1位置。
-
run.sh文件
- 用-file方式
# 文件名:run.sh HADOOP_CMD="/usr/local/src/hadoop-2.6.1/bin/hadoop" STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.1/share/hadoop/tools/lib/hadoop-streaming-2.6.1.jar" INPUT_FILE_PATH_1="/mytest/The_Man_of_Property.txt" OUTPUT_PATH="/mytest/output_file_broadcast" $HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH #如果运行失败,把如下代码的注释删除后运行。 $HADOOP_CMD jar $STREAM_JAR_PATH \ -input $INPUT_FILE_PATH_1 \ -output $OUTPUT_PATH \ -mapper "python map.py mapper_func white_list" \ #mapper_func是map.py文件里的一个函数名字,white_list是此函数的参数,这里传入的参数white_list是下面-file的white_list文件。执行时自动定位到map.py文件的mapper_func函数位置,相当于java的反射。 -reducer "python red.py" \ -jobconf "mapred.reduce.tasks=3" \ -file ./map.py \ #-file "./map.py" \,加不加引号都可以 -file ./red.py \ -file ./white_list
- 用-cacheFile方式
- 或者可以用-cacheFile编写run.sh(集群上传),上传大文件,分发快。-file表示本地上传,文件一般比较小。
- 代码:只要把 " -file ./white_list " 改成" -cacheFile “hdfs://master:9000/mytest/white_list#ABC” \ "
# 文件名:run.sh HADOOP_CMD="/usr/local/src/hadoop-2.6.1/bin/hadoop" STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.1/share/hadoop/tools/lib/hadoop-streaming-2.6.1.jar" INPUT_FILE_PATH_1="/mytest/The_Man_of_Property.txt" OUTPUT_PATH="/mytest/output_file_broadcast" $HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH #-file本地分发,-cacheFile集群hdfs分发,参数#前面是路径文件white_list,#后面是名称(它代表前面路径文件别名)。注意把文件white_list上传到集群dfs的-cacheFile对应路径 $HADOOP_CMD jar $STREAM_JAR_PATH \ -input $INPUT_FILE_PATH_1 \ -output $OUTPUT_PATH \ -mapper "python map.py mapper_func ABC" \ -reducer "python red.py" \ -jobconf "mapred.reduce.tasks=2" \ -jobconf "mapred.job.name=cachefile_demo" \ -cacheFile "hdfs://master:9000/mytest/white_list#ABC" \ -file ./map.py \ -file ./red.py \
- 用-cacheArchive方式
- 当白名单目录文件很多,可以用cacheArchive,分发效率更高而且会把复杂的目录结构统一管理起来。如果用cacheFile需要把每个文件都上传,或者放在一个总的目录里,则map.py需要列出目录每个文件去读对应去配套开发。
- 用tar cvzf w.tar.gz white_list1 white_list2命令打包,得到gzip格式的包w.tar.gz后上传。
- 把w.tar.gz包上传到hdfs上,上传前是一个压缩文件,会自动帮忙解压成目录,这个目录本地在/usr/local/src/hadoop-1.2.1/tmp/mapred/local/taskTracker/distcache/7205497718363743885_-2016522075_437997088/master/w.tar.gz。hadoop环境会自动帮忙解压gzip这个格式成目录,所以可以ls w.tar.gz/。
- 代码:只要把上面程序的-cacheFile改成 -cacheArchive “hdfs://master:9000/mytest/w.tar.gz#ABC” \。cacheFile和cacheArchive 两者都要注意如果是多级目录,map.py需要列出目录一个一个读文件配套对应去开发。
# 文件名:run.sh HADOOP_CMD="/usr/local/src/hadoop-2.6.1/bin/hadoop" STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.1/share/hadoop/tools/lib/hadoop-streaming-2.6.1.jar" INPUT_FILE_PATH_1="/mytest/The_Man_of_Property.txt" OUTPUT_PATH="/mytest/output_file_broadcast" $HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH $HADOOP_CMD jar $STREAM_JAR_PATH \ -input $INPUT_FILE_PATH_1 \ -output $OUTPUT_PATH \ -mapper "python map.py mapper_func ABC" \ -reducer "python red.py" \ -jobconf "mapred.reduce.tasks=2" \ -jobconf "mapred.job.name=cachefile_demo" \ -cacheArchive "hdfs://master:9000/mytest/w.tar.gz#ABC" \ -file ./map.py \ -file ./red.py \
-
map.py文件
# encoding: utf-8
# 文件名:map.py
#!/usr/bin/python
import os
import sys
import gzip
def get_file_handler(f):
file_in = open(f, 'r')
return file_in
def get_cachefile_handlers(f):
f_handlers_list = []
if os.path.isdir(f):
for fd in os.listdir(f):
f_handlers_list.append(get_file_handler(f + '/' + fd))
return f_handlers_list
def read_local_file_func(f):
word_set = set()
for cachefile in get_cachefile_handlers(f):
for line in cachefile:
word = line.strip()
word_set.add(word)
return word_set
def mapper_func(white_list_fd):
word_set = read_local_file_func(white_list_fd)
for line in sys.stdin:
ss = line.strip().split(' ')#每一行通过空格的方式做split得到list数据结构数据。
for s in ss:
word = s.strip()
if word != "" and (word in word_set):#有效判断和白名单过滤
#if word != "":
print "%s\t%s" % (s, 1)# %s表示字符串
#print s+"\t"+"1"
#print '\t'.join([s,"1"])
#程序入口
if __name__ == "__main__":
module = sys.modules[__name__]
#sys.modules以字典形式储存程序的所有模块,__name__当前模块。sys.modules是一个全局字典,该字典是python启动后就加载在内存中。每当程序员导入新的模块,sys.modules都将记录这些模块。字典sys.modules对于加载模块起到了缓冲的作用。当某个模块第一次导入,字典sys.modules将自动记录该模块。当第二次再导入该模块时,python会直接到字典中查找,从而加快了程序运行的速度。
func = getattr(module, sys.argv[1])
#sys.argv说白了就是一个从程序外部获取参数的桥梁,sys.argv[0]表示程序本身(文件路径),所以从参数1开始,表示获取的参数了。
args = None
if len(sys.argv) > 1:
args = sys.argv[2:]
func(*args)
- red.py文件
# encoding: utf-8
#文件名:red.py,和上述实践案例1的red_new.py文件相同
import sys
cur_word=None
sum=0
for line in sys.stdin:
ss=line.strip().split('\t')
if len(ss)!=2:
continue
word,cnt=ss
if cur_word==None:
cur_word=word
if cur_word!=word:
print '\t'.join([cur_word,str(sum)])
cur_word=word
sum=0
sum+=int(cnt)
print '\t'.join([cur_word,str(sum)])
- 目录white_list下的文件(文件名可以任意)
you
fight
again
fly
fall
the
- The_Man_of_Property.txt文件,和上述实践案例1的1.data文件相同
Is not until you fly that you fall ,
when the sun come again ,
you are unstopable .
when the dreams come again ,
you fight .
运行命令:bash run.sh或sh run.sh
提前手动测试:cat The_Man_of_Property.txt | python map.py mapper_func white_list | sort -k1 | python red.py( > result.local)
验证结果准确:
cat The_Man_of_Property.txt | grep -o 'again' | wc -l
#注意不能用下一行代码。虽然-w表示按照单词,但是cat The_Man_of_Property.txt | grep -w ‘you' 返回的是含有“you”的完整行并且对”you“进行显示颜色,所以它返回的仍是含有“again”的行数。
cat The_Man_of_Property.txt | grep -cw 'again'
3.全局排序
(1)问题:
有多个文件a.txt和b.txt,需要使用mapreduce对文件内容进行合并且按第一列排序输出
(2)方法一(缺点:靠一个reduce很危险):
(i)理论
- 一个reduce,配置-jobconf mapred.reduce.tasks=1。
- 如果input参数特别多,可用通配符。
- mapreduce默认按字符排序而非数字排序
- 需要正序排序:所以设置base_count =10000, new_key = base_count + int(key),前提是需要排序的字段<base_count =10000。然后输出可以还原回来,print str(int(key) - base_count)+‘\t’+val。
- 逆序:base_count =99999, new_key = base_count - int(key)。然后输出可以还原回来,print str(base_count - int(key))+‘\t’+val。
set -e 和 set -x
推荐使用这个,因为使用set -e -x时代码遇到错误会退出,而不使用会跳过出错代码继续执行下面代码。
注意使用set -e -x时,对于这行代码“ HADOOP_CMD fs -rmr -skipTrash OUTPUT_SORT_PATH ”,若没有OUTPUT_SORT_PATH目录,要暂时注释这个代码或者创建这个目录,否则会报错。
———————————————————
在你开始构思并写下具体的代码逻辑之前,先插入一行set -e和一行set -x。
———————————————————
set -x会在执行每一行 shell 脚本时,把执行的内容输出来。它可以让你看到当前执行的情况,里面涉及的变量也会被替换成实际的值。
———————————————————
set -e会在执行出错时结束程序,就像其他语言中的“抛出异常”一样。(准确说,不是所有出错的时候都会结束程序,见下面的注) set -e, 这句语句告诉bash如果任何语句的执行结果不是true则应该退出。这样的好处是防止错误像滚雪球般变大导致一个致命的错误,而这些错误本应该在之前就被处理掉。
———————————————————
使用-e帮助你检查错误。如果你忘记检查(执行语句的结果),bash会帮你执行。不幸的是,你将无法检查$?,因为如果执行的语句不是返回0,bash将无法执行到检查的代码。
(ii)实践
- run.sh文件
# 文件名:run.sh
set -e -x
HADOOP_CMD="/usr/local/src/hadoop-2.6.1/bin/hadoop"
STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.1/share/hadoop/tools/lib/hadoop-streaming-2.6.1.jar"
INPUT_FILE_PATH_A="/mytest/mapreduce/mr_allsort_1reduce_python/input/a.txt"
INPUT_FILE_PATH_B="/mytest/mapreduce/mr_allsort_1reduce_python/input/b.txt"
OUTPUT_SORT_PATH="/mytest/mapreduce/mr_allsort_1reduce_python/output_sort"
$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_SORT_PATH
# Step 3.
$HADOOP_CMD jar $STREAM_JAR_PATH \
-input $INPUT_FILE_PATH_A,$INPUT_FILE_PATH_B\
-output $OUTPUT_SORT_PATH \
-mapper "python map_sort.py" \
-reducer "python red_sort.py" \
-jobconf "mapred.reduce.tasks=1" \
-file ./map_sort.py \
-file ./red_sort.py \
- map_sort.py文件
# encoding: utf-8
# 文件名:map_sort.py
#!/usr/local/bin/python
import sys
#正序,base_count = 10000
base_count = 99999
for line in sys.stdin:
ss = line.strip().split('\t')
key = ss[0]
val = ss[1]
new_key = base_count - int(key)
#正序,new_key = base_count + int(key)
print "%s\t%s" % (new_key, val)
- red_sort.py文件
# encoding: utf-8
# 文件名:red_sort.py
#!/usr/local/bin/python
import sys
#base_value = 10000
base_value = 99999
for line in sys.stdin:
key, val = line.strip().split('\t')
#正序,print str(int(key) - base_value) + "\t" + val
print str(base_value - int(key)) + "\t" + val
- a.txt文件
- b.txt文件
手动测试:cat a.txt b.txt | python map_sort.py | sort -k1 | python red_sort.py
执行:
hadoop fs -put a.txt b.txt /mytest/mapreduce/mr_allsort_1reduce_python/input/
bash run.sh
结果:
(3)方法二(多个reduce并发):
(i)理论
-
配置的理解
- -jobconf mapred.reduce.tasks=2 \
- 设置两个桶,reduce个数设置为2.
- -jobconf stream.num.map.output.key.fields=2 \
- 指定前两个字段做key。
- 指定前两个字段做key。
- -jobconf num.key.fields.for.partition=1 \
- 指定哪一个做partition。
- 数据:aaa bbb ccc。通过设置这个参数可以指定哪一个做partition,这里可以指定aaa或bbb或ccc做partition。
- -partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner
- -jobconf mapred.reduce.tasks=2 \
-
配置代码
-jobconf mapred.reduce.tasks=2 \ -jobconf stream.num.map.output.key.fields=2 \ -jobconf num.key.fields.for.partition=1 \ -partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner
-
假如设置reduce个数=2
- 那么map_sort.py要开发对应的代码来支持,首先划分key的两个范围1-50,50-100。然后设置key的范围是1-50的桶号为0,设置50-100的key桶号为1。
思路
reduce1: 1-50
reduce2: 51-110
要想全局排序,不仅桶内部需要有序(框架自带),而且桶与桶之间也需要排序(与partition有关,这里根据范围创建分区id)。
(ii)实践
- run.sh文件
# 文件名:run.sh
set -e -x
HADOOP_CMD="/usr/local/src/hadoop-2.6.1/bin/hadoop"
STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.1/share/hadoop/tools/lib/hadoop-streaming-2.6.1.jar"
INPUT_FILE_PATH_A="/mytest/mapreduce/allsort_python/input/a.txt"
INPUT_FILE_PATH_B="/mytest/mapreduce/allsort_python/input/b.txt"
OUTPUT_SORT_PATH="/mytest/mapreduce/allsort_python/output_sort"
$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_SORT_PATH
# Step 3.
$HADOOP_CMD jar $STREAM_JAR_PATH \
-input $INPUT_FILE_PATH_A,$INPUT_FILE_PATH_B\
-output $OUTPUT_SORT_PATH \
-mapper "python map_sort.py" \
-reducer "python red_sort.py" \
-file ./map_sort.py \
-file ./red_sort.py \
-jobconf mapred.reduce.tasks=2 \
-jobconf stream.num.map.output.key.fields=2 \
-jobconf num.key.fields.for.partition=1 \
-partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner
#-jobconf stream.map.output.field.separator=' ' \
- map_sort.py文件
# encoding: utf-8
# 文件名:map_sort.py
#!/usr/local/bin/python
import sys
base_count = 10000
for line in sys.stdin:
ss = line.strip().split('\t')
key = ss[0]
val = ss[1]
new_key = base_count + int(key)
red_idx = 1
#原来的key>50时桶号idx=1,key<50时idx=0.
if new_key < (10100 + 10000) / 2:
red_idx = 0
print "%s\t%s\t%s" % (red_idx, new_key, val)
- red_sort.py文件
# encoding: utf-8
# 文件名:red_sort.py
#!/usr/local/bin/python
import sys
base_count = 10000
for line in sys.stdin:
idx_id, key, val = line.strip().split('\t')
new_key = int(key) - base_count
print '\t'.join([str(new_key), val])
- a.txt文件
1 hadoop
31 hadoop
51 hadoop
71 hadoop
91 hadoop
- b.txt文件
10 java
30 java
50 java
70 java
90 java
100 java
手动测试:cat a.txt b.txt | python map_sort.py | sort -k1,2 | python red_sort.py
执行:bash run.sh
4.指定key和partition操作实践
- aaa.txt文件
d.1.5.23 e.9.4.5 e.5.9.22 e.5.1.45 e.5.1.23 a.7.2.6 f.8.3.3
- run.sh文件
- -jobconf stream.num.map.output.key.fields=3 \
- 取前三个字段作为key,比如一行数据a b c d,key=a b c,value=d。
- -jobconf stream.map.output.field.separator=. \
- 指输入的数据怎样进行分割,指定特殊分割符为" . ",默认情况是制表符\t。
- -jobconf map.output.key.field.separator=. \
- 指对key的内部怎样分割,这里可以删除这个配置效果也是一样的。
- -jobconf mapred.text.key.partitioner.options=-k2,3 \
- partition按照第二、三列分,比如一行数据a b c d,partition=b c。
- -jobconf mapred.reduce.tasks=3
- 3个reduce
- -jobconf stream.num.map.output.key.fields=3 \
# 文件名:run.sh
set -e -x
HADOOP_CMD="/usr/local/src/hadoop-2.6.1/bin/hadoop"
STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.1/share/hadoop/tools/lib/hadoop-streaming-2.6.1.jar"
INPUT_FILE_PATH_A="/mytest/mapreduce/mr_partiotion_key/input/aaa.txt"
OUTPUT_SORT_PATH="/mytest/mapreduce/mr_partiotion_key/output_sort"
$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_SORT_PATH
# Step 3.
$HADOOP_CMD jar $STREAM_JAR_PATH \
-input $INPUT_FILE_PATH_A \
-output $OUTPUT_SORT_PATH \
-mapper "cat" \
-reducer "cat" \
-jobconf stream.num.map.output.key.fields=3 \
-jobconf stream.map.output.field.separator=. \
-jobconf mapred.text.key.partitioner.options=-k2,3 \
-jobconf mapred.reduce.tasks=3
-
手动测试:cat aaa.txt | sort -t ‘.’ -k2,3 | sort -t ‘.’ -k1
- map和reduce都是cat,代码只是根据第2、3列分区且对key(前三列)排序,相当于sort -t ‘.’ -k2,3 | sort -t ‘.’ -k1。
- map和reduce都是cat,代码只是根据第2、3列分区且对key(前三列)排序,相当于sort -t ‘.’ -k2,3 | sort -t ‘.’ -k1。
-
执行:bash run.sh
- partition相同的每一行数据会分到同一个reduce中,同一个reduce中的数据按key排序。
- partition相同的每一行数据会分到同一个reduce中,同一个reduce中的数据按key排序。
5.JOIN
- a.txt文件
aaa1 100
aaa100 111
bbb1 122
bbb100 133
ccc1 144
ccc100 155
- b.txt文件
aaa1 hadoop
aaa100 spark
bbb1 spark
bbb100 hadoop
ccc1 hive
ccc100 hadoop
- map_a.py文件
- 获得中间文件如下图
- 获得中间文件如下图
# encoding: utf-8
# 文件名:map_a.py
#!/usr/local/bin/python
import sys
for line in sys.stdin:
ss = line.strip().split(' ')
key = ss[0]
val = ss[1]
print "%s\t1\t%s" % (key, val)
- map_b.py文件
- 获得中间文件如下图
- 获得中间文件如下图
# encoding: utf-8
# 文件名:map_b.py
#!/usr/local/bin/python
import sys
for line in sys.stdin:
ss = line.strip().split(' ')
key = ss[0]
val = ss[1]
print "%s\t2\t%s" % (key, val)
- red_join.py文件
- 相同的key会放到一起,可以用
- 相同的key会放到一起,可以用
# encoding: utf-8
# 文件名:red_join.py
#!/usr/local/bin/python
import sys
val_1 = ""
for line in sys.stdin:
key, flag, val = line.strip().split('\t')
#实现简单,还有很多情况
if flag == '1':
val_1 = val
elif flag == '2' and val_1 != "":
val_2 = val
print "%s\t%s\t%s" % (key, val_1, val_2)
val_1 = ""
- run.sh文件
# 文件名:run.sh
set -e -x
HADOOP_CMD="/usr/local/src/hadoop-2.6.1/bin/hadoop"
STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.1/share/hadoop/tools/lib/hadoop-streaming-2.6.1.jar"
INPUT_FILE_PATH_A="/mytest/mapreduce/mrjoin_python/input/a.txt"
INPUT_FILE_PATH_B="/mytest/mapreduce/mrjoin_python/input/b.txt"
OUTPUT_A_PATH="/mytest/mapreduce/mrjoin_python/output_a"
OUTPUT_B_PATH="/mytest/mapreduce/mrjoin_python/output_b"
OUTPUT_JOIN_PATH="/mytest/mapreduce/mrjoin_python/output_join"
#$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_A_PATH $OUTPUT_B_PATH $OUTPUT_JOIN_PATH
#$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_JOIN_PATH
# Step 1.
$HADOOP_CMD jar $STREAM_JAR_PATH \
-input $INPUT_FILE_PATH_A \
-output $OUTPUT_A_PATH \
-mapper "python map_a.py" \
-file ./map_a.py \
# Step 2.
$HADOOP_CMD jar $STREAM_JAR_PATH \
-input $INPUT_FILE_PATH_B \
-output $OUTPUT_B_PATH \
-mapper "python map_b.py" \
-file ./map_b.py \
# Step 3.
$HADOOP_CMD jar $STREAM_JAR_PATH \
-input $OUTPUT_A_PATH,$OUTPUT_B_PATH \
-output $OUTPUT_JOIN_PATH \
-mapper "cat" \
-reducer "python red_join.py" \
-file ./map_join.py \
-file ./red_join.py \
-jobconf stream.num.map.output.key.fields=2 \
-jobconf num.key.fields.for.partition=1
- 执行:bash run.sh
6.与二分算法结合
- ip.lib.txt文件,这里只显示前10条数据。
- 已存在IP地址和物理地址信息的表
0.0.0.0 0.255.255.255 NULL IANA保留地址 NULL
1.0.0.0 1.0.0.255 亚洲 亚太地区 NULL
1.0.1.0 1.0.1.255 亚洲 中国 福建
1.0.2.0 1.0.3.255 亚洲 中国 福建
1.0.4.0 1.0.7.255 大洋洲 澳大利亚 NULL
1.0.8.0 1.0.15.255 亚洲 中国 广东
1.0.16.0 1.0.31.255 亚洲 日本 NULL
1.0.32.0 1.0.63.255 亚洲 中国 广东
1.0.64.0 1.0.127.255 亚洲 日本 NULL
1.0.128.0 1.0.255.255 亚洲 泰国 NULL
- cookie_ip.txt文件
- 第一个字段代表一个用户,第二个字段是ip地址。
- 是要处理的输入文件,每处理一行信息,需要获得其实际物理地址。
ECEE8FBBBB 113.224.76.226
ED38780B1D 106.36.217.145
120BB4FB44 113.109.42.83
9D4EC87B4B 219.153.212.31
AF0E43C785 111.77.229.40
4AAAEB560B 60.13.190.132
- map.py文件
- 使用二分算法查找实际物理地址
# encoding: utf-8
# 文件名:map.py
#!/usr/bin/python
import sys
#ch2 = lambda x: '.'.join([str(x/(256**i)%256) for i in range(3,-1,-1)])
ip_convert = lambda x:sum([256**j*int(i) for j,i in enumerate(x.split('.')[::-1])])
def load_ip_lib_func(ip_lib_fd):
ip_lib_list = []
file_in = open(ip_lib_fd, 'r')
for line in file_in:
ss = line.strip().split(' ')
if len(ss) != 5:
continue
start_ip = ss[0].strip()
end_ip = ss[1].strip()
area = ss[2].strip()
country = ss[3].strip()
province = ss[4].strip()
ip_lib_list.append((ip_convert(start_ip), ip_convert(end_ip), area, country, province))
return ip_lib_list
# 从ip_lib_list列表中根据ip地址二分查找对应的实际物理地址
def get_addr(ip_lib_list, ip_str):
ip_num = ip_convert(ip_str)
low_index =