همهی راهنماها
راهنمایی با این جستوجو پیدا نشد.
مفاهیم
پایپلاین، عملیات، تلاش و پارتیشن در Reqtide از هم جدا هستند. هر قانون کجا قرار دارد و بررسیها به چه ترتیبی اجرا میشوند.
در این صفحه
هر بلوک کد این صفحه از examples/10-concepts.php (opens in a new tab) یا examples/09-choosing-policies.php (opens in a new tab) گرفته شده است. یک اسکریپت هر دو فایل را اجرا میکند و خروجی را با خروجی ثبتشده مقایسه میکند.
حالت در حافظهٔ همین پردازه میماند و Reqtide هیچوقت callback در حال اجرا را قطع نمیکند. محدودیتهای Reqtide را ببینید.
چهار چیزی که Reqtide از هم جدا نگه میدارد
| چیز | چیست | طول عمر |
|---|---|---|
| پایپلاین (pipeline) | پیکربندی (سیاستها و عددهایشان) بهعلاوهٔ حالتی که این سیاستها نگه میدارند: توکنهای محدودیت نرخ، تاریخچهٔ مدار و سهمیهٔ تلاش مجدد. | یک بار ساخته میشود و برای همهٔ فراخوانیها دوباره به کار میرود. |
| عملیات | یک کار منطقی که میخواهید انجام شود، مثلاً «این سفارش را بخوان». یک context، یک مهلت کل و یک نتیجه دارد. | یک بار فراخوانی execute(). |
| تلاش | یک فراخوانی واقعی callable شما. عملیات یک تلاش انجام میدهد، یا چند تلاش اگر تلاش مجدد مجاز باشد. | یک ارسال. |
| پارتیشن | حالت متعلق به چه کسی است: برنامه، محیط، موضوع و، در صورت نیاز، یک مستأجر (tenant). | تا وقتی که پایپلاین زنده است. |
چون این چهار چیز از هم جدا هستند، یک پایپلاین میتواند همزمان به چند عملیات سرویس بدهد، بیآنکه دادهای از هیچ فراخوانی را بین آنها شریک کند. یک مهلت کل هم میتواند همهٔ تلاشهای یک عملیات را پوشش دهد.
پایپلاین یک بار ساخته میشود
کلاس PipelineBuilder گزینههای شما را جمع میکند و ترکیب آنها را بررسی میکند. متد compileInMemory() حالت را برای همین پردازه وصل میکند و یک CompiledPipeline برمیگرداند. سازنده مقدار پیشفرض ندارد. هر سیاست فقط وقتی وجود دارد که متدش را صدا بزنید و هر عدد را هم خودتان میدهید.
$catalog = PipelineBuilder::profile('concepts', 'v1')
->partition('shop', 'prod', 'catalog-api')
->attemptTimeout(Duration::seconds(2), AttemptTimeoutRequirement::Cooperative)
->rateLimit(PipelineBuilder::tokenBucket(5, 5, Duration::seconds(1)))
->circuitBreaker(PipelineBuilder::lastCalls(10), 4, Ratio::of(1, 2), Duration::seconds(30), 1, 1, Duration::seconds(10))
->compileInMemory(ExceptionClassifier::create());
$explanation = $catalog->explain();
echo "profile: {$explanation->profileId->value} {$explanation->profileVersion->value}\n";
echo 'enabled policies: ', implode(', ', $explanation->enabledPolicies), "\n";
foreach ($explanation->capabilityNotes as $note) {
echo " {$note}\n";
}متد explain() آنچه را ساختهاید توصیف میکند. چیزی را فراخوانی نمیکند، به حالت دست نمیزند و I/O ندارد. برای یک خط گزارش هنگام راهاندازی به کار میآید، یا در آزمون برای اطمینان از اینکه پایپلاین همان است که فکر میکنید:
profile: concepts v1
enabled policies: attempt, retry(maxAttempts=1), rate_limit, circuit
invocation=synchronous
settlement=inline_return_or_throw
attempt_timeout=unsupported
cancellation=checkpoint_only
scheduling=bounded_blocking
queue_ownership=none
detached_completion=unsupported
nonblocking=not_claimed
physical_stop=not_guaranteed
require_finite_backend_io=falseاول فهرست سیاستهای فعال را بخوانید. retry(maxAttempts=1) همیشه هست و تا وقتی retry() را صدا نزدهاید یعنی «بدون تلاش مجدد».
یادداشتهای توانایی (capability notes) میگویند runtime چه کارهایی از دستش برمیآید. مقدار attempt_timeout=unsupported و مقدار physical_stop=not_guaranteed یعنی runtime همگام نمیتواند callable شما را متوقف کند. مقدار scheduling=bounded_blocking یعنی تأخیرهای تلاش مجدد واقعاً میخوابند.
پایپلاین را هنگام راهاندازی بسازید و دوباره از آن استفاده کنید. حالتی که محدودیتها را کار میاندازد در همین شیء است، پس پایپلاینی که برای هر درخواست از نو بسازید هر بار همهچیز را فراموش میکند. پایپلاین حالت هیچ عملیاتی را جدا نگه نمیدارد، پس عملیاتهای یک پردازه میتوانند با خیال راحت آن را شریک شوند.
برای یک تلاش چه اتفاقی میافتد
پایپلاین برای هر تلاش بررسیها را به یک ترتیب ثابت اجرا میکند:
| ترتیب | بررسی | اگر جواب منفی باشد |
|---|---|---|
| 1 | مهلت کل و context | عملیات با deadline_exceeded یا operation_cancelled تمام میشود. |
| 2 | واجد شرایط بودن برای تلاش مجدد (تلاشهای بعد از اولی) | آخرین نتیجهٔ شما برگردانده میشود. |
| 3 | حداقل زمان باقیمانده برای تلاش (تلاشهای بعد از اولی) | آخرین نتیجهٔ شما برگردانده میشود. |
| 4 | سهمیهٔ تلاش مجدد که یک توکن رزرو میکند (تلاشهای بعد از اولی) | نتیجهٔ retry_budget_exhausted ثبت میشود و آخرین نتیجهٔ شما برگردانده میشود. |
| 5 | مدارشکن | کد circuit_open و callable شما اجرا نمیشود. |
| 6 | سقف اجرای همزمان (Bulkhead) | کد concurrency_capacity_exhausted. |
| 7 | دورهٔ انتظار مشترک | کد cooldown_active یا cooldown_exceeds_deadline. |
| 8 | محدودیت نرخ | کد rate.exhausted یا کدی مرتبط. |
| 9 | ارسال | اجرای callable شما. |
اگر یکی از بررسیهای بعدی رد کند، مجوزهایی که بررسیهای قبلی گرفته بودند آزاد میشوند. تلاش آزمایشی مدار بدون شمارش رها میشود، جای سقف همزمانی آزاد میشود و توکن رزروشدهٔ تلاش مجدد پس داده میشود. وقتی پایپلاین بین دو تلاش مجدد صبر میکند، چیزی را در دست نگه نمیدارد. رد شدن هیچوقت به callable شما نمیرسد و شکست وابستگی شما به حساب نمیآید.
هر عملیات یک مهلت کل و چند تلاش دارد
برای هر عملیات با newContext() یک context میسازید. این context مهلت کل، اعلام شما دربارهٔ تکرار فراخوانی، یک راهکار جایگزین (Fallback) اختیاری و یک شناسهٔ عملیات را حمل میکند. در هر تلاش، پایپلاین یک AttemptContext به callable شما میدهد.
$retrying = PipelineBuilder::profile('concepts', 'v1')
->partition('shop', 'prod', 'catalog-api')
->attemptTimeout(Duration::seconds(2), AttemptTimeoutRequirement::Cooperative)
->retry(3, BackoffMode::Constant, Duration::milliseconds(5), Duration::milliseconds(5), JitterMode::None)
->retryBudget(10, 10, Duration::seconds(60), Duration::seconds(10))
->compileInMemory(ExceptionClassifier::create()->retryOn(Hiccup::class));
$operationIds = [];
$attemptIds = [];
$flaky = static function (AttemptContext $attempt) use (&$operationIds, &$attemptIds): string {
$operationIds[] = $attempt->operationId->value;
$attemptIds[] = $attempt->attemptId->lowerHex;
if ($attempt->dispatchedAttemptNumber < 3) {
throw new Hiccup('try again');
}
return "succeeded on attempt {$attempt->dispatchedAttemptNumber}";
};
$safety = OperationSafety::replayableRead(ReplaySafetyContract::forReadOnlyCallable('list-products', Duration::seconds(3600)));
echo $retrying->execute($flaky, $retrying->newContext(Duration::seconds(30), $safety, operationId: 'concepts-1')), "\n";
echo 'attempts: ', count($attemptIds), ', distinct attempt ids: ', count(array_unique($attemptIds)), "\n";
echo 'distinct operation ids: ', count(array_unique($operationIds)), ' (', $operationIds[0], ")\n";succeeded on attempt 3
attempts: 3, distinct attempt ids: 3
distinct operation ids: 1 (concepts-1)این مثال چهار چیز را نشان میدهد.
- شناسهٔ عملیات در هر سه تلاش یکی است و هر تلاش شناسهٔ تلاش خودش را دارد. اگر در خط گزارش شناسهٔ عملیات را بنویسید، تلاشهای یک عملیات کنار هم جمع میشوند.
- مقدار
dispatchedAttemptNumberارسالهای واقعی را میشمارد. این عدد را در خطهای گزارش خود بنویسید. - هر context را درست پیش از فراخوانی بسازید، نه هنگام راهاندازی. مهلت کل از لحظهای شروع میشود که
newContext()آن context را میسازد، پس context قدیمی بودجهٔ تقریباً تمامشده دارد. - یک context را هیچوقت بین دو عملیات شریک نکنید. هر context نام یک عملیات را دارد.
مهلت کل
مدتی که به newContext() میدهید بودجهٔ کل عملیات است و تلاشها، انتظار بین آنها و fallback را با هم میپوشاند. این مدت باید مثبت باشد، چون مهلتی که از قبل تمام شده فقط میتواند شکست بخورد.
هر قطعه کد در راهنماهای محافظت با همین دو گزینهٔ الزامی شروع میشود. مثال مهلت کل هم از همین تنظیم استفاده میکند:
/** Every pipeline below starts from the same two required options. */
$base = static fn(): PipelineBuilder => PipelineBuilder::profile('choosing', 'v1')
->partition('shop', 'prod', 'catalog-api')
->attemptTimeout(Duration::seconds(2), AttemptTimeoutRequirement::Cooperative);
$ok = static fn(): string => 'ok';
$context = static fn(CompiledPipeline $pipeline): ExecutionContext => $pipeline->newContext(Duration::seconds(30));echo "-- 6. Deadline: one budget for the whole operation\n";
$pipeline = $base()->compileInMemory(ExceptionClassifier::create());
$slow = static function (): string {
usleep(30_000);
return 'finished late';
};
try {
$pipeline->execute($slow, $pipeline->newContext(Duration::milliseconds(5)));
} catch (DeadlineExceededException $e) {
echo "late result replaced by {$e->reason()->value}\n";
}-- 6. Deadline: one budget for the whole operation
late result replaced by deadline_exceededاجرای callable کند بالا تا آخر ادامه یافت. Reqtide مهلت کل را بین گامها بررسی میکند و داخل کد شما نه، پس نتیجهای که دیر برسد با deadline_exceeded عوض میشود. خود مقدار در سوابق تلاش حالت تفصیلی در دسترس میماند. Reqtide مهلت کل را پیش از هر پذیرش و هر ارسال بررسی میکند. بعد از گذشتن مهلت کل هیچ تلاشی شروع نمیشود، و تلاش مجدد هم شروع نمیشود اگر تأخیرش در زمان باقیمانده جا نشود.
مهلت کل، تایماوت کد شما نیست
مهلت کل callable در حال اجرا را قطع نمیکند. وقتی مهلت تلاش را Cooperative اعلام میکنید، یعنی میگویید کد شما زمان را رعایت میکند، مثلاً با تایماوت لایهٔ انتقال. اعمال آن از دست runtime همگام برنمیآید.
پارتیشن
پارتیشن مشخص میکند پایپلاین حالت چه کسی را نگه میدارد: یک برنامه، یک محیط، یک موضوع و، در صورت نیاز، یک مستأجر. موضوع همان چیزی است که از آن محافظت میکنید، مثلاً catalog-api، و هیچوقت اعتبارنامه نیست. همهٔ سیاستهای یک پایپلاین پارتیشن آن را شریکاند.
هر پایپلاین دقیقاً به یک پارتیشن خدمت میکند. اگر context ساختهشده توسط پایپلاین یک پارتیشن دیگر را به آن بدهید، Reqtide پیش از ارسال هر چیزی آن را رد میکند:
$orders = PipelineBuilder::profile('concepts', 'v1')
->partition('shop', 'prod', 'orders-api')
->attemptTimeout(Duration::seconds(2), AttemptTimeoutRequirement::Cooperative)
->rateLimit(PipelineBuilder::tokenBucket(5, 5, Duration::seconds(1)))
->compileInMemory(ExceptionClassifier::create());
try {
$catalog->execute(static fn(): string => 'never runs', $orders->newContext(Duration::seconds(30)));
} catch (InvalidConfigurationException $e) {
echo $e->getMessage(), "\n";
}The execution context partition must equal every selected policy partition.موضوع را بر اساس چیزی انتخاب کنید که میخواهید ظرفیتش را حفظ کنید: برای هر وابستگی یک موضوع، و وقتی محدودیتها برای هر مستأجر جدا هستند، برای هر مستأجر یک پایپلاین. در حالت درونحافظه هر پایپلاین کامپایلشده پشتیبانهای خودش را دارد، پس دو پایپلاین هیچوقت سهمیه را شریک نمیشوند، حتی برای یک پارتیشن. شناسه و نسخهٔ profile نام پیکربندی را میدهند. وقتی تنظیمات عوض میشود نسخه را هم عوض کنید، چون حالت با آن کلیدگذاری میشود.
چه چیزی شکست حساب میشود
سیاستها بر اساس طبقهبندی هر تلاش عمل میکنند. طبقهبند (classifier) شما تعیین میکند هر نتیجه چه معنایی دارد: قابل تلاش مجدد است، برای مدارشکن شکست حساب میشود، یا با fallback قابل جایگزینی است. کلاس ExceptionClassifier محافظهکارانه شروع میکند. انتخاب سیاستها را ببینید.
ادامه
- انتخاب سیاستها مشکلها را به محافظتها نگاشت میکند.
- تلاش مجدد و سهمیه و محدودیت نرخ دو محافظتی را پوشش میدهند که بیشترین اثر را بر هم دارند.
- مدیریت خطا توضیح میدهد وقتی یک محافظت «نه» میگوید چه چیزی بیرون میآید.
این صفحه مفید بود؟