رفتن به متن راهنما
همه‌ی راهنماها

شروع به کار

پایپ‌لاینی بسازید که تماس ناپایدار را دوباره امتحان کند، نرخ آن را محدود کند، با مدارشکن محافظت شود و مهلت کل داشته باشد. حدود ده دقیقه.

برنامه Reqtide PHP 0.1.0آخرین بررسی زمان خواندن 8 دقیقه

در این صفحه

برای این راهنما به PHP نسخهٔ 8.3 یا بالاتر (64 بیتی)، Composer و بستهٔ نصب‌شده طبق نصب نیاز دارید. همهٔ بلوک‌های کد این صفحه بخشی از examples/06-getting-started.php (opens in a new tab) هستند. یک اسکریپت این مثال را اجرا می‌کند و خروجی را با خروجی ثبت‌شده مقایسه می‌کند.

کتابخانهٔ Reqtide حالت خود را در حافظهٔ یک پردازش PHP نگه می‌دارد. به محدودیت‌های Reqtide نگاه کنید.

گام 1: کلاس‌ها را بارگذاری کنید

PHP
use Enaxon\Reqtide\Execution\AttemptContext;
use Enaxon\Reqtide\Execution\OperationSafety;
use Enaxon\Reqtide\Execution\ReplaySafetyContract;
use Enaxon\Reqtide\Outcome\ExceptionClassifier;
use Enaxon\Reqtide\Pipeline\PipelineBuilder;
use Enaxon\Reqtide\Policy\AttemptTimeoutRequirement;
use Enaxon\Reqtide\Policy\BackoffMode;
use Enaxon\Reqtide\Policy\JitterMode;
use Enaxon\Reqtide\Value\Duration;
use Enaxon\Reqtide\Value\Ratio;

require __DIR__ . '/vendor/autoload.php';

در پروژهٔ شما خط آخر autoloader مربوط به Composer را بارگذاری می‌کند. مثال داخل مخزن همین کار را با examples/autoload.php انجام می‌دهد.

گام 2: خطای گذرا را مشخص کنید

PHP
/** The failure we treat as transient. Anything else is a plain failure and is never retried. */
final class UpstreamTimeout extends \RuntimeException {}

این کتابخانه هیچ‌وقت حدس نمی‌زند کدام استثنای شما برای تلاش مجدد امن است. کلاس‌ها را در گام بعد خودتان نام می‌برید. استثنایی که نام نبرید یک شکست ساده است. دوباره امتحان نمی‌شود، راهکار جایگزین (Fallback) جای آن را نمی‌گیرد و مدارشکن (circuit breaker) آن را شکست می‌شمارد.

گام 3: پایپ‌لاین را یک بار بسازید

PHP
$pipeline = PipelineBuilder::profile('payments', 'v1')
    ->partition('shop', 'prod', 'payments-api')
    // One attempt may take at most 2 s (checked by you, not enforced inside your code: "Cooperative").
    ->attemptTimeout(Duration::seconds(2), AttemptTimeoutRequirement::Cooperative)
    // At most 3 attempts in total (the first call counts), exponential backoff from 10 ms up to 100 ms.
    ->retry(3, BackoffMode::Exponential, Duration::milliseconds(10), Duration::milliseconds(100), JitterMode::Full)
    // Retries draw from a budget so a failing dependency is not hammered: 10 tokens, 10 more per minute.
    ->retryBudget(10, 10, Duration::seconds(60), Duration::seconds(10))
    // Token bucket: 5 tokens, refilled 5 per second. Every dispatch, retries included, costs 1 token.
    ->rateLimit(PipelineBuilder::tokenBucket(5, 5, Duration::seconds(1)))
    // Judge the last 10 completed calls; from 4 samples on, open at >= 1/2 failures, for 30 s, then allow 1 probe.
    ->circuitBreaker(PipelineBuilder::lastCalls(10), 4, Ratio::of(1, 2), Duration::seconds(30), 1, 1, Duration::seconds(10))
    // Only UpstreamTimeout may be retried.
    ->compileInMemory(ExceptionClassifier::create()->retryOn(UpstreamTimeout::class));

هر خط یک تصمیم است. چون Reqtide مقدار پیش‌فرض ندارد، هر عدد را خودتان تعیین می‌کنید.

فراخوانیچه چیزی را تعیین می‌کند
فراخوانی profile('payments', 'v1')نام و نسخهٔ این پیکربندی. نسخه در هر گزارش خطا می‌آید.
فراخوانی partition('shop', 'prod', 'payments-api')مشخص می‌کند این حالت متعلق به کیست: برنامه، محیط و وابستگی‌ای که از آن محافظت می‌کنید. حالت با همین کلید نگه داشته می‌شود، پس برای هر وابستگی موضوع جدا بگذارید.
فراخوانی attemptTimeout(...)مهلت هر تلاش، یعنی بیشترین زمانی که یک تلاش می‌تواند بگیرد. Cooperative یعنی کد شما باید خودش زمان را رعایت کند، چون زمان‌بند همگام نمی‌تواند آن را متوقف کند.
فراخوانی retry(3, ...)حداکثر 3 تلاش با احتساب تلاش اول. تأخیر نمایی (backoff) از 10 میلی‌ثانیه شروع می‌شود و حداکثر به 100 میلی‌ثانیه می‌رسد. لرزش تصادفی (jitter) کامل است.
فراخوانی retryBudget(10, 10, 60 s, 10 s)سهمیهٔ تلاش مجدد: یک مخزن مشترک با 10 توکن که در هر دقیقه 10 توکن به آن اضافه می‌شود. وابستگی‌ای که از کار افتاده سه برابر ترافیک نمی‌گیرد. هر وقت retry() بیش از یک تلاش را مجاز کند، این گزینه الزامی است.
فراخوانی rateLimit(tokenBucket(5, 5, 1 s))محدودیت نرخ با سطل توکن (token bucket) به ظرفیت 5 که در هر ثانیه 5 توکن پر می‌شود. هر ارسال یک توکن خرج می‌کند، چه تلاش اول باشد چه تلاش مجدد.
فراخوانی circuitBreaker(lastCalls(10), 4, 1/2, 30 s, 1, 1, 10 s)مدارشکن 10 تماس آخر را فقط وقتی بررسی می‌کند که دست‌کم 4 نمونه وجود داشته باشد. اگر سهم شکست‌ها به 1/2 یا بیشتر برسد، باز می‌شود، 30 ثانیه باز می‌ماند و بعد 1 تماس آزمایشی (probe) را می‌پذیرد. یک تماس آزمایشی موفق آن را می‌بندد. آخرین آرگومان مدت اعتبار مجوز است. این مدت باید بیش از 1 ثانیه از مهلت هر تلاش بیشتر باشد.
فراخوانی compileInMemory(...)ترکیب تنظیمات را بررسی می‌کند و backendهای حافظه را برای این پردازش وصل می‌کند. اگر اشتباهی باشد، InvalidConfigurationException پرتاب می‌شود و نام گزینه را می‌گوید.
هر فراخوانی در پایپ‌لاین چه چیزی را تعیین می‌کند.

پایپ‌لاین را هنگام راه‌اندازی بسازید و دوباره استفاده کنید. حالتی که محدودیت‌ها را کار می‌اندازد در همین شیء است. اگر برای هر درخواست پایپ‌لاین تازه بسازید، هر بار همه‌چیز را فراموش می‌کند.

گام 4: تماسی را که باید محافظت شود بنویسید

PHP
$calls = 0;
$callApi = static function (AttemptContext $attempt) use (&$calls): string {
    ++$calls;
    if ($calls < 3) {
        throw new UpstreamTimeout('upstream timed out');
    }

    return "ok on attempt {$attempt->dispatchedAttemptNumber}";
};

تابع callable شما یک AttemptContext می‌گیرد که شناسهٔ عملیات، شمارهٔ تلاش و مهلت کل را دارد. مقدار را برگردانید و استثناهای خودتان را پرتاب کنید. این callable دو بار شکست می‌خورد و بار سوم موفق می‌شود، تا تلاش‌های مجدد را ببینید.

گام 5: با یک context اجرا کنید

PHP
// One context per operation. Its deadline covers all attempts and waits together. forReadOnlyCallable() is YOUR
// statement that running this callable again is safe; Reqtide cannot check it, and refuses to retry without it.
$context = $pipeline->newContext(
    Duration::seconds(30),
    OperationSafety::replayableRead(ReplaySafetyContract::forReadOnlyCallable('get-payment', Duration::seconds(3600))),
);

echo $pipeline->execute($callApi, $context), "\n";
echo "invocations: {$calls}\n";

کد را اجرا کنید. خروجی این است:

Text
ok on attempt 3
invocations: 3

کار به این ترتیب پیش رفت.

  • متد newContext() مهلت کل را ساخت: 30 ثانیه برای کل عملیات، با احتساب تلاش‌های مجدد و انتظارها. این context اعلام شما را هم همراه دارد که تکرار این تماس امن است. متد forReadOnlyCallable() همین اعلام را انجام می‌دهد. کتابخانه نمی‌تواند درستی آن را بررسی کند و بدون آن تلاش مجدد نمی‌کند.
  • تلاش 1 و 2 UpstreamTimeout پرتاب کردند و شما آن را قابل تلاش مجدد معرفی کرده بودید. هر بار پایپ‌لاین به اندازهٔ تأخیر backoff صبر کرد، یک توکن از سهمیهٔ تلاش مجدد برداشت و یک توکن محدودیت نرخ هم کسر کرد.
  • تلاش 3 مقداری برگرداند و execute() همان مقدار را بی‌تغییر برگرداند. تحلیل ایستا نوع آن را می‌شناسد: string.
  • سه ارسال سه توکن محدودیت نرخ مصرف کردند. هزینهٔ هر ارسال هیچ‌وقت برگردانده نمی‌شود. پس سهمیه را برای ترافیک مورد انتظار ضرب در تعداد تلاش‌ها تعیین کنید.

وقتی چیزی اشتباه است

احتمالاً شما هم ابتدا این دو اشتباه را می‌کنید. هر دو را Reqtide پیش از ارسال هر چیزی گزارش می‌دهد. مدل کامل در مدیریت خطا آمده است.

پیام اول هنگام ساخت پایپ‌لاین می‌آید:

Text
retry(maxAttempts=3) requires retryBudget(): retries must be paid for so a failing dependency is not hammered. Every call also needs an explicit replay-safety assertion, for example ReplaySafetyContract::forReadOnlyCallable(); Reqtide never infers that repeating a callable is safe.

پیام دوم وقتی می‌آید که پایپ‌لاینی با تلاش مجدد، context بدون این اعلام بگیرد:

Text
maxAttempts greater than one requires an explicit replay-safety contract; the operation is Unsafe.

چیزهایی که باید به یاد داشته باشید

  • حالت در حافظهٔ همین پردازش PHP است. در PHP-FPM، پایپ‌لاینی که داخل یک درخواست ساخته شود، هر درخواست را با سهمیهٔ کامل و مدار بسته شروع می‌کند. آن را برای هر worker بلندعمر یک بار بسازید، یا بپذیرید که محدودیت‌ها برای هر درخواست جدا باشند. حالت مشترک به بستهٔ Redis نیاز دارد (حالت مشترک با Redis).
  • مهلت کل را Reqtide بین گام‌ها بررسی می‌کند. callable شما را قطع نمی‌کند.
  • تأخیرهای این مثال واقعاً می‌خوابند، در تکه‌های حداکثر 5 میلی‌ثانیه. برای آزمون‌هایی که نباید بخوابند، به آزمون با ساعت مجازی نگاه کنید.

جزئیات بیشتر در محدودیت‌های Reqtide آمده است.

گام‌های بعد