У пошуках загубленого гігабіту або трохи про вікна в TCP

Приводом для написання цієї статті послужила лінь, яка, як відомо, двигун прогресу і свідок появи на світ неймовірно полегшуючих життя речей.

У моєму випадку це була лінь пояснювати в тисячний раз клієнту, чому він орендував канал точка-точка і в договорі чорним по білому написано Ethernet 1Гбіт/с, а він як не вимірює, але трохи та менше виходить.

Де інше? Чому недобір? Куди дівався інтернет з дроту? А може його і зовсім страшно обдурили?

Ну що ж, давайте шукати, а заодно напишемо замітку, яку буде не соромно показати тисячі першому клієнту, у якого буде недосдача швидкості.

Важливо

Якщо ви мережевий інженер, то не читайте цю статтю. Вона образить всі ваші почуття, тому що написана максимально простою і доступною мовою з безліччю упущень.

Історія про вікна. Світ до вікон

Отже, щоб зрозуміти, де швидкість, нам доведеться розібратися в тому, як працює TCP в плані забезпечення надійності з'єднання.

Як нам всім відомо, у ТСР є фінт останньої надії, коли всі хитрощі доставити кадр до одержувача не спрацювали, він просто заново відправляє зіпсований або загублений кадр.

Як це виглядає в найпростішому випадку:

  • Кадр надсилається передавачем. На передавачі включається таймер, протягом якого від одержувача має бути отримано підтвердження АСК про успішне отримання кадру або явна вказівка, що кадр був зіпсований/загублений в дорозі - NACK
  • Якщо після закінчення таймера АСК не отримано, пакет відправляється ще раз
  • Якщо приходить NACK, джерело повторює відправку кадру
  • А якщо АСК отримано, джерело відправляє наступний кадр

Графічно цей алгоритм легко представляється на часовій шкалі:

Називається цей алгоритм методом простою джерела, що відразу дає нам зрозуміти його головний мінус: катастрофічно неефективне використання каналу зв'язку. Технічно, нічого не заважає передавачу відразу після відправки першого кадру відправляти другий, але ми примушуємо його чекати приходу ACK/NACK або закінчення таймера.

Тому важливо розуміти, що процеси відправлення та отримання АСКів можуть йти незалежно один від одного. На цій ідеї і був народжений метод ковзаючого вікна.

Вікно перше. Ковзне. Теоретичне.

Після усвідомлення мінусів попереднього методу, на думку спадає ідея дозволити джерелу передавати пакети в максимально можливому для нього темпі, без очікування підтвердження від прийомника. Але не нескінченна їх кількість, а обмежена якимось буфером, який називається вікном, а його розмір вказує на кількість кадрів яку дозволено передати без очікування підтверджень.

Повернемося до малюнків:

Для наочності розглянемо вікно розміром К кадрів, в якому знаходяться пакети (1...N) в якийсь момент часу, тобто у нас була відправлена пачка кадрів, кожен кадр у міру своєї можливості досяг одержувача, той їх обробив і відправив підтвердження для кожного.

Тепер ускладнюємо ситуацію, включивши час.

У момент приходу АСК на перший кадр, останній ще не був навіть відправлений, але оскільки ми знаємо про успішність доставки, у вікно можна додати наступний за порядком кадр, тобто вікно зсувається і тепер включає в себе кадри 2... N + 1. Коли приходить ACK на 2-й кадр, вікно знову зсувається: 3...N+2. І так далі. Виходить, що вікно як-би «» ковзає «» по потоку пакетів. Або пакети через нього, тут кому як зручніше представляти.

Таким чином, всі кадри глобально діляться на три види:

  • Минуле. Були відправлені, були отримані підтвердження
  • Суворе сьогодення. Складається з відправлених кадрів, але без отриманих підтверджень і тих, хто вже у вікні, але стоїть у черзі на відправку
  • Світле майбутнє. Кадри в черзі на потрапляння у вікно.

І як це впливає на швидкість зв'язку?

Як ми бачимо з графіка, необхідною умовою для максимальної утилізації каналу зв'язку є регулярне надходження підтверджень в рамках дії вікна. АСК не зобов'язані приходити в чіткому порядку, головне щоб АСК на перший кадр у вікні прийшов раніше, ніж буде відправлений останній кадр, інакше спрацює таймер невідповіді, кадр буде відправлений заново, а рух вікна припиниться.

Також, варто відзначити, що в більшості реалізацій алгоритму ковзного вікна, підтвердження приходять не на кожен пакет, а відразу на всю прийняту пачку (називається Selective Ack). Це дозволяє збільшити ефективну утилізацію каналу за рахунок зниження обсягу службової інформації.

Отже, що ж ми маємо в сухому залишку? Які параметри мають істотний вплив на ефективність передачі даних між двома точками?

Їх два:

  • Розмір вікна, який вибирається меншим з двох: вікно, оголошене одержувачем (розмір його буфера) або CWND - розмір, визначений відправником на основі RTT
  • Саме RTT: час прийому-передачі, рівний часу, витраченим на відправку сигналу, плюс час необхідний для підтвердження про прийом.

І ми не повинні передати більше, ніж готовий прийняти одержувач або пропустити мережу.

Давайте уявимо, що у нас супер надійна мережа, де пакети практично ніколи не губляться і не б'ються. У такій мережі нам вигідно мати вікно максимально можливого розміру, що дозволить нам мінімізувати паузи між відправкою кадрів.

У поганій же мережі ситуація зворотна - при частих втратах і великій кількості битих пакетів нам важливо доставити кожен з них в цілості і збереження, тому ми зменшуємо вікно так, щоб трафік якомога менше губився і ми уникли фатальних перевідправок. Жертвувати в цьому випадку доводиться швидкістю.

І як нам всім відомо, реальність - це суміш двох крайніх випадків, тому в реальній мережі розмір вікна величина змінна. Причому він може бути змінений, як в односторонньому порядку на будь-якій стороні, так і за погодженням.

Проміжне резюме. Як ми бачимо, навіть без прив'язки до конкретних протоколів, суто технічно вкрай складно утилізувати канал на всі 100%, тому що хочемо ми того чи ні, але навіть в лабораторних умовах між кадрами будуть мінімальні, але затримки, які і не дадуть нам досягти заповітних 100% утилізації смуги пропускання.

А що вже говорити про реальні мережі? Ось у другій частині ми розглянемо одну з реалізацій на прикладі TCP.

Вікно друге. Реалізація в TCP

Чим чудовий ТСР? На основі ненадійних дейтаграм IP, він дозволяє забезпечити надійну доставку повідомлень.

Під час встановлення логічного з'єднання модулі ТСР обмінюються такими параметрами:

  • Розмір буфера отримувача - це верхнє обмеження розміру вікна
  • Початковий порядковий номер байта, з якого почнеться відлік потоку даних в рамках цієї сесії

Відразу запам'ятовуємо важливу особливість - в ТСР вікно оперує не кількістю кадрів, а кількістю байт. Це означає, що вікно означає безліч нумерованих байт неструктурованого потоку даних від верхніх протоколів. Звучить громіздко, але простіше написати не виходить.

Отже, ТСР протокол дуплексний, а значить кожна сторона в будь-який момент часу виступає і як відправник, і як одержувач. Отже, з кожної сторони повинен бути буфер для прийому сегментів і буфер для ще не відправлених сегментів. Але крім того, повинен бути ще і буфер для копій вже відправлених сегментів, на які ще не отримали підтвердження про прийом.

До речі, звідси випливає особливість: у двох напрямках умови мережі можуть бути різними, а значить різний розмір вікон і різна пропускна здатність.

У такій ситуації танець ведеться від можливостей одержувача. Після встановлення з "єднання обидві сторони висилають один одному вікна прийому. Отримувач запам'ятовує його розмір і розуміє скільки байт можна відправити, не чекаючи АСК.

Далі включаються механізми описані в першій главі. Надсилання сегментів, очікування підтвердження, повторна відправка в разі необхідності тощо. Важлива відмінність від теоретичних досліджень - це комбінування різних методик. Так, наприклад, отримання декількох сегментів, що прийшли по порядку, відбувається автоматично, перериваючись, тільки якщо збивається черговість надходження. Це одна з функцій буфера отримувача - відновити порядок сегментів. І те, якщо в потоці виявляється розрив, ТСР модуль може повторити запит втраченого сегмента.

Пара слів про буфер копій на відправнику. У всіх сегментів, що лежать в цьому буфері, працює таймер. Якщо за час таймера приходить відповідний АСК, сегмент видаляється. Якщо ні - відправляється заново. Можлива така ситуація, що по таймеру сегмент буде відправлений ще раз до того, як прийде АСК, але в цьому випадку повторний сегмент просто буде відкинутий одержувачем.

І ось від цього тайм-ауту на очікування і залежить продуктивність ТСР. Буде занадто короткий - з'являться надлишкові перевідправки пакетів. Занадто довгий - отримаємо простої через очікування неіснуючих АСК.

Насправді ТСР визначає розмір тайм-ауту за складним адаптивним алгоритмом, де враховуються швидкість, надійність, протяжність лінії і безліч інших факторів. Але в загальних рисах він такий:

  • При відправці кожного сегмента заміряється час до приходу АСК.
  • Одержувані значення усереднюються з ваговими коефіцієнтами, що зростають від минулого до майбутнього. Це дозволяє більш новим даним робити більший вплив на підсумковий результат.
  • Потім вважається середнє значення від усереднень на попередньому кроці і виходить величина таймауту. Але якщо розкид величин дуже великий, то враховується ще й дисперсія.

Але щось ми все більше про вікно відправки, хоча вікно прийому представляє з себе більш цікаву сутність. На різних кінцях з'єднання вікна зазвичай мають різний розмір. У світі клієнт-серверних технологій, які перемогли, не доводиться очікувати, що клієнт буде готовий оперувати вікном того ж розміру, який може обробляти сервер.

Точно так само розмір його може змінюватися динамічно, залежно від стану мережі, але тут неправильний вибір передбачає вже «подвійну» відповідальність. У разі отримання даних Більших, ніж може обробити ТСР модуль, вони будуть відкинуті, на джерелі спрацює таймер, він перевідправить дані, вони знову не потраплять у розмір вікна тощо.

З іншого боку, встановлення занадто маленького вікна призведе до використання каналу на швидкості рівної швидкості передачі одного сегмента. Тому розробники ТСР запропонували схему, в якій при встановленні з'єднання розмір вікна встановлюється відносно великим і в разі проблем починає скорочуватися в два рази за крок. Це дійсно виглядає дивно, тому були створені реалізації ТСР, що повторюють звичну нам логіку: почати з малого вікна і, якщо мережа справляється, то почати його збільшувати.

Але на розмір вікна прийому може впливати не тільки приймаюча сторона, але і відправник даних. Якщо ми бачимо, що АСК регулярно приходять пізніше таймерів, що доводиться часто перевідправляти сегменти, то джерело може виставити своє значення вікна прийому і буде діяти правило найменшого - буде прийнято найменше значення, хто б його не призначив.

Залишається розглянути ще один варіант розвитку подій, а саме перевантаження ТСР-з'єднання. Цей стан мережі характеризується тим, що на проміжних і віконечних вузлах виникають черги пакетів. У даному випадку у приймача є два варіанти:

  • Зменшити розмір вікна.
  • Зовсім відмовитися від прийому, встановивши розмір вікна, рівний нулю.

Хоча повністю закрити з'єднання таким чином не можна. Існує спеціальний індекс терміновості, який примушує порт прийняти сегмент даних, навіть якщо для цього доведеться очистити існуючий буфер. А відправник, який прийняв нульовий розмір вікна, не втрачає надії і періодично відправляє контрольні запити на прийомник і, якщо той вже готовий для прийняття нових даних, то у відповідь він отримає новий, не нульовий, розмір вікна.

Разом

Капітан підказує - якщо одна TCP сесія в принципі не може забезпечувати 100% утилізацію каналу, то використовуй дві. Якщо ми говоримо про клієнта, який взяв в оренду канал точка-точка і підняв в ньому GRE тунель, то нехай підніме другий. Щоб вони не билися за смугу, загортаємо в перший важливий трафік, у другій - всяку дурницю і страшно зарізаємо йому швидкість. Цього якраз вистачить на те, щоб вибрати залишки смуги, яку перша сесія не може взяти суто технічно.

COM_SPPAGEBUILDER_NO_ITEMS_FOUND