使用array_merge导致内存不足的反思

简介: 从用户喜欢表分批拿到数据,通过array_merge()组装,再批量插入到数据分析表。测试的时候因为数据量小,没有出现问题。随着业务增长,在查询范围内已经超过3万条数据。3万条数据在8核32G的单机上已经提示内存溢出了。

故事背景


  1. 从用户喜欢表分批拿到数据,通过array_merge()组装,再批量插入到数据分析表。
  2. 测试的时候因为数据量小,没有出现问题。随着业务增长,在查询范围内已经超过3万条数据。
  3. 3万条数据在8核32G的单机上已经提示内存溢出了。


解决问题的思路


  1. 设计初衷是不全量分析数据,只取查询范围内有喜欢动作的用户
  2. 尽量减少DB操作,把计算和拼接数据的操作交给程序
  3. 因为没有考虑到程序的计算也是有上限的,所有解决问题的思路在于上面的1、2保持不变,需要找到一个平衡点。


优化后的思路是:


核心代码如下:


//最近7天喜欢的数据
public static function likeBetweenDuration($begin, $end)
{
    $limit = 1000;
    $offset = 0;
    $users = [];
    do {
        $sponsorUserIds = self::query()
            ->selectRaw('userid,createtime')
            ->distinct()
            ->whereBetween('createtime', [$begin, $end])
            ->orderBy('createtime')
            ->offset($offset)
            ->limit($limit)
            ->get()
            ->toArray();
        $beLikedUserIds = self::query()
            ->selectRaw('"otherUserid",createtime')
            ->distinct()
            ->whereBetween('createtime', [$begin, $end])
            ->orderBy('createtime')
            ->offset($offset)
            ->limit($limit)
            ->get()
            ->toArray();
        $sponsorUserIds = array_column($sponsorUserIds, 'userid');
        $beLikedUserIds = array_column($beLikedUserIds, 'otherUserid');
        $likesUserIds = array_unique(array_merge($sponsorUserIds, $beLikedUserIds));
        $userIds = array_map(function ($value) {
            return ['userid' => $value];
        }, $likesUserIds);
        UserActionRecord::recordBatch($userIds);
        echo "推荐算法需要的喜欢\n";
        echo 'arrayCount:' . count($userIds) . "\n";
        $offset = $offset + $limit;
        echo '偏移量:' . $offset . "\n";
        usleep(10); //休眠10毫秒
    } while ($userIds);
    return $users;
}


优化前的思路是:


分批从DB中读取,通过array_merge()拼接所有数据,将所有数据通过一条sql批量插入数据库。


核心代码如下:


//分批取值的方法
public static function likeBetweenDuration($begin, $end, $select = 'userid,"otherUserid"')
{
    $limit = 200;
    $offset = 0;
    $users = [];
    do {
        $thisUsers = self::query()
            ->selectRaw($select)
            ->whereBetween('createtime', [$begin, $end])
            ->orderBy('createtime')
            ->offset($offset)
            ->limit($limit)
            ->get()
            ->toArray();
        $users = array_merge($users, $thisUsers);
        $offset = $offset + $limit;
    } while ($thisUsers);
    return $users;
}
//获得所有数据,再去重,插入数据库
$likes = UserRelationSingle::likeBetweenDuration(Utility::recommendCalcTimestamp(), Utility::recommendCalcEndTimestamp());
$sponsorUserIds = array_column($likes, 'userid');
$beLikedUserIds = array_column($likes, 'otherUserid');
$likesUserIds = array_unique(array_merge($sponsorUserIds, $beLikedUserIds));
$userIds = array_map(function ($value) {
    return ['userid' => $value];
}, $likesUserIds);
UserActionRecord::recordBatch($userIds);
echo "UserActionRecord 批量记录有喜欢行为的用户:" .
    json_encode($userIds) . "\n";


总结


  1. 优化前的array_merge()一定会随着数据的增多出现内存不足的情况,而优化后的代码就不会。
  2. 优化后的思路array_merge()每次最多只会处理2千条数据。


思路对比:


  1. 优化前的思路尝试使用尽量少的sql,减少DB操作,把压力交给程序(PHP函数)去处理,忽略了内存问题。
  2. 优化后的思路较好的平衡了DB操作和程序之间的平衡关系,分配读取的sql没有变; 之前的一次写入改成了多次写入,规避了内存问题,同时每次DB插入之后休眠10毫秒,减轻DB压力。
相关文章
|
10天前
|
安全 测试技术 数据库
代码危机:“内存溢出” 事件的深度剖析与反思
初涉编程时,我坚信严谨逻辑能让代码顺畅运行。然而,“内存溢出”这一恶魔却以残酷的方式给我上了一课。在开发电商平台订单系统时,随着订单量增加,系统逐渐出现处理迟缓甚至卡死的情况,最终排查发现是订单状态更新逻辑中的细微错误导致内存无法及时释放,进而引发内存溢出。这次经历让我深刻认识到微小错误可能带来巨大灾难,从此对待代码更加谨慎,并养成了定期审查和测试的习惯。
27 0
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
488 1
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
2月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
2月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
29 3
|
2月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
61 1
|
2月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
3月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
137 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
3月前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
3月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
74 2