場景
- 數據庫
- 數量級
- 索引
業務需要實時對集合中的tel去重
嘗試1 (distinct)
- 使用mongo distinct 報錯
- distinct too big, 16mb cap
嘗試2 (aggregate)
- 第一次分組 獲取不同的tel
- 第二次分組 計算這些號碼的數量
耗時極長
- 300集合
- 索引查詢到 1749988
- 去重後 1306650
$pipeline = [
[
'$match' => $where
],
[
'$group' => [
'_id' => '$tel'
]
],
[
'$group' => [
'_id' => 1,
'count' => ['$sum' => 1]
]
],
];
$allowDiskUse = ['allowDiskUse' => true];
$list = MongoCrawlerTels::raw(function ($collection) use ($pipeline, $allowDiskUse) {
return $collection->aggregate($pipeline, $allowDiskUse);
});
嘗試3 (寫入文件)
- mongo cursor 取出1749988數據, 每行一個號碼寫入文件
- sort target.txt | uniq | wc -l
- 先使用sort排序,再使用uniq去重,使用wc統計行數
致命的是寫入文件的時間 ,消耗了超過17秒, 所以沒有辦法滿足性能需求
嘗試4 (利用redis的Set)
- 計劃利用redis的無序集合, 這種可以使用SCARD輕易的獲得去重後的電話號碼數量
性能問題, 寫入redis已經超過17秒了
嘗試5(數組)
- 數組當然是最方便的了, 但是需要測試下內存會不會爆
- 從mongo中使用遊標去除176萬數據 消耗9.5秒時間, 寫入數據消耗200-300毫秒; 基本滿足需求; 但需要繼續優化(下一步看看elasticsearch)
測試(當前消耗的內存)
- 當前的極限情況, 300萬都取出來, 重複的部分佔30%100
消耗內存 : 128.00398254395MB , 總分配: 128.37540435791 MB 當前消耗: 128.37540435791 MB 插入的數量 : 2100000 消耗時間 0.42975521087646秒, 去重後的數量2100000
$memory_start = memory_get_usage();
$start_time = microtime(true);
$arr = [];
$total = 3000000* 0.7;
for ($i = 0; $i < $total; $i++) {
$arr[$i] = '';
}
$end_time = microtime(true);
$memory_end = memory_get_usage();
$memory_now = $memory_end/(1024*1024);
$memory_need = ($memory_end - $memory_start)/(1024*1024);
$memory_get_peak_usage = memory_get_peak_usage()/(1024*1024);
$msg = '消耗內存 : ' . $memory_need . 'MB , 總分配: ' . $memory_get_peak_usage . ' MB 當前消耗: ' . $memory_now. ' MB 插入的數量 : ' . $i . ' 消耗時間 ' . ($end_time - $start_time) . '秒, 去重後的數量' . count($arr);
echo $msg . PHP_EOL;
推測一年後的內存使用情況
+ 每天增長7000
+ 重複的部分 30%
消耗內存 : 128.00398254395MB , 總分配: 128.37540435791 MB 當前消耗: 128.37540435791 MB 插入的數量 : 3888500 消耗時間 0.71930503845215秒, 去重後的數量3888500
- 詭異
- 3888500索引的數組和2100000消耗的內存幾乎一致
$memory_start = memory_get_usage();
$start_time = microtime(true);
$arr = [];
$total = (3000000 + 7000*365) * 0.7;
for ($i = 0; $i < $total; $i++) {
$arr[$i] = '';
}
$end_time = microtime(true);
$memory_end = memory_get_usage();
$memory_now = $memory_end/(1024*1024);
$memory_need = ($memory_end - $memory_start)/(1024*1024);
$memory_get_peak_usage = memory_get_peak_usage()/(1024*1024);
$msg = '消耗內存 : ' . $memory_need . 'MB , 總分配: ' . $memory_get_peak_usage . ' MB 當前消耗: ' . $memory_now. ' MB 插入的數量 : ' . $i . ' 消耗時間 ' . ($end_time - $start_time) . '秒, 去重後的數量' . count($arr);
echo $msg . PHP_EOL;
private function _setUniqueTelNumberForList(array $where)
{
$this->setListTelList();
$option = [
'projection' => [
'tel' => 1,
'apikey' => 1,
'_id' => 0
],
];
$cursor = DB::connection('mongodb_backend')->collection('crawler_tels')->raw(function ($collection) use ($where, $option) {
return $collection->find($where, $option);
});
$memory_start = memory_get_usage();
$start_time = microtime(true);
$i = 0;
foreach ($cursor as $item) {
$i++;
$this->list_tel_list['all'][$item->tel] = '';
$this->list_tel_list['list_apikey'][$item->apikey][$item->tel] = '';
}
$end_time = microtime(true);
$memory_end = memory_get_usage();
$memory_now = $memory_end/(1024*1024);
$memory_need = ($memory_end - $memory_start)/(1024*1024);
$memory_get_peak_usage = memory_get_peak_usage()/(1024*1024);
$msg = '消耗內存 : ' . $memory_need . 'MB , 總分配: ' . $memory_get_peak_usage . ' MB 當前消耗: ' . $memory_now. ' MB ' . $this->product_id . ' 插入的數量 : ' . $i . ' 消耗時間 ' . ($end_time - $start_time) . '秒, 去重後的數量' . count($this->list_tel_list['all'] ?? []);
$action = 'unqiue';
$params = request()->post();
MongoLog::create(compact('msg', 'action', 'params'));
}
php 多線程
- php多線程不可以使用web服務中
- 多個線程從公用一個遊標, 從各個線程獲取結果也挺麻煩的
- 嘗試過之後放棄了
elasticsearch
es Vs mongo 遊標拉去數據的速度
- es 條件
- size 從1000 每次實驗 +1000 直到10000爲止; 2000的時候消耗的時間是最少的
- filter的形式
$params = [
'index' => $this->index,
'size' => 2000,
'scroll' => '30s',
'body' => [
'query' => [
'bool' => [
'filter' => [
[
"range" => ["amount_date" => ["gte" => "20190101"]]
],
]
]
]
]
];
單純的從elasticsearch中分頁拉去數據,實際上還沒有mongo快
mongo 聚合操作