QCon北京「鸿蒙专场」火热来袭!即刻报名,与创新同行~ 了解详情
写点什么

Twitter 的 RPC 框架 Finagle 简介

  • 2014-05-30
  • 本文字数:4138 字

    阅读完需:约 14 分钟

Finagle 是 Twitter 基于 Netty 开发的支持容错的、协议无关的 RPC 框架,该框架支撑了 Twitter 的核心服务。来自 Twitter 的软件工程师 Jeff Smick撰文详细描述了该框架的工作原理和使用方式

在Jeff Smick 的博客文章中,介绍了Twitter 的架构演进历程。Twitter 面向服务的架构是由一个庞大的Ruby on Rails 应用转化而来的。为了适应这种架构的变化,需要有一个高性能的、支持容错的、协议无关且异步的RPC 框架。在面向服务的架构之中,服务会将大多数的时间花费在等待上游服务的响应上,因此使用异步的库能够让服务并发地处理请求,从而充分发挥硬件的潜能。Finagle 构建在Netty 之上,并不是直接在原生NIO 之上构建的,这是因为Netty 已经解决了许多Twitter 所遇到的问题并提供了干净整洁的API。

Twitter 构建在多个开源协议之上,包括 HTTP、Thrift、Memcached、MySQL 以及 Redis。因此,网络栈需要足够灵活,以保证能与这些协议进行交流并且还要具有足够的可扩展性以支持添加新的协议。Netty 本身没有与任何特定的协议绑定,对其添加协议支持非常简单,只需创建对应的事件处理器(event handler)即可。这种可扩展性产生了众多社区驱动的协议实现,包括 SPDY 、PostrgreSQL、WebSockets、IRC 以及 AWS。

Netty 的连接管理以及协议无关性为 Finagle 奠定了很好的基础,不过 Twitter 的有些需求是 Netty 没有原生支持的,因为这些需求都是“较高层次的”。比如,客户端需要连接到服务器集群并且要进行负载均衡。所有的服务均需要导出指标信息(如请求率、延迟等),这些指标为调试服务的行为提供了有价值的内部信息。在面向服务架构中,一个请求可能会经过数十个服务,所以如果没有跟踪框架的话,调试性能问题几乎是不可能的。为了解决这些问题,Twitter 构建了 Finagle。简而言之,Finagle 依赖于 Netty 的 IO 多路复用技术(multiplexing),并在 Netty 面向连接的模型之上提供了面向事务(transaction-oriented)的框架。

Finagle**** 的工作原理

Finagle 强调模块化的理念,它会将独立的组件组合在一起。每个组件可以根据配置进行替换。比如,所有的跟踪器(tracer)都实现了相同的接口,这样的话,就可以创建跟踪器将追踪数据存储到本地文件、保持在内存中并暴露为读取端点或者将其写入到网络之中。

在 Finagle 栈的底部是 Transport,它代表了对象的流,这种流可以异步地读取和写入。Transport 实现为 Netty 的 ChannelHandler,并插入到 ChannelPipeline 的最后。当 Finagle 识别到服务已经准备好读取数据时,Netty 会从线路中读取数据并使其穿过 ChannelPipeline,这些数据会被 codec 解析,然后发送到 Finagle 的 Transport。从这里开始,Finagle 将数据发送到自己的栈之中。

对于客户端的连接,Finagle 维持了一个 Transport 的池,通过它来平衡负载。根据所提供的连接池语义,Finagle 可以向 Netty 请求一个新的连接,也可以重用空闲的连接。当请求新的连接时,会基于客户端的 codec 创建一个 Netty ChannelPipeline。一些额外的 ChannelHandler 会添加到 ChannelPipeline 之中,以完成统计(stats)、日志以及 SSL 的功能。如果所有的连接都处于忙碌的状态,那么请求将会按照所配置的排队策略进行排队等候。

在服务端,Netty 通过所提供的 ChannelPipelineFactory 来管理 codec、统计、超时以及日志等功能。在服务端 ChannelPipeline 中,最后一个 ChannelHandler 是 Finagle 桥(bridge)。这个桥会等待新进入的连接并为每个连接创建新的 Transport。Transport 在传递给服务器实现之前会包装一个新的 channel。然后,会从 ChannelPipeline 之中读取信息,并发送到所实现的服务器实例中。

  1. Finagle 客户端位于 Finagle Transport 之上,这个 Transport 为用户抽象了 Netty;
  2. Netty ChannelPipeline 包含了所有的 ChannelHandler 实现,这些实现完成实际的工作;
  3. 对于每一个连接都会创建 Finagle 服务器,并且会为其提供一个 Transport 来进行读取和写入;
  4. ChannelHandler 实现了协议的编码 / 解码逻辑、连接级别的统计以及 SSL 处理。

桥接Netty与 ****Finagle

Finagle 客户端使用 ChannelConnector 来桥接 Finagle 与 Netty。ChannelConnector 是一个函数,接受 SocketAddress 并返回 Future Transport。当请求新的 Netty 连接时,Finagle 使用 ChannelConnector 来请求一个新的 Channel,并使用该 Channel 创建 Transport。连接会异步建立,如果连接成功的话,会使用新建立的 Transport 来填充 Future,如果无法建立连接的话,就会产生失败。Finagle 客户端会基于这个 Transport 分发请求。

Finagle 服务器会通过 Listener 绑定一个接口和端口。当新的连接创建时,Listener 创建一个 Transport 并将其传入一个给定的函数。这样,Transport 会传给 Dispatcher,它会根据指定的策略将来自 Transport 的请求分发给 Service。

Finagle**** 的抽象

Finagle 的核心概念是一个简单的函数(在这里函数式编程很关键),这个函数会从 Request 生成包含 Response 的 Future:

复制代码
type Service[Req, Rep] = Req => Future[Rep]

Future 是一个容器,用来保存异步操作的结果,这样的操作包括网络 RPC、超时或磁盘的 I/O 操作。Future 要么是空——此时尚未有可用的结果,要么成功——生成者已经完成操作并将结果填充到了 Future 之中,要么失败——生产者发生了失败,Future 中包含了结果异常。

这种简单性能够促成很强大的结构。在客户端和服务器端,Service 代表着相同的 API。服务器端实现 Service 接口,这个服务器可以用来进行具体的测试,Finagle 也可以将其在某个网络接口上导出。客户端可以得到 Service 的实现,这个实现可以是虚拟的,也可以是远程服务器的具体实现。

比如说,我们可以通过实现 Service 创建一个简单的 HTTP Server,它接受 HttpReq 并返回代表最终响应的 Future[HttpRep]:

复制代码
val s: Service[HttpReq, HttpRep] = new Service[HttpReq, HttpRep] {
def apply(req: HttpReq): Future[HttpRep] =
Future.value(HttpRep(Status.OK, req.body))
}
<p>Http.serve(":80", s)</p>

这个样例在所有接口的 80 端口上暴露该服务器,并且通过 twitter.com 的 80 端口进行使用。但是,我们也可以选择不暴露服务器而是直接使用它:

复制代码
server(HttpReq("/")) map { rep => transformResponse(rep) }

在这里,客户端代码的行为方式是一样的,但是并不需要网络连接,这就使得客户端和服务器的测试变得很简单直接。

客户端和服务器提供的都是应用特定的功能,但通常也会需要一些与应用本身无关的功能,举例来说认证、超时、统计等等。Filter 为实现应用无关的特性提供了抽象。

Filter 接受一个请求以及要进行组合的 Service:

复制代码
type Filter[Req, Rep] = (Req, Service[Req, Rep]) => Future[Rep]

在应用到 Service 之前,Filter 可以形成链:

复制代码
recordHandletime andThen
traceRequest andThen
collectJvmStats
andThen myService

这样的话,就能够很容易地进行逻辑抽象和关注点分离。Finagle 在内部大量使用了 Filter,Filter 有助于促进模块化和可重用性。

Filter 还可以修改请求和响应的数据及类型。下图展现了一个请求穿过过滤器链到达 Service 以及响应反向穿出的过程:

对请求失败的管理

在扩展性的架构中,失败是常见的事情,硬件故障、网络阻塞以及网络连接失败都会导致问题的产生。对于支持高吞吐量和低延迟的库来说,如果它不能处理失败的话,那这样库是没有什么意义的。为了获取更好的失败管理功能,Finagle 在吞吐量和延迟上做了一定的牺牲。

Finagle 可以使用主机集群实现负载的平衡,客户端在本地会跟踪它所知道的每个主机。它是通过计数发送到某个主机上的未完成请求做到这一点的。这样的话,Finagle 就能将新的请求发送给最低负载的主机,同时也就能获得最低的延迟。

如果发生了失败的请求,Finagle 会关闭到失败主机的连接,并将其从负载均衡器中移除。在后台,Finagle 会不断地尝试重新连接,如果 Finagle 能够重新建立连接的话,就会再次将其添加到负载均衡器之中。

服务的组合

Finagle 将服务作为函数的理念能够编写出简单且具有表述性的代码。例如,某个用户对其时间线(timeline)的请求会涉及到多个服务,核心包括认证服务、时间线服务以及 Tweet 服务。它们之间的关系可以很简洁地进行表述:

复制代码
val timelineSvc = Thrift.newIface[TimelineService](...) // #1
val tweetSvc = Thrift.newIface[TweetService](...)
val authSvc = Thrift.newIface[AuthService](...)
val authFilter = Filter.mk[Req, AuthReq, Res, Res] { (req, svc) => // #2
authSvc.authenticate(req) flatMap svc(_)
}
val apiService = Service.mk[AuthReq, Res] { req =>
timelineSvc(req.userId) flatMap {tl =>
val tweets = tl map tweetSvc.getById(_)
Future.collect(tweets) map tweetsToJson(_) }
}
} //#3
Http.serve(":80", authFilter andThen apiService) // #4
// #1 创建每个服务的客户端
// #2 创建过滤器来认证传入的请求
// #3 创建服务,将已认证的时间线请求转换为 json 响应
// #4 使用认证过滤器和服务,在 80 端口启动新的 HTTP 服务器

在上面的例子中,创建了时间线服务、Tweet 服务以及认证服务的客户端,创建了一个 Filter 来认证原始的请求,最后,实现了服务,将其与认证过滤器组合起来,并暴露在 80 端口上。

当收到请求时,认证过滤器会尝试进行认证,如果失败的话就会立即返回并不会影响到核心服务。认证成功后,AuthReq 将会发送到 API 服务上。服务会使用附带的 userId 借助时间线服务查找该用户的时间线,这里会返回一个 tweet id 的列表,然后对其进行遍历获取关联的 tweet,最终请求的 tweet 列表会收集起来并作为 JSON 返回。在这里我们将并发的事情全部留给了 Finagle 处理,并不用关心线程池以及竞态条件的问题,代码整洁清晰并且很安全。

以上介绍了 Finagle 的基本功能以及简单的用法。Finagle 支撑了 Tweet 巨大的网络传输增长,同时还降低了延迟以及对硬件的需求。目前,Finagle 与 Netty 社区积极合作,在完善产品的同时,也为社区做出了贡献。Finagle 内部会更加模块化,从而为升级到 Netty 4 铺平道路。

关于 Finagle 的更多文档和样例,可以参考其站点

2014-05-30 07:5432953

评论

发布
暂无评论
发现更多内容

阿里云崩了:企业未来该怎么选择云厂商?

轶天下事

如何在淘宝的item_search_seller API中获取店铺列表?

技术冰糖葫芦

API 文档

剧情继续:马斯克曝出OpenAI前员工举报信,董事会与奥特曼谈判回归

Openlab_cosmoplat

智能监控,高效观测 IT 系统瓶颈

观测云

IT 智能监控

AppLink结合金蝶云星空作订单信息同步流程

RestCloud

零代码 APPlink

HashMap HashTable ConcurrentMap 中key value是否可以为null

javaNice

Java

企业如何选择一款高效的ETL工具

RestCloud

ETL

透过一台电视,看到万家星闪

脑极体

通信

如何获取item_search_guang API中与“爱逛街”相关的API接口?

技术冰糖葫芦

API 文档

软件测试/测试开发丨人工智能时代软件测试的变化

测试人

人工智能 软件测试

专访|OpenTiny 开源社区 常浩:完成比完美更重要

OpenTiny社区

开源 Vue 前端 富文本编辑器

编程新手如何提高编程能力?

代码生成器研究

低代码究竟能干什么?

代码生成器研究

程序员指南|学会与大模型相处,提升个人开发效率

飞算JavaAI开发助手

程序员 软件开发 人工智能「 ChatGPT

让公有云服务“宁安如梦”的“定心丸”在哪里?

轶天下事

2023 IoTDB Summit 应用实例议题详解 | 报名到场即送卫衣!

Apache IoTDB

开源之夏 2023 | Databend 社区项目总结与分享

Databend

编程到底难在哪里?

代码生成器研究

2024年企业软件定制开发必须了解的4大趋势

飞算JavaAI开发助手

数字化转型 企业 定制软件开发 技术服务

以太坊铭文聚合交易平台 Scorpio,铭文爆发的新推手?

股市老人

低代码:数字化转型趋势下的快速开发方式

互联网工科生

低代码 数字化

C++ LibCurl实现Web指纹识别

不在线第一只蜗牛

c++ 编程 web socket LibC

当前各类厂商纷纷入局低代码赛道,关于低代码未来的发展前景如何?

代码生成器研究

2023中关村论坛系列活动——英特尔智能医疗健康创新合作论坛在京成功举办

E科讯

idea如何新建一个多模块的springCloud项目

javaNice

Java SpringCloud

低代码PaaS开发平台

树上有只程序猿

低代码 PaaS 私有化部署

JD-GUI 反编译jar包

javaNice

Java

埃森哲使用 Amazon CodeWhisperer 助力开发人员提高工作效率

亚马逊云科技 (Amazon Web Services)

Java Python 人工智能 S3 Amazon CodeWhisperer

乌卡时代确定性稀缺,企业多云战略最需看中什么?

轶天下事

Twitter的RPC框架Finagle简介_Java_张卫滨_InfoQ精选文章