Using Armeria to write your
RPC
koji lin
Armeria
● 非同步 RPC library
● LINE 的開放原始碼專案
○ Trustin Lee
● Java 8 
● HTTP/1・2 
● Thrift/gRPC 
● 尤其適合在 Microservices 架構
Synchronous? Asynchrnous?
● 在同步下,如果一個處理需要等待,thread 必須等
待完成才能繼續執行
● 同步處理在大量存取下不易 scale up
○ Concurrent requests 受限於 max threads 
○ CPU 花費成本在 contention & context switch 
○ 較快或優先程度較高的存取會被較慢的存取拖累
Armeria is Asynchronous
● 建立於 Netty 之上
● Max concurrent requests >>> max threads
● 較慢的存取不會阻礙到其他存取
● 程式庫內部也非同步化
○ DNS 查詢
○ Connection pool
● 0.20.x reactive stream 化
Why HTTP/2 ?
● 一個連線上可以同時來往多個 request / response
● 不需要一個存取就建一個連線
from https://www.nginx.com/blog/7-tips-for-faster-http2-performance/
Why HTTP/2 ? (cont'd)
● 解決了 HTTP/1 的許多問題
○ Binary header compression
○ 更少的連線
● 比較容易從 HTTP/1 轉換
● 可以利用各種現存的工具
○ cURL,瀏覽器,Wireshark
Netty 夠穩定嗎?
● HTTP/1
○ Twitter
○ Apple
● HTTP/2
○ gRPC
○ LINE internal use
Thrift
● 跨平台跟語言的服務開發框架
○ 透過 compiler 自動生成程式,支援 C++, Java,
Python, Ruby, Perl, C#, Go, Javascript ...等等
○ 支援各種傳輸格式/協定
● 原本是 Facebook 開發,現為 Apache Thrift 開源
● 定義好 service method 就可以快速開發 RPC
client 和 server
○ .thrift 本身就是良好的文件
● 比 text-based 格式更有效率
Thrift hello world on Armeria
● 撰寫 .thrift 定義 service method
1. namespace java idv.koji.example.service
2. service HelloService {
3. string hello(1:string name)
4. }
Thrift hello world on Armeria (cont’d)
● 使用 thrift compiler 產生好 client/server 溝通部分
● 實作邏輯
1. public class Foo implements
HelloService.AsyncIface {
2. public void hello(String name,
AsyncMethodCallback cb) {
3. cb.onComplete(name + ", hello world!");
4. }
5. }
● 利用 Armeria 建立 Server
1. ServerBuilder sb = new ServerBuilder();
2. sb.port(8080, SessionProtocol.HTTP);
3. sb.serviceAt(
4. "/hello",
5. ThriftService.of(fooHandler,
6. SerializationFormat.THRIFT_BINARY)
7. .decorate(LoggingService::new));
8. sb.build().start();
Thrift hello world on Armeria (cont’d)
● 利用 Armeria 建立 Client
1. HelloService.Iface helloService =
2. Clients.newClient(
3. "tbinary+http://127.0.0.1:8080/hello",
4. HelloService.AsyncIface.class);
5.
6. helloService.hello("Armeria",
7. new AsyncMethodCallback<String>() {
8. ...
9. });
Thrift hello world on Armeria (cont’d)
● 跟 Swagger 類似
1. ServerBuilder sb = new ServerBuilder();
2. sb.serviceAt("/foo/", THttpService.of(...))
3. .serviceAt("/bar/", THttpService.of(...))
4. .serviceUnder("/docs/", new
5. DocService());
文件產生器
和 legacy webapp 共存
● 將 legacy 的程式透過 Tomcat Servlet Engine 執行
● Tomcat 不需綁定 port,還可以透過 armeria 得到
HTTP/2 的好處
1. ServerBuilder sb = new ServerBuilder();
2. sb.serviceUnder("/legacy/",
3. TomcatService.forFileSystem(
4. "/var/example.war"));
靜態頁面
1. ServerBuilder sb = new ServerBuilder();
2. sb.serviceUnder("/images/",
3. HttpFileService.forFileSystem(
4. "/var/lib/www/images"));
HTTP Server
1. ServerBuilder sb = new ServerBuilder();
2. sb.port(8080, SessionProtocol.HTTP);
3. builder.serviceAt("/http",
4. new AbstractHttpService() {
5. @Override protected void
6. doPost(ServiceRequestContext ctx,
7. HttpRequest req,
8. HttpResponseWriter res)... {
9. res.respond(HttpStatus.OK,
10. MediaType.PLAIN_TEXT_UTF_8,
11. "test");
12. }
13. }).build().start();
HTTP Client
1. HttpClient httpClient = Clients.newClient(
2. "none+http://example.com/",
3. HttpClient.class);
4. AggregatedHttpMessage textResponse =
5. httpClient
6. .get("/foo/bar.txt") // Publisher
7. .aggregate() // CompletableFuture
8. .join();
Client side load balancing
● Armeria 使用單一 HTTP/2 連線,所以在某些狀況
如 L4 下,需要自己的 LB 機制
1. EndpointGroupRegistry.register(
2. "myGroup",
3. new StaticEndpointGroup(
4. Endpoint.of("1.2.3.4", 8080), ...),
5. EndpointSelectionStrategy.
6. WEIGHTED_ROUND_ROBIN);
7. Clients.newClient(
8. "none+http://group:myGroup/foo/bar",
9. SimpleHttpClient.class);
Metrics
● Dropwizard Metrics
● 使用 DropwizardMetricCollectingService decorate
1. builder.serviceAt(
2. "/hello",
3. THttpService.of(new HelloHandler())
4. .decorate(
5. DropwizardMetricCollectingService
6. .newDecorator(metricRegistry,
7. "hello-service")));
Zipkin 整合
● Twitter 的開放源碼專案
● 分散式服務追蹤系統,追蹤跨網路存取的一連串
API 呼叫過程
● 在存取產生時放入一些資訊在 request 中,送到後
台可視化
Zipkin 整合 (cont’d)
1. Brave brave = new
2. Brave.Builder("HelloService")
3. .spanCollector(
4. new ScribeSpanCollector("127.0.0.1",
5. 9410))
6. .traceSampler(Sampler.create(1f))
7. .build();
8. server.serviceAt("/hello",
9. ThriftService.of(new HelloService())
10. .decorate(HttpTracingService
11. .decorator(brave));
Circuit Breaker
● 有多個遠端呼叫的系統最怕超時的服務所造成的連
鎖反應
● http://martinfowler.com/bliki/CircuitBreaker.html
http://developers.linecorp.com/blog/ja/?p=3684
Circuit Breaker (cont’d)
1. decorator =
2. CircuitBreakerClient.newDecorator(
3. new CircuitBreakerBuilder("hello")
4. .failureRateThreshold(0.1).build());
5.
6. new ClientBuilder("tbinary+http://...")
7. .decorator(ThriftCall.class,
8. ThriftReply.class,
9. decorator)
10. .build(HelloService.Iface.class);
Circuit Breaker (cont’d)
● Circuit Breaker 開啟時會丟出 FailFastException
● 可以針對 host, method 等級去做設定
○ newPerMethodDecorator
○ newPerHostDecorator
○ newPerHostAndMethodDecorator
gRPC support
● https://github.com/line/armeria/pull/247
1. ServerBuilder sb = new ServerBuilder();
2. sb.port(8080, SessionProtocol.HTTP);
3. GrpcService grpcService = new
4. GrpcServiceBuilder()
5. .addService(new HiService())
6. .build();
7. sb.serviceUnder("/", grpcService);
8. sb.build().start();
● 理應可以支援 HTTP/1,但現在沒有 HTTP/1 client
● 尚未支援文件產生器 DocService
● 尚未支援 json 格式
○ 類似 gRPC gateway
gRPC support (cont’d)
Retrofit with armeria client
● https://github.com/line/armeria/pull/297
1. HttpClient httpClient =
2. Clients.newClient(ClientFactory.DEFAULT,
3. "none+http://localhost:8080",
4. HttpClient.class);
5. Retrofit retrofit =
6. ArmeriaRetrofit.builder(httpClient)
7. .addConverterFactory(...)
8. .build();
9. MyApi api = retrofit.create(MyApi.class);
Armeria 之於應用程式開發者
● 良好的抽象設計
○ 一樣的方式模式建立 http/thrift client 和 server
● 方便的 decorator 模式
○ 預設已提供 logging, metrics, tracing, circuit
breaker…
○ 依需求可以用同模式實作各種功能 例如: ACL,
rate-limiting...等等
Is Armeria production ready?
Shop architecture using armeria
● 內部與外部都從 thrift socket client/server 實作切
換到 armeria
○ 內部都是 HTTP/2
○ 外部則 HTTP/2 & HTTP/1 都有
● 與 Spring MVC 共存
● Circuit breaker
● Metrics + Zipkin tracing
# of backend requests
但是...
● 非同步化不只是更換框架/程式庫就完成
○ 非同步化的程式不易撰寫跟維護
○ 我們使用 Dagger Producers 去實作非同步的相依
流程
● server/client 都使用 armeria,記得不要使用 client
的 future#get 之類的阻塞方法
○ event loop 下造成同 thread 等待跑在同個 thread
下的 task, 造成永遠阻塞
但是... (cont’d)
● 非同步出錯時,exception 的 stack trace 資訊不足
更多資訊
● http://line.github.io/armeria/
● https://github.com/line/armeria
● 歡迎直接到 issue 提出建議跟需求
Q&A

Using armeria to write your RPC

  • 1.
    Using Armeria towrite your RPC koji lin
  • 2.
    Armeria ● 非同步 RPClibrary ● LINE 的開放原始碼專案 ○ Trustin Lee ● Java 8  ● HTTP/1・2  ● Thrift/gRPC  ● 尤其適合在 Microservices 架構
  • 3.
    Synchronous? Asynchrnous? ● 在同步下,如果一個處理需要等待,thread必須等 待完成才能繼續執行 ● 同步處理在大量存取下不易 scale up ○ Concurrent requests 受限於 max threads  ○ CPU 花費成本在 contention & context switch  ○ 較快或優先程度較高的存取會被較慢的存取拖累
  • 4.
    Armeria is Asynchronous ●建立於 Netty 之上 ● Max concurrent requests >>> max threads ● 較慢的存取不會阻礙到其他存取 ● 程式庫內部也非同步化 ○ DNS 查詢 ○ Connection pool ● 0.20.x reactive stream 化
  • 5.
    Why HTTP/2 ? ●一個連線上可以同時來往多個 request / response ● 不需要一個存取就建一個連線 from https://www.nginx.com/blog/7-tips-for-faster-http2-performance/
  • 6.
    Why HTTP/2 ?(cont'd) ● 解決了 HTTP/1 的許多問題 ○ Binary header compression ○ 更少的連線 ● 比較容易從 HTTP/1 轉換 ● 可以利用各種現存的工具 ○ cURL,瀏覽器,Wireshark
  • 7.
    Netty 夠穩定嗎? ● HTTP/1 ○Twitter ○ Apple ● HTTP/2 ○ gRPC ○ LINE internal use
  • 8.
    Thrift ● 跨平台跟語言的服務開發框架 ○ 透過compiler 自動生成程式,支援 C++, Java, Python, Ruby, Perl, C#, Go, Javascript ...等等 ○ 支援各種傳輸格式/協定 ● 原本是 Facebook 開發,現為 Apache Thrift 開源 ● 定義好 service method 就可以快速開發 RPC client 和 server ○ .thrift 本身就是良好的文件 ● 比 text-based 格式更有效率
  • 9.
    Thrift hello worldon Armeria ● 撰寫 .thrift 定義 service method 1. namespace java idv.koji.example.service 2. service HelloService { 3. string hello(1:string name) 4. }
  • 10.
    Thrift hello worldon Armeria (cont’d) ● 使用 thrift compiler 產生好 client/server 溝通部分 ● 實作邏輯 1. public class Foo implements HelloService.AsyncIface { 2. public void hello(String name, AsyncMethodCallback cb) { 3. cb.onComplete(name + ", hello world!"); 4. } 5. }
  • 11.
    ● 利用 Armeria建立 Server 1. ServerBuilder sb = new ServerBuilder(); 2. sb.port(8080, SessionProtocol.HTTP); 3. sb.serviceAt( 4. "/hello", 5. ThriftService.of(fooHandler, 6. SerializationFormat.THRIFT_BINARY) 7. .decorate(LoggingService::new)); 8. sb.build().start(); Thrift hello world on Armeria (cont’d)
  • 12.
    ● 利用 Armeria建立 Client 1. HelloService.Iface helloService = 2. Clients.newClient( 3. "tbinary+http://127.0.0.1:8080/hello", 4. HelloService.AsyncIface.class); 5. 6. helloService.hello("Armeria", 7. new AsyncMethodCallback<String>() { 8. ... 9. }); Thrift hello world on Armeria (cont’d)
  • 13.
    ● 跟 Swagger類似 1. ServerBuilder sb = new ServerBuilder(); 2. sb.serviceAt("/foo/", THttpService.of(...)) 3. .serviceAt("/bar/", THttpService.of(...)) 4. .serviceUnder("/docs/", new 5. DocService()); 文件產生器
  • 14.
    和 legacy webapp共存 ● 將 legacy 的程式透過 Tomcat Servlet Engine 執行 ● Tomcat 不需綁定 port,還可以透過 armeria 得到 HTTP/2 的好處 1. ServerBuilder sb = new ServerBuilder(); 2. sb.serviceUnder("/legacy/", 3. TomcatService.forFileSystem( 4. "/var/example.war"));
  • 15.
    靜態頁面 1. ServerBuilder sb= new ServerBuilder(); 2. sb.serviceUnder("/images/", 3. HttpFileService.forFileSystem( 4. "/var/lib/www/images"));
  • 16.
    HTTP Server 1. ServerBuildersb = new ServerBuilder(); 2. sb.port(8080, SessionProtocol.HTTP); 3. builder.serviceAt("/http", 4. new AbstractHttpService() { 5. @Override protected void 6. doPost(ServiceRequestContext ctx, 7. HttpRequest req, 8. HttpResponseWriter res)... { 9. res.respond(HttpStatus.OK, 10. MediaType.PLAIN_TEXT_UTF_8, 11. "test"); 12. } 13. }).build().start();
  • 17.
    HTTP Client 1. HttpClienthttpClient = Clients.newClient( 2. "none+http://example.com/", 3. HttpClient.class); 4. AggregatedHttpMessage textResponse = 5. httpClient 6. .get("/foo/bar.txt") // Publisher 7. .aggregate() // CompletableFuture 8. .join();
  • 18.
    Client side loadbalancing ● Armeria 使用單一 HTTP/2 連線,所以在某些狀況 如 L4 下,需要自己的 LB 機制 1. EndpointGroupRegistry.register( 2. "myGroup", 3. new StaticEndpointGroup( 4. Endpoint.of("1.2.3.4", 8080), ...), 5. EndpointSelectionStrategy. 6. WEIGHTED_ROUND_ROBIN); 7. Clients.newClient( 8. "none+http://group:myGroup/foo/bar", 9. SimpleHttpClient.class);
  • 19.
    Metrics ● Dropwizard Metrics ●使用 DropwizardMetricCollectingService decorate 1. builder.serviceAt( 2. "/hello", 3. THttpService.of(new HelloHandler()) 4. .decorate( 5. DropwizardMetricCollectingService 6. .newDecorator(metricRegistry, 7. "hello-service")));
  • 20.
    Zipkin 整合 ● Twitter的開放源碼專案 ● 分散式服務追蹤系統,追蹤跨網路存取的一連串 API 呼叫過程 ● 在存取產生時放入一些資訊在 request 中,送到後 台可視化
  • 21.
    Zipkin 整合 (cont’d) 1.Brave brave = new 2. Brave.Builder("HelloService") 3. .spanCollector( 4. new ScribeSpanCollector("127.0.0.1", 5. 9410)) 6. .traceSampler(Sampler.create(1f)) 7. .build(); 8. server.serviceAt("/hello", 9. ThriftService.of(new HelloService()) 10. .decorate(HttpTracingService 11. .decorator(brave));
  • 22.
    Circuit Breaker ● 有多個遠端呼叫的系統最怕超時的服務所造成的連 鎖反應 ●http://martinfowler.com/bliki/CircuitBreaker.html http://developers.linecorp.com/blog/ja/?p=3684
  • 23.
    Circuit Breaker (cont’d) 1.decorator = 2. CircuitBreakerClient.newDecorator( 3. new CircuitBreakerBuilder("hello") 4. .failureRateThreshold(0.1).build()); 5. 6. new ClientBuilder("tbinary+http://...") 7. .decorator(ThriftCall.class, 8. ThriftReply.class, 9. decorator) 10. .build(HelloService.Iface.class);
  • 24.
    Circuit Breaker (cont’d) ●Circuit Breaker 開啟時會丟出 FailFastException ● 可以針對 host, method 等級去做設定 ○ newPerMethodDecorator ○ newPerHostDecorator ○ newPerHostAndMethodDecorator
  • 25.
    gRPC support ● https://github.com/line/armeria/pull/247 1.ServerBuilder sb = new ServerBuilder(); 2. sb.port(8080, SessionProtocol.HTTP); 3. GrpcService grpcService = new 4. GrpcServiceBuilder() 5. .addService(new HiService()) 6. .build(); 7. sb.serviceUnder("/", grpcService); 8. sb.build().start();
  • 26.
    ● 理應可以支援 HTTP/1,但現在沒有HTTP/1 client ● 尚未支援文件產生器 DocService ● 尚未支援 json 格式 ○ 類似 gRPC gateway gRPC support (cont’d)
  • 27.
    Retrofit with armeriaclient ● https://github.com/line/armeria/pull/297 1. HttpClient httpClient = 2. Clients.newClient(ClientFactory.DEFAULT, 3. "none+http://localhost:8080", 4. HttpClient.class); 5. Retrofit retrofit = 6. ArmeriaRetrofit.builder(httpClient) 7. .addConverterFactory(...) 8. .build(); 9. MyApi api = retrofit.create(MyApi.class);
  • 28.
    Armeria 之於應用程式開發者 ● 良好的抽象設計 ○一樣的方式模式建立 http/thrift client 和 server ● 方便的 decorator 模式 ○ 預設已提供 logging, metrics, tracing, circuit breaker… ○ 依需求可以用同模式實作各種功能 例如: ACL, rate-limiting...等等
  • 29.
  • 31.
  • 32.
    ● 內部與外部都從 thriftsocket client/server 實作切 換到 armeria ○ 內部都是 HTTP/2 ○ 外部則 HTTP/2 & HTTP/1 都有 ● 與 Spring MVC 共存 ● Circuit breaker ● Metrics + Zipkin tracing
  • 33.
    # of backendrequests
  • 38.
    但是... ● 非同步化不只是更換框架/程式庫就完成 ○ 非同步化的程式不易撰寫跟維護 ○我們使用 Dagger Producers 去實作非同步的相依 流程 ● server/client 都使用 armeria,記得不要使用 client 的 future#get 之類的阻塞方法 ○ event loop 下造成同 thread 等待跑在同個 thread 下的 task, 造成永遠阻塞
  • 39.
  • 40.
  • 41.