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

Proxygen — це набір бібліотек для використання протоколу 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-стек?
Інтеграція
Можливість швидко і легко інтегруватися в існуючу інфраструктуру 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 © 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style ліцензії 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 © 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style ліцензії 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.60 GHz і 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.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.