这代码需要优化吗

下面是一个定时任务,半小时执行一次,然后会给用户发送邮件,这块代码有什么地方能提升的,或者优化的?

    public function handle()
    {
        Log::info('order_send_care_mails======================start');
        $cur_time = Carbon::now()->format('Y-m-d');
        O::where('send_number', '<', 3)->where('expected_time','=',$cur_time)
            ->chunk(100, function ($result) {
                $care_mails = config('care_mails');
                foreach ($result as $row) {
                    $send_number = $row->send_number + 1;
                    $type = $this->type[$send_number]['type'];
                    $view_template = 'email.' . $row->lang . '.care.' . $type;
                    //获取标题
                    $title = '';
                    if (isset($care_mails[$row->lang]["{$type}_title"])) {
                        $title = $care_mails[$row->lang]["{$type}_title"];
                    }
                    if (stripos($title, '{{product_name}}') !== false) {
                        $title = str_replace('{{product_name}}', $row->product_name, $title);
                    }

                    //处理内容
                    $link = [];
                    if (isset($care_mails[$row->lang]['link'][$row->product_name])) {
                        $link = $care_mails[$row->lang]['link'][$row->product_name];
                        if (!empty($row->product_feature) && isset($link[$row->product_feature])) {
                            $link = $link[$row->product_feature];
                        }else{
                            continue;
                        }
                    }

                    if (empty($link['Reached']) || empty($link['Guide']) || empty($link['FAQ'])){
                        continue;
                    }

                    $body = view($view_template, [
                        'link' => $link,
                        'name' => $row->name,
                        'product_name' => $row->product_name,
                        'version' => $row->version
                    ])->render();

                    //发送邮件
                    self::sendmail($row->email, $title, $body, $send_number);

                    //更新数据
                    $row->send_number = $send_number;
                    $row->expected_time = self::getNextExpectedTime($send_number, $row->created_at);
                    $row->send_time = date("Y-m-d H:i:s");
                    $row->save();
                }
            });
        Log::info('order_send_care_mails======================end');
    }

    public function sendmail($email, $title, $body, $send_number)
{
    Log::info($email . ' ' . $title);
  $form = $this->type[$send_number];
  Mail::queue(new \App\Mail\Common\Send($form['email'], $email, $form['name'], $title, $body));
  }
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 22

O::where 这个看着就很nice

1年前 评论
温柔的閪佬 1年前

不用优化,关注用户量增长就好了。到了你半小时发不完的时候,就要优化了

1年前 评论
铁牛 (楼主) 1年前

不用,能跑就别动

1年前 评论
铁牛 (楼主) 1年前

抽象一下,这个代码要查出数据库的一个集合,然后对集合的每条记录做一个处理。

这样,两个方法就可以被抽象出来了。

至少是这个程度。

1年前 评论
陈先生

不需要,这样公司永远需要你。

1年前 评论

使用 chunk() 的话,看你后面更新字段里有 send_number 这个也是条件语句,用 chunk 会有可能导致少处理数据的哦,建议使用 chunkById()

比如有50条满足条件的数据(send_number 都都是3),方便举例的话id从1-50

  1. 第一次 chunk 处理 [sql条件:limit 10 offset 0],那就剩下40条满足了
  2. 理想状态下第二次 chunk 应该是从 id为20开始处理,但是因为第一次处理将 send_number + 1 则都大于3,那么第二次基于条件 [[sql条件:limit 10 offset 1],那实际是从第 30 处理,因为第一次已经将数据更新则前面10条已经不满足where条件了。

这就会出现少处理数据的情况,而 chuckById() 是会记录上一次查询的最大 id,从而避免这种情况。具体可以自己看看sql

1年前 评论
铁牛 (楼主) 1年前
Four (作者) 1年前

O::where 这个看着就很nice

1年前 评论
温柔的閪佬 1年前

1.命名统一使用驼峰写法

file

2.重复的变量可以抽出来,可以使用phpstormRefactor -> Introduce Variable

file

3.$title = $care_mails[$row->lang]["{$type}_title"] ?? '';写法更加直观

file

4.配置文件可以提到闭包外面使用use引入
Laravel

1年前 评论
铁牛 (楼主) 1年前

加锁 万一意外情况半小时跑不完

1年前 评论

优化建议:

  1. 减少数据库查询次数

在循环中,每次都会执行一次数据库查询,可以使用 Eloquent 的 with 方法预加载关联模型,减少查询次数。

  1. 减少字符串拼接次数

在循环中,每次都会进行字符串拼接,可以使用 PHP 的 sprintf 函数进行格式化输出,减少字符串拼接次数。

  1. 减少邮件发送次数

在循环中,每次都会发送一封邮件,可以将邮件地址和邮件内容存储到一个数组中,循环结束后再进行批量发送,减少邮件发送次数。

  1. 减少日志记录次数

在循环中,每次都会记录一次日志,可以将日志记录放在循环外面,减少日志记录次数。

优化后的代码如下:

public function handle() { Log::info('order_send_care_mails======================start'); $cur_time = Carbon::now()->format('Y-m-d'); O::with('product')->where('send_number', '<', 3)->where('expected_time','=',$cur_time) ->chunk(100, function ($result) { $care_mails = config('care_mails'); $mails = []; foreach ($result as $row) { $send_number = $row->send_number + 1; $type = $this->type[$send_number]['type']; $view_template = sprintf('email.%s.care.%s', $row->lang, $type); //获取标题 $title = ''; if (isset($care_mails[$row->lang]["{$type}_title"])) { $title = $care_mails[$row->lang]["{$type}_title"]; } if (stripos($title, '{{product_name}}') !== false) { $title = str_replace('{{product_name}}', $row->product->name, $title); }

            //处理内容
            $link = [];
            if (isset($care_mails[$row->lang]['link'][$row->product->name])) {
                $link = $care_mails[$row->lang]['link'][$row->product->name];
                if (!empty($row->product_feature) && isset($link[$row->product_feature])) {
                    $link = $link[$row->product_feature];
                }else{
                    continue;
                }
            }

            if (empty($link['Reached']) || empty($link['Guide']) || empty($link['FAQ'])){
                continue;
            }

            $body = view($view_template, [
                'link' => $link,
                'name' => $row->name,
                'product_name' => $row->product->name,
                'version' => $row->version
            ])->render();

            //存储邮件地址和邮件内容
            $mails[] = [
                'email' => $row->email,
                'title' => $title,
                'body' => $body,
                'send_number' => $send_number,
            ];

            //更新数据
            $row->send_number = $send_number;
            $row->expected_time = self::getNextExpectedTime($send_number, $row->created_at);
            $row->send_time = date("Y-m-d H:i:s");
            $row->save();
        }

        //批量发送邮件
        foreach ($mails as $mail) {
            self::sendmail($mail['email'], $mail['title'], $mail['body'], $mail['send_number']);
        }
    });
Log::info('order_send_care_mails======================end');

}

public function sendmail($email, $title, $body, $send_number) { Log::info($email . ' ' . $title); $form = $this->type[$send_number]; Mail::queue(new \App\Mail\Common\Send($form['email'], $email, $form['name'], $title, $body)); }

1年前 评论
sanders

我一般不会在 Job 里面写数据库查询之类的业务逻辑,因为我认为 Job 的职责只负责定义队列中的行为,如:传递负载、重试策略和唯一性等。

业务逻辑部分我会单独进行封装。

另外考虑到你的代码中是根据查询结果迭代发送邮件,你的代码会访问邮件服务器,执行时间取决于 迭代次数 * 邮件服务器通讯时长,那么当这两个变量不可控时,你的任务超时的几率将非常不稳定,所以我建议你将发送邮件单独拆出一个新 Job来执行。

最后要解决的问题是总数据量的问题,我注意到你这里在使用 chunk,将数据分批处理,当你的数据量足够大时,分批的数量也将是不可控的。这也可能造成任务执行超时,所以我建议当前任务中或在分发任务的位置只取要处理邮件的总数,并将分批参数(offset,limit)拆分好分发到任务中。如果你使用的是 Laravel 10 版本也可以使用任务批处理功能,这样能监视到各个批次执行的状态。

1年前 评论

邮件不多就不用管,邮件太多的话可以专门用几个队列来处理发邮件的逻辑

1年前 评论
减少数据库查询次数

当前代码中的每次查询都是通过数据库来获取需要处理的订单,这会增加数据库负载。可以考虑缓存订单数据,或者使用更轻量级的缓存(如Redis)来存储订单数据。这样可以减少数据库查询次数,提高性能。

批量处理数据

目前的代码使用了 Laravel 的 chunk 方法来处理数据,但是这样每次只会处理100个订单。可以尝试调整该值,或者使用更高效的批量处理方法来提高处理效率。

减少邮件发送次数

目前的代码对于每个订单,会在不同的时间点发送多封邮件,这会导致邮件发送次数过多。可以考虑将多封邮件合并成一封,或者对邮件发送进行限制,避免发送过多的邮件。

使用异步发送邮件

当前代码使用了 Laravel 的邮件队列来发送邮件,但是这些邮件是同步发送的,即处理每个订单时会等待邮件发送完成后才会继续处理下一个订单。可以尝试使用异步发送邮件,将邮件发送放入队列中,并且使用单独的进程或服务器来处理邮件发送任务,这样可以避免主程序的阻塞。

使用更快的邮件发送服务

当前代码使用 Laravel 的默认邮件发送方式,但是这样可能会导致邮件发送速度较慢。可以尝试使用更快的邮件发送服务,如 SendGrid、Amazon SES 等。

减少模板渲染次数

当前代码中的邮件正文使用了 Blade 模板引擎进行渲染,但是这样会增加模板渲染的次数,导致性能降低。可以尝试使用其他更高效的模板引擎,或者将模板渲染结果缓存起来,避免重复渲染。

1年前 评论

不用能跑就行 跑不动上队列,扔到队列里面消费就好

1年前 评论
Buffett-Cai

人跟代码其中一个能跑就行

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!