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

مفاهیم

پایپ‌لاین، عملیات، تلاش و پارتیشن در Reqtide از هم جدا هستند. هر قانون کجا قرار دارد و بررسی‌ها به چه ترتیبی اجرا می‌شوند.

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

در این صفحه

هر بلوک کد این صفحه از 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 برمی‌گرداند. سازنده مقدار پیش‌فرض ندارد. هر سیاست فقط وقتی وجود دارد که متدش را صدا بزنید و هر عدد را هم خودتان می‌دهید.

PHP
$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 ندارد. برای یک خط گزارش هنگام راه‌اندازی به کار می‌آید، یا در آزمون برای اطمینان از اینکه پایپ‌لاین همان است که فکر می‌کنید:

Text
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 شما می‌دهد.

PHP
$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";
Text
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 را با هم می‌پوشاند. این مدت باید مثبت باشد، چون مهلتی که از قبل تمام شده فقط می‌تواند شکست بخورد.

هر قطعه کد در راهنماهای محافظت با همین دو گزینهٔ الزامی شروع می‌شود. مثال مهلت کل هم از همین تنظیم استفاده می‌کند:

PHP
/** 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));
PHP
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";
}
Text
-- 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 پیش از ارسال هر چیزی آن را رد می‌کند:

PHP
$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";
}
Text
The execution context partition must equal every selected policy partition.

موضوع را بر اساس چیزی انتخاب کنید که می‌خواهید ظرفیتش را حفظ کنید: برای هر وابستگی یک موضوع، و وقتی محدودیت‌ها برای هر مستأجر جدا هستند، برای هر مستأجر یک پایپ‌لاین. در حالت درون‌حافظه هر پایپ‌لاین کامپایل‌شده پشتیبان‌های خودش را دارد، پس دو پایپ‌لاین هیچ‌وقت سهمیه را شریک نمی‌شوند، حتی برای یک پارتیشن. شناسه و نسخهٔ profile نام پیکربندی را می‌دهند. وقتی تنظیمات عوض می‌شود نسخه را هم عوض کنید، چون حالت با آن کلیدگذاری می‌شود.

چه چیزی شکست حساب می‌شود

سیاست‌ها بر اساس طبقه‌بندی هر تلاش عمل می‌کنند. طبقه‌بند (classifier) شما تعیین می‌کند هر نتیجه چه معنایی دارد: قابل تلاش مجدد است، برای مدارشکن شکست حساب می‌شود، یا با fallback قابل جایگزینی است. کلاس ExceptionClassifier محافظه‌کارانه شروع می‌کند. انتخاب سیاست‌ها را ببینید.

ادامه