Finagle:一个支持多协议的RPC系统

标签: 搜索引擎 | 发表时间:2012-03-01 15:37 | 作者:黄言之
出处:http://blog.sina.com.cn/netreview
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[HttpRequest, HttpResponse] {
  def apply(request: HttpRequest) =
    Future(new DefaultHttpResponse(HTTP_1_1, OK))
}

val address = new InetSocketAddress(10000)

val server: Server[HttpRequest, HttpResponse] = ServerBuilder()
  .name("MyWebServer")
  .codec(Http())
  .bindTo(address)
  .build(service)
建立一个 HTTP 客户端就更简单了:
val client: Service[HttpRequest, HttpResponse] = 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[AuthenticatedRequest, HttpResponse]
  ) = {
      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 日

相关 [finagle 协议 rpc] 推荐:

Finagle:一个支持多协议的RPC系统

- - 互联网旁观者
Finagle是一个协议不可知的,异步的,用于 JVM 的 RPC 系统,它使得在 Java、Scala 或任何基于 JVM 的语言重构建鲁棒的客户端和服务器非常容易. 在 Twitter.com 上面即使是渲染最简单的网页也需要十多个说着不同协议的网络服务的合作. 比如,为了渲染首页,应用程序需要向社交网络图(Social Graph)服务、Memcached、数据库、以及许多其它网络服务发出请求.

基于dubbo框架下的RPC通讯协议性能测试 - lengfo

- - 博客园_首页
  Dubbo RPC服务框架支持丰富的传输协议、序列化方式等通讯相关的配置和扩展. 更多关于dubbo详细介绍请参照官方文档( http://alibaba.github.io/dubbo-doc-static/Home-zh.htm).   1、支持常见的传输协议:RMI、Dubbo、Hessain、WebService、Http等,其中Dubbo和RMI协议基于TCP实现,Hessian和WebService基于HTTP实现.

Hadoop RPC机制

- - 企业架构 - ITeye博客
RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议. Hadoop底层的交互都是通过 rpc进行的. 例如:datanode和namenode 、tasktracker和jobtracker、secondary namenode和namenode之间的通信都是通过rpc实现的.

Storm Akka Finagle对比及使用场景分析

- - CSDN博客云计算推荐文章
本文翻译自: http://blog.samibadawi.com/2013/04/akka-vs-finagle-vs-storm.html. Storm Akka Finagle对比及使用场景分析. Storm、Akka、Finagle是三个开源的分布式并行处理框架,都基于JVM运行. 他们在解决下面这些问题上十分有用:.

JAVA RPC 通讯框架

- - 经验沉淀 知识结晶
Bison 是一个JAVA 程间的通信框架,基于apache mina 实现,对mina进行了byteBuffer 缓冲区重用以及半包出处时减少拷贝. 客户端(bison-client) 功能点. 2 支持高用性:高可用的一个基本原则,可以接受快速的失败,但不能接受长时间的等待. Githup地址:https://github.com/gavenpeng/Bison.

Avro RPC 对比测试

- - 行业应用 - ITeye博客
J2EE平台常采用多层分布式的架构体系. 分布式服务节点之间需要通讯和交互(业务节点和资源节点之间),服务端和客户端需要交互(终端客户端需要调用服务端的远程服务,客户端有C实现的,也有Java等其他语言实现的). 因此基础平台需要提供一个稳定、高效的、可伸缩的RPC服务性组件. 稳定,高性能;作为一个基础性的骨架组件,高可用性和高性能是必备的;传输层希望是面向连接的TCP通信.

RPC原理详解 - 永志

- - 博客园_首页
RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性.  为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用. 下面我们将具体细化 stub 结构的实现. 客户方等待调用执行完成并返回结果. 客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果.

zmq-rpc:基于zeromq网络层编写的protobuf RPC框架

- Shengbin - codedump
阅读过zmq的代码之后,感觉这个网络层是我目前见过最高效的–线程之间使用lockfree的消息队列保存消息,可以启动多个I/O线程分担压力等等特性.于是决定基于它写一个protobuf RPC的框架.. 另外,这里使用的protobuf是旧版本2.3.0,新版本2.4.1的生成的RPC service接口跟原来不太一致,暂时还没有去研究它.BTW,升级版本之后导致原来的接口发生变化这是一个很操蛋的事情..

【RPC框架HttpInvoker一】HttpInvoker:Spring自带RPC框架

- - 开源软件 - ITeye博客
HttpInvoker是Spring原生的RPC调用框架,HttpInvoker同Burlap和Hessian一样,提供了一致的服务Exporter以及客户端的服务代理工厂Bean,这篇文章主要是复制粘贴了Hessian与Spring集成一文,. 【RPC框架Hessian四】Hessian与Spring集成.

集成libevent,google protobuf的RPC框架

- goodman - C++博客-那谁的技术博客
chenshuo的evproto同样也是集成libevent与google protobuf的RPC框架,不过在对libevent的使用上,这里的做法与他不尽相同:. 1) 他使用了libevent自带的RPC功能, 而这里只使用到libevent对网络I/O进行的封装的最基本的功能.. eventrpc项目目前是avidya下的一个子项目,avidya项目的定位是实现一些分布式的玩具系统(比如google已经公开论文的chubby,mapreduce,GFS等),也许以后不一定能被用上,但是也要实践做一把.由于有一个好用的RPC框架是做分布式的必需品,所有首先实现eventrpc这个子项目了,以后也许还会实现其他语言的版本,如python,java..