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-mode | per-line, per-connection, all-lines, all-connections, per-worker |
work-mode | pause, drop |
در parser فعلی این stringها به حروف کوچک و بزرگ حساساند.
فیلد اختیاری
| فیلد | پیشفرض | توضیح |
|---|---|---|
token-recharge-rate | 10 | فاصله refill شدن tokenها به میلیثانیه. باید بزرگتر از 0 باشد. |
token-recharge-rate مقدار پهنایباند را تعیین نمیکند؛ فقط فاصله زمانی افزودن token به bucket را مشخص میسازد.
برای مثال، با این تنظیم:
bytes-per-sec: 1000token-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 جا نشود:
- buffer درون
SpeedLimitدر صف قرار میگیرد. - سمت source با callbackهای
Pauseواتروال متوقف میشود. - یک timer روی worker منتظر refill شدن tokenها میماند.
- داده صفشده به همان ترتیب ارسال میشود.
- با خالی شدن صف، ارسال در سمتی که بهصورت 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 است.
متادیتای نود
| ویژگی | مقدار |
|---|---|
| flag | kNodeFlagChainHead |
| previous node | مجاز |
| next node | مجاز |
| layer group | kNodeLayerAnything |
required_padding_left | 0 |
اشتباههای رایج
- بیشتر از یک فیلد سرعت تنظیم نکنید.
- برای packet chainها از
pauseاستفاده نکنید مگر اینکه packetهای تاخیردار قابل قبول باشند. - از
per-workerانتظار یک سقف مشترک سراسری نداشته باشید؛ سقف عملی با تعداد workerها افزایش مییابد. - در packet chainها
per-lineرا per-client یا per-flow فرض نکنید. token-recharge-rateرا بسیار بزرگ نگذارید، مگر اینکه خروجی burstی برایتان قابل قبول باشد.