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