← Назад в базу знаний

Разница между HTTP/2 мультиплексированием и SOCKS5 при парсинге через прокси: где теряется скорость

Разница между HTTP/2 мультиплексированием и SOCKS5 при парсинге через прокси: где теряется скорость

Что вообще происходит под капотом

Когда ты гонишь парсинг через прокси, есть два принципиально разных уровня, на которых живёт «много запросов одновременно». HTTP/2 мультиплексирование — это уровень прикладного протокола. SOCKS5 — это уровень сессии, транспорт. Их постоянно путают, потому что оба «умеют много соединений», но делают это совершенно по-разному и с разной ценой.

HTTP/2 берёт одно TCP-соединение и нарезает его на потоки (streams). Каждый stream имеет свой ID, чётные/нечётные, и живёт независимо. SOCKS5 просто устанавливает TCP-туннель до целевого хоста — и всё, дальше клиент сам решает, что туда пихать. Один SOCKS5-коннект = один TCP-сокет до цели. Хочешь 50 параллельных запросов — открываешь 50 SOCKS5-соединений.

Отсюда растёт вся разница в скорости. И в граблях.

Мультиплексирование: экономия на handshake

TCP handshake — это 1 RTT. TLS handshake — ещё 1-2 RTT (TLS 1.2 — два, TLS 1.3 — один). Если ты открываешь 100 соединений к одному хосту, ты платишь этот RTT-налог 100 раз. При задержке 80 мс до сервера это 8000 мс чистого ожидания только на установку, ещё до первого байта полезной нагрузки.

HTTP/2 платит один раз. Дальше все 100 запросов идут в уже установленном соединении. На бумаге — огромная экономия. На практике всё упирается в то, как прокси-сервер обрабатывает эти потоки.

Вот типичный замер через curl с разными режимами:

```bash

curl -w "connect: %{time_connect} tls: %{time_appconnect} total: %{time_total}\n" \

-o /dev/null -s https://target.example/api/item/1

for i in \$(seq 1 50); do

curl -w "%{time_total}\n" -o /dev/null -s \

--proxy socks5h://127.0.0.1:1080 \

https://target.example/api/item/\$i

done | awk '{s+=\$1; n++} END {print "avg:", s/n, "n:", n}'

```

Первый вариант покажет полный handshake. Второй — усреднённое время по 50 запросам, каждый со своим SOCKS5-туннелем. Разница между этими числами и есть та цена, которую ты платишь за архитектуру.

Где SOCKS5 реально быстрее

Парадокс: в некоторых сценариях SOCKS5 обгоняет HTTP/2 мультиплексирование. Причины две.

Первая — head-of-line blocking на уровне TCP. HTTP/2 мультиплексирует потоки поверх одного TCP-соединения. Если один TCP-сегмент потерялся, ядро блокирует доставку всех последующих сегментов, включая данные других streams. Все потоки встают, пока не придёт ретрансмит. При потерях 1-2% на длинном плече (Москва — Франкфурт, например) это превращается в регулярные паузы по 200-400 мс. SOCKS5 с 50 отдельными соединениями теряет только одно из них — остальные 49 едут дальше.

Вторая — реализация прокси. Многие прокси-серверы терминируют HTTP/2 на входе, но внутри открывают обычные HTTP/1.1-соединения к цели. То есть мультиплексирование есть только на участке клиент-прокси, а дальше — снова зоопарк из одиночных коннектов. Выигрыш съедается.

Таблица: что где теряется

| Параметр | HTTP/2 мультиплекс | SOCKS5 (N коннектов) |

|---|---|---|

| Handshake на запрос | 0 после первого | 1 RTT + TLS каждый |

| TCP HOL blocking | Есть, блокирует все streams | Нет, изоляция по сокетам |

| Память на соединение | ~64 КБ буфер + HPACK | ~200 КБ на сокет (ядро) |

| Лимит параллелизма | SETTINGS_MAX_CONCURRENT_STREAMS (обычно 100-128) | Ограничен ulimit -n и портами |

| Стоимость на 1000 запросов | 1 сокет | 1000 сокетов в TIME_WAIT |

| Работа через прокси | Требует CONNECT или h2-туннель | Нативно, любой TCP |

Цифры по памяти — усреднённые для Linux 5.x с дефолтным `net.ipv4.tcp_rmem`. На 1000 параллельных SOCKS5-соединений ты легко упираешься в `ulimit -n 1024` и получаешь `Too many open files` посреди парсинга. Это классические грабли.

Пример: парсер на Python с обоими режимами

Покажу разницу на живом коде. Сначала SOCKS5 через `httpx` с `socksio`:

```python

import asyncio

import httpx

PROXY = "socks5://127.0.0.1:1080"

async def fetch_socks(client, url):

r = await client.get(url)

return r.status_code

async def main_socks(urls):

limits = httpx.Limits(max_connections=200, max_keepalive_connections=0)

async with httpx.AsyncClient(proxy=PROXY, limits=limits, timeout=15) as c:

tasks = [fetch_socks(c, u) for u in urls]

return await asyncio.gather(*tasks, return_exceptions=True)

```

`max_keepalive_connections=0` тут намеренно — каждый запрос открывает новый SOCKS5-туннель. Если поставить keepalive, ты фактически получишь пул TCP-соединений до цели, что уже ближе к тому, что делает HTTP/2, только без мультиплексирования.

Теперь HTTP/2 через тот же прокси, но с явным h2:

```python

import httpx

async def main_h2(urls):

async with httpx.AsyncClient(

proxy="http://127.0.0.1:8080",

http2=True,

limits=httpx.Limits(max_connections=1, max_keepalive_connections=1),

timeout=15,

) as c:

tasks = [c.get(u) for u in urls]

return await asyncio.gather(*tasks, return_exceptions=True)

```

Обрати внимание: `max_connections=1`. Весь параллелизм идёт через streams одного соединения. Если прокси на том конце поддерживает h2 end-to-end — это самый быстрый вариант. Если прокси даунгрейдит до HTTP/1.1 — ты получишь очередь, потому что HTTP/1.1 не умеет параллельные запросы в одном соединении без pipelining (который почти никто не включает).

Когда прокси ломает мультиплексирование

Тут начинается самое интересное. Прокси-сервер может:

1. Поддерживать h2 на входе и на выходе — тогда всё ок.

2. Поддерживать h2 на входе, HTTP/1.1 на выходе — теряешь весь смысл.

3. Работать только через CONNECT-туннель — тогда HTTP/2 идёт end-to-end, прокси видит только зашифрованный поток.

Третий вариант часто самый быстрый, но требует, чтобы прокси умел CONNECT и не резал по SNI. Проверить, что реально происходит, можно так:

```bash

curl -v --proxy http://proxy:8080 --http2 https://target.example/ 2>&1 | grep -E "ALPN|HTTP/2|CONNECT"

```

Если в выводе `ALPN, server accepted to use h2` — хорошо. Если `HTTP/1.1` — прокси даунгрейдит, и мультиплексирование работает только до прокси. Это критично для парсинга: ты думаешь, что гонишь 100 параллельных streams, а по факту стоишь в очереди на одном HTTP/1.1-коннекте к цели.

Кейс с парсингом маркетплейса

Пример: парсер карточек товара, 5000 URL, цель — один домен за прокси во Франкфурте. Задержка клиент-прокси 45 мс, прокси-цель 12 мс.

Первый запуск — SOCKS5, 200 параллельных туннелей, без keepalive. Результат: 5000 запросов за 340 секунд, среднее 68 мс на запрос. Ошибок 4% — часть по таймауту, часть по 429 от целевого сервера, потому что 200 одновременных TCP-соединений с одного IP выглядят как флуд.

Второй запуск — HTTP/2 через тот же прокси, `max_connections=1`. Прокси поддерживал h2 end-to-end. Результат: 5000 запросов за 95 секунд, среднее 19 мс. Потому что handshake один раз, HPACK-сжатие заголовков, и целевой сервер видит один stream-мультиплекс, а не 200 сокетов. 429 не пришло ни разу.

Третий запуск — HTTP/2, но прокси с даунгрейдом до HTTP/1.1. Результат: 5000 запросов за 610 секунд. Хуже SOCKS5, потому что один HTTP/1.1-коннект обрабатывает запросы строго последовательно. Вот она, разница между «поддерживает h2» и «делает вид».

Что делать на практике

Не выбирай между ними вслепую. Сначала выясни, что умеет твой прокси. Потом считай.

Если цель — один домен и прокси держит h2 — иди в HTTP/2. Экономия на handshake окупает всё. Через lexic.ml, например, h2-туннелирование работает end-to-end, и это снимает главную проблему — даунгрейд на выходе.

Если целей много (сотни доменов) — HTTP/2 мультиплексирование теряет смысл, потому что одно соединение к одному хосту. Тут SOCKS5 с пулом коннектов и ротацией выигрывает. Или гибрид: SOCKS5 для установки соединений, HTTP/2 внутри каждого туннеля.

Если потери пакетов выше 1% — SOCKS5 с изоляцией по сокетам надёжнее. HOL blocking в HTTP/2 на плохом канале убивает всю параллельность.

Настройки, которые реально влияют

На стороне клиента для SOCKS5-пула критичны:

```bash

ulimit -n 65535

sysctl -w net.ipv4.ip_local_port_range="1024 65535"

sysctl -w net.ipv4.tcp_tw_reuse=1

```

Без этого 5000 запросов через SOCKS5 упрутся в исчерпание портов или файловых дескрипторов. TIME_WAIT на 60 секунд при 200 rps — это 12000 висящих сокетов, которые жрут порты.

Для HTTP/2 критично:

```nginx

http2_max_concurrent_streams 256;

http2_idle_timeout 60s;

http2_recv_buffer_size 256k;

```

`http2_max_concurrent_streams` по умолчанию 128. Если твой парсер шлёт больше — лишние запросы ждут в очереди, и ты не понимаешь, почему скорость упала. Поднимай до 256, но следи за памятью на прокси.

Итог без итога

HTTP/2 мультиплексирование выигрывает, когда: один домен, хороший канал, прокси держит h2 end-to-end. Проигрывает при потерях пакетов, множестве доменов и даунгрейд-прокси.

SOCKS5 выигрывает на разнородных целях, плохих каналах и когда нужна изоляция запросов. Проигрывает на handshake-налоге и лимитах ОС.

Мерить надо. Один прогон на 1000 URL в обоих режимах даст больше, чем любая теория. И смотри не на среднее время, а на p95 и p99 — хвосты показывают, где именно теряется скорость.

✔️Купить прокси