bloom_filter服務(wù)
bloom_filter的簡介與使用請看這里。
bloom_filter的配置文件conf.ini如下
[filter]
cnt=4
num_bits=4294967296
num_hash_func=7
[server]
pid_file=/var/run/bloom_filter.pid
daemonlize=1
port=8000
cnt表示啟動的bloom_filter的數(shù)量
num_bits表示位數(shù)組大小
cnt* num_bits/(8 * 1024 * 1024 * 1024)即為bloom_filter運行時所占用的內(nèi)存大小(GB)。
數(shù)據(jù)插入
提供兩種方式:
1、遠(yuǎn)程http請求。curl http://w21*********.8000/?ks=1,2 -X POST 。這種方式網(wǎng)絡(luò)開銷較大,數(shù)據(jù)量很大時不推薦。
2、本地文件導(dǎo)入。當(dāng)啟動bloom_filter服務(wù)時,會默認(rèn)讀取當(dāng)前目錄下名為new_users的文件(待改進(jìn)),將每一行當(dāng)作一條記錄插入bloom_filter中。速度可達(dá)100,000條/秒。
數(shù)據(jù)查詢
http請求,curl http://w21********:8000/?ks=1,2。
在w6機器上,qps可達(dá)4000/s。由于每個http請求中可以帶多個參數(shù),因此可以支持每秒上萬個記錄的查詢?nèi)蝿?wù)。
storm計算新用戶
任務(wù):計算2014年1月1號的游戲級新用戶。
啟動bloom_filter服務(wù)。
在機器上啟動bloom_filter服務(wù),并將2014年1月1號前的所有游戲級新用戶采用讀取本地文件的方式導(dǎo)入bloom_filter中。
編寫storm的bolt腳本。
程序從標(biāo)準(zhǔn)輸入中,每讀取30條記錄,將就其拼接成一條curl請求,然后訪問bloom_filter服務(wù),根據(jù)返回值計算這里面有多少個新用戶。
$filter_service = "http://w21*******:8000/?ks=";
$uv_count = 0;
$record_combine = array();
while(($line = fgets(STDIN)) !== false) {
$line = trim($line);
$record = explode("\t", $line);
$id = $record[0];
if(count($record)%3 !== 0) {
echo "$id\tack\n";
fwrite(STDERR, "wrong data".$line."\n");
flush();
continue;
}
for($i = 0; $i<count($record); $i+=3){
$qid = $record[$i+1];
$gkey = $record[$i+2];
$record_combine[] = $qid.$gkey;}
if(count($record_combine)===30){
$request = $filter_service;
$request .= implode(",", $record_combine);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $request);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$filter_result = curl_exec($ch);
curl_close($ch);
$uv_count +=substr_count($filter_result,"false");
fwrite(STDERR, "uv is ".$uv_count."\n");
unset($record_combine);
}
echo "$id\tack\n"; flush();
}
一次查詢多條記錄,能夠大幅度減少網(wǎng)絡(luò)壓力與耗時。
提交storm任務(wù)到測試集群,并發(fā)送數(shù)據(jù)。
運行run.sh腳本提交任務(wù)。然后發(fā)送異步數(shù)據(jù),
cat glogin_daily_2014_1_1.dat | async_send -batch 100 webgame_statistic_49 >/dev/null
這里采用 -batch指令,將日志每300行發(fā)送一次。
單個spout的發(fā)送數(shù)據(jù)的速度有限,為30~40萬次/小時,若一次只發(fā)送一行日志,將大幅度降低storm的吞吐量
誤判率
理論誤判率計算公式在storm中使用bloom_filter消重服務(wù)。
m為位數(shù)組大小,k為哈希函數(shù)個數(shù),n為記錄數(shù)。
令m=235,k=7,n=240,000,000。
帶入公式計算得到的理論誤判率為:5.64e-10。
從程序中查詢得到的程序理論誤判率為:1.4e-4。
storm運行結(jié)果與線上數(shù)據(jù)比對后,得到程序?qū)嶋H誤判率為:1.86e-3。
令m=236,k=7,n=240,000,000。
帶入公式計算得到的理論誤判率為:4.79e-12。
從程序中查詢得到的程序理論誤判率為:1.95e-6。
storm運行結(jié)果與線上數(shù)據(jù)比對后,得到程序?qū)嶋H誤判率為:9.3e-4。
程序?qū)嶋H誤判率比理論值大
結(jié)果比對
1、將storm任務(wù)運行時中bloom_fliter的返回結(jié)果(json)保存成日志
2、去掉json中的花括號{}。
cat log | awk '/"*"/ {print $0}' >process_log
3、找出返回結(jié)果為true的記錄,并去掉引號""。
cat process_log | awk -F ":" '{gsub(/\"/, ""); if(match($2,/true/)) print $1}' >process_log_true
4、去掉記錄的空格
sed 's/ //g' process_log_true > process_log_final
5、從hadoop中導(dǎo)出2014年1月1號的新用戶的記錄,并存成文本。
hive -e
"SELECT gp.qid, gp.gkey FROM
(SELECT distinct qid, gkey FROM glogin_daily WHERE year!=2014) np
RIGHT OUTER JOIN
(SELECT distinct qid, gkey FROM glogin_daily where year=2014 and month = 1 and day = 1) gp
ON np.qid = gp.qid AND np.gkey = gp.gkey
WHERE np.qid IS NOT NULL and np.gkey IS NOT NULL and gp.qid IS NOT NULL and gp.gkey IS NOT NULL;"
> exists_game_2014_01_01.dat
6、比對storm與hadoop中的記錄。發(fā)現(xiàn)確實是bloom_filter誤判了。
例如記錄為"101326892zwj",并沒有在以往的日志中出現(xiàn)過,確實是新用戶,但bloom_filter中卻認(rèn)為是已存在的用戶,返回結(jié)果為true。
注意的問題
1、bloom_filter的誤判率,與占用的總內(nèi)存有關(guān)。就是說,存儲2.4億數(shù)據(jù),使用4個512M的bloom_filter,與使用1個2G的bloom_filter的誤判率是一樣的。從公式也可以看出來。
2、storm集群若與bloom_filter的服務(wù)器跨機房,則會因為網(wǎng)絡(luò)延遲,導(dǎo)致bolt中的數(shù)據(jù)沒有被及時處理而堵塞。導(dǎo)致spout經(jīng)常性失敗 。
3、為防止iptable的設(shè)置導(dǎo)致bloom_filter不可用,可以開放8000端口。 sudo iptables -I INPUT -p tcp --dport 8000 -j ACCEPT
4、hadoop中的日志,不能只看分區(qū),由于hydra任務(wù)的堆積,會使今天的部分日志記錄到明天的分區(qū)中。因此需要指定year,month,day來導(dǎo)出日志