Приводом для написання цієї статті послужила лінь, яка, як відомо, двигун прогресу і свідок появи на світ неймовірно полегшуючих життя речей.
У моєму випадку це була лінь пояснювати в тисячний раз клієнту, чому він орендував канал точка-точка і в договорі чорним по білому написано Ethernet 1Гбіт/с, а він як не вимірює, але трохи та менше виходить.
Де інше? Чому недобір? Куди дівався інтернет з дроту? А може його і зовсім страшно обдурили?
Ну що ж, давайте шукати, а заодно напишемо замітку, яку буде не соромно показати тисячі першому клієнту, у якого буде недосдача швидкості.
Якщо ви мережевий інженер, то не читайте цю статтю. Вона образить всі ваші почуття, тому що написана максимально простою і доступною мовою з безліччю упущень.
Отже, щоб зрозуміти, де швидкість, нам доведеться розібратися в тому, як працює TCP в плані забезпечення надійності з'єднання.
Як нам всім відомо, у ТСР є фінт останньої надії, коли всі хитрощі доставити кадр до одержувача не спрацювали, він просто заново відправляє зіпсований або загублений кадр.
Як це виглядає в найпростішому випадку:
Графічно цей алгоритм легко представляється на часовій шкалі:
Називається цей алгоритм методом простою джерела, що відразу дає нам зрозуміти його головний мінус: катастрофічно неефективне використання каналу зв'язку. Технічно, нічого не заважає передавачу відразу після відправки першого кадру відправляти другий, але ми примушуємо його чекати приходу ACK/NACK або закінчення таймера.
Тому важливо розуміти, що процеси відправлення та отримання АСКів можуть йти незалежно один від одного. На цій ідеї і був народжений метод ковзаючого вікна.
Після усвідомлення мінусів попереднього методу, на думку спадає ідея дозволити джерелу передавати пакети в максимально можливому для нього темпі, без очікування підтвердження від прийомника. Але не нескінченна їх кількість, а обмежена якимось буфером, який називається вікном, а його розмір вказує на кількість кадрів яку дозволено передати без очікування підтверджень.
Повернемося до малюнків:
Для наочності розглянемо вікно розміром К кадрів, в якому знаходяться пакети (1...N) в якийсь момент часу, тобто у нас була відправлена пачка кадрів, кожен кадр у міру своєї можливості досяг одержувача, той їх обробив і відправив підтвердження для кожного.
Тепер ускладнюємо ситуацію, включивши час.
У момент приходу АСК на перший кадр, останній ще не був навіть відправлений, але оскільки ми знаємо про успішність доставки, у вікно можна додати наступний за порядком кадр, тобто вікно зсувається і тепер включає в себе кадри 2... N + 1. Коли приходить ACK на 2-й кадр, вікно знову зсувається: 3...N+2. І так далі. Виходить, що вікно як-би «» ковзає «» по потоку пакетів. Або пакети через нього, тут кому як зручніше представляти.
Таким чином, всі кадри глобально діляться на три види:
І як це впливає на швидкість зв'язку?
Як ми бачимо з графіка, необхідною умовою для максимальної утилізації каналу зв'язку є регулярне надходження підтверджень в рамках дії вікна. АСК не зобов'язані приходити в чіткому порядку, головне щоб АСК на перший кадр у вікні прийшов раніше, ніж буде відправлений останній кадр, інакше спрацює таймер невідповіді, кадр буде відправлений заново, а рух вікна припиниться.
Також, варто відзначити, що в більшості реалізацій алгоритму ковзного вікна, підтвердження приходять не на кожен пакет, а відразу на всю прийняту пачку (називається Selective Ack). Це дозволяє збільшити ефективну утилізацію каналу за рахунок зниження обсягу службової інформації.
Отже, що ж ми маємо в сухому залишку? Які параметри мають істотний вплив на ефективність передачі даних між двома точками?
Їх два:
І ми не повинні передати більше, ніж готовий прийняти одержувач або пропустити мережу.
Давайте уявимо, що у нас супер надійна мережа, де пакети практично ніколи не губляться і не б'ються. У такій мережі нам вигідно мати вікно максимально можливого розміру, що дозволить нам мінімізувати паузи між відправкою кадрів.
У поганій же мережі ситуація зворотна - при частих втратах і великій кількості битих пакетів нам важливо доставити кожен з них в цілості і збереження, тому ми зменшуємо вікно так, щоб трафік якомога менше губився і ми уникли фатальних перевідправок. Жертвувати в цьому випадку доводиться швидкістю.
І як нам всім відомо, реальність - це суміш двох крайніх випадків, тому в реальній мережі розмір вікна величина змінна. Причому він може бути змінений, як в односторонньому порядку на будь-якій стороні, так і за погодженням.
Проміжне резюме. Як ми бачимо, навіть без прив'язки до конкретних протоколів, суто технічно вкрай складно утилізувати канал на всі 100%, тому що хочемо ми того чи ні, але навіть в лабораторних умовах між кадрами будуть мінімальні, але затримки, які і не дадуть нам досягти заповітних 100% утилізації смуги пропускання.
А що вже говорити про реальні мережі? Ось у другій частині ми розглянемо одну з реалізацій на прикладі TCP.
Чим чудовий ТСР? На основі ненадійних дейтаграм IP, він дозволяє забезпечити надійну доставку повідомлень.
Під час встановлення логічного з'єднання модулі ТСР обмінюються такими параметрами:
Відразу запам'ятовуємо важливу особливість - в ТСР вікно оперує не кількістю кадрів, а кількістю байт. Це означає, що вікно означає безліч нумерованих байт неструктурованого потоку даних від верхніх протоколів. Звучить громіздко, але простіше написати не виходить.
Отже, ТСР протокол дуплексний, а значить кожна сторона в будь-який момент часу виступає і як відправник, і як одержувач. Отже, з кожної сторони повинен бути буфер для прийому сегментів і буфер для ще не відправлених сегментів. Але крім того, повинен бути ще і буфер для копій вже відправлених сегментів, на які ще не отримали підтвердження про прийом.
До речі, звідси випливає особливість: у двох напрямках умови мережі можуть бути різними, а значить різний розмір вікон і різна пропускна здатність.
У такій ситуації танець ведеться від можливостей одержувача. Після встановлення з "єднання обидві сторони висилають один одному вікна прийому. Отримувач запам'ятовує його розмір і розуміє скільки байт можна відправити, не чекаючи АСК.
Далі включаються механізми описані в першій главі. Надсилання сегментів, очікування підтвердження, повторна відправка в разі необхідності тощо. Важлива відмінність від теоретичних досліджень - це комбінування різних методик. Так, наприклад, отримання декількох сегментів, що прийшли по порядку, відбувається автоматично, перериваючись, тільки якщо збивається черговість надходження. Це одна з функцій буфера отримувача - відновити порядок сегментів. І те, якщо в потоці виявляється розрив, ТСР модуль може повторити запит втраченого сегмента.
Пара слів про буфер копій на відправнику. У всіх сегментів, що лежать в цьому буфері, працює таймер. Якщо за час таймера приходить відповідний АСК, сегмент видаляється. Якщо ні - відправляється заново. Можлива така ситуація, що по таймеру сегмент буде відправлений ще раз до того, як прийде АСК, але в цьому випадку повторний сегмент просто буде відкинутий одержувачем.
І ось від цього тайм-ауту на очікування і залежить продуктивність ТСР. Буде занадто короткий - з'являться надлишкові перевідправки пакетів. Занадто довгий - отримаємо простої через очікування неіснуючих АСК.
Насправді ТСР визначає розмір тайм-ауту за складним адаптивним алгоритмом, де враховуються швидкість, надійність, протяжність лінії і безліч інших факторів. Але в загальних рисах він такий:
Але щось ми все більше про вікно відправки, хоча вікно прийому представляє з себе більш цікаву сутність. На різних кінцях з'єднання вікна зазвичай мають різний розмір. У світі клієнт-серверних технологій, які перемогли, не доводиться очікувати, що клієнт буде готовий оперувати вікном того ж розміру, який може обробляти сервер.
Точно так само розмір його може змінюватися динамічно, залежно від стану мережі, але тут неправильний вибір передбачає вже «подвійну» відповідальність. У разі отримання даних Більших, ніж може обробити ТСР модуль, вони будуть відкинуті, на джерелі спрацює таймер, він перевідправить дані, вони знову не потраплять у розмір вікна тощо.
З іншого боку, встановлення занадто маленького вікна призведе до використання каналу на швидкості рівної швидкості передачі одного сегмента. Тому розробники ТСР запропонували схему, в якій при встановленні з'єднання розмір вікна встановлюється відносно великим і в разі проблем починає скорочуватися в два рази за крок. Це дійсно виглядає дивно, тому були створені реалізації ТСР, що повторюють звичну нам логіку: почати з малого вікна і, якщо мережа справляється, то почати його збільшувати.
Але на розмір вікна прийому може впливати не тільки приймаюча сторона, але і відправник даних. Якщо ми бачимо, що АСК регулярно приходять пізніше таймерів, що доводиться часто перевідправляти сегменти, то джерело може виставити своє значення вікна прийому і буде діяти правило найменшого - буде прийнято найменше значення, хто б його не призначив.
Залишається розглянути ще один варіант розвитку подій, а саме перевантаження ТСР-з'єднання. Цей стан мережі характеризується тим, що на проміжних і віконечних вузлах виникають черги пакетів. У даному випадку у приймача є два варіанти:
Хоча повністю закрити з'єднання таким чином не можна. Існує спеціальний індекс терміновості, який примушує порт прийняти сегмент даних, навіть якщо для цього доведеться очистити існуючий буфер. А відправник, який прийняв нульовий розмір вікна, не втрачає надії і періодично відправляє контрольні запити на прийомник і, якщо той вже готовий для прийняття нових даних, то у відповідь він отримає новий, не нульовий, розмір вікна.
Капітан підказує - якщо одна TCP сесія в принципі не може забезпечувати 100% утилізацію каналу, то використовуй дві. Якщо ми говоримо про клієнта, який взяв в оренду канал точка-точка і підняв в ньому GRE тунель, то нехай підніме другий. Щоб вони не билися за смугу, загортаємо в перший важливий трафік, у другій - всяку дурницю і страшно зарізаємо йому швидкість. Цього якраз вистачить на те, щоб вибрати залишки смуги, яку перша сесія не може взяти суто технічно.