Несколько дней назад зарелизилась новая версия Nginx — 1.13.10. Главная фича данного релиза — это нативная поддержка проксирования HTTP/2, и, как следствие, gRPC.
Наверное, сейчас, когда мир заполонили микросервисы, а также гетерогенные стеки технологий, каждый знает, что такое gRPC. Если нет, то это, как protobuf (который gRPC в том числе может использовать для сериализации), или Apache Thrift на стероидах. Технология позволяет организовать взаимодействие множества сервисов друг с другом в крайне эффективной манере.
Высокая производительность gRPC достигается благодаря нескольким вещам: использованию HTTP/2 для мультиплексирования, сжатию данных. Кроме того, фреймворк побуждает программистов разрабатывать свои сервисы в неблокирующем стиле (a-ka NIO), используя внутри себя такие библиотеки, как Netty.
Изображение взято с 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.
Из коробки вы получите целый пакет благ. 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 — проверьте сами.
Автор: Денис