リトライ処理の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) { // 何かしらの処理 }
※動作保証はしてない