首先簡單介紹PageRank的算法公式:
(圖片來源:http://en.wikipedia.org/wiki/Page_rank)
PR(A)即A的PageRank值;d爲阻尼因子,一般設爲0.85;L(B)即B網站所有的出鏈數量(即B網站內的所有鏈接的數量)。
所以公式的意義是:A的PageRank值=(1-d)+d*(鏈接到A的所有網站的PR值/該網站的所有出鏈數量之和)。這裏首次計算時爲每個鏈接附上一個初始值,我設的是0.85。
公式相對還是比較簡單的,至於原理推薦幾篇自己學習參考的文章:http://en.wikipedia.org/wiki/Page_rank(英文),http://www.cnblogs.com/FengYan/archive/2011/11/12/2246461.html(講解原理,不是太容易懂)。
下面主要講解mapreduce的實現。
- 輸入文件格式:
a b,c,d,e,f,g,h
b a,c,d,r,g
c s,f,g,w,h,b
d f,e,s,t,g,a
e f,s,a,c,t,g,h
f d,s,a,q,v,g,h
g d,e,t,g,h,j,y
h d,e,t,g,h,y,j
i d,w,a,c,d,s
j a,c,v,f,d,s
k d,f,h,r,s,a
其中字母代表鏈接,第一個字母是網站鏈接,其後的字符串代表該網站所有的出鏈,以','分割。
- Mapper的實現:
public class PageRankMapper extends Mapper<Text, Text, Text, Text> {
private static final Log log = LogFactory.getLog(PageRankMapper.class);
public static float factor = 0.85f;// 阻尼因子
@Override
protected void setup(Context context) throws IOException,
InterruptedException {
// 獲取阻尼因子值,默認0.85
factor = context.getConfiguration().getFloat("mapred.pagerank.factor",
0.85f);
}
@Override
protected void map(Text key, Text value, Context context)
throws IOException, InterruptedException {
log.info(key.toString() + ";" + value.toString());
// 輸入文件格式爲:A b,c,d,...
// 即key爲目標網站,value爲A所有的出鏈,並以','分割
String[] outLinks = value.toString().split(",");
// 分割key,獲取key的rank值,以','分割
String[] link = key.toString().split(",");
float rank = factor;
if (link.length > 1) {
rank = Float.parseFloat(link[1]);// 存在rank值,則取得,不存在則設爲默認值:阻尼因子
}
int outLinkLen = outLinks.length;// A的出鏈數量
// 遍歷A所有的出鏈,輸出格式:key-A的各個出鏈(b,c,d...);value-[A+','+rank+','+outLinkLen]
// 得到每個鏈接的所有入鏈的PR值以及鏈接到該鏈接的鏈接的出鏈數
for (String s : outLinks) {
context.write(new Text(s), new Text(link[0] + ";" + rank + ";"
+ outLinkLen));
}
// 還需要輸出A的所有出鏈信息,以便進行下一次mapreduce任務的處理
context.write(new Text(link[0]), value);
}
}
這是Mapper的實現代碼,根據輸入文件,這裏選擇KeyValueInputFormat,讀入的key爲一個網站鏈接,而value爲該網站的所有出鏈,並以','分割。對value進行按','進行分割,得到該網站所有的出鏈,然後將出鏈作爲key,value爲之前的key+','+其rank值。這裏注意,因爲第一次計算時所有網站無rank值,故設置一個初始值,我這裏設的是0.85(同阻尼因子)。同時還需要將輸入的key+rank值以及輸入的value一起輸出,以便下次job的執行。
說明一下,除了第一次job之外,其他的job輸入文件的格式其實如下:
a,0.85 b,c,d,e,f,g,h
b,0.85 a,c,d,r,g
c,0.85 s,f,g,w,h,b
d,0.85 f,e,s,t,g,a
e,0.85 f,s,a,c,t,g,h
f,0.85 d,s,a,q,v,g,h
g,0.85 d,e,t,g,h,j,y
h,0.85 d,e,t,g,h,y,j
i,0.85 d,w,a,c,d,s
j,0.85 a,c,v,f,d,s
k,0.85 d,f,h,r,s,a
相比第一次的輸入文件,這裏的輸入文件的key發生了變化,key是網站鏈接+','+其rank值組成,初始時無初始值,所以沒有後面的。Reducer的實現:
public class PageRankReducer extends Reducer<Text, Text, Text, Text> {
private static final Log log = LogFactory.getLog(PageRankReducer.class);
public static float factor = 0.85f;// 阻尼因子
@Override
protected void setup(Context context) throws IOException,
InterruptedException {
// 獲取阻尼因子值,默認0.85
factor = context.getConfiguration().getFloat("mapred.pagerank.factor",
0.85f);
}
@Override
protected void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
log.info(key.toString());
float rank = 1 - factor;// PageRank值
String[] str;
Text outLinks = new Text();// 記錄該鏈接的所有出鏈信息
// 集合的數據位key的所有入鏈鏈接的page,rank,count值,以及key的所有出鏈信息
for (Text t : values) {
// 入鏈信息以';'分割,出鏈信息以','分割,以此區別
str = t.toString().split(";");
if (str.length == 3) {
// 計算key的rank值=(1-d)+d*key的入鏈rank值/其出鏈數
rank += Float.parseFloat(str[1]) / Integer.parseInt(str[2])
* factor;
} else {
outLinks.set(t.toString());
}
}
context.write(new Text(key.toString() + "," + rank), outLinks);
}
}
以上是Reducer的實現代碼。經過Mapper階段的計算,到達Reducer這裏的數據結構是:key-->{page1;rank1;count1,page2;rank2;count2,...,page1,page2...}。
根據key的所有入鏈的rank值和其對應的出鏈數量,可以輕鬆的計算出key的rank值,然後將key+‘,’+rank作爲key,value則是[page1,page2,...pagen],輸出到文件,在作爲下次job的輸入文件進行計算,也就得到了上面提到的輸入文件格式。
主要代碼就這兩個,剩下的代碼主要完成多次迭代計算的功能,就不貼出來了,想參考的可以從點擊打開鏈接(網盤)下載完整代碼。
歡迎大家一起討論學習,謝謝