原文地址:http:///blog/2016/03/19/finagle1/
微服務架構可能是時下最熱的一種架構模式了. 這篇系列里, 我想介紹一些常用的微服務框架. 通過學習這些框架, 我們將會了解實現微服務的過程中會遇到哪些問題, 以及這些微服務框架是如何幫助我們解決這些問題的. 所以這是一篇關于微服務實踐的系列, 我不會討論太多概念性的東西. 系列末尾我會給出一些微服務架構相關的鏈接, 感興趣的可以參考.
微服務不同于單一架構應用, 是典型的分布式場景, 各服務之間通過IPC進行通信. 實現微服務的過程中, 我們需要解決以下問題:
1. 服務注冊和服務發(fā)現.
2. 根據應用選擇合適的通信協(xié)議和數據協(xié)議. 例如可以選用thrift, protocol buffer或REST.
3. 服務負載均衡. 一個服務一般會部署多個實例. 如果使壓力均勻分布是需要考慮的問題.
4. 服務路由與限流.
5. 容錯處理. 相對于單機應用, 分布式環(huán)境下錯誤發(fā)生的概率會大大提高, 服務宕機, 網絡不可用的情況時常發(fā)生.
6. 服務監(jiān)控. 各服務實例的性能指標, 例如請求響應時間, 請求并發(fā)數量, 以及服務實例的部署數量等.
7. 事務一致性. 一般來說這個問題需要我們結合業(yè)務自己處理, 框架不會給我們太多幫助.
好的微服務框架應該能幫助我們解決上面的全部或者大部分問題. 這里我選擇JVM上比較熱門的三個微服務框架: Finagle, spring Cloud(NetflixOSS), Dubbox. 我會從實例入手, 介紹這些框架的使用方式, 特點和適用場景.
首先來看Finagle. Finagle是Twitter在2011年開源的一款RPC框架,
在國外使用較多, 例如Pinterest, Nest, Tumblr, 感興趣的可以Google. Finagle有著較為豐富的生態(tài)圈, 例如可以使用Finch很方便的實現REST,
使用Finagle
OAuth2實現OAuth認證, 使用zipkin實現服務監(jiān)控.
Finagle使用Scala開發(fā), 官方宣稱同時支持scala和Java語言.
學習Finagle的使用之前, 首先要了解Finagle中的三個核心概念: Future, Service, Filter.
1. Future
Finagle使用的Future是com.twitter.util.Future .
由于Future非常實用, 從Scala2.10開始被加入到官方庫scala.concureent.Future .
Java8中也引入了一個類似的接口java.util.concurrent.CompletableFuture .
Future是對異步操作的抽象, 你可以將Future理解為一個容器, 這個容器包含一個異步操作. 一個Future容器可能處于三個狀態(tài)中的一種: 異步操作還沒有完成, 操作已經完成了并包含了成功結果, 操作失敗并包含了異常結果. Future一種很常用的用法是可以注冊成功或失敗的回調函數, 例如下面的Java代碼:
1
2
3
4
5
6
7
8
9
|
responseFuture.onSuccess(func(response -> {
System.out.println(String.format("response status: %s, response string: %s",
response.status().toString(), response.contentString()));
return BoxedUnit.UNIT;
}));
responseFuture.onFailure(func(e -> {
System.out.println("error: " + e.toString());
return BoxedUnit.UNIT;
}));
|
我在responseFuture 上注冊了一個成功的回調函數和失敗的回調函數,
當Future對應的操作完成時, 會簡單的打印出結果或異常信息. Future另外一個十分強大的用法是組合.例如下面的Java代碼:
1
2
3
4
5
|
Future<User> authenticatedUser = User.authenticate(email, password)
Future<Seq<Tweet>> lookupTweets = authenticatedUser.flatMap(user -> Tweet.findAllByUser(user))
//#1
|
這段代碼首先根據email和password獲取user對象, 然后獲取user對應的所有微博. 我解釋下這段代碼的執(zhí)行邏輯. 首先調用User.authenticate(email,
password) 方法進行用戶認證, 返回的對象是Future, 代表這是一個異步操作. 注意我們拿到的是Future, 這個時候我們還沒有真正的拿到user對象. 接下來flatMap方法就派上用場了. 在上面代碼中, flatMap函數簽名應該是這樣的:
1
2
3
4
5
|
//Java8中并沒有這個函數, 這里只是用來解釋概念.
//Java8中CompletableFuture的thenCompose方法類似于flatMap
Future<Seq<Tweet>> flatMap(Function<User, Future<Seq<Tweet>>) {
//...
}
|
簡單來說, flatMap的作用是將Future<A> 轉換成Future<B> ,
在這個例子里, 是將Future<User> 轉換成Future<Seq<Tweet>> .
通過flatMap這種方式, 我們的代碼寫起來很像是同步執(zhí)行的, 但是實際上Future中的操作是由一個叫做Scheduler的組件去執(zhí)行的, 你可以將Scheduler理解為一個ExecutorService, 即我們的代碼是由其他線程異步執(zhí)行的. 上面的代碼中, 當代碼執(zhí)行到#1 位置的時候,
其實認證用戶和獲取微博這兩個操作可能并沒有真正被執(zhí)行.
Future與flatMap的概念都來源于函數式編程. 在Haskell中, flatMap叫做綁定(bind), 而Future可以近似看作Monad(單子). 對函數式編程中的Monad感興趣的朋友可以參考我之前的文章.
Future還有其他一些很有用的方法, 例如從異常中恢復的rescue方法, 連接多個Future的join方法等, 這里就不展開了. Future在Finagle中無處不在, Finagle的設計哲理之一就是能異步的盡量異步, 大部分操作都不會阻塞. 例如下面我們要說的Service和Filter, 返回的結果都是Future. 如果你之前主要使用Spring或者Servlet這種技術, 可能剛學習Finagle的時候覺得有些難以理解. 這很正常,
在后面的文章我會詳細介紹如何使用Future編程, 你會發(fā)現其實這種異步編程習慣與之前相比沒有太大的不同.只是ThreadLocal在這種環(huán)境下失效了, 不過好在我們有替代品 :)
2. Service
Service是Finagle中的核心概念. Service可以被理解為接收一個Request參數, 返回一個Future對象的函數. 如果定義為Java的抽象類, 原型如下:
1
2
3
4
5
6
|
//Service在Finagle中是用Scala代碼定義的, 這里只是用來解釋概念.
public abstract class Service<Request, Response> {
public abstract Future<Response> apply(Request r);
}
|
如果用Spring MVC類比, Finagle的Service就類似于Controller的方法, 可以用來處理客戶端的請求. 例如要在Finagle中實現一個Echo服務器, 代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class Server extends Service<Request, Response> {
@Override
public Future<Response> apply(Request request) {
Response response = Response.apply(Version.Http11$.MODULE$, Status.Ok());
response.setContentString(request.getContentString());
return Future.value(response);
}
public static void main(String[] args) throws Exception {
Server service = new Server();
ListeningServer server = Http.server().
withLabel("echo-server").
serve(new InetSocketAddress(8081), service);
Await.result(server);
}
}
|
注意Service的返回值是Future, 代表操作可以是異步完成的.
3. Filter
Finagle Filter類似于Servlet Filter, 可以對Service的請求和響應進行過濾. 不過Finagle Filter使用類型參數明確定義了 輸入輸出的參數類型, Finagle Filter如果定義為Java的抽象類, 原型如下:
1
2
3
4
5
6
|
//Filter在Finagle中是用Scala代碼定義的, 這里只是用來解釋概念.
public abstract class Filter<ReqIn, RepOut, ReqOut, RepIn> {
public abstract Future<RepOut> apply(ReqIn request, Service<ReqOut, RepIn> service);
}
|
對于ReqIn, RepOut, ReqOut, RepIn這四個類型參數的定義, 可以參考下圖. 
ReqIn和ReqOut分別是Filter的入參和出參, 而RepIn和RepOut則是Service的入參和出參. 我們來看看Filter在代碼中的實際用法:
1
2
3
4
5
6
7
8
9
10
|
val baseService = new Service[HttpRequest, HttpResponse] {
def apply(request: HttpRequest) =
Future(new DefaultHttpResponse(HTTP_1_1, OK))
}
val authorize = new RequireAuthorization(…)
val handleExceptions = new HandleExceptions(...)
val decoratedService: Service[HttpRequest, HttpResponse] =
handleExceptions andThen authorize andThen baseService
|
我們定義了一個Service對象baseService, 兩個Filter對象authorize和handleExceptions. 通過filter的andThen方法, 我們能夠很簡單的將Filter和Service組裝到一起, 這有點類似于在web.xml中定義了一個Servlet, 以及兩個Filter來攔截針對Servlet的請求. 不過毫無疑問Finagle這種使用方式更加直觀, 并且不容易出錯.
現在我們已經了解了Finagle的基本概念, 下一篇我將結合實例介紹如何使用Finagle進行開發(fā).
|