Хабрахабр

NGINX и gRPC теперь настоящие друзья

Несколько дней назад зарелизилась новая версия Nginx — 1.13.10. Главная фича данного релиза — это нативная поддержка проксирования HTTP/2, и, как следствие, gRPC.

Наверное, сейчас, когда мир заполонили микросервисы, а также гетерогенные стеки технологий, каждый знает, что такое gRPC. Если нет, то это, как protobuf (который gRPC в том числе может использовать для сериализации), или Apache Thrift на стероидах. Технология позволяет организовать взаимодействие множества сервисов друг с другом в крайне эффективной манере.

Высокая производительность gRPC достигается благодаря нескольким вещам: использованию HTTP/2 для мультиплексирования, сжатию данных. Кроме того, фреймворк побуждает программистов разрабатывать свои сервисы в неблокирующем стиле (a-ka NIO), используя внутри себя такие библиотеки, как Netty.

image

Изображение взято с https://www.slideshare.net/borisovalex/enabling-googley-microservices-with-grpc-at-jdkio-2017

Другая важная фишка gRPC — это нативная поддержка backpressure. Это свойство реализовано с помощью абстракции deadline: инитный таймаут клиента экспозится через всю чепочку сервисов. Если следующий вызов не влезет в заданный дедлайн (таймаут), то вся чепочка вызовов будет зафейлена. Это защищает систему от цепной реакции. Подробнее об этом рассказывал Александр Борисов из Google. Можно посмотреть этот доклад на youtube.

Вернёмся к нашим баранам: Nginx и gRPC. С первого взгляда может показаться, что это две несовместимые технологии. Nginx используется, как точка входа в систему. В то время, как gRPC — это инстурмент для взаимодействие микросервисов внутри системы. Однако это не всегда так.

Рассмотрим компанию, которая разрабатывает API. У этой компании могут быть мобильные приложения, которые потребляют это же API. Приложения обычно не могут ходить напрямую в микросервисы, которые не доступны из публичной сети. Поэтому требуется некоторый Gateway, который будет принимать запросы из вне и проксировать их во внутренние микросервисы.

Функцию Gateway могут выполняться несколько классов систем. Во-первых, это может быть честное приложение на каком-нибудь языке программирования. Из плюсов такого подхода — большая гибкость. Из минусов — часто, пониженная производительность. Кроме того, программируя свой Gateway, достаточно просто наделать багов, которые могут повлиять на безопастность системы.

Другой вариант реализации Gateway — использование готовое решение из класса Reverse Proxy. Это может быть знакомый всем Nginx. Но есть и другие современные альтернативы. Это и Envoy, это и Træfik, это и Caddy. Наверное, преимущества Прокси всем понятны: это быстро, это надежно. Мы получаем из коробки балансировку трафика. Мы получаем из коробки SSL-терминирование. Кроме того, в любом Прокси обычно реализована очень гибкая система роутинга, которая позволяет по разным URL маршутизовать трафик в разные приложения.

Итак, мы поняли, что иногда нам нужно заэкспозить gRPC наружу системы, видимо, используя какой-то Reverse Proxy. Но вот незадача. У нас на проекте Nginx, ничего модного мы не хотим, а в старичке бага — нет возможности проксировать HTTP/2. Решение есть — обновиться до 1.13.10! Ребята наконец-то сделали нативную поддержку проксирования HTTP/2, а также gRPC.

image

Из коробки вы получите целый пакет благ. TLS-терминирование, балансировка трафика по нодам, мощный роутинг, а также ряд других известных вам Nginx фич.

Всё, что нужно сделать, чтобы начать использовать проксирование gRPC трафика — это подщаманить конфиг (и, возможно, собрать Nginx с парой новых модулей, если вы собираете Прокси сами). HelloWorld конфиг описывается так:

server { listen 80 http2; charset utf-8; access_log logs/access.log; location / { grpc_pass grpc://movie:6565; }
}

Я сам человек простой: пока не увижу — не поверю. Поэтому накидал для демонстрации Демку, где есть Сервер, который отдаёт список лучших фильмов (набор заданных строк), и есть Клиент, который читает эти фильмы. Клиент и сервер работают через Nginx.

Пишим фильмы мы так:

@Override
public void getRating(Moviesrating.GetRatingRequest request, StreamObserver<Moviesrating.GetRatingResponse> responseObserver) { log.info("getRating(): request={}", request); List<String> bestMovies = Arrays.asList( "The Shawshank Redemption", "The Godfather", "The Dark Knight", "Interstellar" ); responseObserver.onNext(Moviesrating.GetRatingResponse.newBuilder() .addAllMovie(bestMovies) .build()); responseObserver.onCompleted();
}

А так мы читаем фильмы:

@GetMapping("/top")
Mono<List<Movie>> top() { log.info("top()"); ListenableFuture<Moviesrating.GetRatingResponse> ratingFuture = moviesRatingStub.getRating( Moviesrating.GetRatingRequest.newBuilder().build()); CompletableFuture<List<Movie>> completable = new CompletableFuture<List<Movie>>() { @Override public boolean cancel(boolean mayInterruptIfRunning) { boolean result = ratingFuture.cancel(mayInterruptIfRunning); super.cancel(mayInterruptIfRunning); return result; } }; ratingFuture.addListener(() -> { try { completable.complete(ratingFuture.get().getMovieList().stream() .map(Movie::new) .collect(Collectors.toList())); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }, executor); return Mono.fromFuture(completable);
}

Всё работает, мужики из Nginx не обманули, можно верить. А если не верите — https://github.com/Hixon10/grpc-nginx — проверьте сами.

Показать больше

Похожие публикации

Кнопка «Наверх»