场景
- 数据库
- 数量级
- 索引
业务需要实时对集合中的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快
![对比](https://i-blog.csdnimg.cn/blog_migrate/1edc6d97ac1cfc082b0a4fd88045195d.png)
mongo 聚合操作