リトライ処理のTraitを作ってみた

ずいぶん前に個人的に作ってたものを備忘録で記載。

Exponential Backoff and Jitterで。 TODOで残してるとこがダサいけど、ベスプラがよくわからず。

こういうところを極めるなら、まずは数学をおさらいしなければかなと思う。

trait RetryTrait
{
    /**
     * リトライ処理回数内部カウント用
     * @var int
     */
    private $retryCount = 0;
 
    /**
     * @var bool
     */
    private $jitterEnabled = true;
 
    /**
     * @param string $function
     * @param array  $options
     * @param int    $maxAttempts
     * @param int    $interval micro_sec
     * @return mixed
     * @throws \Exception
     */
    public function retry(string $function, array $options = [], int $maxAttempts = 5, int $interval = 1000000)
    {
        $result = false;
 
        for ($i = 0; $i < $maxAttempts + 1; $i++) {
            try {
                // 成功時にはそのまま処理終了
                $result = $this->$function($options);
                break;
            } catch (\Exception $e) {
                // 失敗時にはExceptionを返却
                $result = $e;
            }
 
            // リクエスト上限到達でリトライ中止
            if ($this->retryCount == $maxAttempts) {
                throw $result;
            }
            $this->retryCount++;
 
            // インターバルをおいて、次回処理へ
            $this->interval($interval);
        }
 
        return $result;
    }
 
    /**
     * リトライ回数から遅延秒数を決定し、処理を止める(sleep)
     *
     * @param int $maxAttempts
     * @param int $interval
     */
    public function interval(int $interval)
    {
        /**
         * TODO 根拠のない数式のため、ベスプラがあればそちらに差し替える
         */
        $waitTime = pow(2, $this->retryCount) / ($this->retryCount + 1) * $interval;
        usleep($this->jitter($waitTime));
    }
 
    /**
     * ±10%のばらつきを与える
     * Jitterが不要な場合はそのまま返却
     *
     * @param int $waitTime
     * @return int
     */
    public function jitter(int $waitTime): int
    {
        return $this->jitterEnabled
            ? mt_rand($waitTime * 0.9, $waitTime * 1.1)
            : $waitTime;
    }
}

実際に使うときはこんな感じ

use RetryTrait;

const MAX_ATTEMPTS = 5;
 
/**
 * リトライ時のインターバル(初期秒数:micro_sec)
 */
const RETRY_INTERVAL = 1000000;


$this->retry('callback', [$param1, $param2], self::MAX_ATTEMPTS, self::RETRY_INTERVAL);

/**
 * @param array $options
 * @return mixed
 * @throws \Exception
 */
public function callback(array $options)
{
    // 何かしらの処理
}

※動作保証はしてない