Proxygen — HTTP-фреймворк для С++ от Facebook

в 9:09, , рубрики: c++, http, Блог компании Инфопульс Украина, Сетевые технологии

Proxygen — HTTP-фреймворк для С++ от Facebook - 1Proxygen — это коллекция библиотек для использования протокола HTTP на С++, включающая в числе прочего очень простой в использовании HTTP-сервер. Кроме классического HTTP/1.1 фреймворк Proxygen поддерживает SPDY/3 и SPDY/3.1. Вскоре также будет полностью поддерживаться HTTP/2.

Proxygen не задумывался как замена Apache или nginx — эти проекты сфокусированы на создании достаточно гибких и конфигурируемых веб-серверов, позволяющих благодаря тонкой настройке добиться максимальной производительности. Задачей Proxygen является работать достаточно хорошо на дефолтных настройках, давая программисту простые в использовании веб-сервер и веб-клиент, легко интегрирующиеся в уже существующие проекты. Мы хотим помочь людям строить веб-сервисы на С++ с меньшими затратами и мы верим, что Proxygen — отличный фреймворк для этого. Вы можете почитать документацию по нему и подключиться к разработке на Github.

Предыстория

Proxygen начинался как проект по созданию настраиваемого высокопроизводительного обратного прокси с функцией балансировки нагрузки около четырёх лет назад. Мы планировали, что Proxygen станет библиотекой для генерации прокси-серверов (вы небось уже догадались об этом из самого названия Proxygen). Но с тех пор он серьёзно эволюционировал. Мы осознаём, что уже существует приличное количество программ, решающих подобные задачи (Apache, nginx, HAProxy, Varnish, etc), тем ни менее, мы решили пойти своим путём.

Почему мы создали свой собственный HTTP-стек?

Интеграция

Возможность быстро и легко интегрироватьcя в существующую инфраструктуру Facebook была критически важной. Например, возможность администрировать нашу HTTP-инфраструктуру с такими инструментами как Thrift упрощает интеграцию с существующими системами. Возможность легко отслеживать и измерять производительность Proxygen с помощью таких систем как ODS (наше внутреннее средство мониторинга) даёт возможность быстро реагировать на новые данные и модифицировать продукт. Создание собстенного HTTP-стека дало нам возможность более тесно взаимодействовать с нужными нам системами и компонентами.

Переиспользоавние кода

Мы хотели создать фундамент для построения сетевых компонентов для всех наших проектов. В данный момент более дюжины наших внутренних систем построены с использованием Proxygen, включая части таких систем как Haystack, HHVM, наши балансировщики HTTP-трафика, кое-какие части нашей мобильной инфраструктуры. Proxygen представляет собой платформу, где мы можем, например, работать над поддержкой протокола HTTP/2 и как только он будет полностью готов — получить его поддержку во всех наших продуктах.

Масштабируемость

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

Функционал

Некоторое количество фич на момент начала написания Proxygen отсутствовало в других аналогичных проектах (а в некоторых отсутствует и сейчас). Часть из этих возможностей для нас очень полезны: SPDY, WebSockets, HTTP/1.1 (keep-alive), TLS-фальстарт, кое-какие особенности распределения нагрузки. Построение собственного HTTP-стека развязало нам руки в плане реализации этого функционала.

Изначально запущенный в 2011 году несколькими нашими инженерами, стремившимися сделать использование HTTP-протокола более эффективным, Proxygen разрабатывался командой из 3-4 основных разработчиков и дюжиной внутренних контрибьютеров. Основные вехи проекта:

  • 2011 — Начало разработки Proxygen. В том же году проект начал обрабатывать часть реального траффика Facebook.
  • 2012 — Добавление поддержки SPDY/2.
  • 2013 — Выход в продакшн SPDY/3, начало работы над SPDY/3.1
  • 2014 — Выход в продакшн SPDY/3.1, начало работы над HTTP/2

Есть ещё некоторое количество важных моментов в разработке, но мы думаем, что код расскажет эту историю лучше нас.

В данный момент у нас есть уже несколько лет опыта использования Proxygen. Библиотека обработала уже триллионы HTTP(S) и SPDY-запросов. Мы считаем, что уже достигли того этапа, когда этим проектом не стыдно поделиться с сообществом.

Архитектура

Ядро HTTP-слоя разделено на четыре абстракции: сессия, кодек, транзакция и обработчик. Для каждого соединения создаётся сессия. Каждая сессия имеет кодек, отвечающий за сериализацию и десериализацию фреймов на HTTP-сообщения. Сессия отвечает за направление каждого сообщения от кодека в определённую транзакцию. Пользователь библиотеки отвечает за написание обработчиков приходящих в транзакцию сообщений. Этот дизайн позволяет нам поддерживать новые мультиплексируемые протоколы, такие как SPDY и HTTP/2.

image

Proxygen активно использует возможности последнего стандарта С++ и зависит от Thrift и Folly. Мы использовали семантику перемещения для избежания затрат на копирование больших объектов вроде буферов тела и заголовков запросов и ответов. Кроме того, используя под капотом неблокируемый ввод-вывод и линуксовый epoll мы создали сервер эффективный как по использованию памяти, так и по затратам процессорного времени.

HTTP-сервер

Пример сервера, который мы включили в релиз — хорошая стартовая точка, если вы хотите начать с простого, работающиего из коробки, асинхронного костяка сервера.

EchoServer.cpp

/*
 *  Copyright (c) 2014, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */
#include "EchoHandler.h"
#include "EchoStats.h"
#include "proxygen/httpserver/HTTPServer.h"
#include "proxygen/httpserver/RequestHandlerFactory.h"

#include <folly/Portability.h>
#include <folly/Memory.h>
#include <folly/io/async/EventBaseManager.h>
#include <unistd.h>

using namespace EchoService;
using namespace proxygen;

using folly::EventBase;
using folly::EventBaseManager;
using folly::SocketAddress;

using Protocol = HTTPServer::Protocol;

DEFINE_int32(http_port, 11000, "Port to listen on with HTTP protocol");
DEFINE_int32(spdy_port, 11001, "Port to listen on with SPDY protocol");
DEFINE_int32(thrift_port, 10000, "Port to listen on for thrift");
DEFINE_string(ip, "localhost", "IP/Hostname to bind to");
DEFINE_int32(threads, 0, "Number of threads to listen on. Numbers <= 0 "
             "will use the number of cores on this machine.");

class EchoHandlerFactory : public RequestHandlerFactory {
 public:
  void onServerStart() noexcept override {
    stats_.reset(new EchoStats);
  }

  void onServerStop() noexcept override {
    stats_.reset();
  }

  RequestHandler* onRequest(RequestHandler*, HTTPMessage*) noexcept override {
    return new EchoHandler(stats_.get());
  }

 private:
  folly::ThreadLocalPtr<EchoStats> stats_;
};

int main(int argc, char* argv[]) {
  gflags::ParseCommandLineFlags(&argc, &argv, true);
  google::InitGoogleLogging(argv[0]);
  google::InstallFailureSignalHandler();

  std::vector<HTTPServer::IPConfig> IPs = {
    {SocketAddress(FLAGS_ip, FLAGS_http_port, true), Protocol::HTTP},
    {SocketAddress(FLAGS_ip, FLAGS_spdy_port, true), Protocol::SPDY},
  };

  if (FLAGS_threads <= 0) {
    FLAGS_threads = sysconf(_SC_NPROCESSORS_ONLN);
    CHECK(FLAGS_threads > 0);
  }

  HTTPServerOptions options;
  options.threads = static_cast<size_t>(FLAGS_threads);
  options.idleTimeout = std::chrono::milliseconds(60000);
  options.shutdownOn = {SIGINT, SIGTERM};
  options.handlerFactories = RequestHandlerChain()
      .addThen<EchoHandlerFactory>()
      .build();

  HTTPServer server(std::move(options));
  server.bind(IPs);

  // Start HTTPServer mainloop in a separate thread
  std::thread t([&] () {
    server.start();
  });

  t.join();
  return 0;
}

EchoHandler.cpp

/*
 *  Copyright (c) 2014, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */
#include "EchoHandler.h"

#include "EchoStats.h"
#include "proxygen/httpserver/RequestHandler.h"
#include "proxygen/httpserver/ResponseBuilder.h"

using namespace proxygen;

namespace EchoService {

EchoHandler::EchoHandler(EchoStats* stats): stats_(stats) {
}

void EchoHandler::onRequest(std::unique_ptr<HTTPMessage> headers) noexcept {
  stats_->recordRequest();
}

void EchoHandler::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {
  if (body_) {
    body_->prependChain(std::move(body));
  } else {
    body_ = std::move(body);
  }
}

void EchoHandler::onEOM() noexcept {
  ResponseBuilder(downstream_)
    .status(200, "OK")
    .header("Request-Number",
            folly::to<std::string>(stats_->getRequestCount()))
    .body(std::move(body_))
    .sendWithEOM();
}

void EchoHandler::onUpgrade(UpgradeProtocol protocol) noexcept {
  // handler doesn't support upgrades
}

void EchoHandler::requestComplete() noexcept {
  delete this;
}

void EchoHandler::onError(ProxygenError err) noexcept {
  delete this;
}

}

Мы провели бенчмарк нашего эхо-сервера на компьютере с 32 логическими ядрами Intel® Xeon® CPU E5-2670 @ 2.60GHz и 16 GiB памяти, варируя количество рабочих потоков от одного до восьми. Мы запускали клиент на той же машине, дабы избежать сетевых задержек и вот что мы получили:

# Параметры настройки клиента:
# На каждый рабочий поток сервера по 2 клиента
# 400 одновременно открытых соединений
# 100 запросов на соединение
# 60 секунд тестирования
# В результатах указано среднее значение по результатам 10 тестов
# простой GET, 245 байт заголовков запроса, 600 байт ответа (без сохранения на диск)
# SPDY/3.1 позволяет до 10 параллельных запросов на соединение

image

Хотя сам по себе эхо-сервер достаточно примитивная штука по сравнению с настоящим веб-сервером, этот бенчмарк всё же показывает насколько эффективно Proxygen работает со SPDY и HTTP/2. HTTP-сервер из комплекта Proxygen легко использовать и он сразу работает достаточно производительно, хотя мы больше фокусировались на простоте применения, чем на максимально возможной скорости работы. К примеру, модель фильтров в сервере даёт вам возможность обрабатывать некоторые общие блоки данных по определённых для них алгоритмам, причём таким образом, что каждый отдельный блок кода алгоритма легко поддаётся юнит тестерованию. С другой стороны, необходимость аллокация памяти, связанная с этой моделью фильтров не идеальна для высокопроизводительных приложений.

Влияние

Proxygen позволяет нам быстро реализовать нужный функционал, выпустить его в продакшн и сразу получить результат. К примеру, нам было интересно оценить формат сжатия заголовков запросов HPACK, но к сожалению у нас не было ни клиентов HTTP/2, ни серверов, да и вообще спецификация HTTP/2 сама по себе всё ещё в процессе разработки. Proxygen позволил нам реализовать HPACK, попробовать использовать его поверх SPDY и выкатить релиз одновременно на наши сервера и мобильные клиенты. Возможность оперативно экспериментировать с реальным трафиком в формате HPACK дало нам возможность понять его реальную производительность и оценить выгоды от его использования.

Открытый код

Кодовая база Proxygen находится в состоянии активной разработки и будет продолжать эволюционировать. Если вам нравится протокол HTTP, высокопроизводительный сетевой код, современный С++, мы будем рады поработать с вами! Пожалуйста, не стесняйтесь присылать пулреквесты на Github.

Мы активно участвуем в разработке открытых проектов и всегда ищем возможность поделиться своим кодом с сообществом. Команда разработки сетевой инфраструктуры уже выложила в опенсор Thrift и Proxygen — два важных сетевых компонента Facebook. Мы надеемся, что они найдут своё применения и в других проектах.

Спасибо всем инженерам, поучаствовашим в разработке данного проекта: Ajit Banerjee, David Gadling, Claudiu Gheorghe, Rajat Goel, Peter Griess, Martin Lau, Adam Lazur, Noam Lerner, Xu Ning, Brian Pane, Praveen Kumar Ramakrishnan, Adam Simpkins, Viswanath Sivakumar и Woo Xie.

Автор: tangro

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js