这代码需要优化吗

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

    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));
  }
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 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年前 评论

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