Armeria: The Only Thrift, gRPC, REST Microservice Framework You'll Need

Armeria: The Only Thrift, gRPC, REST Microservice Framework You'll Need

YouTube video: https://youtu.be/hLlctum1pIA

The founder of Netty introduces a new microservice framework ‘Armeria’. It is unique because:

1. It has Netty-based high-perf HTTP/2 implementation.
2. It lets you run gRPC, Thrift, REST, even Servlet webapp on single TCP port in single JVM.
3. It integrates with various Reactive Streams implementations such as RxJava, Reactor and Spring Webflux.

Armeria is a Netty-based open-source Java microservice framework which provides an HTTP/2 client and server implementation. It is different from any other RPC frameworks in that it supports both gRPC and Thrift. It also supports RESTful services based on Reactive Streams API and even a legacy web applications that run on Tomcat or Jetty, allowing you to mix and match different technologies into a service which means you do not need to launch multiple JVMs or open multiple TCP/IP ports just because you have to support multiple protocols or migrate from one to another.

In this session, Trustin Lee, the founder of Netty project and Armeria, shows:

- What Armeria is.
- How to serve gRPC, Thrift and RESTful services on a single TCP/IP port and a single JVM.
- How to make your legacy Tomcat or Jetty-based application and modern reactive RPC service coexist.
- How to use Armeria’s universal decorator API to apply common functionalities such as circuit breaker, DNS-based service discovery, distributed tracing and automatic retry, regardless of the protocol, which was previously impossible with other RPC frameworks which focused on a single protocol.

Previously presented at:

- TWJUG in Taiwan on Aug 15, 2019
- J on the Beach in Spain on May 16, 2019

Related slides:

- gRPC for the Thrifty: gRPC at Slack, by Josh Wills - https://static.sched.com/hosted_files/grpconf19/ff/gRPC%20for%20the%20Thrifty.pdf
- Armeria: LINE's Next Generation RPC Layer - https://speakerdeck.com/trustin/armeria-lines-next-generation-rpc-layer

9b123f408258511b201ca1230f260340?s=128

Trustin Lee

August 15, 2019
Tweet

Transcript

  1. @armeria_project Trustin Lee, LINE Aug 2019 Armeria The Only Thrift

    · gRPC · REST Microservice Framework You’ll Need
  2. @armeria_project What is Armeria? A user-friendly Java microservice framework based

    on asynchronous and reactive model with HTTP/2 and modern RPC protocols in mind
  3. @armeria_project What is Armeria? A user-friendly Java microservice framework based

    on asynchronous and reactive model with HTTP/2 and modern RPC protocols in mind
  4. @armeria_project One fine day of synchronous microservice Shard 1 Shard

    2 Shard 3 A bunch of clients Thread 1 Thread 2 Thread 3 Thread 4 Pending requests (Queue) Workers Read S1 Read S2 Read S3 Read S1 Read S2 Read S3 Read S1 Read S2 Read S3 Read S1 Read S2 Read S3 Read S2 Read S3 Read S1 Read S1 Read S2 Read S3 Read S1
  5. @armeria_project … until Shard 2 ruins it. Shard 1 Shard

    2 Shard 3 A bunch of clients Thread 1 Thread 2 Thread 3 Thread 4 Pending requests (Queue) Workers Read S1 Read S2 Read S3 Read S1 Read S2 Read S3 Read S1 Read S2 Read S3 Read S1 Read S2 Read S3 Read S2 Read S3 Read S1 Read S1 Read S2 Read S3 Read S1 Timeout!
  6. @armeria_project Shard 1 & 3: Why are no requests coming?

    Workers: We’re busy waiting for Shard 2. Shard 1 Shard 2 Shard 3 A bunch of clients Thread 1 Thread 2 Thread 3 Thread 4 Pending requests (Queue) Workers Read S1 Read S2 Read S3 Read S1 Read S2 Read S3 Read S1 Read S2 Read S3 Read S1 Read S2 Read S3 Read S2 Read S3 Read S1 Read S2 Read S2 Read S2 Read S2 Timeouts! Timeouts! Timeouts! Timeouts!
  7. @armeria_project How can we solve this? • Add more CPUs?

    – They are very idle. • Add more threads? – They will all get stuck with Shard 2 in no time. – Waste of CPU cycles – context switches are not free. – Waste of memory – each thread needs its own stack. • Result: – Fragile system that falls apart even on a tiny backend failure – Inefficient system that takes more memory and CPU than what it actually needs • Maybe you can work around this, but you’ll have to keep tuning and adding hacks.
  8. @armeria_project What is Armeria? A user-friendly Java microservice framework based

    on asynchronous and reactive model with HTTP/2 and modern RPC protocols in mind
  9. @armeria_project Problems with large payloads • We solved blocking problem

    with asynchronous programming, but can we send 10MB personalized response to 100K clients? – Can’t hold that much in RAM – 10MB × 100K = 1TB • What if we/they send too fast? – Different bandwidth & processing power • We need ‘just enough buffering.’ – Expect OutOfMemoryError otherwise.
  10. @armeria_project What is Armeria? A user-friendly Java microservice framework based

    on asynchronous and reactive model with HTTP/2 and modern RPC protocols in mind
  11. @armeria_project RPC vs. HTTP impedance mismatch • RPC has been

    hardly a 1st-class citizen in web frameworks. – Method name – Parameter names and values – Return value or exception POST /some_service HTTP/1.1 Host: example.com Content-Length: 96 <binary request> HTTP/1.1 200 OK Host: example.com Content-Length: 192 <binary response> Failed RPC call
  12. @armeria_project Access logs re-imagined • What if we provide access

    logs with … – Universal API for gRPC · Thrift · REST – RPC-level information – Precise send · receive timings • Imagine streaming to data processing pipeline, e.g. Kafka. 192.167.1.2 - - [10/Oct/2000:13:55:36 -0700] "POST /some_service HTTP/1.1" 200 2326
  13. @armeria_project Making a debug call • Sending an ad-hoc query

    in RPC is hard. – Find a proper service definition, e.g. .thrift or .proto files – Set up code generator, build, IDE, etc. – Write some code that makes an RPC call. • HTTP in contrast: – cURL, telnet command, web-based tools and more. • There’s some effort to make things easier. – gRPC reflection service + grpcurl • What if we build something more convenient and collaborative?
  14. @armeria_project More than one RPC protocol • Supports both gRPC

    & Thrift … – on a single HTTP port – in a single JVM – without any proxies? • Can serve non-RPC requests together. – REST API – Static files – Exposing metrics – Health-check requests from load balancers – Traditional JEE webapps • Share common logic between different endpoints!
  15. @armeria_project What is Armeria? A user-friendly Java microservice framework based

    on asynchronous and reactive model with HTTP/2 and modern RPC protocols in mind
  16. @armeria_project Better UX for devs • The last yet most

    important • Nobody wants a framework that’s hard to use. • Can we design … – a user-friendly asynchronous API – … with modest learning curve?
  17. @armeria_project Our answer – Armeria • Asynchronous + High-performance –

    Netty with JNI-based /dev/epoll transport and BoringSSL • Seamless integration with Reactive Streams – RxJava2, Project Reactor, Spring WebFlux… • Ability to mix different types of services in a single server – gRPC, Thrift, REST, Tomcat, Jetty… • Not just HTTP/2 – gRPC on HTTP/1 – HAProxy • Easy to learn and fun to use (hopefully)
  18. @armeria_project Armeria by examples HTTP · REST

  19. @armeria_project Hello, world! Server server = new ServerBuilder() .http(8080) .https(8443)

    .tlsSelfSigned() .haproxy(8080) .service("/", (ctx, req) -> HttpResponse.of("Hello, world!")) .build(); server.start(); Protocol auto-detection at 8080
  20. @armeria_project Hello, world – Parameterized Server server = new ServerBuilder()

    .http(8080) .service("/hello/:name", (ctx, req) -> HttpResponse.of("Hello, %s!", ctx.pathParam("name"))) .build(); server.start();
  21. @armeria_project Hello, world – Annotated Server server = new ServerBuilder()

    .http(8080) .annotatedService(new Object() { @Get("/hello/:name") public String hello(@Param String name) { return String.format("Hello, %s!", name); } }) .build(); server.start();
  22. @armeria_project There are more! • CompletableFuture and reactive responses •

    Query parameters and cookies • Request and response converters • Exception handlers • Content type negotiation • Regular-expression path mapping • Aims to be on-par or better than <your favorite web framework>
  23. @armeria_project Armeria by examples Thrift · gRPC

  24. @armeria_project gRPC & Thrift basics a. Write an IDL file

    – .proto or .thrift b. A compiler compiles it into .java files. c. Implement interfaces generated at ‘b’. namespace java com.example service HelloService { string hello(1:string name) } syntax = "proto3"; package grpc.hello; option java_package = "com.example"; service HelloService { rpc Hello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
  25. @armeria_project gRPC Server server = new ServerBuilder() .http(8080) .service(new GrpcServiceBuilder().addService(new

    MyHelloService()) .build()) .build(); class MyHelloService extends HelloServiceGrpc.HelloServiceImplBase { @Override public void hello(HelloRequest req, StreamObserver<HelloReply> cb) { HelloReply reply = HelloReply.newBuilder() .setMessage("Hello, " + req.getName() + '!') .build(); cb.onNext(reply); cb.onCompleted(); } }
  26. @armeria_project Thrift Server server = new ServerBuilder() .http(8080) .service("/hello", THttpService.of(new

    MyHelloService())) .build(); class MyHelloService implements HelloService.AsyncIface { @Override public void hello(String name, AsyncMethodCallback<String> cb) { cb.onComplete("Hello, " + name + '!'); } }
  27. @armeria_project What’s better than the official impls? • gRPC –

    Works on both HTTP/1 and 2 – gRPC-Web support, i.e. can call gRPC services from JavaScript frontends • Thrift – HTTP/2 – TTEXT (human-readable REST-ish JSON) • Can mix gRPC, Thrift, REST, Tomcat, Jetty, … • Can leverage the common Armeria features – Decorators – Web-based dashboard
  28. @armeria_project Decorating services Separation of concerns a.k.a. filter, decorator, interceptor,

    middleware
  29. @armeria_project Intercepting an HTTP request THttpService.of(new MyHelloService()) .decorate((delegate, ctx, req)

    -> { logger.info("{} Received a request", ctx); HttpResponse res = delegate.serve(ctx, req); res.completionFuture().thenAccept(unused -> { logger.info("{} Sent a response", ctx); }); return res; }) • Can’t access RPC information yet. • Need to decompose THttpService into: – ThriftCallService – RPC layer – THttpService – HTTP layer
  30. @armeria_project Intercepting an RPC request • Complete access to the

    RPC call information • Override the parameters and methods ThriftCallService.of(new MyHelloService()) .decorate((delegate, ctx, req) -> { logger.info("{} Method: {}, Params: {}", ctx, req.method(), req.params()); RpcResponse res = delegate.serve(ctx, req); res.thenAccept(value -> { logger.info("{} Result: {}", ctx, value); }); return res; }) .decorate(THttpService.newDecorator())
  31. @armeria_project Decorator as core extension mechanism • Request throttling •

    Metric collection • Distributed tracing (Zipkin) • HTTP content encoding • HTTP authn/z • CORS policies • Circuit breakers • Automatic retries • …
  32. @armeria_project Distributed tracing with Zipkin import brave.Tracing; import brave.sampler.Sampler; Server

    server = new ServerBuilder() .http(8080) .service("/my_service", myService.decorate( BraveService.newDecorator( Tracing.newBuilder() .currentTraceContext(…) .localServiceName("myService") .spanReporter(spanReporter) .sampler(Sampler.create(/* 5% */ 0.05f)) .build()))) .build();
  33. @armeria_project Playing better with RPC Armeria documentation service

  34. @armeria_project Armeria documentation service • Enabled by adding DocService to

    ServerBuilder • Browse and invoke RPC services in an Armeria server – No fiddling with binary payloads – Send a request without writing code • Supports gRPC, Thrift and annotated services • We have a plan to add: – Metric monitoring console – Runtime configuration editor, e.g. logger level
  35. @armeria_project

  36. @armeria_project • Share the URL to reproduce a call.

  37. @armeria_project All pieces together The ultimate all-in-one micro service

  38. @armeria_project ServerBuilder sb = new ServerBuilder(); // Thrift services sb.service("/thrift/foo",

    THttpService.of(myThriftFooService)); sb.service("/thrift/bar", THttpService.of(myThriftBarService)); // gRPC services sb.service(new GrpcServiceBuilder().addService(myGrpcFooService) .addService(myGrpcBarService) .build()); // Static files sb.service("prefix:/files", HttpFileService.forFileSystem("htdocs")); // Legacy webapps sb.service("prefix:/webapp/foo", TomcatService.forFileSystem("foo.war")); sb.service("prefix:/webapp/bar", TomcatService.forFileSystem("bar.war")); // Documentation service sb.service("prefix:/internal/docs", new DocService()); // L7 health check service sb.service("/internal/healthcheck", HealthCheckService.of()); // Prometheus exposition sb.service("/internal/prometheus", new PrometheusExpositionService(...)); // Enable logging for all services. sb.decorator(LoggingService.newDecorator()); // Enable distributed tracing for all services. sb.decorator(BraveService.newDecorator(...)); // Send all access logs to Kafka. sb.accessLogWriter(new KafkaAccessLogWriter(...), true);
  39. @armeria_project What about client-side?

  40. @armeria_project Armeria client API • Similar experience to the server-side

    API – e.g. Common types, Decorators, RPC-friendliness • Client-specific stuff – Circuit breakers – Automatic retries – Client-side load balancing – Retrofit integration – https://square.github.io/retrofit/
  41. @armeria_project // Send DNS queries periodically to discover service hosts

    (k8s style). // Send health check requests to /internal/healthcheck and remove dead hosts. EndpointGroup group = HealthCheckedEndpointGroup.of( DnsServiceEndpointGroup.of("my-service.cluster.local"), "/internal/healthcheck"); // Register the group into the registry. EndpointGroupRegistry.register("myService", group, EndpointSelectionStrategy.WEIGHTED_ROUND_ROBIN); // Create a new HTTP client that connects to the group // with retries, circuit breaker and logging. HttpClient client = new HttpClientBuilder("http://group:myService") .decorator(RetryingHttpClient.newDecorator(onServerErrorStatus())) .decorator(CircuitBreakerHttpClient.newDecorator(...)) .decorator(LoggingClient.newDecorator()) .build(); // Send a request and print the response content. client.get("/greet").aggregate() .thenAccept(res -> System.out.println(res.contentUtf8())) .exceptionally(cause -> { /* Handle an error. */ });
  42. @armeria_project Got interested?

  43. @armeria_project Let’s build Armeria together! • Give it a try.

    • Ask questions. • Request new features. • Tell us what rocks and sucks. • Consider joining the effort.
  44. @armeria_project Meet us at GitHub github.com/line/armeria line.github.io/armeria