原文地址:http:///blog/2016/03/19/finagle1/
微服務(wù)架構(gòu)可能是時下最熱的一種架構(gòu)模式了. 這篇系列里, 我想介紹一些常用的微服務(wù)框架. 通過學(xué)習(xí)這些框架, 我們將會了解實現(xiàn)微服務(wù)的過程中會遇到哪些問題, 以及這些微服務(wù)框架是如何幫助我們解決這些問題的. 所以這是一篇關(guān)于微服務(wù)實踐的系列, 我不會討論太多概念性的東西. 系列末尾我會給出一些微服務(wù)架構(gòu)相關(guān)的鏈接, 感興趣的可以參考.
微服務(wù)不同于單一架構(gòu)應(yīng)用, 是典型的分布式場景, 各服務(wù)之間通過IPC進行通信. 實現(xiàn)微服務(wù)的過程中, 我們需要解決以下問題:
1. 服務(wù)注冊和服務(wù)發(fā)現(xiàn).
2. 根據(jù)應(yīng)用選擇合適的通信協(xié)議和數(shù)據(jù)協(xié)議. 例如可以選用thrift, protocol buffer或REST.
3. 服務(wù)負載均衡. 一個服務(wù)一般會部署多個實例. 如果使壓力均勻分布是需要考慮的問題.
4. 服務(wù)路由與限流.
5. 容錯處理. 相對于單機應(yīng)用, 分布式環(huán)境下錯誤發(fā)生的概率會大大提高, 服務(wù)宕機, 網(wǎng)絡(luò)不可用的情況時常發(fā)生.
6. 服務(wù)監(jiān)控. 各服務(wù)實例的性能指標, 例如請求響應(yīng)時間, 請求并發(fā)數(shù)量, 以及服務(wù)實例的部署數(shù)量等.
7. 事務(wù)一致性. 一般來說這個問題需要我們結(jié)合業(yè)務(wù)自己處理, 框架不會給我們太多幫助.
好的微服務(wù)框架應(yīng)該能幫助我們解決上面的全部或者大部分問題. 這里我選擇JVM上比較熱門的三個微服務(wù)框架: Finagle, spring Cloud(NetflixOSS), Dubbox. 我會從實例入手, 介紹這些框架的使用方式, 特點和適用場景.
首先來看Finagle. Finagle是Twitter在2011年開源的一款RPC框架,
在國外使用較多, 例如Pinterest, Nest, Tumblr, 感興趣的可以Google. Finagle有著較為豐富的生態(tài)圈, 例如可以使用Finch很方便的實現(xiàn)REST,
使用Finagle
OAuth2實現(xiàn)OAuth認證, 使用zipkin實現(xiàn)服務(wù)監(jiān)控.
Finagle使用Scala開發(fā), 官方宣稱同時支持scala和Java語言.
學(xué)習(xí)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)中的一種: 異步操作還沒有完成, 操作已經(jīng)完成了并包含了成功結(jié)果, 操作失敗并包含了異常結(jié)果. Future一種很常用的用法是可以注冊成功或失敗的回調(diào)函數(shù), 例如下面的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 上注冊了一個成功的回調(diào)函數(shù)和失敗的回調(diào)函數(shù),
當Future對應(yīng)的操作完成時, 會簡單的打印出結(jié)果或異常信息. 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
|
這段代碼首先根據(jù)email和password獲取user對象, 然后獲取user對應(yīng)的所有微博. 我解釋下這段代碼的執(zhí)行邏輯. 首先調(diào)用User.authenticate(email,
password) 方法進行用戶認證, 返回的對象是Future, 代表這是一個異步操作. 注意我們拿到的是Future, 這個時候我們還沒有真正的拿到user對象. 接下來flatMap方法就派上用場了. 在上面代碼中, flatMap函數(shù)簽名應(yīng)該是這樣的:
1
2
3
4
5
|
//Java8中并沒有這個函數(shù), 這里只是用來解釋概念.
//Java8中CompletableFuture的thenCompose方法類似于flatMap
Future<Seq<Tweet>> flatMap(Function<User, Future<Seq<Tweet>>) {
//...
}
|
簡單來說, flatMap的作用是將Future<A> 轉(zhuǎn)換成Future<B> ,
在這個例子里, 是將Future<User> 轉(zhuǎn)換成Future<Seq<Tweet>> .
通過flatMap這種方式, 我們的代碼寫起來很像是同步執(zhí)行的, 但是實際上Future中的操作是由一個叫做Scheduler的組件去執(zhí)行的, 你可以將Scheduler理解為一個ExecutorService, 即我們的代碼是由其他線程異步執(zhí)行的. 上面的代碼中, 當代碼執(zhí)行到#1 位置的時候,
其實認證用戶和獲取微博這兩個操作可能并沒有真正被執(zhí)行.
Future與flatMap的概念都來源于函數(shù)式編程. 在Haskell中, flatMap叫做綁定(bind), 而Future可以近似看作Monad(單子). 對函數(shù)式編程中的Monad感興趣的朋友可以參考我之前的文章.
Future還有其他一些很有用的方法, 例如從異常中恢復(fù)的rescue方法, 連接多個Future的join方法等, 這里就不展開了. Future在Finagle中無處不在, Finagle的設(shè)計哲理之一就是能異步的盡量異步, 大部分操作都不會阻塞. 例如下面我們要說的Service和Filter, 返回的結(jié)果都是Future. 如果你之前主要使用Spring或者Servlet這種技術(shù), 可能剛學(xué)習(xí)Finagle的時候覺得有些難以理解. 這很正常,
在后面的文章我會詳細介紹如何使用Future編程, 你會發(fā)現(xiàn)其實這種異步編程習(xí)慣與之前相比沒有太大的不同.只是ThreadLocal在這種環(huán)境下失效了, 不過好在我們有替代品 :)
2. Service
Service是Finagle中的核心概念. Service可以被理解為接收一個Request參數(shù), 返回一個Future對象的函數(shù). 如果定義為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中實現(xiàn)一個Echo服務(wù)器, 代碼如下:
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的請求和響應(yīng)進行過濾. 不過Finagle Filter使用類型參數(shù)明確定義了 輸入輸出的參數(shù)類型, 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這四個類型參數(shù)的定義, 可以參考下圖. 
ReqIn和ReqOut分別是Filter的入?yún)⒑统鰠? 而RepIn和RepOut則是Service的入?yún)⒑统鰠? 我們來看看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這種使用方式更加直觀, 并且不容易出錯.
現(xiàn)在我們已經(jīng)了解了Finagle的基本概念, 下一篇我將結(jié)合實例介紹如何使用Finagle進行開發(fā).
|