【架构】Twitter高性能RPC框架Finagle介绍
Twitter的RPC框架Finagle简介
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之中读取信息,并发送到所实现的服务器实例中。
- Finagle客户端位于Finagle Transport之上,这个Transport为用户抽象了Netty;
- Netty ChannelPipeline包含了所有的ChannelHandler实现,这些实现完成实际的工作;
- 对于每一个连接都会创建Finagle服务器,并且会为其提供一个Transport来进行读取和写入;
- 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))
}
Http.serve(":80", s)
这个样例在所有接口的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的更多文档和样例,可以参考其站点。
参考资料:
http://www.infoq.com/cn/news/2014/05/twitter-finagle-intro
有没有人能对twitter的finagle和国内的dubbo做个对比? :http://www.zhihu.com/question/31440152
月PV破150亿:Tumblr架构揭密:http://os.51cto.com/art/201202/317899.htm
Finagle 介绍:http://wiki.jikexueyuan.com/project/scala/finagle.html
【架构】Twitter高性能RPC框架Finagle介绍的更多相关文章
- Twitter的RPC框架Finagle简介
Twitter的RPC框架Finagle简介 http://www.infoq.com/cn/news/2014/05/twitter-finagle-intro
- 基于Protobuf的分布式高性能RPC框架——Navi-Pbrpc
基于Protobuf的分布式高性能RPC框架——Navi-Pbrpc 二月 8, 2016 1 简介 Navi-pbrpc框架是一个高性能的远程调用RPC框架,使用netty4技术提供非阻塞.异步.全 ...
- C# -- 高性能RPC框架:Socean.RPC
简介 Socean.RPC是一个.Net下的高性能RPC框架,框架以高性能.高稳定性为目标,底层基于socket,无第三方库引用,代码简洁,总代码量大约在2000行,框架性能较高,在普通PC上测试,长 ...
- Google 高性能 RPC 框架 gRPC 1.0.0 发布(附精彩评论)
gRPC是一个高性能.开源.通用的RPC框架,面向移动和HTTP/2设计,是由谷歌发布的首款基于Protocol Buffers的RPC框架. gRPC基于HTTP/2标准设计,带来诸如双向流.流控. ...
- [转]RPC 框架通俗介绍
关于RPC 你的题目是RPC(远程过程调用,Remote Procedure Call)框架,首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服 ...
- GRPC 1.3.4 发布,Google 高性能 RPC 框架(Java C++ Go)
GRPC 1.3.4 发布了,GRPC 是一个高性能.开源.通用的 RPC 框架,面向移动和 HTTP/2 设计,是由谷歌发布的首款基于 Protocol Buffers 的 RPC 框架. GRPC ...
- Dubbo学习笔记0:RPC框架Dubbo介绍
整体来说,一个公司业务系统的演进流程基本都是从单体应用到多应用.在单体应用时,不同业务模块相互调用直接在本地JVM进程内就可以完成,而变为多个应用时,相互之间进行通信就不能简单的进行本地调用了,因为不 ...
- 分布式架构探索 - 1. RPC框架之Java原生RMI
1. 什么是RPC RPC(Remote Procedure Call)即远程过程调用,指的是不同机器间系统方法的调用,这和 同机器动态链接库(DLL)有点类似,只不过RPC是不同机器,通过网络通信来 ...
- 一个高性能RPC框架的连接管理
既然说连接,先对EpollServer的连接管理做个介绍吧.客户端与服务器一次conn,被封装成为Connection类在服务器进行管理. 服务器连接有三种类型,分别为: enum EnumConne ...
随机推荐
- 【转载】C++ typedef用法小结
http://www.cnblogs.com/charley_yang/archive/2010/12/15/1907384.html 第一.四个用途 用途一: 定义一种类型的别名,而不只是简单的宏替 ...
- NoSQL 数据库应用
类型 部分代表 特点 列存储 Hbase Cassandra Hypertable 顾名思义,是按列存储数据的.最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查 ...
- nginx用户权限问题
nginx配置文件里指定woker进程用户是要确定这个用户的权限,如果出现问题时查看出错日志,看看是否为权限问题
- Java String lastIndexOf() 方法
Java String lastIndexOf() 方法 测试代码 public class Test { public static void main(String[] args) { // -- ...
- 从Windows复制文件到Linux显示乱码问题
(1).文件名乱码 这并不是所有人都会碰到的问题,一般常见于使用putty的用户.使用convmv命令可以解决这个问题. 我写详细一点还原真实场景,首先我来上传一个测试文件“a此文件在windows下 ...
- Flask实战第57天:UEditor编辑器集成以及配置上传文件到七牛
相关链接 UEditor:http://ueditor.baidu.com/website/ 下载地址:http://ueditor.baidu.com/website/download.html# ...
- sqlldr load UTF8 error
The default length semantics for all datafiles (except UFT-16) is byte. So in your case you have a C ...
- Python开发基础-Day17面向对象编程介绍、类和对象
面向对象变成介绍 面向过程编程 核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西.主要应用在一旦完成很少修改的地方,如linux ...
- mysql 删除表记录 delete和truncate table区别
MySQL中删除表记录delete from和truncate table的用法区别: mysql中有两种删除表中记录的方法: (1)delete from语句, (2)truncate table语 ...
- 【HDU 5730】Shell Necklace
http://acm.hdu.edu.cn/showproblem.php?pid=5730 分治FFT模板. DP:\(f(i)=\sum\limits_{j=0}^{i-1}f(j)\times ...