分布式RPC框架
分布式RPC框架:Polaris
在长期的业务发展过程中,对于系统间的交互,我们使用了Socket、RMI、Hessian、JSON等技术,针对每种技术,都需要维护一套相应的故障转移、故障恢复、追踪框架,对于商业平台多条业务线的大量系统交互来说,经常面临着硬件故障问题,此种交互方式引起的维护成本及故障迁移成本都是巨大的。另外,多种不同的接口技术也面临着接口兼容性的问题,例如对于RMI来说,我们就遇到了Spring从2.5.6升级到3.1.0所带来的RMI接口不兼容导致的接口调用错误问题。最后,我们内部也有一些跨语言的调用需求,比如用C++查询广告物料信息、Python脚本做统计,这些都会涉及到接口调用,而使用不能跨语言的技术(例如RMI和Hessian)成本会非常高。因此,我们需要统一内部系统接口交互方式,并降低接口的管理和维护成本。
在实践过程中,我们比较了WebService、ProtocolBuffer、HTTP/JSON、Dubbo以及Thrift等技术。其中,WebService基于XML,兼容性良好,但其序列化/反序列化性能较差;ProtocolBuffer和HTTP/JSON无服务接口/地址描述;Dubbo的框架过于庞大;Thrift在接口定义文档、数据类型支持、跨语言等方面存在优势,然而对服务地址和服务管理方面支持力度不够,并且存在类型侵入问题。在分析比较之后,我们选定了Thrift,并在此基础上开发了服务注册中心,解决服务地址和服务管理方面的问题。对于类型侵入问题,从描述语言来看,长远来看它生成的POJO代码应该可以像WebService一样,消除类型侵入(例如,Facebook提供了开源的Swift框架,已经朝这个方向迈进了一步)。我们目前是提供了一系列的方法和转换类,自主进行类型转换适配工作。
我们基于Thrift构建的分布式RPC框架Polaris如图5所示。
我们对Polaris的服务端和客户端都进行了增强。在服务器端,我们提供了标准的HTTP服务器,能够很轻易地将一个Thrift服务发布到一个URL上。同时,也集成了标准的认证和授权框架,有效地保证了业务的安全性;另外,我们在运维监控方面做了很多增强,能够监控每个方法的执行时间以及执行参数,这样的话可以面向整个商业平台,建立标准的监控统计架构。因为我们提供的服务是基于HTTP的,因此部分统计是可以直接沿用线上已有的监控统计软件,也有效减少了重新构建监控框架的成本。
在客户端,主要添加了失败重试、提供了类RPC的调用方式,并且提供了一种抽象的机制简化了测试成本。通过Spring的依赖注入,可以支持在仅调整配置,不改变客户端代码的情况下进行本地调用和远程HTTP调用的切换。使用本地调用时可以更快地进行单元测试,在完成单元测试之后,可以直接通过调整配置切换成远程HTTP调用,在此过程中无需任何代码的调整。此方案还对接口迁移,比如从RMI接口迁移至Thrift接口提供了很大的帮助。一般情况下,大型复杂依赖系统内部接口间的依赖关系都会特别复杂,在进行接口梳理和迁移时成本和风险都非常高,特别是在系统的服务化改造过程中需要将内部接口提升为API接口的时候。在这种情况下,可以通过引入一个Thrift模块,将此模块依赖于需要提升为API接口的模块,而其他模块则仅依赖于此Thrift模块。在此调整过程中,可以随着业务版本的开发同步进行,而且可以重用大部分单元测试稍作调整即可使用,这样的话能够将成本和风险都降至最低。
事实上,Thrift有标准的定义语言,能够对服务接口、异常、接口参数和返回值进行描述,同时支持Set、Map之类的数据结构。然而,它没有描述服务地址以及服务依赖关系等。因此,我们也构建了服务中心Aura,它的概念架构图6所示。
图6中的通信框架服务端和通信框架客户端已经在前面介绍了。服务中心的主要功能是对Thrift接口描述语言文件进行管理,管理其发布/订阅关系。实际上,它定义了一种团队间互相协作的方法,此协作遵循面向服务体系结构。在开发实践中,我们不鼓励依赖通过接口描述语言直接生成的代码,鼓励依赖于接口描述语言生成代码,这样能够保证耦合性,减少代码升级所带来的成本。另外,通过管理服务的所有者和服务的订阅者,能够迅速地了解到服务间的依赖关系以及服务演化时所带来的相关影响。
在商业平台内部有多个业务系统使用分布式RPC框架Polaris,包括资金、计费、客户、财务、合同、物料、报告等,也覆盖了Java、C++、Python等语言,目前已经完成大部分接口的迁移,利用服务中心Aura,构建了完善的服务发布、发现和使用的流程,也针对其安全性、易用性和可维护性做了很多工作,因为有了一致的技术集,连怎么去使用都有了最佳实践,迁移后的服务接口显著减少了重复开发成本,有效地提升了沟通效率,降低了风险。