Finagle 一個支持多協議的RPC系統

Finagle是一個協議不可知的,異步的,用於 JVM 的 RPC 系統,它使得在 Java、Scala 或任何基於 JVM 的語言重構建魯棒的客戶端和服務器非常容易。


在 Twitter.com 上面即使是渲染最簡單的網頁也需要十多個說着不同協議的網絡服務的合作。比如,爲了渲染首頁,應用程序需要向社交網絡圖(Social Graph)服務、Memcached、數據庫、以及許多其它網絡服務發出請求。他們每個都使用不同的協議:Thrift、Memcached、 MySQL等等。此外,這些服務之間還相互交談——他們既是服務器又是客戶端。比如,社交網絡圖服務就提供了一個 Thrift 接口,但是它也從一個 MySQL 集羣裏面獲取信息。

在這樣一個系統裏面,服務中斷最常見的原因就是這些部件之間在發生故障的時候糟糕的交互;常見的故障包括崩潰的主機和極高的時延差異。這些故障可以通過讓工作隊列任務堆積、TCP 連接攪動(churn)、耗光內存和文件描述符等方式在系統裏面疊加起來。在最糟的情況下,用戶就會看到失敗鯨

構造一個穩定的分佈式系統的挑戰

複雜的網絡服務器和客戶端有很多活動部件:故障檢測器、負載平衡器、失效備援策略(failover strategy)等等。這些部件之間需要達到一種精緻的平衡,以便對大型產品系統裏面的故障有足夠的彈性。

故障檢測器、負載平衡器等部件的不同協議的很多不同實現使得這個任務變得尤其困難。比如,Thrift 的背壓(back pressure)策略就和 HTTP 的不同。在事故的時候確保在這種異構系統上的覆蓋率非常具有挑戰性。

我們的方法

我們設計了一個能夠用於我們所有協議的基本網絡服務器和客戶端組件的單一實現Finagle 是一個協議不可知的、異步的、用於 Java 虛擬機的遠程過程調用(RPC)系統,它可能讓在 Java、Scala或任何基於 JVM 的語言上構建魯棒的客戶端和服務器變得很容易。Finagle 支持廣泛的基於請求/答覆的 RPC 協議和很多類型的流協議。

Finagle 提供了以下功能的魯棒實現:

  • 連接池(connection pool):帶有限流(throttling)支持以防止 TCP 連接攪動(churn);
  • 故障檢測器(failure detector),用於識別太慢或者崩潰了的主機;
  • 失效備援策略(failover strategies),用於把流量從不健康的主機上引開;
  • 平衡負載器(load-balancer),包括“最少連接”和其它策略;以及
  • 背壓(back-pressure)技術,用於保護服務器免受客戶端濫用或者疊羅漢(或DoS攻擊)。
此外,Finagle 還讓構造和部署下列服務變得容易:
  • 發佈標準統計信息、日誌和異常報告;
  • 支持跨協議的分佈式追蹤(以 Dapper 形式);
  • 選擇性地使用 ZooKeeper 用於集羣管理;以及
  • 支持常見切分(sharding)策略。
我們相信我們的工作是卓有成效的——我們現在能夠非常輕鬆、安全地編寫和部署一個網絡服務了。

Twitter 裏的 Finagle

今天,Finagle 已經部署到了 Twitter 多個前端和後端的運行產品中,包括我們的 URL 爬蟲(crawler)和 HTTP 代理。我們計劃更廣泛地部署 Finagle。
一個基於 Finagle 的體系結構 (開發中)

上圖展示了一個全面使用 Finagle 的未來體系結構。比如,User Service 是一個使用 Finalge Memcached 客戶端的 Finagle 服務器,並和 Finagle Kestrel Service 交談。

Finagle 如何工作

Finagle 非常靈活且易於使用,因爲它是構造在幾個簡單的、可組合的基本元素上:Future,Services,以及 Filters。

Future 對象

在 Finagle 中,Future 對象是對於所有異步計算的統一抽象。一個 Future 表示了一個尚未完成的計算,其可能成功也可能失敗。使用 Future 兩個最基本的方法是:
  • 阻塞並等待計算結束返回
  • 註冊一個回調函數,在計算最終成功或失敗時 Future 回調
如果任務需要在計算結束之後繼續異步執行,你可以指定一個成功回調函數和一個失敗回調函數。回調函數通過 onSuccess 和 onFailure 函數註冊:
val request: HttpRequest =
  new DefaultHttpRequest(HTTP_1_1, GET, "/")
val responseFuture: Future[HttpResponse] = client(request)

responseFuture onSuccess { responseFuture =>
  println(responseFuture)
} onFailure { exception =>
  println(exception)
}
組合 Future

Future 可以以有趣的方式組合或者轉換,從而做到一些常常在函數式程序設計裏面看到的組合行爲。比如,你可以通過 map 把一個 Future[String] 轉換成 Future[Int]:
val stringFuture: Future[String] = Future("1")
val intFuture: Future[Int] = stringFuture map { string =>
  string.toInt
}
類似地,你還可以用 flatMap 把一系列 Future 串成一個流水線:
val authenticatedUser: Future[User] =
  User.authenticate(email, password)

val lookupTweets: Future[Seq[Tweet]] =
  authenticatedUser flatMap { user =>
    Tweet.findAllByUser(user)
  }

在這個例子裏面,User.authenticate() 是異步執行的;Tweet.findAllByUser() 在最終結果上被調用。在 Scala 裏面這可以用另一種方式表達,用 for 語句:
for {
  user <- User.authenticate(email, password)
  tweets <- Tweet.findAllByUser(user)
} yield tweets
當用 flatMap 或者 for 語句串聯 Future 的時候,處理錯誤和異常也非常簡單。在上面的例子中,ifUser.authenticate() 異步地拋出了一個異常,接下來對於 Tweet.findAllByUser() 的調用永遠也不會發生。取而代之,流水線的結果表達式仍然是 Future[Seq[Tweet]] 類型,但是它含有異常值而不是推文。你可以使用 onFailure 回調函數或者其他組合計數來處理異常。

和其它異步編程技術(比如 CPS:Continuation-Passing Style)相比,Future 有一個很好的性質,就是你可以更容易的編寫出清楚且魯棒的異步代碼,即使是帶有複雜的散佈/收集(scatter/gather)操作:
val severalFutures = Seq[Future[Int]] =
  Seq(Tweet.find(1), Tweet.find(2), ...)
val combinedFuture: Future[Seq[Int]] =
  Future.collect(severalFutures)

Service 對象

Service 是一個函數,其接受一個請求,返回一個 Future 對象作爲答覆。注意客戶端和服務器都是用 Service 對象表示的。

要創建一個 Service 對象,你需要繼承抽象的 Service 類並監聽一個端口。下面是一個簡單的 HTTP 服務器,監聽端口 10000:
val service = new Service[HttpRequestHttpResponse] {
  def apply(request: HttpRequest) =
    Future(new DefaultHttpResponse(HTTP_1_1, OK))
}

val address = new InetSocketAddress(10000)

val server: Server[HttpRequestHttpResponse] = ServerBuilder()
  .name("MyWebServer")
  .codec(Http())
  .bindTo(address)
  .build(service)
建立一個 HTTP 客戶端就更簡單了:
val client: Service[HttpRequestHttpResponse] = ClientBuilder()
  .codec(Http())
  .hosts(address)
  .build()

// Issue a request, get a response:
val request: HttpRequest =
  new DefaultHttpRequest(HTTP_1_1, GET, "/")

client(request) onSuccess { response =>
  println("Received response: " + response)
}

Filter 對象

Filter 是一種把你的應用程序中不同的階段的孤立出來組成一個流水線的有用的方式。比如,你可能需要在你的 Service 開始接受請求前處理異常、授權等問題。

一個 Filter 包裹了一個 Service,且潛在地,把 Service 的輸入和輸出類型轉換成其它類型。換一句話說,Filter 是一個轉換器。下面是一個用來確保一個 HTTP 請求有合法的 OAuth 證書的、且使用一個異步的認證服務的 filter。

下面是一個修飾了 Service 的Filter:
class RequireAuthentication(a: Authenticator) extends Filter[...] {
  def apply(
    request: Request,
    continue: Service[AuthenticatedRequestHttpResponse]
  ) = {
      a.authenticate(request) flatMap {
        case AuthResult(OK, passport) =>
          continue(AuthenticatedRequest(request, passport))
        case AuthResult(Error(code)) =>
          Future.exception(new RequestUnauthenticated(code))
    }
  }
}
Finagle 是一個開源項目,使用 Apache License, Version 2.0。源代碼和文檔都可以在 GitHub 上找到。

鳴謝

Finagle 最早構思來自 Marius Eriksen 和 Nick Kallen。其它主要貢獻人員有 Arya Asemanfar, David Helder, Evan Meagher, Gary McCue, Glen Sanford, Grant Monroe, Ian Ownbey, Jake Donham, James Waldrop, Jeremy Cloud, Johan Oskarsson, Justin Zhu, Raghavendra Prabhu, Robey Pointer, Ryan King, Sam Whitlock, Steve Jenson, Wanli Yang, Wilhelm Bierbaum, William Morgan, Abhi Khune, and Srini Rajagopal。

原文鏈接:http://engineering.twitter.com/2011/08/finagle-protocol-agnostic-rpc-system.html
原文發表日期:2011 年 8 月 19 日
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章