Categories
程式開發

复杂分布式架构下的计算治理之路:计算中间件Linkis


前言

在当前的复杂分布式架构环境下,服务治理已经大行其道。但目光往下一层,从上层APP、Service,到底层计算引擎这一层面,却还是各个引擎各自为政,Client-Server模式紧耦合满天飞的情况。如何做好“计算治理”,让复杂环境下各种类型的大量计算任务,都能更简洁、灵活、有序、可控的提交执行,和保障成功返回结果?计算中间件Linkis就是上述问题的最佳实践。

一、复杂分布式架构环境下的计算治理有什么问题?

1. 什么是复杂分布式架构环境?

分布式架构,指的是系统的组件分布在通过网络相连的不同计算机上,组件之间通过网络传递消息进行通信和协调,协同完成某一目标。一般来说有水平(集群化)和垂直(功能模块切分)两个拆分方向,以解决高内聚低耦合、高并发、高可用等方面问题。

多个分布式架构的系统,组成分布式系统群,就形成了一个相对复杂的分布式架构环境。通常包含多种上层应用服务,多种底层基础计算存储引擎。如下图1所示:

图片

2. 什么是计算治理?

就像《微服务设计》一书中提到的,如同城市规划师在面对一座庞大、复杂且不断变化的城市时,所需要做的规划、设计和治理一样,庞大复杂的软件系统环境中的各种区域、元素、角色和关系,也需要整治和管理,以使其以一种更简洁、优雅、有序、可控的方式协同运作,而不是变成一团乱麻。

在当前的复杂分布式架构环境下,大量APP、Service间的通信、协调和管理,已经有了从SOA(Service-Oriented Architecture)到微服务的成熟理念,及从ESB到Service Mesh的众多实践,来实现其从服务注册发现、配置管理、网关路由,到流控熔断、日志监控等一系列完整的服务治理功能。服务治理框架的“中间件”层设计,可以很好的实现服务间的解耦、异构屏蔽和互操作,并提供路由、流控、状态管理、监控等治理特性的共性提炼和复用,增强整个架构的灵活性、管控能力、可扩展性和可维护性。

但目光往下一层,你会发现在从APP、Service,到后台引擎这一层面,却还是各个引擎各自为政,Client-Server模式紧耦合满天飞的情况。在大量的上层应用,和大量的底层引擎之间,缺乏一层通用的“中间件”框架设计。类似下图2的网状。

图片

计算治理,关注的正是上层应用和底层计算(存储)引擎之间,从Client到Server的连接层范围,所存在的紧耦合、灵活性和管控能力欠缺、缺乏复用能力、可扩展性、可维护性差等问题。要让复杂分布式架构环境下各种类型的计算任务,都能更简洁、灵活、有序、可控的提交执行,和成功返回结果。如下图3所示:

图片

3. 计算治理问题描述

更详细的来看计算治理的问题,可以分为如下治(architecture,架构层面)和理(insight,细化特性)两个层面。

(1)计算治理之治(architecture)-架构层面问题。

紧耦合问题,上层应用和底层计算存储引擎间的CS连接模式。

所有APP& Service和底层计算存储引擎,都是通过Client-Server模式相连,处于紧耦合状态。以Analytics Engine的Spark为例,如下图4:

图片

这种状态会带来如下问题:

  • 引擎client的任何改动(如版本升级),将直接影响每一个嵌入了该client的上层应用;当应用系统数量众多、规模庞大时,一次改动的成本会很高;
  • 直连模式,导致上层应用缺乏,对跨底层计算存储引擎实例级别的,路由选择、负载均衡等能力;或者说依赖于特定底层引擎提供的特定连接方式实现,有的引擎有一些,有的没有;
  • 随着时间推移,不断有新的上层应用和新的底层引擎加入进来,整体架构和调用关系将愈发复杂,可扩展性、可靠性和可维护性降低。

重复造轮子问题,每个上层应用工具系统都要重复解决计算治理问题。

每个上层应用都要重复的去集成各种client,创建和管理client到引擎的连接及其状态,包括底层引擎元数据的获取与管理。在并发使用的用户逐渐变多、并发计算任务量逐渐变大时,每个上层应用还要重复的去解决多个用户间在client端的资源争用、权限隔离,计算任务的超时管理、失败重试等等计算治理问题。

图片

想象你有10个并发任务数过百的上层应用,不管是基于Web的IDE开发环境、可视化BI系统,还是报表系统、工作流调度系统等,每个接入3个底层计算引擎。上述的计算治理问题,你可能得逐一重复的去解决10*3=30遍,而这正是当前在各个公司不断发生的现实情况,其造成的人力浪费不可小觑。

扩展难问题,上层应用新增对接底层计算引擎,维护成本高,改动大。

在CS的紧耦合模式下,上层应用每新增对接一个底层计算引擎,都需要有较大改动。

以对接Spark为例,在上层应用系统中的每一台需要提交Spark作业的机器,都需要部署和维护好Java和Scala运行时环境和变量,下载和部署Spark Client包,且配置并维护Spark相关的环境变量。如果要使用Spark on YARN模式,那么你还需要在每一台需要提交Spark作业的机器上,去部署和维护Hadoop 相关的jar包和环境变量。再如果你的Hadoop集群需要启用Kerberos的,那么很不幸,你还需要在上述的每台机器去维护和调试keytab、principal等一堆Kerberos相关配置。

图片

这还仅仅是对接Spark一个底层引擎。随着上层应用系统和底层引擎的数量增多,需要维护的关系会是个笛卡尔积式的增长,光Client和配置的部署维护,就会成为一件很令人头疼的事情。

应用孤岛问题,跨不同应用工具、不同计算任务间的互通问题。

多个相互有关联的上层应用,向后台引擎提交执行的不同计算任务之间,往往是有所关联和共性的,比如需要共享一些用户定义的运行时环境变量、函数、程序包、数据文件等。当前情况往往是一个个应用系统就像一座座孤岛,相关信息和资源无法直接共享,需要手动在不同应用系统里重复定义和维护。

典型例子是在数据批处理程序开发过程中,用户在数据探索开发IDE系统中定义的一系列变量、函数,到了数据可视化系统里往往又要重新定义一遍;IDE系统运行生成的数据文件位置和名称,不能直接方便的传递给可视化系统;依赖的程序包也需要从IDE系统下载、重新上传到可视化系统;到了工作流调度系统,这个过程还要再重复一遍。不同上层应用间,计算任务的运行依赖缺乏互通、复用能力。

图片

(2)计算治理之理(insight)-细化特性问题:

除了上述的架构层面问题,要想让复杂分布式架构环境下,各种类型的计算任务,都能更简洁、灵活、有序、可控的提交执行,和成功返回结果,计算治理还需关注高并发,高可用,多租户隔离,资源管控,安全增强,计算策略等等细化特性问题。这些问题都比较直白易懂,这里就不一一展开论述了。

二、基于计算中间件Linkis的计算治理-治之路(Architecture)

1. Linkis 架构设计介绍

  • 核心功能模块与流程

计算中间件Linkis,是微众银行专门设计用来解决上述紧耦合、重复造轮子、扩展难、应用孤岛等计算治理问题的。当前主要解决的是复杂分布式架构的典型场景-数据平台环境下的计算治理问题。

Linkis作为计算中间件,在上层应用和底层引擎之间,构建了一层中间层。能够帮助上层应用,通过其对外提供的标准化接口(如HTTP, JDBC, Java …),快速的连接到多种底层计算存储引擎(如Spark、Hive、TiSpark、MySQL、Python等),提交执行各种类型的计算任务,并实现跨上层应用间的计算任务运行时上下文和依赖的互通和共享。且通过提供多租户、高并发、任务分发和管理策略、资源管控等特性支持,使得各种计算任务更灵活、可靠、可控的提交执行,成功返回结果,大大降低了上层应用在计算治理层的开发和运维成本、与整个环境的架构复杂度,填补了通用计算治理软件的空白。

图片

图片

要更详细的了解计算任务通过Linkis的提交执行过程,我们先来看看Linkis核心的“计算治理服务”部分的内部架构和流程。如下图:

图片

计算治理服务:计算中间件的核心计算框架,主要负责作业调度和生命周期管理、计算资源管理,以及引擎连接器的生命周期管理。

公共增强服务:通用公共服务,提供基础公共功能,可服务于Linkis各种服务及上层应用系统。

其中计算治理服务的主要模块如下:

  • 入口服务Entrance,负责接收作业请求,转发作业请求给对应的Engine,并实现异步队列、高并发、高可用、多租户隔离

  • 应用管理服务AppManager,负责管理所有的EngineConnManager和EngineConn,并提供EngineConnManager级和EngineConn级标签能力;加载新引擎插件,向RM申请资源, 要求EM根据资源创建EngineConn;基于标签功能,为作业分配可用EngineConn。

  • 资源管理服务 ResourceManager,接收资源申请,分配资源,提供系统级、用户级资源管控能力,并为EngineConnManager级和EngineConn提供负载管控。

  • 引擎连接器管理服务 EngineConn Manager,负责启动EngineConn,管理EngineConn的生命周期,并定时向RM上报资源和负载情况。

  • 引擎连接器EngineConn,负责与底层引擎交互,解析和转换用户作业,提交计算任务给底层引擎,并实时监听底层引擎执行情况,回推相关日志、进度和状态给Entrance。

如上图所示,一个作业的提交执行主要分为以下11步:

1.上层应用向计算中间件提交作业,微服务网关SpringCloud Gateway接收作业并转发给Entrance。

2. Entrance消费作业,为作业向AppManager申请可用EngineConn。

3.如果不存在可复用的Engine,AppManager尝试向ResourceManager申请资源,为作业启动一个新EngineConn。

4.申请到资源,要求EngineConnManager依照资源启动新EngineConn

5.EngineConnManager启动新EngineConn,并主动回推新EngineConn信息。

6. AppManager将新EngineConn分配给Entrance,Entrance将EngineConn分配给用户作业,作业开始执行,将计算任务提交给EngineConn。

7.EngineConn将计算任务提交给底层计算引擎。

8.EngineConn实时监听底层引擎执行情况,回推相关日志、进度和状态给Entrance,Entrance通过WebSocket,主动回推EngineConn传过来的日志、进度和状态给上层应用系统。

9.EngineConn执行完成后,回推计算任务的状态和结果集信息,Entrance将作业和结果集信息更新到JobHistory,并通知上层应用系统。

10.上层应用系统访问JobHistory,拿到作业和结果集信息。

11.上层应用系统访问Storage,请求作业结果集。

计算任务管理策略支持

在复杂分布式环境下,一个计算任务往往不单会是简单的提交执行和返回结果,还可能需要面对提交失败、执行失败、hang住等问题,且在大量并发场景下还需通过计算任务的调度分发,解决租户间互相影响、负载均衡等问题。

Linkis通过对计算任务的标签化,实现了在任务调度、分发、路由等方面计算任务管理策略的支持,并可按需配置超时、自动重试,及灰度、多活等策略支持。如下图11。

图片

基于Spring Cloud微服务框架

说完了业务架构,我们现在来聊聊技术架构。在计算治理层环境下,很多类型的计算任务具有生命周期较短的特征,如一个Spark job可能几十秒到几分钟就执行完,EngineConn(EnginConnector)会是大量动态启停的状态。前端用户和Linkis中其他管理角色的服务,需要能够及时动态发现相关服务实例的状态变化,并获取最新的服务实例访问地址信息。同时需要考虑,各模块间的通信、路由、协调,及各模块的横向扩展、负载均衡、高可用等能力。

基于以上需求,Linkis 实际是基于Spring Cloud微服务框架技术,将上述的每一个模块/角色,都封装成了一个微服务,构建了多个微服务组,整合形成了Linkis的完整计算中间件能力。如下图12:

图片

从多租户管理角度,上述服务可区分为租户相关服务,和租户无关服务两种类型。租户相关服务,是指一些任务逻辑处理负荷重、资源消耗高,或需要根据具体租户、用户、物理机器等,做隔离划分、避免相互影响的服务,如Entrance, EnginConn(EnginConnector) Manager, EnginConn;其他如App Manger, Resource Manager、Context Service等服务,都是租户无关的。

Eureka 承担了微服务动态注册与发现中心,及所有租户无关服务的负载均衡、故障转移功能。

Eureka有个局限,就是在其客户端,对后端微服务实例的发现与状态刷新机制,是客户端主动轮询刷新,最快可设1秒1次(实际要几秒才能完成刷新)。这样在Linkis这种需要快速刷新大量后端EnginConn等服务的状态的场景下,时效得不到满足,且定时轮询刷新对Eureka server、对后端微服务实例的成本都很高。

为此我们对Spring Cloud Ribbon做了改造,在其中封装了Eureka client的微服务实例状态刷新方法,并把它做成满足条件主动请求刷新,而不会再频繁的定期轮询。从而在满足时效的同时,大大降低了状态获取的成本。如下图:

图片

Spring Cloud Gateway 承担了外部请求Linkis的入口网关的角色,帮助在服务实例不断发生变化的情况下,简化前端用户的调用逻辑,快速方便的获取最新的服务实例访问地址信息。

Spring Cloud Gateway有个局限,就是一个WebSocket客户端只能将请求转发给一个特定的后台服务,无法完成一个WebSocket客户端通过网关API对接后台多个WebSocket微服务,而这在我们的Entrance HA等场景需要用到。

为此Linkis对Spring Cloud Gateway做了相应改造,在Gateway中实现了WebSocket路由转发器,用于与客户端建立WebSocket连接。建立连接成功后,会自动分析客户端的WebSocket请求,通过规则判断出请求该转发给哪个后端微服务,然后将WebSocket请求转发给对应的后端微服务实例。详见Github上Linkis的Wiki中,“Gateway的多WebSocket请求转发实现”一文。

图片

Spring Cloud OpenFeign提供的HTTP请求调用接口化、解析模板化能力,帮助Linkis构建了底层RPC通信框架。

但基于Feign的微服务之间HTTP接口的调用,只能满足简单的A微服务实例根据简单的规则随机选择B微服务之中的某个服务实例,而这个B微服务实例如果想异步回传信息给调用方,是无法实现的。同时,由于Feign只支持简单的服务选取规则,无法做到将请求转发给指定的微服务实例,无法做到将一个请求广播给接收方微服务的所有实例。

Linkis基于Feign实现了一套自己的底层RPC通信方案,集成到了所有Linkis的微服务之中。一个微服务既可以作为请求调用方,也可以作为请求接收方。作为请求调用方时,将通过Sender请求目标接收方微服务的Receiver;作为请求接收方时,将提供Receiver用来处理请求接收方Sender发送过来的请求,以便完成同步响应或异步响应。如下图示意。详见Github上Linkis的Wiki中,“Linkis RPC架构介绍”一文。

图片

至此,Linkis对上层应用和底层引擎的解耦原理,其核心架构与流程设计,及基于Spring Cloud微服务框架实现的,各模块微服务化动态管理、通信路由、横向扩展能力介绍完毕。

2.   解耦:Linkis如何解耦上层应用和底层引擎

Linkis作为计算中间件,在上层应用和底层引擎之间,构建了一层中间层。上层应用所有计算任务,先通过HTTP、WebSocket、Java等接口方式提交给Linkis,再由Linkis转交给底层引擎。原有的上层应用以CS模式直连底层引擎的紧耦合得以解除,因此实现了解耦。如下图所示:

图片

通过解耦,底层引擎的变动有了Linkis这层中间件缓冲,如引擎client的版本升级,无需再对每一个对接的上层应用做逐个改动,可在Linkis层统一完成。并能在Linkis层,实现对上层应用更加透明和友好的升级策略,如灰度切换、多活等策略支持。且即使后继接入更多上层应用和底层引擎,整个环境复杂度也不会有大的变化,大大降低了开发运维工作负担。

3.   复用:对于上层应用,Linkis如何凝练计算治理模块供复用,避免重复开发

上层应用复用Linkis示例(Scriptis)

有了Linkis,上层应用可以基于Linkis,快速实现对多种后台计算存储引擎的对接支持,及变量、函数等自定义与管理、资源管控、多租户、智能诊断等计算治理特性。

好处:

以微众银行与Linkis同时开源的,交互式数据开发探索工具Scriptis为例,Scriptis的开发人员只需关注Web UI、多种数据开发语言支持、脚本编辑功能等纯前端功能实现,Linkis包办了其从存储读写、计算任务提交执行、作业状态日志更新、资源管控等等几乎所有后台功能。基于Linkis的大量计算治理层能力的复用,大大降低了Scriptis项目的开发成本,使得Scritpis目前只需要有限的前端人员,即可完成维护和版本迭代工作。

如下图,Scriptis项目99.5%的代码,都是前端的JS、CSS代码。后台基本完全复用Linkis。

图片

4.   快速扩展:对于底层引擎,Linkis如何以很小的开发量,实现新底层引擎快速对接

模块化可插拔的计算引擎接入设计,新引擎接入简单快速

对于典型交互式模式计算引擎(提交任务,执行,返回结果),用户只需要buildApplication和executeLine这2个方法,没错,2个方法,2个方法,就可以完成一个新的计算引擎接入Linkis,代码量极少。示例如下。

(1). AppManager部分:用户必须实现的接口是ApplicationBuilder,用来封装新引擎连接器实例启动命令。

//用户必须实现的方法: 用于封装新引擎连接器实例启动命令

def buildApplication(protocol:Protocol):ApplicationRequest  

(2). EngineConn部分:用户只需实现executeLine方法,向新引擎提交执行计算任务:

 //用户必须实现的方法:用于调用底层引擎提交执行计算任务
def executeLine(context: EngineConnContext,code: String): ExecuteResponse   

引擎相关其他功能/方法都已有默认实现,无定制化需求可直接复用。

5. 连通,Linkis如何打通应用孤岛

通过Linkis提供的上下文服务,和存储、物料库服务,接入的多个上层应用之间,可轻松实现环境变量、函数、程序包、数据文件等,相关信息和资源的共享和复用,打通应用孤岛。

图片

  • Context Service上下文服务介绍

Context Service(CS)为不同上层应用系统,不同计算任务,提供了统一的上下文管理服务,可实现上下文的自定义和共享。在Linkis中,CS需要管理的上下文内容,可分为元数据上下文、数据上下文和资源上下文3部分。

图片

元数据上下文,定义了计算任务中底层引擎元数据的访问和使用规范,主要功能如下:

  • 提供用户的所有元数据信息读写接口(包括Hive表元数据、线上库表元数据、其他NOSQL如HBase、Kafka等元数据);

  • 计算任务内所需元数据的注册、缓存和管理。

  • 数据上下文,定义了计算任务中数据文件的访问和使用规范。管理数据文件的元数据。

  • 运行时上下文,管理各种用户自定义的变量、函数、代码段、程序包等。

  • 同时Linkis 也提供了统一的物料管理和存储服务,上层应用可根据需要对接,从而可实现脚本文件、程序包、数据文件等存储层的打通。

三、基于计算中间件Linkis的计算治理-理之路(Insight)

Linkis计算治理细化特性设计与实现介绍,在高并发、高可用、多租户隔离、资源管控、计算任务管理策略等方面,做了大量细化考量和实现,保障计算任务在复杂条件下成功执行。

1.  计算任务的高并发支持

Linkis的Job基于多级异步设计模式,服务间通过高效的RPC和消息队列模式进行快速通信,并可以通过给Job打上创建者、用户等多种类型的标签进行任务的转发和隔离来提高Job的并发能力。通过Linkis可以做到1个入口服务(Entrance)同时承接超1万+在线的Job请求。

多级异步的设计架构图如下:

图片

如上图所示Job从GateWay到Entrance后,Job从生成到执行,到信息推送经历了多个线程池,每个环节都通过异步的设计模式,每一个线程池中的线程都采用运行一次即结束的方式,降低线程开销。整个Job从请求—执行—到信息推送全都异步完成,显著的提高了Job的并发能力。

这里针对计算任务最关键的一环Job调度层进行说明,海量用户成千上万的并发任务的压力,在Job调度层中是如何进行实现的呢?

在请求接收层,请求接收队列中,会缓存前端用户提交过来的成千上万计算任务,并按系统/用户层级划分的调度组,分发到下游Job调度池中的各个调度队列;到Job调度层,多个调度组对应的调度器,会同时消费对应的调度队列,获取Job并提交给Job执行池进行执行。过程中大量使用了多线程、多级异步调度执行等技术。示意如下图:

图片

2. 其他细化特性

Linkis还在高可用、多租户隔离、资源管控、计算任务管理策略等方面,做了很多细化考量和实现。篇幅有限,在这里不再详述每个细化特性的实现,可参见Github上Linkis的Wiki。后继我们会针对Linkis的计算治理-理之路(Insight)的细化特性相关内容,再做专题介绍。

四、结语

基于如上解耦、复用、快速扩展、连通等架构设计优点,及高并发、高可用、多租户隔离、资源管控等细化特性实现,计算中间件Linkis在微众生产环境的应用效果显著。极大的助力了微众银行一站式大数据平台套件WeDataSphere的快速构建,且构成了WeDataSphere全连通、多租户、资源管控等企业级特性的基石。

Linkis在微众应用情况如下图:

图片

我们已将Linkis开源,Github repo地址:
https://github.com/WeBankFinTech/Linkis

欢迎对类似计算治理问题感兴趣的同学,参与到计算中间件Linkis的社区协作中,共同把Linkis建设得更加完善和易用。

作者介绍:

邸帅,微众银行大数据平台负责人,主导微众银行WeDataSphere大数据平台套件的建设运营与开源,具备丰富的大数据平台开发建设实践经验。