任务调度功能

Laravel 命令行调度器允许你在 Laravel 中对命令调度进行清晰流畅的定义。且使用这个任务调度器时,你只需要在你的服务器上创建单个 Cron 入口接口。你的任务调度在 app/Console/Kernel.php 的 schedule 方法中进行定义。

如果定时任务代码直接配置在 linux 下的 crontab 下运行,则修改定时任务相关代码后,不需要做任何重启。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 启动调度器,一般配置在 www 用户下
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

# 使用闭包来定义任务调度
$schedule->call(new DeleteRecentUsers)->daily();

# 使用 Artisan 命令调度
$schedule->command('emails:send --force')->daily();

# 队列任务调度
$schedule->job(new Heartbeat, 'heartbeats')->everyFiveMinutes();

定时任务实现方案

参考文章

1
php artisan make:command SyncBindVipZny // 创建定时任务文件:command/SyncBindVipZny.php

定时任务代码写在 app/console/commands 下。一些非定时任务的脚本也可以写在 commands 下

常驻进程实现方案

常驻进程写在 app/jobs 下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
php artisan make:job TestJob

# 需启动 horizon,访问路由:http://xxx/horizon/,一般使用 supervisor 配置
php artisan horizon # 队列监控,用来代替 Laravel 自带的 artisan queue:listen/work,需要常驻运行

# 将作业推送到特定的队列的几种方式
dispatch(new InvoiceEmail($order))->onConnection('连接名称')->onQueue('队列名称');
InvoiceEmail::dispatch($order)->onQueue('队列名称');
dispatchNow(new InvoiceEmail($order))->onQueue('队列名称'); // 使用此方法时,队列任务将不会排队,并立即在当前进程中运行
Queue::connection('连接名称')->pushOn('队列名称', new InvoiceEmail($order)); // Laravel 5.0 及之前旧版本的用法
Queue::push(new InvoiceEmail($order), '', '队列名称'); // Laravel 5.0 及之前旧版本的用法

// 在给定的秒数之后推送作业的几种方式
dispatch(new InvoiceEmail($order))->onQueue('emails')->delay(60);
Queue::later(60, new InvoiceEmail($order), '', '队列名称'); // Laravel 5.0 及之前旧版本的用法
Queue::laterOn('队列名称', 60, new InvoiceEmail($order)); // Laravel 5.0 及之前旧版本的用法

// 推送多个作业
Queue::bulk([
    new InvoiceEmail($order),
    new ThankYouEmail($order)
]);

// 推送特定队列上的多个作业
Queue::bulk([
    new InvoiceEmail($order),
    new ThankYouEmail($order)
], null, 'emails');

运行队列处理器

  • php artisan queue:listen
  • php artisan queue:work
  • php artisan horizon

docker 如何启动 horizon

1
2
3
4
5
# 进入 docker 环境
docker exec -it php sh

# 运行 horizon
php artisan horizon

supervisor 部署

Supervisor 是一个 Python 写的进程管理工具,有时一个进程需要在后台运行,并且意外挂掉后能够自动重启,就需要这么一个管理进程的工具。

在 linux 下安装supervisor,然后在 supervisor 下配置两项任务:

1
2
php artisan horizon # 常驻进程,user 需指定为 www 用户
php artisan schedule:run # 定时任务,这里也可以直接使用 crontab -e -u www 配置,

Laravel 日志拥有者经常变更为 root

  1. 起因:一、在服务器上使用了 root crontab,致使生成的日志文件的所属者全为 root 用户,导致程序在写入日志显示没有权限写入;二、也可能是 supervisor 配置的常驻任务没有配置在 www 下

  2. 解决方法。再执行定时器时,用在 www 用户下进行定时设置:命令为

    1
    2
    3
    
    crontab -e -u www
    
    supervisor 配置:user=www
    

配置文件解读

  • config/database.php: redis 数据库及其他类型数据库的配置
  • config/queue.php: 队列驱动配置
  • config/horizon.php: 队列管理工具配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// config/queue.php: 队列驱动类型
'connections' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'queue', // 配置连接哪个 redis 数据库,用于 job 的存储
        'queue' => 'default', // 默认队列名称
        'retry_after' => 300, // 任务过期:这个选项指定了队列连接在重试一个任务前应该等它执行多久
        'block_for' => 3, // 阻塞:将失败任务重新放入 Redis 数据库以及处理器轮询之前阻塞多久
    ],
]

// config/database.php
'redis' => [
    'default' => [
        'host' => env('REDIS_HOST', 'redis'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => 0,
    ],
    'horizon-task' => [
        'host' => env('REDIS_HOST', 'redis'),
        'password' => env('REDIS_PASSWORD_QUEUE', null),
        'port' => env('REDIS_PORT_QUEUE', 6379),
        'database' => 1,
    ],
]

// config/horizon.php: Queue Worker Configuration
'use' => 'horizon-task', // 配置 horizon 监控台相关信息存储的 redis 库,请看上面 database.php 的 redis 配置
'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default', 'test'], // 队列名称配置
            'balance' => 'simple', // 负载均衡策略:false/simple/auto
            'processes' => 20, // 进程数
            'tries' => 2, // 任务失败重启次数
            'timeout' => 120, // 队列超时时间
            'sleep' => 3 // 队列进程睡眠时间:当任务在队列中可用时,处理器将会一直无间隔地处理任务。 然而, sleep 选项定义了如果没有新任务的时候处理器将会「睡眠」多长时间。在处理器睡眠时,它不会处理任何新任务 —— 任务将会在队列处理器再次启动后执行。
        ],
        // 配置多个"supervisors",便于资源分配
        'supervisor-2' => [
            'connection' => 'redis',
            'queue' => ['task'],
            'balance' => 'simple',
            'processes' => 30,
            'tries' => 3,
            'timeout' => 600,
            'sleep' => 3
        ],
    ],
]

// Horizon包括一个Metrics仪表板,它提供关于您的作业和队列等待时间以及吞吐量的信息。为了填充此仪表板,应配置Horizon的快照Artisan命令,使其每五分钟通过应用程序的计划程序运行一次
/**
 * Define the application's command schedule.
 *
 * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
 * @return void
 */
protected function schedule(Schedule $schedule)
{
    $schedule->command('horizon:snapshot')->everyFiveMinutes();
}

// 默认情况下,你只能在 local 环境下访问这个后台。如果想要为后台定义更多的特定访问策略,需要使用 Horizon:auth 方法。auth 方法接收一个返回 truefalse 的回调,从而决定用户是否可以访问 Horizon 后台。通常,我们会在 AppServiceProvider 的 boot 方法中调用 Horizon:auth:
Horizon::auth(function ($request) {
    // return true / false;
});

好文推荐

配置 Horizon 的 queue, balance, processes 参数以及 Redis 中的优先级