پرش به مطلب اصلی

SpeedLimit

SpeedLimit یک tunnel عبوری است که نرخ عبور بایت‌های payload را با token bucket محدود می‌کند. این نود داده protocol را تغییر نمی‌دهد، header اضافه یا حذف نمی‌کند و destination context را دست نمی‌زند؛ فقط تعیین می‌کند payload اکنون عبور کند، منتظر بماند یا کنار گذاشته شود.

هر دو جهت از همان bucket استفاده می‌کنند. برای نمونه، اگر سرعت یک bucket برابر 1 MB/s باشد، مجموع upload و download آن bucket همین ظرفیت را با هم تقسیم می‌کنند.

جایگاه رایج

برای stream:

TcpListener -> SpeedLimit -> TcpConnector
TcpListener -> TlsServer -> SpeedLimit -> TcpConnector

برای packet یا datagram:

UdpListener -> SpeedLimit -> UdpConnector
TunDevice -> SpeedLimit -> UdpStatelessSocket

برای ترافیک stream معمولاً حالت pause مناسب‌تر است تا داده اضافه منتظر بماند. برای packet و datagram معمولاً drop بهتر است، چون packet قدیمی اغلب از packet ازدست‌رفته هم کم‌ارزش‌تر است.

نمونه stream

در این نمونه هر connection یک bucket جدا با سرعت 256 KiB/s دارد و داده اضافی تا پر شدن دوباره tokenها در صف می‌ماند.

{
"name": "speed-limit",
"type": "SpeedLimit",
"settings": {
"kilo-bytes-per-sec": 256,
"limit-mode": "per-line",
"work-mode": "pause"
},
"next": "next-node"
}

نمونه limit مشترک

در این نمونه همه lineهای عبوری از این instance یک bucket مشترک 8 MiB/s دارند.

{
"name": "global-speed-limit",
"type": "SpeedLimit",
"settings": {
"mega-bytes-per-sec": 8,
"limit-mode": "all-lines",
"token-recharge-rate": 20,
"work-mode": "pause"
},
"next": "next-node"
}

نمونه packet drop

در این نمونه هر worker یک bucket مشترک 500000 bytes/s دارد و chunkهایی که token کافی برایشان موجود نیست کنار گذاشته می‌شوند.

{
"name": "packet-speed-limit",
"type": "SpeedLimit",
"settings": {
"bytes-per-sec": 500000,
"limit-mode": "per-worker",
"work-mode": "drop"
},
"next": "next-node"
}

فیلدهای اجباری

settings الزامی است و باید objectی غیرخالی باشد.

دقیقاً یکی از فیلدهای سرعت باید تنظیم شود:

فیلدمعنی
bytes-per-secبایت بر ثانیه.
kilo-bytes-per-secکیبی‌بایت بر ثانیه، ضرب در 1024.
mega-bytes-per-secمبی‌بایت بر ثانیه، ضرب در 1024 * 1024.

مقدار انتخاب‌شده باید عددی بزرگ‌تر از 0 باشد. تنظیم هم‌زمان بیش از یک فیلد سرعت باعث شکست ساخت نود می‌شود.

این فیلدها هم اجباری هستند:

فیلدمقدارهای مجاز
limit-modeper-line, per-connection, all-lines, all-connections, per-worker
work-modepause, drop

در parser فعلی این stringها به حروف کوچک و بزرگ حساس‌اند.

فیلد اختیاری

فیلدپیش‌فرضتوضیح
token-recharge-rate10فاصله refill شدن tokenها به میلی‌ثانیه. باید بزرگ‌تر از 0 باشد.

token-recharge-rate مقدار پهنای‌باند را تعیین نمی‌کند؛ فقط فاصله زمانی افزودن token به bucket را مشخص می‌سازد.

برای مثال، با این تنظیم:

  • bytes-per-sec: 1000
  • token-recharge-rate: 10

bucket تقریباً هر 10 ms به اندازه 10 byte token دریافت می‌کند. interval کوچک‌تر معمولاً خروجی روان‌تری می‌دهد و interval بزرگ‌تر می‌تواند ترافیک را burstی‌تر کند.

Limit modeها

modeرفتار
per-line / per-connectionهر line در واتروال bucket خودش را دارد. دو line هم‌زمان با 1 MB/s می‌توانند در مجموع حدود 2 MB/s مصرف کنند.
all-lines / all-connectionsهمه lineهای این tunnel instance یک bucket سراسری و atomic میان workerها دارند. با 1 MB/s، کل نود همین ظرفیت را میان همه lineها تقسیم می‌کند.
per-workerهر worker bucket مشترک خودش را دارد. با 4 worker و 1 MB/s، سقف عملی کل می‌تواند حدود 4 MB/s شود.

هر bucket در ابتدا پر است و ظرفیتی برابر حدود یک ثانیه از نرخ تنظیم‌شده دارد. در نتیجه ممکن است در شروع کار، نزدیک به یک ثانیه ترافیک را به شکل burst عبور دهد و سپس روی میانگین تعیین‌شده پایدار شود.

Work modeها

pause

pause برای ترافیک ترتیبی مانند TCP طراحی شده است.

وقتی payload در bucket جا نشود:

  1. buffer درون SpeedLimit در صف قرار می‌گیرد.
  2. سمت source با callbackهای Pause واتروال متوقف می‌شود.
  3. یک timer روی worker منتظر refill شدن tokenها می‌ماند.
  4. داده صف‌شده به همان ترتیب ارسال می‌شود.
  5. با خالی شدن صف، ارسال در سمتی که به‌صورت local متوقف شده بود از سر گرفته می‌شود.

اگر buffer صف‌شده از token موجود بزرگ‌تر باشد، SpeedLimit می‌تواند بخش ابتدایی آن را بفرستد و باقی را تا tick بعدی در صف نگه دارد. با این کار ترتیب بایت‌ها حفظ و نرخ میانگین کنترل می‌شود.

حالت pause bufferها را در حافظه نگه می‌دارد؛ بنابراین limit بسیار پایین، peer کند یا نرخ ورودی بالا می‌تواند مصرف حافظه و latency را افزایش دهد.

drop

drop برای ترافیک packet و datagram طراحی شده است.

برای هر payload chunk:

  • اگر bucket برای کل chunk به اندازه کافی token داشته باشد، chunk فرستاده می‌شود
  • اگر token کافی نباشد، کل chunk کنار گذاشته می‌شود

در حالت drop، payload نه تکه‌تکه می‌شود و نه برای بعد در صف می‌ماند. این رفتار معمولاً برای UDP و packet chain مناسب‌تر است، زیرا datagramهای قدیمی اغلب دیگر کاربردی ندارند.

جهت‌ها و backpressure

limiter در هر دو جهت رفتاری متقارن دارد:

  • upstream و downstream از همان منطق bucket انتخاب‌شده استفاده می‌کنند
  • داده صف‌شده در upstream، سمت قبلی را متوقف می‌کند
  • داده صف‌شده در downstream، سمت بعدی را متوقف می‌کند
  • حالت Pause و Resume خارجی به‌خاطر سپرده می‌شود تا SpeedLimit سمتی را که هنوز از بیرون pause است به اشتباه resume نکند

در نتیجه حالت pause با backpressure معمول واتروال هماهنگ می‌شود و صرفاً درون callback منتظر نمی‌ماند.

نکته packet-line

packet lineهای واتروال، lineهای کمکی و محلی هر worker هستند که برای packetهای زیادی دوباره به کار می‌روند. در packet chain، per-line یعنی bucket متصل به packet line پایدار همان worker، نه یک bucket واقعی برای هر flow یا client. اگر limit بر اساس worker می‌خواهید، per-worker را صریح انتخاب کنید. اگر یک cap مشترک بین همه workerها می‌خواهید، all-lines را انتخاب کنید.

برای chainهای packet یا UDP-style معمولاً work-mode: "drop" انتخاب بهتری است، مگر اینکه عمداً delayed packet delivery و backpressure بخواهید.

رفتار Finish

line state در Init مقداردهی اولیه می‌شود. هنگام Finish، نود state محلی را از بین می‌برد، timerهای تخلیه صف را لغو می‌کند، bufferهای صف‌شده را آزاد می‌کند و سپس Finish واتروال را در همان جهت می‌فرستد.

این tunnel چیزی به ابتدای payload اضافه نمی‌کند؛ بنابراین required_padding_left برابر 0 است.

متادیتای نود

ویژگیمقدار
flagkNodeFlagChainHead
previous nodeمجاز
next nodeمجاز
layer groupkNodeLayerAnything
required_padding_left0

اشتباه‌های رایج

  • بیشتر از یک فیلد سرعت تنظیم نکنید.
  • برای packet chainها از pause استفاده نکنید مگر اینکه packetهای تاخیردار قابل قبول باشند.
  • از per-worker انتظار یک سقف مشترک سراسری نداشته باشید؛ سقف عملی با تعداد workerها افزایش می‌یابد.
  • در packet chainها per-line را per-client یا per-flow فرض نکنید.
  • token-recharge-rate را بسیار بزرگ نگذارید، مگر اینکه خروجی burstی برایتان قابل قبول باشد.