Рубрики
Без рубрики

Разработка обратного прокси-сервера: Почему Golang работает лучше, чем Java (Spring Boot), Углубленный анализ

Обратный прокси-сервер В микросервисе Обратный прокси-сервер – популярный термин. Давайте опишем Обратное… Помеченный как proxy, go, java, spring boot.

В микросервисах популярным термином является обратный прокси-сервер. Давайте опишем обратный прокси со следующей картинкой:

Допустим, у вас есть несколько сервисов, которые могут иметь разные типы функциональности. Чтобы использовать эти функции, ваш веб-сайт или мобильное приложение должны знать путь к домену (конечные точки API) этих служб. Но вместо того, чтобы возлагать на клиента эту ответственность, вы можете абстрагировать эту логику.

Таким образом, у вас может быть отдельная служба, и ваш клиент может подключаться к этому серверу. Теперь, основываясь на вашем запросе, этот сервер перенаправит запросы на соответствующие службы. Поэтому эта отдельная служба называется Обратным прокси-сервером.

Допустим, у вас есть три отдельных сервиса, и они:

  • Допустим, у вас есть три отдельных сервиса, и они:
  • Допустим, у вас есть три отдельных сервиса, и они:
  • Допустим, у вас есть три отдельных сервиса, и они:

И ваш клиент использует эти три функции. Итак, вы можете создать обратный прокси-сервер. Ваш клиент может подключиться к этому обратному прокси-серверу, скажем, mycool-reverse-proxy.com .

Теперь, скажем, ваш клиент хочет получить список всех крутых продуктов. Итак, он запрашивает прокси-сервер Итак, он запрашивает прокси-сервер

Весь смысл обратного прокси-сервера заключается в том, чтобы абстрагировать вашу внутреннюю логику. Таким образом, клиенту не нужно знать, сколько у вас услуг, их адреса или где они находятся.

Круто, не правда ли? Да!

Теперь еще раз посмотрите на приведенное выше изображение. Что здесь делает обратный прокси-сервер? Он находится между вашими клиентами и вашими службами и маршрутизирует запросы.

Вы можете подумать, что обратный прокси-сервер – это балансировщик нагрузки. Но это не так. У них совершенно разные виды обязанностей, и они означают разные вещи.

А теперь давайте быстро посчитаем!

Скажем, клиенты будут вызывать Службу A 10 раз, Службу B 20 раз, Службу C 5 раз. Но поскольку обратный прокси-сервер находится между клиентами и службами, все запросы будут проходить через него, не так ли?

Да, конечно. Все 35 запросов будут проходить через него. Теперь, когда ваш бизнес растет, вы получаете больше нагрузки. Таким образом, вы масштабируете свои услуги. Но, как мы видели, обратный прокси-сервер испытывает общую нагрузку на все сервисы. Итак, вы будете выполнять горизонтальное/вертикальное масштабирование. Вы добавляете больше машин.

Итак, при разработке обратного прокси-сервера вы будете выбирать язык, скажем, Java, Node, Golang и т.д. Оказывает ли этот язык какое-либо влияние на обратный прокси-сервер или существуют какие-либо различия в производительности между этими языками в обратном прокси-сервере? Давайте выясним.

В этой статье я сосредоточусь только на анализе производительности обратного прокси-сервера в Spring Boot (который представляет собой фреймворк на основе Java) против Golang.

Поток – это место, где информация выполняется в последовательном порядке. Потоки выполняются в процессоре. Если у вас есть одно ядро и у вас много потоков, эти потоки будут выполняться один за другим. Скажем, у вас есть 3 потока A, B, C. Затем в одноядерном процессоре будет выполняться A, затем B, затем C. Но они не будут выполняться параллельно. Но при выполнении потоков они не будут выполняться последовательно. Таким образом, не произойдет, что A будет выполнено полностью, тогда B будет выполнено полностью, тогда C будет выполнено полностью.

Что произойдет, некоторые инструкции A будут выполнены, затем ОС приостановит выполнение потока A и начнет выполнение потока B. После выполнения некоторых инструкций B ОС может возобновить выполнение потока A или начать выполнение потока C. Какой поток будет выполняться, определяется планировщиком, управляемым операционной системой.

Это называется переключением контекста. Но пока поток приостановлен и начинает выполняться другой поток, как мы можем вернуться к первому потоку и начать выполнение с того места, где мы остановились?

Что ж, вот и две вещи, которые сделали это возможным.

  • Указатель инструкции, который знает, какая строка была выполнена последней
  • Стек, содержащий локальные переменные, а также указатели на переменные, выделенные в куче.

С помощью этой информации планировщик может легко приостановить поток и запустить другие потоки и начать выполнение потока с того места, где он был приостановлен.

Java – это язык, основанный на JVM. И когда вы создаете поток в Java, JVM сопоставляет этот поток с потоком уровня операционной системы. Ваш поток Java и поток операционной системы в основном представляют собой сопоставление 1: 1. Поток уровня операционной системы имеет фиксированный размер стека. Она не может расти бесконечно. На 64- разрядной машине |/размер составляет 1 МБ . Таким образом, если ваша память составляет 1 ГБ, вы можете иметь приблизительно 1000 нити. И поскольку JVM сопоставляет поток с потоком уровня операционной системы в сопоставлении 1: 1, вы можете создать приблизительно 1000 потоки с языком на основе JVM говорят на Java. Таким образом, поток Java является тяжелым , имеет фиксированный размер стека, и у вас может быть около 1 КБ потока в 1 ГБ памяти.

Подпрограмма – это облегченный поток, управляемый средой выполнения Go. Это немного отличается от потока Java, который мы видели ранее. Планировщик Goroutine планирует выполнение goroutine. Но забавный факт заключается в том, что маршруты не сопоставляются 1: 1 с потоком на уровне операционной системы. Несколько подпрограмм отображаются в один поток операционной системы. Таким образом, он мультиплексируется в поток операционной системы. И еще один интересный факт о программе Goroutine заключается в том, что Go не имеет стека фиксированного размера. Скорее всего, он может расти или уменьшаться в зависимости от полученных данных. Итак, Goroutine использует эту функцию. В среднем размер стека новой подпрограммы по умолчанию составляет около 2 КБ и он может расти или уменьшаться по мере необходимости. Таким образом, в 1 ГБ памяти вы можете иметь 1024 * 1024/,24,288 это множество чисел, если они совпадают.

Как описано выше, JVM использует потоки уровня операционной системы. Поэтому, когда потоки выполняют расписание планировщика на уровне операционной системы, выполняйте поток. Таким образом, переключение контекста происходит на уровне операционной системы. Когда ядро выполняет переключение контекста, оно должно выполнить большой объем работы. В результате для переключения контекста требуется некоторое время (уровень микросекунд).

С другой стороны, Golang имеет свой собственный планировщик Goroutine, специально и оптимально созданный для этой задачи. Планировщик Goroutine сопоставляет Goroutine с потоком и процессом уровня операционной системы в ядре. И это оптимально для того, чтобы переключение контекста занимало меньше времени.

Итак, после обсуждения всех теоретических вопросов мы подходим к этому пункту. Поскольку Spring boot выполняется на сервере Tomcat, каждый запрос обрабатывается отдельным потоком. Итак, когда поступает запрос, для обработки этого запроса выделяется поток. И поскольку он использует JVM внизу, мы увидели, что эти потоки являются потоками уровня операционной системы.

С другой стороны, если вы используете Goroutine для обслуживания каждого запроса, т.Е. Когда приходит запрос, вы выделяете Goroutine для обработки запроса, он будет работать лучше, чем Spring boot. Потому что мы уже видели, что Goroutine имеет определенные преимущества перед потоком в предыдущем абзаце.

И мы также видели, что в 1 ГБ оперативной памяти будет больше подпрограмм, чем потоков (5,24,288 против 1000). Таким образом, вы можете обрабатывать больше запросов с помощью сервиса Golang.

Поскольку обратный прокси-сервер обычно будет испытывать все нагрузки вашей системы, поэтому там всегда будет большое количество запросов. И если вы справитесь с этим с помощью облегченной горутины, вы сможете использовать все преимущества Горутин, чтобы получить высокую пропускную способность и лучшую производительность и одновременно обслуживать больше запросов.

С большой властью приходит большая ответственность.

Хотя в Goroutines есть много положительных сторон, есть и некоторые минусы. В Spring boot будет фиксированный объем пула потоков. Итак, когда приходит запрос, поток берется из пула, а когда работа завершена, поток снова сохраняется в пуле. Это обрабатывается сервером Tomcat.

Принимая во внимание, что в мире Golang это не обрабатывается автоматически. Итак, либо вы разработали транспортный уровень с помощью знаменитого пакета Golang net/http , либо используете какой-нибудь фреймворк типа Gin-gonic по умолчанию пула подпрограмм нет. Итак, вам придется справиться с этим вручную.

Но вы, возможно, задаетесь вопросом, зачем мне нужен бассейн? Вот почему?

Когда ваш код будет развернут, всегда будет присутствовать ОС, будь то на сервере или в блоках Kubernetes. И в операционной системе есть термин, называемый ulimit . ulimit – это дескриптор файла и индикатор доступа к ресурсу ввода-вывода. Когда мы делаем сетевой запрос из вашего кода во внешний мир, открывается TCP-соединение, а затем, после установления связи, выполняется запрос. И ulimit обозначает, сколько файловых дескрипторов может быть в операционной системе, которая отвечает за создание TCP-соединений. Чем больше улимит у вас есть больше TCP-соединений, которые вы можете создать.

ОС Linux имеет значение ulimit около 2^16 536 . В системе Mac значение по умолчанию равно 252 . Но вы всегда можете увеличить его ulimit -n number_of_ulimit_you_want .

И это одна из точек отказа в Goroutine.

Что происходит при обратном прокси-сервере? Мы видели, что запрос поступает на прокси-сервер, а затем на основе запроса обратный прокси перенаправляет запрос на любую нижестоящую службу. И для этого обратный прокси-сервер выполняет исходящий запрос от самого себя. А для выполнения исходящего запроса требуется TCP-соединение, которое в основном представляет собой сетевой ввод-вывод. И файловые дескрипторы обрабатывают это. И ulimit обозначает, сколько файловых дескрипторов может иметь ОС.

Вы можете запустить почти 5 24 288 подпрограмм в 1 ГБ оперативной памяти. Теперь в обратном прокси, если вы реализуете его с помощью Golang и у вас нет пула подпрограмм, произойдет следующее: вы можете получать огромное количество запросов, и ваш сервер не будет зависать. Но поскольку обратный прокси-сервер перенаправит запрос на все другие нижестоящие серверы, он будет выполнять все исходящие запросы. В результате откроется так много TCP-соединений, и все они являются сетевыми Ввод-вывод. Таким образом, файловые дескрипторы будут обрабатывать их все. Поэтому, если количество исходящих запросов к вашей другой службе превышает количество ulimit , которое у вас есть, вы получите ошибку слишком много открытых файлов .

Итак, вот почему у вас должен быть пул подпрограмм, основанный на ulimit у вас есть, чтобы вы не впали в вышеуказанную ошибку.

Так… Я провел эксперимент, создав сервис продукта, написанный на Spring boot. Затем я разработал два обратных прокси-сервера. Один написан на Spring boot, а другой написан на Golang (я использовал Gin-gonic для маршрутизатора). Затем я использовал JMeter для нагрузочного тестирования всей системы. Вот и результат.

Период нарастания 100 секунд 10000 2 — 0 208 — 150 1 — 0 966.7 — 902.0 95.14 — 88.99 4.14 — 2.70 100.77801 — 101.02337
Период нарастания 100 секунд 20000 3 — 0 342 — 125 1 — 0 966.7 — 902.0 94.75 — 88.50 7.82 — 2.79 100.36483 — 100.47373
Период нарастания 100 секунд 30000 3 — 0 275 — 119 1 — 0 966.7 — 902.0 283.20 — 265.22 5.85 — 2.34 299.98200 — 301.09599
Период нарастания 200 секунд 30000 5 — 0 495 — 125 1 — 0 966.7 — 902.0 141.68 — 88.50 14.20 — 3.19 150.07654 — 150.38348
Период нарастания 300 секунд 30000 2 — 0 245 — 121 0 — 0 966.7 — 902.0 94.66 — 88.37 5.00 — 2.89 100.26671 — 100.31834
Период нарастания 100 секунд 80000 4 — 0 497 — 168 1 — 0 966.7 — 902.0 188.8 — 176.35 11.63 — 3.49 199.98850 — 200.20772

Определение этой матрицы выглядит следующим образом: Количество запросов – это количество образцов с одной и той же меткой. Среднее значение – это среднее время набора результатов. Min – это кратчайшее время для образцов с одинаковой меткой. Max – самое продолжительное время для образцов с одной и той же меткой Пропускная способность измеряется в запросах в секунду/минуту/час. Единица измерения времени выбирается таким образом, чтобы отображаемая скорость составляла не менее 1,0. Когда пропускная способность сохраняется в CSV-файл, она выражается в запросах в секунду, т.е. 30,0 запросов в минуту сохраняется как 0,5. Принятый КБ/сек – это пропускная способность, измеряемая в килобайтах в секунду. Время измеряется в миллисекундах. Стандартное отклонение – это мера изменчивости набора данных. JMeter вычисляет стандартное отклонение популяции (аналог функции STDEVP) Средние байты – среднее арифметическое байтов ответа для выборок с одинаковой меткой.

Как вы можете видеть из приведенной выше матрицы, Golang работает лучше, чем Spring Boot. Стандартное отклонение означает, что система Spring Boot сильно отклоняется.

Давайте посмотрим на CPU и memory использование прокси-сервера Golang и прокси-сервера Spring Boot.

На моей машине hexacore я запустил службу продукта, написанную в Spring boot. Затем сначала я запустил прокси-сервер spring boot, а затем взял матрицу. После этого я запустил Go proxy и взял матрицу. Они заключаются в следующем:

7.2 Прокси-сервер весенней загрузки 124.2 46
0.1 Перейти к Прокси 7.8 26

Так… Golang действительно использовал меньше процессора и памяти по сравнению с прокси-сервером Spring boot. И, основываясь на предыдущем теоретическом обсуждении, это ожидаемо.

Итак, можно сказать, что когда прокси-сервер будет развернут на сервере, с точки зрения ресурсов, таких как процессор и память, Golang работает лучше, чем Spring Boot. Таким образом, в случае масштабирования, такого как горизонтальное масштабирование, потребуется больше прокси-сервера Spring boot для обслуживания огромного объема нагрузки. Принимая во внимание, что для этого потребуется меньше прокси-сервера Go, потому что группа автоматического масштабирования обычно отслеживает использование ЦП и памяти экземпляров, когда дело доходит до масштабирования.

Итак, на сегодня все. Надеюсь, вам это понравилось.

Увидимся в другой статье. А до тех пор наслаждайся жизнью.

Ссылка

  1. Ссылка
  2. Ссылка

3. Ссылка

Оригинал: “https://dev.to/samsadsajid/designing-a-reverse-proxy-why-golang-performs-better-than-java-spring-boot-an-in-depth-analysis-18oe”