Mongo 大数据字段去重的实现

35 篇文章 0 订阅

场景

  • 数据库
    • mongo
  • 数量级
    • 300万
    • 每天增长5000 – 10000
  • 索引
    • 都已经建好了
  • 业务需要实时对集合中的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 + 7000*365) * 0.7;
$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;
  • 结论放在数组中最近一年的时间是没有问题的
    • 毕竟很少搜索到这么大的时间范围(2年)

  /**
     * 去重号码数量
     * @param array $where
     */
    private function _setUniqueTelNumberForList(array $where)
    {
        // 初始化属性
        $this->setListTelList();

        // 获取mongo 游标
        $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 聚合操作
  • 待尝试
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值