задний план
Да, вы правильно прочитали, это многопоточность на интерфейсе, а неNode. На этот раз исследование началось с недавней разработки.В требованиях к разработке, связанных с потоковым видео, был обнаружен специальный код статуса.Его зовут206~
Чтобы эта статья не была скучной, давайте сначала добавим в текст рендеры. (с3.7Mнапример, фотографии большого размера).
Сравнение эффекта анимации (один поток — слева и 10 потоков — справа)
Сравнение времени (один поток против 10 потоков)
Если вы немного взволнованы, когда видите это, то, пожалуйста, продолжайте слушать меня, тогда давайте возьмем сумку и посмотрим, как происходит весь процесс.
GET /360_0388.jpg HTTP/1.1
Host: limit.qiufeng.com
Connection: keep-alive
...
Range: bytes=0-102399
HTTP/1.1 206 Partial Content
Server: openresty/1.13.6.2
Date: Sat, 19 Sep 2020 06:31:11 GMT
Content-Type: image/jpeg
Content-Length: 102400
....
Content-Range: bytes 0-102399/3670627
...(这里是文件流)
Вы можете видеть, что запрос имеет еще одно поле здесьRange: bytes=0-102399, на сервере также есть еще одно полеContent-Range: bytes 0-102399/3670627, а возвращаемый код состояния206.
ТакRangeЧто тогда? Я до сих пор помню, что несколько дней назад я написал статью о загрузке файлов, в которой упоминалось, как загружать большие файлы.Rangeвещи, ноПредыдущийВ систематическом обзоре загрузок файлов нетrangeдля подробного ознакомления.
Все коды ниже находятся вGitHub.com/flower1995116/…
Основное введение в диапазон
Происхождение диапазона
RangeвHTTP/1.1Новое поле в , эта функция также является основным механизмом, который мы используем, например, Thunder, который поддерживает многопоточную загрузку и загрузку с точкой останова. (Вступительная копия, выдержка)
Сначала клиент инициируетRange: bytes=0-xxxзапрос, если сервер поддерживает Range, он будет добавлен в заголовок ответаAccept-Ranges: bytesчтобы указать запрос, который поддерживает диапазон, а затем клиент может инициировать запрос с диапазоном.
Сервер через заголовок запросаRange: bytes=0-xxx Чтобы решить, следует ли выполнять обработку диапазона, если это значение существует и допустимо, только часть запрошенного содержимого файла отправляется обратно, код состояния ответа становится 206, что указывает на частичное содержимое, и устанавливается Content-Range. Если недействителен, возвращается код состояния 416, указывающий, что диапазон запроса не удовлетворяется. Если в заголовке запроса нет Range, сервер ответит нормально и не будет устанавливать Content-Range и т.д.
| Value | Description |
|---|---|
| 206 | Partial Content |
| 416 | Range Not Satisfiable |
Формат диапазона:
Range:(unit=first byte pos)-[last byte pos]
которыйRange: 单位(如bytes)= 开始字节位置-结束字节位置.
Возьмем пример: допустим, мы включили многопоточную загрузку и нам нужно разделить файл размером 5000 байт на 4 потока для загрузки.
- Диапазон: байты=0-1199 первые 1200 байт
- Диапазон: байт=1200-2399 Вторые 1200 байт
- Диапазон: байт=2400-3599 Третий 1200 байт
- Диапазон: байт=3600-5000 последние 1400 байт
Сервер отвечает:
1-й ответ
- Длина контента: 1200
- Content-Range: байты 0-1199/5000
2-й ответ
- Длина контента: 1200
- Диапазон содержимого: байты 1200-2399/5000
3-й ответ
- Длина контента: 1200
- Диапазон содержимого: байты 2400-3599/5000
4-й ответ
- Длина контента: 1400
- Контент-диапазон: байты 3600-5000/5000
Каждый запрос, если это успешно, сервер возвращает поле заголовка ответа, имеет поле диапазона контента, отзывчивый заголовок контента, чтобы сказать, что клиент отправляет количество данных, которые описывают весь диапазон прикрытия и объекта продолжительности респондентов. Общий формат:
Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity length]которыйContent-Range:字节 开始字节位置-结束字节位置/文件大小.
Поддержка браузера
В настоящее время все основные браузеры поддерживают эту функцию.
Поддержка сервера
Nginx
После того, как версия nginx версии 1.9.8 (плюс ngx_http_slice_module) автоматически поддерживается по умолчанию, вы можете добавитьmax_rangesУстановить как0для отмены этой настройки.
Node
Узел не предоставляет по умолчаниюRange Метод обработки, вам нужно написать свой собственный код для обработки.
router.get('/api/rangeFile', async(ctx) => {
const { filename } = ctx.query;
const { size } = fs.statSync(path.join(__dirname, './static/', filename));
const range = ctx.headers['range'];
if (!range) {
ctx.set('Accept-Ranges', 'bytes');
ctx.body = fs.readFileSync(path.join(__dirname, './static/', filename));
return;
}
const { start, end } = getRange(range);
if (start >= size || end >= size) {
ctx.response.status = 416;
ctx.body = '';
return;
}
ctx.response.status = 206;
ctx.set('Accept-Ranges', 'bytes');
ctx.set('Content-Range', `bytes ${start}-${end ? end : size - 1}/${size}`);
ctx.body = fs.createReadStream(path.join(__dirname, './static/', filename), { start, end });
})
или вы можете использоватьkoa-sendэта библиотека.
Практика стрельбища
Обзор архитектуры
Давайте сначала взглянем на обзор диаграммы архитектуры процесса. Один поток очень прост, вы можете скачать его в обычном режиме, вы можете обратиться ко мне, если вы не понимаетеПредыдущийстатья. Если многопоточное, то хлопот больше будет.Качать надо по частям.После скачивания нужно сливать и потом качать. (Для получения информации о методах загрузки, таких как blob, вы все равно можете обратиться кПредыдущий)
код сервера
просто, это правильноRangeсделал совместимым.
router.get('/api/rangeFile', async(ctx) => {
const { filename } = ctx.query;
const { size } = fs.statSync(path.join(__dirname, './static/', filename));
const range = ctx.headers['range'];
if (!range) {
ctx.set('Accept-Ranges', 'bytes');
ctx.body = fs.readFileSync(path.join(__dirname, './static/', filename));
return;
}
const { start, end } = getRange(range);
if (start >= size || end >= size) {
ctx.response.status = 416;
ctx.body = '';
return;
}
ctx.response.status = 206;
ctx.set('Accept-Ranges', 'bytes');
ctx.set('Content-Range', `bytes ${start}-${end ? end : size - 1}/${size}`);
ctx.body = fs.createReadStream(path.join(__dirname, './static/', filename), { start, end });
})
html
Потом давай html писать, тут и говорить нечего, пиши две кнопки для отображения.
<!-- html -->
<button id="download1">串行下载</button>
<button id="download2">多线程下载</button>
<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
общедоступные параметры js
const m = 1024 * 520; // 分片的大小
const url = 'http://localhost:8888/api/rangeFile?filename=360_0388.jpg'; // 要下载的地址
часть с одной резьбой
Однопоточный код загрузки, перейдите непосредственно к запросу наblobметод получения, а затем использоватьblobURLспособ скачать.
download1.onclick = () => {
console.time("直接下载");
function download(url) {
const req = new XMLHttpRequest();
req.open("GET", url, true);
req.responseType = "blob";
req.onload = function (oEvent) {
const content = req.response;
const aTag = document.createElement('a');
aTag.download = '360_0388.jpg';
const blob = new Blob([content])
const blobUrl = URL.createObjectURL(blob);
aTag.href = blobUrl;
aTag.click();
URL.revokeObjectURL(blob);
console.timeEnd("直接下载");
};
req.send();
}
download(url);
}
многопоточная часть
Сначала отправьте запрос головы, чтобы получить размер файла, а затем рассчитайте расстояние скольжения каждого фрагмента в соответствии с длиной и заданным размером фрагмента. пройти черезPromise.allв обратном вызове используйтеconcatenateФункция объединяет фрагментированные буферы в большой двоичный объект, а затем используетblobURLспособ скачать.
// script
function downloadRange(url, start, end, i) {
return new Promise((resolve, reject) => {
const req = new XMLHttpRequest();
req.open("GET", url, true);
req.setRequestHeader('range', `bytes=${start}-${end}`)
req.responseType = "blob";
req.onload = function (oEvent) {
req.response.arrayBuffer().then(res => {
resolve({
i,
buffer: res
});
})
};
req.send();
})
}
// 合并buffer
function concatenate(resultConstructor, arrays) {
let totalLength = 0;
for (let arr of arrays) {
totalLength += arr.length;
}
let result = new resultConstructor(totalLength);
let offset = 0;
for (let arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
download2.onclick = () => {
axios({
url,
method: 'head',
}).then((res) => {
// 获取长度来进行分割块
console.time("并发下载");
const size = Number(res.headers['content-length']);
const length = parseInt(size / m);
const arr = []
for (let i = 0; i < length; i++) {
let start = i * m;
let end = (i == length - 1) ? size - 1 : (i + 1) * m - 1;
arr.push(downloadRange(url, start, end, i))
}
Promise.all(arr).then(res => {
const arrBufferList = res.sort(item => item.i - item.i).map(item => new Uint8Array(item.buffer));
const allBuffer = concatenate(Uint8Array, arrBufferList);
const blob = new Blob([allBuffer], {type: 'image/jpeg'});
const blobUrl = URL.createObjectURL(blob);
const aTag = document.createElement('a');
aTag.download = '360_0388.jpg';
aTag.href = blobUrl;
aTag.click();
URL.revokeObjectURL(blob);
console.timeEnd("并发下载");
})
})
}
Полный пример
// 进入目录
cd file-download
// 启动
node server.js
// 打开
http://localhost:8888/example/download-multiple/index.html
Поскольку Google Chrome имеет ограничения на одно доменное имя в HTTP/1.1, максимальное количество одновременных запросов для одного доменного имени составляет 6.
Это может быть отражено в исходном коде и обсуждениях с официальным персоналом.
Адрес для обсуждения
не говорите .chromium.org/afraid/chromium/…
Исходный код Хрома
// https://source.chromium.org/chromium/chromium/src/+/refs/tags/87.0.4268.1:net/socket/client_socket_pool_manager.cc;l=47
// Default to allow up to 6 connections per host. Experiment and tuning may
// try other values (greater than 0). Too large may cause many problems, such
// as home routers blocking the connections!?!? See http://crbug.com/12066.
//
// WebSocket connections are long-lived, and should be treated differently
// than normal other connections. Use a limit of 255, so the limit for wss will
// be the same as the limit for ws. Also note that Firefox uses a limit of 200.
// See http://crbug.com/486800
int g_max_sockets_per_group[] = {
6, // NORMAL_SOCKET_POOL
255 // WEBSOCKET_SOCKET_POOL
};
Поэтому, чтобы соответствовать этой функции, я разделил файл на 6 сегментов, каждый сегмент520kb(Да, для написания кода требуется число, которое вас любит), то есть открыть 6 потоков для скачивания.
Я скачал 6 раз в один поток и в несколько потоков, и скорость вроде бы одинаковая. Так почему же все иначе, чем мы ожидали?
Исследуйте причины неудач
Я начал тщательно сравнивать два запроса и наблюдать за скоростью двух запросов.
6 потоков одновременно
один поток
Если рассчитать по скорости 3,7M 82 мс, то загрузка 46 КБ занимает около 1 мс, а фактическую ситуацию можно увидеть, 533 КБ, средняя загрузка составляет около 20 мс (время подключения было запланировано, время загрузки чистого контента) .
Я пошел, чтобы найти некоторую информацию и понял, что есть что-то, что называется скоростью нисходящего канала и скоростью восходящего канала.
Фактическая скорость передачи по сети делится на скорость восходящей линии связи и скорость нисходящей линии связи.Скорость исходящего каналаЭто скорость, с которой данные отправляются, а нисходящий канал — это скорость, с которой данные принимаются. ADSL - это метод передачи, реализованный в соответствии с привычкой, которую мы обычно просматриваем в Интернете и отправляем относительно небольшие требования к данным для загрузки данных. Мы говорим, что для 4MШирокополосный доступ, то наша теоретическая максимальная скорость загрузки составляет 512 КБ/с, что является так называемой скоростью нисходящего канала. --Энциклопедия Baidu
Итак, какова наша текущая ситуация?
Сравнив сервер с большой водопроводной трубой, позвольте мне использовать диаграмму, чтобы смоделировать ситуацию с загрузкой одного и нескольких потоков. Левая сторона — серверная, правая — клиентская. (Все следующие случаи считаются идеальными, просто для имитации процесса и без учета эффектов гонки некоторых других программ.)
один поток
Многопоточность
Правильно, так как наш сервер представляет собой большой водопровод, скорость потока фиксированная, а у нашего клиента нет лимита. Если это однопоточный запуск, он будет выполняться с максимальной скоростью пользователя. Если это многопоточность, в качестве примера возьмем 3 потока, это эквивалентно тому, что каждый поток работает на одной трети скорости исходного потока. Комбинированная скорость неотличима от одиночного потока.
Ниже я объясню в нескольких случаях, в какой ситуации сработает наша многопоточность?
Пропускная способность сервера больше, чем пропускная способность пользователя без каких-либо ограничений
По сути, эта ситуация аналогична той, с которой мы столкнулись.
Пропускная способность сервера намного превышает пропускную способность пользователя, что ограничивает скорость одного соединения.
Если сервер ограничивает скорость загрузки одного широкополосного доступа, это также имеет место для большинства из них.Например, Baidu Cloud такой.Например, очевидно, что у вас широкополосный доступ 10M, но фактическая скорость загрузки составляет всего 100 КБ / с. В этом случае мы можем открыть больше потоков для загрузки, потому что это часто ограничивает загрузку одного TCP, конечно, онлайн-среда не означает, что пользователи могут открывать неограниченное количество потоков, ограничения все равно будут , что ограничит максимальный TCP вашего текущего IP. Ограничение загрузки в этом случае часто является максимальной скоростью вашего пользователя. Согласно приведенному выше примеру, если вы открыли 10 потоков и достигли максимальной скорости, потому что независимо от того, насколько велика запись, ваша запись была ограничена, тогда каждый поток будет вытеснять скорость, и открывать больше потоков бесполезно.
улучшить предложения
Из-за Node я не нашел относительно простого способа управления скоростью загрузки, поэтому представил Nginx.
Мы контролируем скорость каждого TCP-соединения до 1M/s.
добавить конфигурациюlimit_rate 1M;
Готов к работе
1.nginx_conf
server {
listen 80;
server_name limit.qiufeng.com;
access_log /opt/logs/wwwlogs/limitqiufeng.access.log;
error_log /opt/logs/wwwlogs/limitqiufeng.error.log;
add_header Cache-Control max-age=60;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,range,If-Range';
if ($request_method = 'OPTIONS') {
return 204;
}
limit_rate 1M;
location / {
root 你的静态目录;
index index.html;
}
}
2. Настройте локальный хост
127.0.0.1 limit.qiufeng.com
Глядя на эффект, скорость в основном нормальная, а многопоточная загрузка быстрее, чем однопоточная. Скорость в основном 5-6:1, но я обнаружил, что если в процессе загрузки несколько раз быстро нажимать, использоватьRangeЗагрузка будет происходить все быстрее и быстрее (подозревается, что Nginx выполняет какое-то кеширование, и я пока не изучал его подробно).
修改代码中的下载地址
const url = 'http://localhost:8888/api/rangeFile?filename=360_0388.jpg';
变成
const url = 'http://limit.qiufeng.com/360_0388.jpg';
Тест скорости загрузки
Помните, что я говорил выше, оHTTP/1.1Один и тот же сайт может иметь только 6 одновременных запросов, а лишние запросы будут помещены в следующий пакет. ноHTTP/2.0Не подлежит этому ограничению, мультиплексирование заменяетHTTP/1.xизПоследовательность и механизмы блокировки. давайте обновимHTTP/2.0Приходите проверить это.
Сертификат должен быть сгенерирован локально. (Метод создания сертификата:nuggets.capable/post/684490…
server {
listen 443 ssl http2;
ssl on;
ssl_certificate /usr/local/openresty/nginx/conf/ssl/server.crt;
ssl_certificate_key /usr/local/openresty/nginx/conf/ssl/server.key;
ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers RC4:HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
server_name limit.qiufeng.com;
access_log /opt/logs/wwwlogs/limitqiufeng2.access.log;
error_log /opt/logs/wwwlogs/limitqiufeng2.error.log;
add_header Cache-Control max-age=60;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,range,If-Range';
if ($request_method = 'OPTIONS') {
return 204;
}
limit_rate 1M;
location / {
root 你存放项目的前缀路径/node-demo/file-download/;
index index.html;
}
}
10 потоков
将单个下载大小进行修改
const m = 1024 * 400;
12 потоков
24 потока
Конечно, чем больше потоков, тем лучше.После тестирования выяснилось, что когда количество потоков достигает определенного числа, скорость будет медленнее. Ниже показан рендеринг 36 одновременных запросов.
Практическое исследование приложений
Какая польза от такого количества загрузок процессов? Правильно, как я сказал в начале, этот механизм сегментирования является основным механизмом для загрузки программного обеспечения, такого как Thunder.
Облачный класс NetEase
Study.163.com/course/Соберитесь…
Мы открыли консоль и легко нашли URL-адрес загрузки, который представлял собой адрес загрузки mp4.
Запустите наш тестовый скрипт из консоли.
// 测试脚本,由于太长了,而且如果仔细看了上面的文章也应该能写出代码。实在写不出可以看以下代码。
https://github.com/hua1995116/node-demo/blob/master/file-download/example/download-multiple/script.js
Скачать
многопоточная загрузка
Видно, что поскольку NetEase Cloud Classroom не имеет ограничений на скорость загрузки одного TCP, скорость улучшения не столь очевидна.
Облако Байду
Давайте протестируем веб-версию Baidu Cloud.
Возьмем в качестве примера файл размером 16,6 МБ.
Откройте веб-страницу Baidu Cloud Disk, нажмите «Загрузить».
В это время нажмите паузу, откройтеchrome -> 更多 -> 下载内容 -> 右键复制下载链接
По-прежнему используйте указанный выше курс NetEase Cloud, чтобы загрузить сценарий курса. Вам просто нужно изменить параметры.
url 改成对应百度云下载链接
m 改成 1024 * 1024 * 2 合适的分片大小~
Скачать
Ограничение скорости одного TCP-соединения в Baidu Cloud действительно негуманно, и оно заняло целых 217 секунд! ! ! Просто файл 17M, мы обычно этим страдаем. (кроме VIP-игроков)
многопоточная загрузка
Поскольку это HTTP/1.1, нам нужно открыть только 6 или более потоков для загрузки. Ниже приведена скорость многопоточной загрузки, которая заняла около 46 секунд.
Давайте посмотрим на разницу в скорости через эту картинку.
Это действительно ароматно. Это бесплатно, и мы реализуем эту функцию только на нашем интерфейсе. Это слишком ароматно. Почему бы вам не попробовать? ?
Недостатки схемы
1. Есть определенные ограничения на верхний предел больших файлов
из-заblobВ основных браузерах есть ограничение на размер верхнего предела, поэтому этот метод все же имеет определенные недостатки.
| Browser | Constructs as | Filenames | Max Blob Size | Dependencies |
|---|---|---|---|---|
| Firefox 20+ | Blob | Yes | 800 MiB | None |
| Firefox < 20 | data: URI | No | n/a | Blob.js |
| Chrome | Blob | Yes | 2GB | None |
| Chrome for Android | Blob | Yes | RAM/5 | None |
| Edge | Blob | Yes | ? | None |
| IE 10+ | Blob | Yes | 600 MiB | None |
| Opera 15+ | Blob | Yes | 500 MiB | None |
| Opera < 15 | data: URI | No | n/a | Blob.js |
| Safari 6.1+* | Blob | No | ? | None |
| Safari < 6 | data: URI | No | n/a | Blob.js |
| Safari 10.1+ | Blob | Yes | n/a | None |
2. На сервере есть ограничение на одиночную скорость TCP
В обычных условиях будут ограничения, поэтому в настоящее время это зависит от ширины и скорости пользователя.
конец
Статья написана на скорую руку, и выражение может быть не совсем точным, если есть ошибки, прошу указать.
Оглядываясь назад и выясняя, есть ли подключаемый модуль для веб-версии Baidu Cloud для ускорения, если нет, создайте подключаемый модуль для веб-версии Baidu Cloud для загрузки~.
использованная литература
Управление пропускной способностью Nginx:blog.huoding.com/2015/03/20/…
openresty развертывает https и включает поддержку http2:вооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооо
Давайте поговорим о диапазоне HTTP:Лепешка 1022.GitHub.IO/2016/12/24/…
❤️ Всем спасибо
Если вы считаете этот контент полезным:
- Поставьте лайк и поддержите его, чтобы больше людей могли увидеть этот контент, ваши лайки — моя самая большая поддержка.
- Обратите внимание на публичный аккаунт
秋风的笔记, свяжитесь с автором и изучите интересные знания вместе. - Загружать и скачивать серии статей