Кратко про микросервисы на Scala и Erlang

—

от автора

Привет, Хабр!

Микросервисы давно являются некой «попсой» для создания гибких, масштабируемых и отказоустойчивых систем. И естественное имеет свою реализацию в функциональном программирование.

В статье рассмотрим два языка программирования, которые выделяются своим функциональным подходом и широким применением в микросервисной архитектуре: Scala и Erlang.

Scala

Scala — это достаточной мощный ЯП, который сочетает в себе функциональные и объектно-ориентированные подходы.

Одной из ключевых фич Scala заключается в его акторной модели, которая (очевидно) основана на концепции акторов. Акторы в Scala представляют собой небольшие вычислительные сущности, которые общаются друг с другом посредством отправки и получения сообщений. Такой подход к параллельному и распределенному программированию обеспечивает высокую степень отказоустойчивости и масштабируемости, что для микросервисов, как мы знаем — очень важно.

ФП в Scala поддерживается мощными функциональными конструкциями, такими как функции высшего порядка, неизменяемые структуры данных и паттерн матчинга.

Кроме того, Scala обладает большой экосистемой библиотек и фреймворков, спецом разработанных для создания микросервисов. Например, Akka — это популярный фреймворк, основанный на акторной модели Scala.

Еще важно отметить, что Scala также имеет преимущества в интеграции с существующим Java-кодом, что делает его привлекательным выбором для компаний, уже имеющих код на Java.

Пример создания микросервиса на Scala

Прежде всего, установим Scala и необходимые зависимости. Также воспользуемся Akka HTTP для обработки HTTP запросов:

// build.sbt name := "microservice-example" version := "1.0" scalaVersion := "2.13.8"  libraryDependencies ++= Seq(   "com.typesafe.akka" %% "akka-actor-typed" % "2.6.17",   "com.typesafe.akka" %% "akka-stream" % "2.6.17",   "com.typesafe.akka" %% "akka-http" % "10.2.8",   "com.typesafe.akka" %% "akka-http-spray-json" % "10.2.8" )

Создадим актор для обработки запросов:

import akka.actor.typed.ActorSystem import akka.actor.typed.scaladsl.Behaviors  object Main extends App {   val system = ActorSystem(Behaviors.empty, "microservice")    // define actors and behavior here }

Создадим простой маршрут для API:

import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Route  object Routes {   def helloRoute: Route =     path("hello") {       get {         complete("Hello, Habr!")       }     } }

Настроим HTTP сервер с использованием Akka HTTP:

import akka.http.scaladsl.Http import akka.actor.typed.scaladsl.adapter._  object Main extends App {   val system = ActorSystem(Behaviors.empty, "microservice")    val routes = Routes.helloRouter    Http().bindAndHandle(routes, "localhost", 8080)(system.toClassic) }

Запускаем:

object Main extends App {   val system = ActorSystem(Behaviors.empty, "microservice")    val routes = Routes.helloRoute // Add more routes as needed    Http().bindAndHandle(routes, "localhost", 8080)(system.toClassic)    println("Server online at http://localhost:8080/") }

Это базовый пример.

Scala также позволяет легко выполнять асинхронные HTTP запросы к внешним сервисам или микросервисам. Пример использования Akka HTTP Client для отправки GET запроса:

import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.model._ import scala.concurrent.Future import scala.util.{Success, Failure} import akka.stream.ActorMaterializer import scala.concurrent.ExecutionContext.Implicits.global  object HttpClientExample {   def main(args: Array[String]): Unit = {     implicit val system = ActorSystem()     implicit val materializer = ActorMaterializer()      val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "http://example.com"))      responseFuture.onComplete {       case Success(response) =>         println(s"Request successful: $response")         // handle response       case Failure(ex) =>         println(s"Request failed: $ex")     }   } }

Scala позволяет создавать гибкие маршруты для обработки HTTP запросов с помощью Akka HTTP. Пример маршрута, который принимает POST запросы на путь /api/data и фильтрует запросы по определенным условиям:

import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Route  object Routes {   def dataRoute: Route =     pathPrefix("api") {       path("data") {         post {           entity(as[String]) { requestData =>             // process the request data             complete("Data received: " + requestData)           }         }       }     } }

Часто есть необходимость в управлении состоянием. Есть различные подходы к этой задаче, включая использование акторов для изоляции состояния. Пример:

import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.{ActorRef, ActorSystem, Behavior}  object StateManagement {   // define messages   sealed trait Command   case class UpdateState(data: String) extends Command   case class GetState(replyTo: ActorRef[String]) extends Command    // define actor behavior   def stateManager(state: String): Behavior[Command] =     Behaviors.receiveMessage {       case UpdateState(data) =>         stateManager(data)       case GetState(replyTo) =>         replyTo ! state         Behaviors.same     }    def main(args: Array[String]): Unit = {     val system = ActorSystem(stateManager("Initial state"), "state-manager")      val stateManagerRef = system       .unsafeUpcast[StateManagement.Command]       .narrow      stateManagerRef ! GetState(System.out)   } }

Erlang

Фича Erlang также как и в Scala заключается в его акторной модели. Также есть механизм supervision, который позволяет строить отказоустойчивые системы. Каждый процесс в Erlang имеет надзорный процесс, который отвечает за его состояние. В случае сбоя процесса, надзорный процесс автоматом перезапускает его

Также Erlang обладает возможностью хот-свопа кода. Т.е можно обновлять приложение в реал тайме без простоев. Новая версия кода может быть внедрена в работающую систему без перезапуска.

И самое главное — Erlang изначально разработан для построения распределенных систем.

Реализация

GenServer является базой для создания микросервисов на Erlang. Он обеспечивает асинхронное взаимодействие и управление состоянием:

-module(example_service). -behaviour(gen_server).  %% API -export([start_link/0, get_state/1]).  %% Callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).  %% State -record(state, {data = []}).  %% API functions start_link() ->     gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).  get_state(Pid) ->     gen_server:call(Pid, get_state).  %% Callback functions init([]) ->     {ok, #state{}}.  handle_call(get_state, _From, State) ->     {reply, State#state.data, State}.  handle_cast(_Msg, State) ->     {noreply, State}.  handle_info(_Info, State) ->     {noreply, State}.  terminate(_Reason, _State) ->     ok.  code_change(_OldVsn, State, _Extra) ->     {ok, State}.

Supervisor обеспечивает отказоустойчивость и перезапуск микросервисов в случае сбоев:

-module(example_supervisor).  -behaviour(supervisor).  %% API -export([start_link/0]).  %% Callbacks -export([init/1]).  %% Init function start_link() ->     supervisor:start_link({local, ?MODULE}, ?MODULE, []).  %% Callback function init([]) ->     {ok, {{one_for_one, 5, 10},           [{example_service, {example_service, start_link, []},             permanent, 5000, worker, [example_service]}]}}.

Развернем на кластере:

%% запуска микросервиса на удаленном узле start_remote_service(Node) ->     {ok, _} = net_kernel:start([example@hostname]),     {ok, _} = rpc:call(Node, example_service, start_link, []).

Масштабирование может быть достигнуто путем запуска доп. экземпляров микросервисов на разных узлах кластера:

%% запуск дополнительных экземпляров микросервиса на разных узлах кластера start_additional_instances(NumInstances, Node) ->     lists:foreach(fun(_) -> start_remote_service(Node) end, lists:seq(1, NumInstances)).

Определяем функцию start_additional_instances, которая принимает два аргумента: NumInstances, представляющий собой количество дополнительных экземпляров микросервиса, которые хотим запустить, и Node, представляющий собой имя узла кластера, на котором мы хотим развернуть эти экземпляры.

Затем используем функцию lists:seq/2, чтобы сгенерировать список чисел от 1 до NumInstances. Далее используем функцию lists:foreach/2, чтобы выполнить функцию start_remote_service для каждого элемента списка.


Статья подготовлена в преддверии старта курса Microservice Architecture. На странице курса вы можете бесплатно посмотреть записи прошедших вебинаров, а также подробно ознакомиться с программой.


ссылка на оригинал статьи https://habr.com/ru/articles/810253/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *