Что быстрее — ASP.NET Core-приложение, развёрнутое в Docker-контейнере на Linux, или такая же программа, но запущенная на Windows-сервере, учитывая то, что всё это работает в службе приложений Azure? Какая из этих конфигураций предлагает более высокий уровень производительности, и о каком «уровне производительности» можно говорить?
Недавно меня заинтересовали эти вопросы, после чего я решил сам всё проверить, сделав следующее:
В обоих случаях приложение собрано с использованием инструмента командной строки
Приложение основано на шаблоне веб-проекта, созданного с помощью dotnet. Я специально старался как можно меньше его усложнять. В нём имеется один обработчик, возвращающий ответы двух типов:
Для развёртывания проекта на Windows-машине я быстро создал проект в VSTS (Visual Studio Team Services), настроил службы с использованием шаблонов ARM, собрал и развернул приложение с применением задач VSTS.
Настройка проекта
Работа с Windows-проектом в Microsoft Azure
В этом GitHub-репозитории вы можете найти код приложения и ARM-шаблоны.
Образ Docker был подготовлен и опубликован с использованием того же подхода, о котором я уже писал, рассказывая о запуске образов Docker в Azure. Так как я уже об этом рассказывал, повторяться тут я не буду. Единственное, на что я хочу обратить ваше внимание — это то, что в одной группе ресурсов Azure нельзя смешивать планы служб приложений, рассчитанные на Linux и Windows. Поэтому для каждого из вариантов приложения я создал собственную группу ресурсов.
Работа с Linux-проектом в Microsoft Azure
Код приложения и файлы для создания образов Docker можно найти в этом репозитории.
Мне нравится утилита Apache Benchmark (ab). Ей удобно пользоваться и она выдаёт результаты, применимые при практической оценке производительности систем, такие, как количество запросов в секунду (Requests Per Second, RPS), время ответа на разных перцентилях (например — сведения о том, что 95% запросов обрабатывается в пределах
Выходные данные ab выглядят примерно так:
И, наконец, я собрал результаты воедино с использованием Python-скрипта, после чего посчитал средние показатели по всем тестам и построил графики двух видов:
В ходе выполнения тестовых сценариев «Hello World» и «Ответ с телом в 1 Кб» на серверы было отправлено около 250 тысяч запросов. Их количество для сценариев, в которых применялись ответы с телом размером 10 Кб и 100 Кб, находилось в районе 110 — 70 тысяч запросов. Эти показатели не выражены в точных цифрах, так как иногда инфраструктура Azure закрывает соединения (connection reset by peer), но количество проанализированных запросов, всё равно, является достаточно большим. Не обращайте особого внимания на абсолютные значения, приводимые ниже: они имеют смысл лишь в плане сравнения друг с другом, так как они зависят и от клиента, и от сервера. Результаты, показанные в следующей таблице, получены с использованием клиента, подключённого к интернету по Wi-Fi-сети.
Эти результаты позволяют сделать вывод о том, что, если тело ответа невелико, приложение, работающее в Docker-контейнере на Linux, гораздо быстрее обрабатывает HTTP-запросы, чем его версия, работающая в среде Windows. Разница между разными вариантами запуска приложения уменьшается по мере роста тела ответа, хотя, всё равно, Linux-вариант оказывается быстрее Windows-варианта. Такой результат может показаться удивительным, так как Windows-сервер, работающий в службе приложений Azure, представляет собой более зрелое решение, нежели Linux-сервер, работающий там же. С другой стороны, виртуализация Docker, в сравнении с другими технологиями виртуализации приложений, не отличается особой требовательностью к системным ресурсам. Возможно, имеются и другие отличия в конфигурациях, позволяющие Linux-хосту работать эффективнее, что особенно заметно при работе с ответами, тела которых невелики.
Вот диаграммы, иллюстрирующие вышеприведённые данные.
Запросов в секунду, средний показатель (больше — лучше)
Время, в течение которого обрабатываются 95% запросов (меньше — лучше)
Время, в течение которого обрабатываются 95% запросов (меньше — лучше)
Вышеописанные испытания производительности были повторены с использованием инструментов Visual Studio Ultimate Web Performance. Клиентская система, в географическом смысле, располагалась там же, где и раньше, но в этот раз она была подключена к интернету по проводной сети. Вот исходный код тестов. В данном случае использовались следующие настройки:
Тестирование серверов с помощью Visual Studio Web Performance (оригинал)
Результаты тестов, проведённых с помощью Visual Studio Web Performance, отличаются от тех, что были проведены с помощью Apache Benchmark, но при этом Linux-система показала себя даже лучше, чем прежде. Различия в результатах испытаний можно объяснить следующими фактами:
Но, всё равно, допустимо напрямую сравнивать результаты испытаний только тогда, когда они выполнены с использованием одного и того же клиента, с применением одних и тех же инструментов и настроек.
Вот таблица со сравнением Linux- и Windows-вариантов приложения.
Вот визуализация этих данных.
Запросов в секунду, средний показатель (больше — лучше)
Время, в течение которого обрабатываются 95% запросов (меньше — лучше)
Я могу провести сравнение Linux+Docker-варианта исследуемого приложения с приложениями, при создании которых использовались другие технологии, испытанные мной за последние дни, лишь используя сценарий «Hello, World». Но при этом не могу не отметить, что даже при таком походе производительность Windows-варианта выглядит слишком низкой. Думаю, что стандартная конфигурация Kestrel (например — количество потоков) не оптимизирована для планов Standard S1. Например — вот средние значения RPS, полученные для других стеков технологий, испытания которых проводились с использованием тех же настроек.
Эти результаты не снижают ценности ранее выполненных испытаний, в ходе которых одно и то же ASP.NET Core-приложение исследовалось в средах Windows и Linux.
Развёртывание проектов в службе приложений Azure с использованием Linux и Docker не ухудшает производительности приложений, что идёт вразрез с тем, чего многие могли бы ожидать, учитывая то, что Windows-хостинг этой платформы представляет собой более зрелое решение. Применение Linux в Azure, на самом деле, выгоднее применения Windows, особенно — в тех случаях, когда речь идёт об обработке запросов, предусматривающих отправку ответов, тела которых имеют небольшие размеры.
Чем вы пользуетесь на серверах — Linux или Windows?
Недавно меня заинтересовали эти вопросы, после чего я решил сам всё проверить, сделав следующее:
- Подготовил простое ASP.NET Core-приложение, используя NET Core 2.0 и C#.
- Развернул один экземпляр этого приложения в службе Standard S1 на Windows-хосте, расположенном в регионе Western Europe.
- Развернул ещё один экземпляр этого приложения в службе Standard S1 с использованием Linux-хоста (регион Western Europe) и Docker-контейнера.
- Для создания нагрузки на серверы использовал Apache Benchmark, запросы отправлялись из Варшавы (Польша) с клиентской системы, работающей под управлением Ubuntu 17.04 и подключённой к интернету по Wi-Fi.
- Повторил испытания, воспользовавшись средством Visual Studio Web Performance Test. Запросы к исследуемым серверам выполнялись из того же города, но в данном случае роль клиента выполнял компьютер, на котором установлена Windows 10, подключённый к интернету через проводную сеть.
- Проанализировал данные, полученные в ходе тестирования, и сравнил результаты.
В обоих случаях приложение собрано с использованием инструмента командной строки
dotnet
в конфигурации Release
. Хостилось приложение с использованием кросс-платформенного сервера Kestrel и было размещено за прокси-сервером, используемым службами Azure. В распоряжении каждого экземпляра приложения были ресурсы, обеспечиваемые планом обслуживания Standard S1, то есть — отдельная виртуальная машина. В ходе выполнения тестов я, кроме того, сравнил производительность ASP.NET Core-приложения, работающего в Docker-контейнере, с производительностью приложений, основанных на других стеках технологий, которые мне доводилось исследовать в последнее время (Go, Python 3.6.2, PyPy 3). В сервере Kestrel используется libuv — поэтому мне интересно было сравнить его с uvloop, учитывая то, что последний является обёрткой для libuv и asyncio (это — встроенный инструмент Python для создания конкурентного кода). Я думаю, что многие NET-разработчики гордятся скоростью Kestrel и работой, проведённой инженерами Microsoft, но при этом забывают о том, что им стоило бы испытывать чувство благодарности и к libuv — С-библиотеке для организации асинхронного ввода-вывода, которая появилась до Kestrel и используется, кроме того, в Node.js и в других подобных проектах. И, кроме того, стоит помнить о том, что, когда перед ASP.NET Core-приложениями находится IIS, нужно принимать во внимание некоторые дополнительные соображения.Подготовка веб-приложения
Приложение основано на шаблоне веб-проекта, созданного с помощью dotnet. Я специально старался как можно меньше его усложнять. В нём имеется один обработчик, возвращающий ответы двух типов:
- При получении запроса без параметров — сообщение
Hello World
с отметкой времени. - При получении запроса со строковым параметром
n
, представляющим собой число, находящееся в диапазоне от 1 до 100 — ответ с телом размеромn
Кб.
app.Run(async (context) =>
{
var request = context.Request;
var s = request.Query["s"];
if (string.IsNullOrEmpty(s)) {
// возврат простого Hello World
var now = DateTime.UtcNow;
await context.Response.WriteAsync($"Hello World, from ASP.NET Core and Net Core 2.0! {now.ToString("yyyy-MM-dd HH:mm:ss.FFF")}");
return;
}
// {...}
});
Развёртывание приложения на Windows-машине
Для развёртывания проекта на Windows-машине я быстро создал проект в VSTS (Visual Studio Team Services), настроил службы с использованием шаблонов ARM, собрал и развернул приложение с применением задач VSTS.
Настройка проекта
Работа с Windows-проектом в Microsoft Azure
В этом GitHub-репозитории вы можете найти код приложения и ARM-шаблоны.
Развёртывание приложения на Linux-машине с использованием Docker-контейнеров
Образ Docker был подготовлен и опубликован с использованием того же подхода, о котором я уже писал, рассказывая о запуске образов Docker в Azure. Так как я уже об этом рассказывал, повторяться тут я не буду. Единственное, на что я хочу обратить ваше внимание — это то, что в одной группе ресурсов Azure нельзя смешивать планы служб приложений, рассчитанные на Linux и Windows. Поэтому для каждого из вариантов приложения я создал собственную группу ресурсов.
Работа с Linux-проектом в Microsoft Azure
Код приложения и файлы для создания образов Docker можно найти в этом репозитории.
Тестирование приложений с использованием Apache Benchmark
Мне нравится утилита Apache Benchmark (ab). Ей удобно пользоваться и она выдаёт результаты, применимые при практической оценке производительности систем, такие, как количество запросов в секунду (Requests Per Second, RPS), время ответа на разных перцентилях (например — сведения о том, что 95% запросов обрабатывается в пределах
n
мс, а 5% — в пределах m
мс, и так далее). Эта утилита идеально подходит для исследования производительности отдельных методов. Я выполнил несколько групп тестов, каждую — в разное время дня. План испытаний выглядел так:Сценарий | Конфигурация |
Hello World | 5000 запросов, 150 одновременных пользователей |
Ответ с телом в 1 Кб | 5000 запросов, 150 одновременных пользователей |
Ответ с телом в 10 Кб | 2000 запросов, 150 одновременных пользователей |
Ответ с телом в 100 Кб | 2000 запросов, 150 одновременных пользователей |
# пример команды
ab -n 5000 -c 150 -l http://linuxaspcorehelloworld-dev-westeurope-webapp.azurewebsites.net/
Выходные данные ab выглядят примерно так:
This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking linuxaspcorehelloworld-dev-westeurope-webapp.azurewebsites.net (be patient)
Server Software: Kestrel
Server Hostname: linuxaspcorehelloworld-dev-westeurope-webapp.azurewebsites.net
Server Port: 80
Document Path: /
Document Length: 72 bytes
Concurrency Level: 150
Time taken for tests: 22.369 seconds
Complete requests: 5000
Failed requests: 0
Keep-Alive requests: 0
Total transferred: 1699416 bytes
HTML transferred: 359416 bytes
Requests per second: 223.53 [#/sec] (mean)
Time per request: 671.065 [ms] (mean)
Time per request: 4.474 [ms] (mean, across all concurrent requests)
Transfer rate: 74.19 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 40 489 289.9 425 3813
Processing: 42 170 227.6 109 3303
Waiting: 41 153 152.9 105 2035
Total: 106 659 359.2 553 4175
Percentage of the requests served within a certain time (ms)
50% 553
66% 650
75% 750
80% 786
90% 948
95% 1036
98% 1282
99% 1918
100% 4175 (longest request)
И, наконец, я собрал результаты воедино с использованием Python-скрипта, после чего посчитал средние показатели по всем тестам и построил графики двух видов:
- Количество запросов в секунду (Requests per seconds, RPS), обработанных сервером.
- 95-й перцентиль времени ответа, другими словами — показатель, отвечающий на вопрос о том, в пределах какого количества миллисекунд будет дан ответ на 95% запросов.
Анализ результатов, полученных с помощью Apache Benchmark
В ходе выполнения тестовых сценариев «Hello World» и «Ответ с телом в 1 Кб» на серверы было отправлено около 250 тысяч запросов. Их количество для сценариев, в которых применялись ответы с телом размером 10 Кб и 100 Кб, находилось в районе 110 — 70 тысяч запросов. Эти показатели не выражены в точных цифрах, так как иногда инфраструктура Azure закрывает соединения (connection reset by peer), но количество проанализированных запросов, всё равно, является достаточно большим. Не обращайте особого внимания на абсолютные значения, приводимые ниже: они имеют смысл лишь в плане сравнения друг с другом, так как они зависят и от клиента, и от сервера. Результаты, показанные в следующей таблице, получены с использованием клиента, подключённого к интернету по Wi-Fi-сети.
Хост | Сценарий | RPS (среднее значение) | 95% запросов выполнено в пределах … мс |
Linux | Hello World | 232,61 | 1103,64 |
Linux | Ответ с телом в 1 Кб | 228,79 | 1129,93 |
Linux | Ответ с телом в 10 Кб | 117,92 | 1871,29 |
Linux | Ответ с телом в 100 Кб | 17,84 | 14321,78 |
Windows | Hello World | 174,62 | 1356,8 |
Windows | Ответ с телом в 1 Кб | 171,59 | 1367,23 |
Windows | Ответ с телом в 10 Кб | 108,08 | 2506,95 |
Windows | Ответ с телом в 100 Кб | 17,37 | 16440,27 |
Эти результаты позволяют сделать вывод о том, что, если тело ответа невелико, приложение, работающее в Docker-контейнере на Linux, гораздо быстрее обрабатывает HTTP-запросы, чем его версия, работающая в среде Windows. Разница между разными вариантами запуска приложения уменьшается по мере роста тела ответа, хотя, всё равно, Linux-вариант оказывается быстрее Windows-варианта. Такой результат может показаться удивительным, так как Windows-сервер, работающий в службе приложений Azure, представляет собой более зрелое решение, нежели Linux-сервер, работающий там же. С другой стороны, виртуализация Docker, в сравнении с другими технологиями виртуализации приложений, не отличается особой требовательностью к системным ресурсам. Возможно, имеются и другие отличия в конфигурациях, позволяющие Linux-хосту работать эффективнее, что особенно заметно при работе с ответами, тела которых невелики.
Сценарий | Linux, прирост RPS в сравнении с Windows |
Hello World | +33,21% |
Ответ с телом в 1 Кб | +33,33% |
Ответ с телом в 10 Кб | +9,1% |
Ответ с телом в 100 Кб | +2,7% |
Вот диаграммы, иллюстрирующие вышеприведённые данные.
Запросов в секунду, средний показатель (больше — лучше)
Время, в течение которого обрабатываются 95% запросов (меньше — лучше)
Время, в течение которого обрабатываются 95% запросов (меньше — лучше)
Тестирование приложений с использованием инструментов Visual Studio Web Performance
Вышеописанные испытания производительности были повторены с использованием инструментов Visual Studio Ultimate Web Performance. Клиентская система, в географическом смысле, располагалась там же, где и раньше, но в этот раз она была подключена к интернету по проводной сети. Вот исходный код тестов. В данном случае использовались следующие настройки:
- Тесты выполнялись по 5 минут.
- В начале количество пользователей равнялось 50.
- Каждые 10 секунд количество пользователей увеличивалось на 10.
- Максимальным количеством пользователей было 150.
Тестирование серверов с помощью Visual Studio Web Performance (оригинал)
Анализ результатов, полученных с помощью Visual Studio Web Performance
Результаты тестов, проведённых с помощью Visual Studio Web Performance, отличаются от тех, что были проведены с помощью Apache Benchmark, но при этом Linux-система показала себя даже лучше, чем прежде. Различия в результатах испытаний можно объяснить следующими фактами:
- Инструменты VS Web Performance, вероятно, по-другому работают с соединениями.
- Для выполнения тестов использовались разные клиенты, по-разному подключённые к интернету.
Но, всё равно, допустимо напрямую сравнивать результаты испытаний только тогда, когда они выполнены с использованием одного и того же клиента, с применением одних и тех же инструментов и настроек.
Хост | Сценарий | RPS (среднее значение) | 95% запросов выполнено в пределах … мс |
Linux | Hello World | 649 | 340 |
Linux | Ответ с телом в 1 Кб | 622 | 380 |
Linux | Ответ с телом в 10 Кб | 568 | 370 |
Linux | Ответ с телом в 100 Кб | 145 | 1220 |
Windows | Hello World | 391 | 400 |
Windows | Ответ с телом в 1 Кб | 387 | 420 |
Windows | Ответ с телом в 10 Кб | 333 | 510 |
Windows | Ответ с телом в 100 Кб | 108 | 1560 |
Вот таблица со сравнением Linux- и Windows-вариантов приложения.
Сценарий | Linux, прирост RPS в сравнении с Windows |
Hello World | +65,98% |
Ответ с телом в 1 Кб | +60,72% |
Ответ с телом в 10 Кб | +70,57% |
Ответ с телом в 100 Кб | +34,26% |
Вот визуализация этих данных.
Запросов в секунду, средний показатель (больше — лучше)
Время, в течение которого обрабатываются 95% запросов (меньше — лучше)
Сравнение с другими стеками технологий в Docker
Я могу провести сравнение Linux+Docker-варианта исследуемого приложения с приложениями, при создании которых использовались другие технологии, испытанные мной за последние дни, лишь используя сценарий «Hello, World». Но при этом не могу не отметить, что даже при таком походе производительность Windows-варианта выглядит слишком низкой. Думаю, что стандартная конфигурация Kestrel (например — количество потоков) не оптимизирована для планов Standard S1. Например — вот средние значения RPS, полученные для других стеков технологий, испытания которых проводились с использованием тех же настроек.
Стек технологий | Сценарий Hello World, RPS |
Go 1.9.1 net/http | ~1000, с пиками в ~1100 |
Python 3.6.1 uvloop, httptools | ~1000, с пиками в ~1200 |
Python 3.6.1 Sanic, uvloop | ~600, с пиками в ~650 |
PyPy 3, Gunicorn, Gevent, Flask | ~600, с пиками в ~650 |
Эти результаты не снижают ценности ранее выполненных испытаний, в ходе которых одно и то же ASP.NET Core-приложение исследовалось в средах Windows и Linux.
Итоги
Развёртывание проектов в службе приложений Azure с использованием Linux и Docker не ухудшает производительности приложений, что идёт вразрез с тем, чего многие могли бы ожидать, учитывая то, что Windows-хостинг этой платформы представляет собой более зрелое решение. Применение Linux в Azure, на самом деле, выгоднее применения Windows, особенно — в тех случаях, когда речь идёт об обработке запросов, предусматривающих отправку ответов, тела которых имеют небольшие размеры.
Чем вы пользуетесь на серверах — Linux или Windows?