原文地址:http://skaka.me/blog/2016/05/01/finagle2/
在上篇文章中我介紹了Finagle中的Future/Service/Filter. 這篇文章裏, 我們將構建一個基於Http協議的echo服務端和客戶端, 下篇文章將構建一個基於thrift協議的客戶端和服務端. 這兩篇文章對應的源代碼地址在Github. 代碼中有Java和Scala版本兩套版本的實現, 但是這裏我只會介紹Java版本.
首先來看echo應用的Server端代碼, 打開java-finagle-example/src/main/java/com/akkafun/finagle/Server.java
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
|
-
在Finagle中, 實現一個RPC服務非常簡單. 只需要繼承Service抽象類, 實現它的apply方法. Service抽象類有兩個類型參數, 第一個類型參數代表的是請求對象, 第二個類型參數代表的是返回對象. 這兩個對象的具體類型與Service實現類使用的具體協議有關. 例如我們在echo服務中使用Http協議, 對應的Request類就是
com.twitter.finagle.http.Request
, 對應的Response類是com.twitter.finagle.http.Response
. 如果是thrift協議, 則這兩個類型參數在Service實現類中都是scala.Array<scala.Byte>
(Array和Byte都是scala中的類, 對應Java中的數組與byte). -
apply方法中, 我們首先使用Response的工廠方法構造一個Response對象. 然後將Request中的請求內容原封不動的設置到Response中, 再將Response設置到Future中返回. 需要最後一步的原因是apply方法的返回值類型是
Future<Response>
, 但是我們在這個方法中不需要進行異步操作, 所以可以直接使用Future.value(response)
將對象包裝成Future返回. 另外, 細心的你應該發現了一行比較礙眼的代碼:Response.apply(Version.Http11$.MODULE$, Status.Ok())
, 其中Version的用法很古怪. 這是Java調用Scala伴生對象的副作用, Scala有一些語法和特性在Java中沒有對應的概念, 這種情況下Java調用Scala的代碼就會比較晦澀. -
爲了啓動Service實例, 我們需要構造一個
com.twitter.finagle.ListeningServer
.withLabel
設置服務名稱,withTracer
設置監控信息, 這個等後面介紹zipkin的時候在解釋. 最後指定端口啓動服務.
現在來看echo應用的Client端代碼, 打開java-finagle-example/src/main/java/com/akkafun/finagle/Client.java
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
|
-
這部分代碼和我們之前的Server類代碼很像. 在Server類中, 我們創建了一個Service實例並監聽了8081端口, 現在客戶端通過newService創建了一個Service的stub.
-
這部分代碼用來構造一個消息內容爲Greetings的Http請求.
-
service.apply(request)
就是一次客戶端到服務端的RPC調用. 這個調用的返回值是Future<Response>
.
而service.apply(request)
是一個異步操作, 主線程調用這個方法並不會阻塞, 有可能主線程退出了實際調用還沒有完成. 所以這裏就要用到Await.ready
了.Await.ready
的作用是等待一個Future執行完成再返回, 是一個同步操作. 通過調用Await.ready
我們就能將一個異步操作轉化成一個同步操作. -
接下來我們在Future上註冊請求成功與失敗的回調函數. 請求成功的回調函數中只是簡單的打印出響應的消息內容.
這裏有個細節需要說明一下. Future的onSuccess方法需要傳入一個Scala的函數特質:scala.Function1[Response, BoxedUnit]
. 如果是Java6或7, 我們可以這樣實現這個特質:
1
2
3
4
5
6
7
8
|
|
在Java8中, 這種匿名類我們一般會使用Lambda代替, 理想情況下寫法是這樣:
1
2
3
4
5
|
|
可惜的是這種寫法編譯不會通過, 因爲只有符合FunctionalInterface
定義的接口才能使用Lambda表達式(什麼是FunctionalInterface
,
請參考Javadoc),
而在Scala2.11中, scala.Function1
不是一個FunctionalInterface
(Scala2.12會兼容Java8).
爲了在這裏使用Lambda, 我們使用了scala-java8-compat這個庫,
調用scala.compat.java8.JFunction.func
方法將一個FunctionalInterface
轉化成scala.Function1
.
可以看出, 在Java中調用Finagle的API不是很方便. 所以Finagle適合以Scala爲主, Java爲輔的項目. 如果項目全是Java, 則值得爲Finagle主要的API寫一層Java的適配層, 來屏蔽Java調用Scala代碼會出現的一些晦澀代碼.
現在我們啓動服務端和客戶端來看看運行結果. 首先啓動Server類, 然後啓動Client. Client運行完畢自動結束, 你應該能在Client的控制檯看到如下輸出:
1
|
|
Server控制檯的輸出:
1
|
|
Http協議比較適合用於對外提供服務, 並且一般會使用REST. 在Finagle中使用REST可以使用Finch庫. 這個庫輕量小巧, API簡單, 提供了一套很方便的對Http消息進行操作的DSL. 如果是內網服務調用, 一般推薦使用結構緊湊, 傳輸效率高的協議. 比如protocol buffer, thrift或Avro. Finagle對thrift有很好的支持, 下篇文章我將介紹在Finagle中如何開發thrift應用.