# 定時任務

通常來說，執行定時任務會通過 Linux 的 `crontab` 命令來實現，但現實情況下，並不是所有開發人員都能夠擁有生產環境的伺服器去設定定時任務的，這裡 [hyperf/crontab](https://github.com/hyperf/crontab) 元件為您提供了一個 `秒級` 定時任務功能，只需通過簡單的定義即可完成一個定時任務的定義。 

# 安裝

```bash
composer require hyperf/crontab
```

# 使用

## 啟動任務排程器程序

在使用定時任務元件之前，需要先在 `config/autoload/processes.php` 內註冊一下 `Hyperf\Crontab\Process\CrontabDispatcherProcess` 自定義程序，如下：

```php
<?php
// config/autoload/processes.php
return [
    Hyperf\Crontab\Process\CrontabDispatcherProcess::class,
];
```

這樣服務啟動時會啟動一個自定義程序，用於對定時任務的解析和排程分發。   
同時，您還需要將 `config/autoload/crontab.php` 內的 `enable` 配置設定為 `true`，表示開啟定時任務功能，如配置檔案不存在可自行建立，配置如下：

```php
<?php
return [
    // 是否開啟定時任務
    'enable' => true,
];
```

## 定義定時任務

### 通過配置檔案定義

您可於 `config/autoload/crontab.php` 的配置檔案內配置您所有的定時任務，檔案返回一個 `Hyperf\Crontab\Crontab[]` 結構的陣列，如配置檔案不存在可自行建立：

```php
<?php
// config/autoload/crontab.php
use Hyperf\Crontab\Crontab;
return [
    'enable' => true,
    // 通過配置檔案定義的定時任務
    'crontab' => [
        // Callback型別定時任務（預設）
        (new Crontab())->setName('Foo')->setRule('* * * * *')->setCallback([App\Task\FooTask::class, 'execute'])->setMemo('這是一個示例的定時任務'),
        // Command型別定時任務
        (new Crontab())->setType('command')->setName('Bar')->setRule('* * * * *')->setCallback([
            'command' => 'swiftmailer:spool:send',
            // (optional) arguments
            'fooArgument' => 'barValue',
            // (optional) options
            '--message-limit' => 1,
        ]),
    ],
];
```

### 通過註解定義

通過 `@Crontab` 註解可以快速完成對一個任務的定義，以下的定義示例與配置檔案定義所達到的目的都是一樣的。定義一個名為 `Foo` 每分鐘執行一次 `App\Task\FooTask::execute()` 的定時任務。

```php
<?php
namespace App\Task;

use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Crontab\Annotation\Crontab;
use Hyperf\Di\Annotation\Inject;

/**
 * @Crontab(name="Foo", rule="* * * * *", callback="execute", memo="這是一個示例的定時任務")
 */
class FooTask
{

    /**
     * @Inject()
     * @var \Hyperf\Contract\StdoutLoggerInterface
     */
    private $logger;

    public function execute()
    {
        $this->logger->info(date('Y-m-d H:i:s', time()));
    }
}
```

### 任務屬性

#### name

定時任務的名稱，可以為任意字串，各個定時任務之間的名稱要唯一。

#### rule

定時任務的執行規則，在分鐘級的定義時，與 Linux 的 `crontab` 命令的規則一致，在秒級的定義時，規則長度從 5 位變成 6 位，在規則的前面增加了對應秒級的節點，也就是 5 位時以分鐘級規則執行，6 位時以秒級規則執行，如 `*/5 * * * * *` 則代表每 5 秒執行一次。注意在註解定義時，規則存在 `\` 符號時，需要進行轉義處理，即填寫 `*\/5 * * * * *`。

#### callback

定時任務的執行回撥，即定時任務實際執行的程式碼，在通過配置檔案定義時，這裡需要傳遞一個 `[$class, $method]` 的陣列，`$class` 為一個類的全稱，`$method` 為 `$class` 內的一個 `public` 方法。當通過註解定義時，只需要提供一個當前類內的 `public` 方法的方法名即可，如果當前類只有一個 `public` 方法，您甚至可以不提供該屬性。

#### singleton

解決任務的併發執行問題，任務永遠只會同時執行 1 個。但是這個沒法保障任務在叢集時重複執行的問題。

#### onOneServer

多例項部署專案時，則只有一個例項會被觸發。

#### mutexPool

互斥鎖使用的 `Redis` 連線池。

#### mutexExpires

互斥鎖超時時間，如果定時任務執行完畢，但解除互斥鎖失敗時，互斥鎖也會在這個時間之後自動解除。

#### memo

定時任務的備註，該屬性為可選屬性，沒有任何邏輯上的意義，僅供開發人員查閱幫助對該定時任務的理解。

### 排程分發策略

定時任務在設計上允許通過不同的策略來排程分發執行任務，目前僅提供了 `多程序執行策略`、`協程執行策略` 兩種策略，預設為 `多程序執行策略`，後面的迭代會增加更多更強的策略。

> 當使用協程風格服務時，請使用 協程執行策略。

#### 更改排程分發策略

通過在 `config/autoload/dependencies.php` 更改 `Hyperf\Crontab\Strategy\StrategyInterface` 介面類所對應的例項來更改目前所使用的策略，預設情況下使用 `Worker 程序執行策略`，對應的類為 `Hyperf\Crontab\Strategy\WorkerStrategy`，如我們希望更改策略為一個新的策略，比如為 `App\Crontab\Strategy\FooStrategy`，那麼如下：

```php
<?php
return [
    \Hyperf\Crontab\Strategy\StrategyInterface::class => \App\Crontab\Strategy\FooStrategy::class,
];
```

##### Worker 程序執行策略 [預設]

策略類：`Hyperf\Crontab\Strategy\WorkerStrategy`   

預設情況下使用此策略，即為 `CrontabDispatcherProcess` 程序解析定時任務，並通過程序間通訊輪詢傳遞執行任務到各個 `Worker` 程序中，由各個 `Worker` 程序以協程來實際執行執行任務。

##### TaskWorker 程序執行策略

策略類：`Hyperf\Crontab\Strategy\TaskWorkerStrategy`   

此策略為 `CrontabDispatcherProcess` 程序解析定時任務，並通過程序間通訊輪詢傳遞執行任務到各個 `TaskWorker` 程序中，由各個 `TaskWorker` 程序以協程來實際執行執行任務，使用此策略需注意 `TaskWorker` 程序是否配置了支援協程。

##### 多程序執行策略

策略類：`Hyperf\Crontab\Strategy\ProcessStrategy`   

此策略為 `CrontabDispatcherProcess` 程序解析定時任務，並通過程序間通訊輪詢傳遞執行任務到各個 `Worker` 程序和 `TaskWorker` 程序中，由各個程序以協程來實際執行執行任務，使用此策略需注意 `TaskWorker` 程序是否配置了支援協程。

##### 協程執行策略

策略類：`Hyperf\Crontab\Strategy\CoroutineStrategy`   

此策略為 `CrontabDispatcherProcess` 程序解析定時任務，並在程序內為每個執行任務建立一個協程來執行。

## 執行定時任務

當您完成上述的配置後，以及定義了定時任務後，只需要直接啟動 `Server`，定時任務便會一同啟動。   
在您啟動後，即便您定義了足夠短週期的定時任務，定時任務也不會馬上開始執行，所有定時任務都會等到下一個分鐘週期時才會開始執行，比如您啟動的時候是 `10 時 11 分 12 秒`，那麼定時任務會在 `10 時 12 分 00 秒` 才會正式開始執行。
