分享

从数据和流程的视角,思考服务编排的落地路径

 何涛262tm518s7 2017-06-24

不管是SOA还是微服务,它们本质上都是服务,服务的碎片化看上去带来了物理上的解耦以及数据上更多的自主,目标是更快速地适应了业务的变化。但IT系统对于用户来说永远是一个整体,离散化带来了更大的集成难度,通过元数据+流程,描述和编排碎片化的服务,是实现服务集成的捷径。

本文主要内容是关于数据、流程与服务之前的关系,以及在服务编排上的一些思考(服务编排是一种传说中的快速开发手段)。有一些观点可能比较理想,也缺乏落地的实践验证,在此期望能给大家有所启发,如果你想和作者交流,欢迎扫描文末二维码加作者微信。本文的主要内容包括:

  • 数据、流程与服务之间的关系

  • 服务适配与数据有着千丝万缕的关系

  • 业务逻辑和业务流程应该可以相互转换

  • 服务离散化之后的工程化协作与平台支撑

依附于应用的服务

这是维基百科描述SOA的一张图,很多时候是我们狭隘了,认为SOA就是基于SOAP的Web Service。

SOA里关于Service的子概念,还有Contract、Interface、Implementation以及Implementation的子概念Business logic、Data。

很多互联网公司,都极力排斥ESB,不仅仅是在商业上排斥,还在技术上排斥(容易成为性能瓶颈),还有一些在企业投资组合上的决策这里就不展开了。可是,最近API Gateway又开始被广泛提及(包括互联网公司)。
以前,我们用WAR交付的时候,里面包含了太多的内容。前端、后端、业务逻辑、数据访问都在一个WAR里。于是我们把上面这种形式的WAR,在架构上归了一个类,叫Monolithic 。

Services包含了大量的业务逻辑,即便是在ESB中进行所谓的「服务编排」,程序员还是要写很多控制代码、数据适配代码。
现在又开始流行「微服务」,好像只要把Interface、implementation、Bussness Logic,编译打包在一起,就可以成为一个Service了。当然还有各种好处,灵活部署,支持水平扩展,交付迅速。

可独立运行的服务

暂时不想在「微服务」这个概念上做过多的纠缠,至少有一点是公认的事实,它是可独立运行的。

这张图里面有几个核心概念,构成了服务:API、SPI、DAI。我在这里需要简单的做一下后续内容的术语约定。

  • API是一个古老的概念,来自于PC桌面应用时代,它主要描述的是一段程序,对外提供的可被其他程序使用的功能。对于服务而言,API描述对外提供的功能。

  • SPI并不是我发明的,只是大家提的比较少,其实在JDK中,有不少SPI的设计模式,主要是用来描述希望外部程序有哪些功能可以被直接使用。有兴趣的,可以去翻一翻JDK。我对SPI的定位是,屏蔽其它相关系统的数据模型转换和网络通信,让业务逻辑的上下文环境更纯净。

  • DAI也许是我创造的,或许别人也提过,我孤陋寡闻了。主要目的是让业务逻辑执行过程中,对于数据的操作变得简单,不用特别关心是缓存还是数据库,是分库分表还是多活,总之是面向业务逻辑友好。

  • SPI和DAI都是站在服务的视角,看待所使用的资源,包括其它横向服务提供的功能资源(SPI)和纵向的数据资源(DAI)。

再说说,业务逻辑和业务流程。一般来说,业务逻辑没有人的参与,而业务流程有人的参与。

即便是在互联网公司,员工内部报销、采购之类的,还得在内部IT系统走报销流程吧,不太能够做到像在电商下单买东西一样自助、自动(那些都是业务逻辑)。

另外,再补充说明一下,关于MiddlePipe和MiddleBox两种设计模式。

图中的所有红框,都代表一个运行期的进程(后续图中,也用类似方法表示,不再额外特别说明)。MiddleBox和MiddlePipe是两种在功能上是等价的,不同的部署模型。

一个建议是,在企业内部系统,尽可能的采用MiddlePipe部署模式,避免MiddleBox模式出现。因为在系统扩容的时候,每部署一套系统,MiddleBox模式都需要部署4个独立进程,或者不部署MiddleBox中的Function1 Function2,这样会很容易形成性能瓶颈。ESB就是这种模式。

根据前面约定的术语,简单看一下从前端/终端,到业务逻辑(流程)以及数据持久化的一个数据流转关系。

  1. 一个App如果后端只依赖一个服务,且这个服务不依赖其它的服务,这种模式叫做前后端分离。

  2. 一个App如果后端只依赖一个服务,且这个服务又依赖多个的服务,这种模式叫做微服务架构。

  3. 如果一个App和Service耦合在一起,这种模式是SOA ,monolithic architecture和service也降级为同一个进程中的module。

  4. 另外,API Gateway应当具备快速部署的能力,处于安全隔离的原因,API Gateway需要采用独享的模式,和App进行一对一的绑定。

  5. 至于Service是否依赖数据库或者数据持久化能力,不一定。

可以明显地感觉到,服务离散化之后,数据也跟着离散化了,如果不进行有效的架构管控的话,每个服务都有权利自己维护自己的后端数据(「会制造出更多的平行宇宙」这句话不一定好理解)。

MiddleBox模式也不是一无是处,要看用着什么地方,比如采用这种模式的API Gateway。


API Gateway一般出现在企业边界,做统一的安全控制。比如面向app的,面向web的,面向企业合作伙伴的(OpenAPI)。

服务的调用与编排

服务的本职工作,是处理数据。当然,服务不仅需要根据业务逻辑处理数据,还需要协同其他服务处理数据。

自左向右是三种处理方式,从简单到复杂。最右面这种也是最常见的(当然也是简化过的)。

publisher和subscriber,是站在数据的角度进行的命名,前者是客户端,后者是服务端。之所以这样命名,一个原因是,服务之间的调用关系,首选异步模式。

  • 每个红框是一个进程,代表一个原子的功能或者能力。

  • 每个服务,由Controller负责调用后续其他的subscriber。

可以看出来,MiddleBox模式,Controller非常类似ESB;反过来MiddlePipe又非常像Dubbo。在多级级联之后,展开的应该是张图,并且是有向图。

为什么很多优化算法无法在交易型的业务系统中得到优化,因为每个服务对于算法来说,都是一个黑盒,无法建模,更无法优化。服务离散化之后,黑盒被打开了。

既然抽象出了Controller这个概念,作为服务运行期的「集成者」,需要深入的研究一下如何快速做集成,看下伪代码。


假设有 f_A, f_B, f_C, f_R四个function是其他服务提供的API,其中f_R是服务本身的内部function,其入参是 f_A, f_B, f_C 的返回结果。

从BEFORE到AFTER是两种不同的controller实现形式。对于,BEFORE,描述的更像是程序员硬编码的业务逻辑(人肉模式)。对于,AFTER,main function 是程序员编写的,里面调用了一个controller的框架,实现了「多个服务的集成」。mr_controller程序员不需要关系实现,只需要关心如何使用。mr_controller在处理逻辑上,类似:

  • 进程内的 fork/join

  • 进程间的 map/reduce

不同之处在与,还需要mr_controller自动完成对于针对f_A, f_B, f_C异常处理的「标准动作」:

  • 框架自动补偿

  • 框架自动重试

BEFORE和AFTER是两个极简单的例子,方法体的本质是上下文的Context,由于简化,没有出现数据的模型转换和临时变量,但是,一般情况下f_R入参是A B C的变形结构。

接下来再谈谈Controller的上下文维护。

  • 上游的服务Context,依赖下游接口返回值的部分数据是常态,比如,只使用下游接口返回值中的某个ID。

  • 数据适配工作大部分都是靠程序员编码完成,程序员大脑是「元数据仓库(也就是类库)」的索引。

  • 一般元数据不参与接口调用,开发期引用类定义,其实是引用元数据。

  • 一般泛型接口,元数据会在使用时参与调用。如果,传入class,其实是传入元数据。如果,传入class的名字,其实是传入元数据的ID,由class loader找到class。

在数据领域的MOF中这样定义元数据及其概念和关系的:

  • 元数据定义数据

  • 元模型定义元数据

  • 元元模型定义元模型

由上往下就是:

  • 数据实现元数据

  • 元数据实现元模型

  • 元模型实现元元模型

程序员在应用层的代码里,主要是在做哪些事呢?我来总结下:

1、编写代码的前提条件

  • 了解业务背景 了解业务知识

2、设计并实现Controller、Context、DTO

  • Controller: 琢磨需要和组合到那几个其他领域功能(API / SPI)

  • Context: 设计一些用户保持上下文的变量(在Scala里,是val不是var)

  • DTO: 对过程中返回值进行模型变换

3、附加的处理一些计算机的技术问题

  • RPC:异步?回调 : 同步,超时?客户端 : 服务端

  • 运行环境基线不标准

  • PaaS层支撑有限

这部分的软件功能,基本上是用人肉在做业务规则的翻译。并且每次做业务变更的时候,就类似老鹰抓小鸡,业务是老鹰,牵一发而动全身。这样的程序员好累,又要懂业务逻辑,也要懂技术实现。

想要直接编排服务,并且即时生效?

对于服务编排本身,是一个业务规则的映射、翻译工作。服务运行期加工的数据是什么?是业务概念在不同场景下的结构化对象。

数据适配的工作量占比集成工作很高。数据适配的范围是入参与出参,上下文临时变量。

  • 面向出参的适配:GraphQL。

  • 面向入参的适配:万能入参的的接口设计不是好事,过多的分支判断,会将多个业务逻辑耦合在一起。

  • 上下文临时变量:目前而言,完全由程序员手工完成。

业务架构,主要和两个方面有关:数据和流程。在数据领域,数据管理里面又有元数据和数据标准两个相辅相成的专业领域。原本元数据和数据标准服务的对象是数据类应用,数据源也是来自于「交易型」业务系统的数据沉淀,并且经过ETL等工作,提炼为「主数据」。

元数据,很少在「交易型」业务系统中内应用(主要做数据的人不做交易型应用,做交易型系统的人不做数据类应用,有点进水不犯河水的感觉)。在这里,我的一个观点是「拓展元数据的应用场景」,对API、SPI等接口的入参进行管理。有条件的,直接通过元数据管理系统,生成这些参数的class代码。

另外,服务目录和业务流程之间的关系,应该是「点和线的关系」,服务是点,流程是线。实现服务可编排的三个要素包括:Controller、Context、DTO。下面我逐一做下解释:

  • Controller,基本上是业务逻辑的映射。

  • Controller能够正常工作需要Context这个「秘书」的协助。Context中的临时变量,应该可以通上游API入参的数据类型和下游SPI入参的数据类型,结合元数据进行推断。

  • DTO完成多到一的数据转换是基本功能,这也是要和元数据紧密结合的。

Controller的实现有三种方式,具体如下:

方式一:作为通用引擎 运行期 元数据解析(性能慢)

  • 动态加载流程元数据

  • 根据上游元数据动态解析上游数据

  • 根据下游元数据,动态构造下游入参数据以及报文,再调用下游接口

方式二:开发期 直接生成代码(性能高,但需要工具支撑)

  • 声明内部变量

  • 接口调用与返回值变量赋值

  • 上下文数据类型转换

方式三:运行期 根据生成代码(性能高,第一次慢)

  • 具体方式类似于JSP的原理

  • 第一次运行比较慢

简单补充下,方式一中Controller可以是个Library,直接在编译期打包集成。可以适用于MiddlePipe和MiddleBox两种部署模式。Controller依赖的Context里面的临时变量,在运行期基本上以Map、Aarray这种弱类型,通过外部封装的逻辑对其维护。

方式二和方式三:生成的代码,和程序员写的代码类似,都会在Controller方法体内,声明变量,这些临时变量的交由JVM之类的进行托管。


对于业务流程(也就是一般情况下,有人参与的情况),Context不能被长期的置于内存中,需要外置。再进一步的,Controller和Context Service部署在一起,就变成了流程服务,MiddleBox的部署模式。

这种模式,和ESB很像。说了很多关于流程以及数据的结合点,回过头来再说说服务本身,有哪些非功能要求。


说了很多关于流程以及数据的结合点,回过头来再说说服务本身,有哪些非功能要求。 

服务端

  • 面向资源的接口风格(REST是一种参考实现)  http(s):////

  • 保证接口幂等性(注意,使用HTTP PUT创建,而不是POST)。(幂等) PUT 是客户端生成 资源ID(PUT http(s):////),(非幂等)POST 是服务端生成 资源ID(POST http(s):///)。

  • 提供进度查询方法 (例如,采用HTTP HEAD)。

  • 提供补偿(冲正/反交易)接口(例如,采用HTTP DELETE)。

  • 提供默认超时时间,并以客户端超时时间为优先。

客户端

  • 能够直接得到服务端是否处理的回复

  • 能够查询处理的进度,以及超时时间

  • 处理完的结果:整体的成功还是失败、同步结果告知或者提供接口被回调。

上下文

  • 临时变量的自动化生成

  • 数据类型的自动转换

客户端不关心

  • 在服务端的背后,有多少个服务根据什么流程进行协同处理

解放生产力,就是不断把新产生的「人维护的上下文」的重复性工作交给系统。处理未知风险,不断把计算机执行的业务逻辑所产生的位置业务风险,转交给人。

服务的物理拆解,交付物变得更离散了

服务的拆解,不一定全都是带来红利,也得交点税。保障当前离散化服务持续交付,一些在工程落地实践上的建议:

  • 一个原子业务能力的定义包括:API / SPI / DAI 定义以及单元测试用例以及Mock数据,并由代码生成文档。

  • 代码的定义与实现充分解耦,架构师负责定义,开发工程师负责实现,并将定义尽可能的上升为标准。

  • API / SPI 中的参数对象遵循数据标准,有在线的元数据系统对其进行统一管理。

  • SPI 的实现,不允许做数据持久化操作。

  • 支撑IT的IT平台:这个平台目前基本上是DevOps,以及「泛DevOps」。

  • 对运行环境的要求。这个环境目前基本上是Docker,对于老系统的改造路径,主要是改造类型应用服务器的中间件,并做好基线管理。

前文讲了很多如何实现业务逻辑的内容,但是没有讲如何自动生成测试用例,因为这些方面还没有进行思考。的精力是有限的,当架构师、开发工程师的工作部分被计算机所承担的时候,可以更多的去关注非正常的业务规则。

老问题没解决,新问题又出现


上面是个跨行业的例子,并且只是示意。对于企业内部而言,不同行业就如同不同的业务部门。服务的离散化,导致数据的离散化、软件交付过程中相应职能与职责的离散化,更多的参与者能够接触到数据,业务的组合出现更多的不确定性。在业务流程的制定上,无法完全描述全量的业务模型,导致出现各方全都正确,但最终出现非法业务的情况。业务安全性分析,需要借助AI、ML等工具与平台进行风险控制。

IT 系统是处理数据的,事件、流、存量,是数据源的三种不同形态,对应了三种不同的处理模式。本次内容谈了多对于服务的思考,但是远远不够的,最多是1/3。

总结与反思


啰嗦了很多,最后总结一些思考过程中的一些感想。

  • 软件大体上解决两类问题人的问题,也就是业务的问题计算机的问题,也就是技术的问题。

  • 软件越贴近业务功能实现,在不同场景下对数据的适配越多,但支撑的工具和平台太少。

  • 人,是一种适配功能很强的「适配器」。

  • 人,对于模糊、定性的描述理解更为准确定性的描述,外延更大,受体可以自己自由适配举例:进出地铁,站在扶梯上,会有语音提示「上下楼梯,请紧握扶手」可,「紧」,对于计算机程序是什么?用手施加10牛顿的力吗?

  • 做好产品的简单原则:基于本质,懂得人性。

作者介绍

王延炯,密码学博士,现任普元信息软件产品部主任架构师。毕业于北京邮电大学网络与交换技术国家重点实验室网络安全中心。曾先后任职于西门子(中国)研究院、普元信息、垂直行业互联网公司。带领团队交付了移动、金融、电信、广电、移动互联网等多个行业、众多IT系统的咨询、设计、研发、实施、维护、优化工作。对分布式架构,企业架构,以及企业IT平台化运营有深入的研究和理解。


欢迎扫描二维码加入由作者主持的“普元云计算研发开放群”,讨论更多微服务、DevOps、容器相关内容,加群暗号“微服务”。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多