Можно ли создать микросервис на Rust? Или, что более важно, должны?
Хотя Kotlin и Python являются доминирующими языками в Tenable, мы уделяем время исследованию того, какие новые технологии стоит учитывать в нашем техническом стеке. Ржавчина - одна из них. Он зарекомендовал себя как мощный инструмент и отличная замена более старым, подверженным ошибкам памяти языкам, таким как C. Rust выразителен, современен и предоставляет отличные инструменты. Но самой замечательной особенностью, конечно же, является его инновационная модель владения, которая может предотвратить большинство ошибок, связанных с памятью, во время компиляции и без сборщика мусора.
Кроме того, мы разрабатываем и поддерживаем ряд архитектур, ориентированных на микросервисы. Любите их или ненавидите, микросервисы чрезвычайно полезны для создания масштабируемых и отказоустойчивых приложений.
Традиционно команды разрабатывают микросервисы с использованием таких языков, как Java или Python, и извлекают выгоду из своих экосистем. Вы можете просто погуглить «микросервис Java» или «микросервис Python», чтобы найти десятки рецептов, как это сделать.
Но можно ли создать микросервис на Rust? Или, что более важно, должны?
Мы решили провести эксперимент и создать простой микросервис, написанный на Rust, чтобы лично убедиться, стоит ли оно того. Идея заключалась в следующем: если мы потерпим неудачу, мы все равно научимся чему-то полезному, а если преуспеем, мы добавим новое замечательное устройство в набор инструментов нашей команды.
Кусочки пазла
Типичный микросервис состоит из:
- HTTP API: большинство микросервисов должны будут предоставлять REST API. По крайней мере, служба должна быть способна отвечать на базовые запросы проверки работоспособности от более крупной службы управления.
- Код для работы с базой данных: обычно это какая-то форма ORM.
- Код для чтения сообщений из очереди, например Kafka.
- Dockerfile для создания службы для развертывания (например, в Kubernetes).
Это то, что нам нужно для создания микросервиса, независимо от используемого языка. Для Java или Python эти вопросы относительно хорошо изучены, и доступно несколько проверенных временем библиотек. Для Rust, как более нового языка, нам нужно было найти и выбрать ящики, а затем написать код, чтобы склеить их вместе.
Ржавый проект
Во-первых, давайте создадим наш проект. Мы будем использовать Rust CLI, Cargo:
mkdir service_rust && cd service_rust cargo init
Это создает два файла: Cargo.toml, который содержит различные метаданные проекта, и src / main.rs, который является точкой входа в наш код. Программа начинается с выполнения функции main ().
Это очень легко скомпилировать и запустить:
cargo run
Веб-API
Существует несколько продвинутых и зрелых веб-фреймворков Rust, любую из которых можно использовать для микросервиса. Большинство из них включают такие функции, как асинхронное выполнение и высокую производительность. Они также обычно не требуют отдельного веб-сервера, в отличие от многих фреймворков Python или Java, что очень удобно при развертывании.
После некоторого исследования мы сузили список до Actix-web, Rocket и Warp.
В конце концов мы выбрали Actix-web: он популярен, прост в использовании, имеет все функции и поставляется вместе с системной библиотекой Actor, которую мы могли бы использовать для потребительского потока Kafka.
Синтаксис несколько напоминает Flask и интегрируется с нашей функцией main ():
Клиент базы данных
Среди Rust ORM королем является Diesel, так что это был очевидный выбор. Он имеет приятный синтаксис запросов и поддерживает все популярные базы данных. Мы можем интегрировать его с Actix с помощью data (), и тогда он станет доступен для использования обработчиками:
Потребитель Kafka
В качестве клиента Kafka мы выбрали rust-rdkafka - он написан не полностью на Rust, а основан на librdkafka, библиотеке C. Однако он поддерживает асинхронную обработку данных и прекрасно интегрируется с Actix. Если хотите, есть и другие варианты, полностью работающие на Rust.
Здесь мы запускаем процессор Kafka в потоке, параллельном API:
Сериализация / десериализация JSON без проблем выполняется Serde, что является еще одной замечательной особенностью Rust.
Dockerfile
Наконец, мы хотели бы скомпилировать код и обернуть его в контейнер Docker, чтобы его можно было отправить в нашу среду разработки. Мы будем использовать многоступенчатую сборку Docker, поскольку код компилируется в один двоичный файл:
Теперь я должен признать: это не работает. Он создает образ, но если мы попытаемся его запустить, он выйдет из строя с сообщением об ошибке примерно такого типа:
standard_init_linux.go:219: exec user process caused: no such file or directory
Причина в том, что некоторые ящики Rust зависят от внешних библиотек. Чтобы они заработали, нам нужно либо установить их в полученный образ, либо статически встроить их в наш двоичный файл. Для реализации последнего мы можем использовать собственный образ «строителя», например, rust-musl-builder (https://github.com/emk/rust-musl-builder):
Теперь сервис строится и даже запускается.
Что не покрывается
Для простоты в этой статье опущены некоторые скучные, но более простые в реализации вещи, а именно:
- Тестирование: Rust имеет встроенную возможность модульного тестирования. Просто попробуйте - это потрясающе.
- Конфигурация: это тривиально в реализации, но в то же время очень зависит от среды.
- Ведение журнала: это довольно просто.
- SSL: хотя мы коснулись этого на этапе сборки, обычно вам также приходится иметь дело с сертификатами и
openssl
кодом инициализации. Однако для изучения этой темы может потребоваться еще одна полноразмерная статья. - Другие вещи, специфичные для нашей среды.
В заключение
В целом, мы считаем наш эксперимент успешным. Да, вы определенно можете создавать микросервисы с помощью Rust. Некоторые аспекты оказались проще, чем ожидалось, некоторые потребовали дополнительной обработки.
Однако за ограниченный период времени мы создали сервис, готовый к отправке, не нуждающийся в веб-сервере или сервере приложений и обладающий расширенными функциями, такими как асинхронное выполнение, что очень обнадеживает.
У микросервиса, созданного на Rust, есть несколько отличительных преимуществ:
- Он обладает отличной производительностью по сравнению с традиционными альтернативами.
- Он свободен от большинства связанных с памятью ошибок, присущих языкам нижнего уровня.
- Он использует преимущества мощной системы типов Rust: многие ошибки могут быть обнаружены во время компиляции.
- Он не требует дополнительной среды выполнения (среда выполнения компилируется в двоичный файл, который легко доставляется).
- Этот двоичный файл не запускает сборщик мусора. Это упрощает контроль ресурсов, используемых службой.
Следует упомянуть и о минусах:
- У Rust более крутая кривая обучения, чем у Python или Java.
- Это также относительно новый язык, поэтому экосистема еще не сформировалась.
- Сообщество, каким бы гостеприимным оно ни было, к тому же значительно меньше.
Rust - чрезвычайно полезный инструмент для создания надежных и производительных архитектур. Несмотря на то, что это все еще относительно новая технология, результаты многообещающие. Однозначно стоит иметь его в своем ящике для инструментов.