Микросервисы на С++. Выдумка или реальность?



    В этой статье я расскажу о том, как создал шаблон (cookiecutter) и настроил окружение для написания REST API сервиса на С++ с использованием docker/docker-compose и пакетного менеджера conan.


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


    Так вот, перед нами встала задача написать высоконагруженный сервис, основной задачей которого был препроцессинг поступающих к нему данных и запись их в БД. И после очередного перекура товарищ предложил мне, как С++ разработчику, написать этот сервис на плюсах. Аргументируя это тем, что так будет быстрее, производительнее, да и вообще, жюри будут в восторге от того, как мы умеем распоряжаться ресурсами команды. На что я ответил, что никогда не занимался такими вещами на С++ и с легкостью могу оставшиеся 20+ часов посвятить поиску, компиляции и компоновке подходящих библиотек. Проще говоря, я струсил. На том и порешили и спокойно дописали все на Python.


    Сейчас же, во время вынужденной самоизоляции я решился разобраться в том, как писать сервисы на С++. Первое, что нужно было сделать, это определиться с подходящей библиотекой. Мой выбор пал на POCO, так как она была написана в объектно-ориентированном стиле, а также могла похвастаться нормальной документацией. Также, встал вопрос о выборе системы сборки. Я до этого момента работал только с Visual Studio, IAR и «голыми» makefile. И ни одна из этих систем меня не прельщала, так как я планировал запускать весь сервис в docker-контейнере. Тогда я решил попробовать разобраться с cmake и интересным пакетным менеджером conan. Этот пакетный менеджер позволял прописать все зависимости в одном файле

    conanfile.txt
    [requires]
    poco/1.9.3
    libpq/11.5

    [generators]
    cmake

    и с помощью простой команды «conan install .» установить необходимые библиотеки. Естественно, также требовалось внести изменения в

    CMakeLists.txt
    include(build/conanbuildinfo.cmake)
    conan_basic_setup()
    target_link_libraries(<target_name> ${CONAN_LIBS})
    

    После этого я начал искать библиотеку для работы с PostgreSQL, так как именно с ней у меня имелся небольшой опыт работы, а также именно с ней взаимодействовали наши сервисы на Python. И знаете, что я узнал? Она есть в POCO! Но conan не знает, что она есть в POCO и не умеет ее билдить, в репозитории лежит устаревший конфигурационный файл (я уже написал об этой ошибке создателям POCO). А значит, придется искать другую библиотеку.

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

    Следующим шагом было написание шаблона сервиса, умеющего обрабатывать запросы.
    Мы должны унаследовать наш класс TemplateServerApp от Poco::Util::ServerApplication и переопределить метод main.

    TemplateServerApp
    #pragma once
    
    #include <string>
    #include <vector>
    #include <Poco/Util/ServerApplication.h>
    
    class TemplateServerApp : public Poco::Util::ServerApplication
    {
        protected:
            int main(const std::vector<std::string> &);
    };

    int TemplateServerApp::main(const vector<string> &)
    {
        HTTPServerParams* pParams = new HTTPServerParams;
    
        pParams->setMaxQueued(100);
        pParams->setMaxThreads(16);
    
        HTTPServer s(new TemplateRequestHandlerFactory, ServerSocket(8000), pParams);
    
        s.start();
        cerr << "Server started" << endl;
    
        waitForTerminationRequest();  // wait for CTRL-C or kill
    
        cerr << "Shutting down..." << endl;
        s.stop();
    
        return Application::EXIT_OK;
    }


    В методе main мы должны задать параметры: порт, количество потоков и размер очереди. А самое главное, должны задать обработчик входящих запросов. Делается это посредством создания фабрики

    TemplateRequestHandlerFactory
    class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
    {
    public:
        virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
        {
            return new TemplateServerAppHandler;
        }
    };


    В моем случае она просто каждый раз создает один и тот же обработчик — TemplateServerAppHandler. Именно здесь мы и можем расположить нашу бизнес-логику.

    TemplateServerAppHandler
    class TemplateServerAppHandler : public HTTPRequestHandler
    {
    public:
        void handleRequest(HTTPServerRequest &req, HTTPServerResponse &resp)
        {
            URI uri(req.getURI());
            string method = req.getMethod();
    
            cerr << "URI: " << uri.toString() << endl;
            cerr << "Method: " << req.getMethod() << endl;
    
            StringTokenizer tokenizer(uri.getPath(), "/", StringTokenizer::TOK_TRIM);
            HTMLForm form(req,req.stream());
    
            if(!method.compare("POST"))
            {
                cerr << "POST" << endl;
            }
            else if(!method.compare("PUT"))
            {
                cerr << "PUT" << endl;
            }
            else if(!method.compare("DELETE"))
            {
                cerr << "DELETE" << endl;
            }
    
            resp.setStatus(HTTPResponse::HTTP_OK);
            resp.setContentType("application/json");
            ostream& out = resp.send();
    
            out << "{\"hello\":\"heh\"}" << endl;
            out.flush();
        }
    };

    Также я создал шаблон класса для работы с PostgreSQL. Для того, чтобы выполнить простой SQL, например создать таблицу, есть метод ExecuteSQL(). Для более сложных запросов или получения данных придется получать connection через GetConnection() и использовать API libpg. (Возможно потом я исправлю эту несправедливость).

    Database
    #pragma once
    
    #include <memory>
    #include <mutex>
    #include <libpq-fe.h>
    
    class Database
    {
    public:
        Database();
        std::shared_ptr<PGconn> GetConnection() const;
        bool ExecuteSQL(const std::string& sql);
    
    private:
        void establish_connection();
        void LoadEnvVariables();
    
        std::string m_dbhost;
        int         m_dbport;
        std::string m_dbname;
        std::string m_dbuser;
        std::string m_dbpass;
    
        std::shared_ptr<PGconn>  m_connection;
    };

    Все параметры для подключения к базе данных берутся из окружения, так что вам также нужно создать и настроить файл .env

    .env
    DATABASE_NAME=template
    DATABASE_USER=user
    DATABASE_PASSWORD=password
    DATABASE_HOST=postgres
    DATABASE_PORT=5432


    Вы можете посмотреть весь код на гитхабе.



    И настал последний этап написания dockerfile и docker-compose.yml. Скажу честно, на это ушла большая часть времени, и не только потому, что я нуб, что необходимо было каждый раз пересобирать библиотеки, а из-за подводных камней conan. Так например, для того, чтобы conan скачал, установил и побилдил необходимые зависимости, ему мало скачать «conan install .», ему также необходимо передать параметр -s compiler.libcxx=libstdc++11, иначе вы рискуете получить кучу ошибок на этапе компоновки вашего приложения. Я просидел с этой ошибкой несколько часов, и надеюсь, что эта статья поможет другим людям решить эту проблему за более короткое время.


    Далее, после написания docker-compose.yml, по совету своего товарища я добавил поддержку cookiecutter и теперь вы можете получить себе полноценный шаблон для REST API сервиса на С++, c настроенным окружением, и поднятой PostgreSQL, просто введя в консоль «cookiecutter https://github.com/KovalevVasiliy/cpp_rest_api_template.git». А затем «docker-compose up --build».


    Надеюсь, данный шаблон поможет новичкам на их нелегком пути разработки REST API приложений на великом и могучем, но таком неповоротливом языке, как С++.
    Также, я очень рекомендую прочитать вот эту статью. В ней подробнее объясняется как работать с POCO и написать свой REST API сервис.

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 52

      +1
      А почему именно conan? Ведь при написании Dockerfile уже взаимодействуешь с пакетным менеджером (apt/yum). В крайнем случае (в репозитории нет нужной версии) можно заиспользовать ExternalProject.
        0
        Возможные аргументы за Conan:
        — кроссплатформенность: это будет работать в разных дистрибутивах Linux, и вообще не только в Linux
        — проще фиксировать версию либы, она сама не обновится
        — можно собирать либы со своими нужными флагами
        — в Сonan central registry могут быть либы, которых нет в официальных репозиториях (хотя может быть и наоборот)
        — Conan вполне удобен и без докера, тогда как управлять зависимости без докерфайла ручными установками нужных пакетов довольно неудобно
          0

          Именно из-за кроссплатформенности. Тк сижу на Mac OS, но хотелось чтоб работало и под Linux.

          +1
          Но conan не знает, что она есть в POCO и не умеет ее билдить, в репозитории лежит устаревший конфигурационный файл (я уже написал об этой ошибке создателям POCO). А значит, придется искать другую библиотеку.

          Интересная логика, без причин завязались на не особо популярный пакетный менеджер и выбирали инструмент по принципу — чего бы нашлось…
            0

            Из-за кроссплатформенности conan. Если есть другие идеи и решения — я только за. Если расскажете.

              0
              vcpkg очень неплох
                0

                Тоже можно везде запускать и он сам разрешает зависимости?

                  0

                  Именно так он и работает

                    0

                    Vcpkg не работает на CentOS7 из коробки.

                  0
                  А чем он лучше?
                    +3
                    Это который даже версию пакета лочить не умеет, да? :) Очень неплохой пакетник, да
                      0

                      Они уже в процессе, похоже. Как минимум в roadmap есть, что уже не может не радовать.

                    0
                    Это больше про критерий выбора фреймворка. Conan не настолько популярен, и многие либы там отсутствуют. Вы бы могли отбранчить poco и положить к себе в конан нужную сборку, все в общем так делают.
                      0
                      Я попробовал написать разработчикам) Еще посмотрел как добавить нужную мне либу в конан. Будет время запилю им пул-реквест.
                  0

                  Так что жюри сказало?

                    0

                    Жюри не смотрели на сервис на плюсах, мы тогда написали все на питоне. Сказали: ну вы и молодцы.

                    –1
                    Я тебя слепил из того, что было.
                    Микросервисом и rest как-то не пахнет.
                      0

                      Ну, основная цель была проверить, можно ли вообще также легко как и на питоне собрать себе сервис. Почему же rest не пахнет и как бы вы решили такую задачу?

                        +1
                        Хорошо, я могу ошибаться, но я не вижу здесь ни одного принципа REST, также не вижу чего-то удобного для дальнейшего использования( архитектура, роутер, взаимодействие с бд, орм).
                        Что я вижу: собранный на коленке из всего подряд hello world, который даже свой «hello world» отдает не правильно.
                        Error: Parse error on line 1:
                        {	hello: heh}
                        --^
                        Expecting 'STRING', '}', got 'undefined'

                        Если задача состоит в rest api на poco, то первых двух ответ в гугле по запросу «poco rest api» хватит, чтобы ее решить.
                          0
                          Это правда, пока разбирался с конаном и докером, забыл поправить hello. Спасибо, что заметили) Задача состояла в создании шаблона, с уже настроенным окружением, чтобы на хакатоне или для домашнего проекта просто сделать docker-compose up.
                      0
                      Conan штука хорошая, но его нужно уметь готовить и с наскоку врятли получится что-то хорошее. Как минимум часть пакетов нужно брать из репозитория bincrafters и иметь в виду, что они с некоторой небольшой вероятностью несостыкуются с пакетами из conan-index-center. Мы у себя активно используем conan, хотя, конечно, не всегда пишем универсальные пакеты, которые будут работать везде, зачастую его можно использовать просто для того, что бы распространять бинари.

                      А по части микросервисов на C++ — нет не выдумка, но больно и дорого, в итоге нужно и доступно далеко не всем. Какой-нибудь требовательный микросервис или, скажем какой-нибудь WebRTC relay, писать на нём вполне можно. Для чего-то более общего, как мне кажется, нет подходящих библиотек или даже вернее будет сказать фреймворков, которые бы скрывали сложность от конечного разработчика.
                        +2

                        Я помню в конце 90-х пытался на C и C++ писать веб-серверы или CGI. Практически всё для HTTP приходилось писать с нуля или копипастить из других проектов. Из вашего комментария можно сделать вывод, что за 20+ лет ничего не изменилось :(

                          +1
                          К сожалению, в 2000-2010 но очень медленно развивался, но сейчас пошло пободрее.
                        +1

                        Микросервисы (как и просто сервисы), конечно, пишутся и на C++. Я пишу, например. Но нужно понимать — зачем. Я разрабатываю рекламные платформы, нагрузка — сотни тысяч запросов в секунду, поэтому C++ позволяет получать больше производительности с одной ноды.
                        Думаю, что если требований к нагрузке особых нет, то и разрабатывать что то на плюсах особо не интересно. Есть куча языков с готовыми библиотеками, где многие вещи делаются в пару строк.

                          0

                          То есть у вас такие сервисы, которые ни вовне не обращаются, ни запросы в БД не делают? Иначе все вот эти оптимизации практически сходят на нет.

                            +4

                            К БД обращаются. Но БД нужны специальные, рассчитанные на такие нагрузки. NoSQL в основном. Aerospike например. Кластер из 8 нод легко переживает 0.5 — 1 миллион запросов в секунду. Но там уже все в сетку и ssd упирается.
                            Реляционные БД используются тоже, но к ним не обращаются на каждый запрос. Только к кэшированные данным. Ну а запись в них только пакетная.
                            И само собой, чем больше локально кэшированных данных, тем лучше.
                            К внешним сервисам тоже обращаюсь. Но они должны уметь отвечать за десятки миллисекунд. Иначе, делаем очередь и уже из неё вызываем медленные сервисы.

                          +3
                          И знаете, что я узнал? Она есть в POCO! Но conan не знает, что она есть в POCO и не умеет ее билдить

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

                          Ох, этим всегда заканчиваются попытки заюзать пакетный менеджер из плюсов. Я уже давно перестал пытаться. Подключить либу по документации (или инструкции со стековерфлов) — это чёткие 15-20 минут и она станет, как влитая. Заюзать пакетный менеджер — от 15 секунд до 4 часов (плюс всегда захватывающий вариант «невозможно в принципе»).
                            0
                            Так хуже ведь не станет всё равно :) Просто теперь алгоритм немного поменялся:
                            1) Выбрали библиотеку
                            2) Проверили, есть ли в Conan
                            3) Если есть, то за 15 секунд подключили
                            4) Если за 15 секунд не справились — плюнули и подключили по старинке.

                            Времени дольше не заняло, а шанс того, что сэкономите время заметно увеличивается. И чем дальше, тем больше либ появляются в Conan и исправляются рецепты.
                            +2

                            Я вот тут как раз в своём pet project пишу C++ микросервисы на основе gRPC. Увы, в штатном конане его нет.
                            В результате, я написал велосипед на cmake, который по минимально описанному конфигу умеет скачивать и собирать сторонние библиотеки через ExternalProject_Add. И никаких питонов.
                            Если дойдут руки до документации, выложу в open source. Пока там, к примеру, даже не пахнет поддержкой винды для некоторых сочетаний компиляторов / платформ. Ну и собрать OpenSSL под винду с помощью mingw без Cygwin — тот ещё квест.

                              0
                              Таких велосипедов много, по ощущениям раз в несколько месяцев что-то подобное появляется на реддите.

                              Вы намного больше бы помогли сообществу, если бы написали рецепт для gRPC в conan.
                                0

                                Увы, пока нет такой цели. И питон я не знаю.
                                Я когда-то писал сборку C++ SDK под все платформы для google agones (куда входит gRPC), намучился и по факту завелосипедили многое. Пока недостаточно энтузиазма, чтобы повторять :)

                              0

                              A POCO действительно хороший выбор для реально нагруженного сервера?

                                +1

                                А это уже другой вопрос. Я хотел посмотреть, возможно ли также быстро как и на питоне написать шаблон, с помощью которого можно написать сервис. Может, замерю работу в будущем)

                                  0

                                  Мне показалось, что POCO не самый лаконичный в плане выразительности вариант. А какие еще альтернативы вы смотрели и почему отказались от них в пользу POCO?

                                    –1
                                    Я не смотрел другие варианты. Знаю, что есть sdk от Microsoft (как по мне довольно трудночитаемый код получается), а потом нашел гайд для Poco и мне показалось, что его будет достаточно для моих задач. Код основы достаточно прост, и его легко будет модифицировать. Кроме того, я не ставил перед собой задачу найти наиболее оптимальный sdk, я хотел лишь создать шаблон, с помощью которого легко и быстро можно создать свой микросервис и подключить его. Я не работаю в кровавом ентерпрайзе и не уверен, что плюсы для него пригодны.
                                      0

                                      Жаль, я думал, что вы провели поиск и сможете рассказать о причинах, которые оттолкнули вас от других вариантов. Такая информация мне лично была бы очень полезна.

                                        0
                                        Если писать hello world, может стоило взять что нибудь header-only, чтобы не заморачиваться с зависимостями и пакетником? Типа crow?
                                          0
                                          Типа crow?

                                          Он же мертв.

                                            0

                                            Не уследил, но никто не гарантирует, что либы, выбранные автором, не постигнет такая же судьба

                                              +1

                                              Так и есть. Но экспериментировать сейчас, имхо, имеет смысл на том, что сейчас еще живо (ну или подает признаки жизни).


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

                                  +1
                                  проще говоря, струсил
                                  я бы назвал это осторожностью
                                    0

                                    Ну вот кстати кажется мне, что в случае плюсов вероятность случайно сделать UB очень уж пугает и очень маловероятно, что она стоит улучшения перфоманса, которое еще и никто не гарантирует. Ведь если задача io bound, то весь прирост от плюсов будет сьеден ожиданиями.
                                    К тому же на плюсах сложно писать асинхронный код.

                                      0
                                      Это все понятно) Поэтому мы и пишем сервисы на питоне с его FastApi. Тут скорее был интерес: а можно ли, насколько удобно, а есть ли пакетный менеджер нормальный, а как запихнуть все это в докер.
                                        –1

                                        Интересно, как живут тысячи компаний, у которых софт на плюсах?

                                          0
                                          Ну, вот я пишу сейчас под IAR и ни с какими менеджерами пакетов и с докером не сталкивался на работе.
                                            –1

                                            Да нормально живут. У нас внутренний репозиторий для самособранных библиотек. Стандартный apt-get install bla-bla. Ну и немного кастомных cmake для их подключения.
                                            Ужасы подключения библиотек сильно преувеличены. В основном, все проблемы огребает тот спец, который разбирается с новой либой (или апдейтит существующую). Остальные просто устанавливают и пользуются.
                                            Докер — тоже не вижу сложностей. С появлением multistage сборок — докером стало очень удобно пользоваться для сборки

                                            0
                                            Пишутся сервисы на С++ нормально.
                                            К тому же на плюсах сложно писать асинхронный код.

                                            Если stackful — Boost.Coroutine2 и вперёд. Если stackless — то C++20 с корутинами + cppcoro и вперёд (но я бы лично сейчас так не делал).
                                            Нужна асинхронная либа для http-сервера — restinio.
                                              +1
                                              К сожалению, stackfull из буста это дикий набор низкоуровневых хаков, которые, видимо, не все компиляторы даже адекватно обрабатывают.

                                              Например, пытался юзать в msvc — ловил прикольные эффекты с goto в catch блок без исключения.
                                              Или цикл while (true) с вызовами async_write/async_read (из буста) выходил после первой итерации — помогала запись bool dummy = true;
                                              while (dummy).

                                              Вобщем, все надежды на с++20, верим и ждём. Пока писать асинхронный код действительно тяжело (или по крайней мере неудобно) по сравнению с другими языками.
                                            –1
                                            Как вариант, может стоит посмотреть в сторону SWIFT, там вроде бы менеджер пакетов уже работает ну и С, С++, Obj-C все из коробки с кучей фреймворков от Apple. Ну и в open source уже есть готовые сервера + связка с БД, итд.
                                              +1
                                              написать высоконагруженный сервис

                                              Что-то не заметил ничего, что бы отражало эту высоконагруженность, если честно.
                                              Неужели POKO настолько хорош, что сам всё сделает? ;)
                                                –1

                                                Так это я писал про хакатон, где мы писали на питоне:)

                                                +1
                                                Есть такой проект github.com/oatpp/oatpp для создания микросервисов на С++. Смотрится хорошо, особенно по бенчмаркам.

                                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                Самое читаемое