基于变更事件驱动的微服务组合平台设计与实现 *
2021-10-26刘晓燕张开琦
王 信,刘晓燕,张开琦,王 星,严 馨
(昆明理工大学信息工程与自动化学院,云南 昆明 650050)
1 引言
微服务作为一种将应用程序组织为松耦合服务的软件开发技术,使得软件系统变得模块化,易于扩展,也有利于对每个模块进行单独的开发和部署。众多互联网企业如Google,Amazon和Twitter等都开始采用微服务架构。然而在实际应用场景中,不仅需要单个服务,更多的是需要编排多个服务的组合,即服务之间的协同工作。微服务架构出现之前,研究人员已经提出了几种用于编排Web服务组合的解决方案,如:用于编排Web服务组合的业务过程执行语言BPEL(Business Process Execution Language)及其扩展[1,2];基于领域特定语言的方法,如S[3]和Bite[4]。然而,这些方案应用在微服务架构之后都失败了,问题在于其要求服务有定义良好的接口以及交互信息的强类型约束,但快速变化的微服务使得难以快速定义其接口并且难以被快速部署。Yahia等人[5]提出了一种基于事件驱动的微服务组合平台,但存在驱动事件形式单一,不支持细粒度数据访问控制和微服务的动态部署,缺乏可扩展性、健壮性,无法检测Web资源如XML文档的更改事件从而触发微服务组合的问题。
本文提出一种改进的微服务组合平台,设计了一种支持细粒度数据访问控制的微服务组合领域特定语言DSL(Domain-Specific Language),实现基于标签的、可配置的数据访问控制,防止服务中的敏感数据泄漏给不受信任的服务,并在DSL层面上丰富了驱动事件的形式:事件之间的逻辑关系和叠加。同时,在现有微服务组合平台中引入Spring Cloud Netflix生态系统,解决了微服务动态部署的问题,增强了可扩展性和容错性。通过设计新的XML文档比较算法,实现对Web资源在内容和结构2个层面的变更检测,作为微服务组合的触发条件,从而弥补了现有解决方案的不足。
2 微服务组合平台概述
本文设计的微服务组合平台,以领域特定语言DSL为基础,DSL可为编程人员在高级业务逻辑与底层系统实现之间提供抽象层,从而使编程人员能专注于业务逻辑的编写。该平台主要包括DSL编译器、运行时系统和Spring Cloud Netflix生态系统。平台体系结构如图1所示。 编程人员首先利用DSL编写要进行编排的具体微服务组合的业务逻辑,由DSL编译器将高级语言(DSL)代码编译成目标代码(JavaScript代码),以在运行时系统上运行。在此过程中,微服务之间的组合被映射为进程间通信。 运行时系统基于Node.js构建,它是一个基于Chrome V8 JavaScript引擎开发的JavaScript运行时系统[6]。每个服务组合被隔离在不同的沙箱里面,因此不同的微服务组合之间不会互相影响。整个微服务组合平台是轻量级的,可在有限资源的服务器如树莓派和Docker容器中部署运行。Spring Cloud Netflix生态系统中的Zuul Gateway网关、Eureka服务注册中心、Ribbon负载均衡组件和Hystrix服务熔断机制将为平台提供微服务动态部署的支持,提高平台健壮性。
Figure 1 Architecture of microservice composition platform图1 微服务组合平台体系结构
2.1 DSL语言
为了实现微服务的组合,本文定义了一种领域特定语言DSL,使编程人员能够以较高抽象层次描述微服务的组合逻辑。本文对现有的微服务组合平台进行改进:通过在DSL层面定义基于标签的访问控制机制,实现可配置的细粒度数据访问控制,防止服务中的敏感数据泄漏给不受信任的服务。对于驱动的事件,能对Web资源不同层面的变化进行变更检测,并对驱动事件的形式进行扩展,以适应更加复杂的应用场景,如事件之间的与关系和或关系组合,同一事件重复出现多次的情况下才触发微服务组合。
DSL的语法以BNF范式的形式定义如下:
comp::= composition{decl+ rule+}
decl::=pool? process ident=require(string);
|ident.init(json?);
|ident.add(ident*(,ident)*);
|ident.setlabel(user-label);
|user-label.setpolicy(motion,object-label);
rule::=on event do {action+}
event::= evt|event and evt|event or evt|(event)|evt countto integer
evt::= evt_kind(as ident)?
evt_kind::= ident:out|ident:err|ident:close| uri changed
action::=stream ident=ident.invoke(json?);
|ident.invoke(json?)
|if(expr) action(else action)?
|rule
|xpath.assign(object-label);
expr::=!expr|expr binop expr|(expr)|ident|string|integer|float|jsonpath|method(expr*(,expr)*)
xpath::={{string}}
jsonpath::={{string}}
method::=ident|jsonpath.ident
binop::=<|>|<=|>=|==|!=|&&|||
uri::=string
object-label::= confidential|secret|public
user-label::= manager|employee|stuff
motion::= read|write
在DSL的语法中,require方法创建进程,返回一个新的进程实例;init表示使用用户定义的参数初始化一个进程;invoke方法表示调用一个进程,该方法返回进程调用产生的输出流的引用。引入的进程池用pool关键字声明,表示可调用多个作用相同、可互相替代的进程,当其中一个进程因错误无法被调用时,可调用进程池中的其他进程,保证了微服务组合中的容错性。add方法表示向进程池内添加新的进程。
微服务组合中的事件驱动规则则由rule来表示,具体为on event do {action+}:即当事件(event)发生时触发某一动作(action)。该事件可为进程被调用后生成的输出流、Web资源的更改或事件之间的逻辑关系。
为了使服务中的敏感数据不被泄漏到其他不被授权的服务中,有必要在DSL层面上设计一种访问控制机制。本文使用的访问控制机制支持可配置的细粒度数据的访问控制,而不是根据“全有或全无”的模式来定义访问控制级别。此处的细粒度是指在XML文档中属性的内容部分。因此,使用了基于标签的访问控制方法LABAC(Lable-Based Access Control)[7]。
基于标签的访问控制方法LABAC不同于基于角色的访问控制RBAC(Role-Based Access Control)将权限仅分配给特定的角色,用户只能属于某一特定角色而被赋予权限,LABAC的核心在于policy的概念,其可以表示一组复杂的布尔规则集,用于对多种不同的属性进行评估,不仅可对用户(user)赋予属性,还可对资源、环境和操作赋予属性,从而可以满足几乎所有的访问控制需求[8]。
如图2所示,object指需要施加访问控制的对象,action表示可对对象进行的操作,如读写等。policy表示三元组(user-label,action,object- label),其含义为具有某特定user-label的用户可以对具有某object-label的对象进行指定的action操作。如前文用BNF范式表示的DSL语法,user-label可为不同的属性,如manager、employee、stuff,且user-label可具有层次嵌套结构,例如,stuff能访问的数据均能被employee访问;employee能访问的数据均能被manager所访问。object-label可给特定对象赋予如confidential、secret、public定义隐私级别的属性,同样具有层次嵌套结构。setlabel和setpolicy分别表示设置用户标签user-label、设置策略policy。为了给细粒度的数据赋予标签object-label,assign函数可对xpath中的对象进行操作。xpath是一种用于XML的查询语言。
Figure 2 Label-based access control model图2 基于标签的访问控制模型
2.2 DSL 编译器
2.1节中定义的DSL代码,可由DSL编译器将其转化为可在运行时系统中执行的JavaScript目标代码。DSL编译器借助ANTLR4[9]实现,ANTLR4是一个语法解析器生成器,可以根据用户所定义的语法生成语法解析器和监听器接口(listener interface)或访问者(visitor),以根据需求生成目标代码。
ANTLR4定义了一种后缀为.g4的DSL语法文件。首先根据.g4文件的语法要求,将2.1节所定义的BNF范式表示的语法转化为.g4文件的语法。之后在Node.js的环境中,由ANTLR4根据.g4文件生成所需要的语法解析器、监听器(listener)和访问者。在此本文选择以监听器(listener)的方式来生成目标代码。
根据为微服务组合定义的语法,ANTLR4生成了Complistener.js文件,其包含用户所定义的每个语法规则的enter和exit函数,但仍然需要用户根据需求自己编写为每个语法规则生成目标代码的指令。但是,这种方法的缺陷在于,一旦DSL被修改,ANTLR4需重新生成Complistener.js文件,该文件原先所有的内容就被擦除,因此需要创建另一个名为JSlistener.js的文件,该JSlistener.js文件中再引入ANTLR4生成的Complistener.js文件,在新的listener文件中编写代码。
除了以上的listener文件,DSL编译器将DSL高级代码转化为目标代码还需要一个额外的implement.js文件。该implement.js文件被引入到listener文件中,主要包含3个函数:OpenTarget、CloseTarget和write,其作用分别是创建目标代码文件、将数组保存到目标代码文件中和将每一行目标代码写入到数组中。
2.3 Spring Cloud Netflix生态系统
在现有的微服务组合平台解决方案中,进行微服务的组合作业之前需要先注册每个微服务的端口等基本信息或定义“服务描述符”,这就使得无法在已有的微服务组合中动态添加新的微服务。此时必须终止微服务组合,重新注册每个微服务的基本信息,再启动微服务组合平台。这使得微服务组合平台缺乏动态部署和可扩展性。本文通过引入Spring Cloud Netflix生态系统中的Eureka服务注册中心,则不需要事先注册微服务信息,依靠服务发现模式即可动态确定微服务的位置,解决了微服务动态部署的问题,提高了可扩展性。此外,Spring Cloud Netflix生态系统中的Zuul Gateway网关、Ribbon负载均衡组件和Hystrix服务熔断机制将为平台提供负载均衡、熔断保护的功能[10],提高了平台的容错性和健壮性。
3 XML文档比较算法
现有的基于事件驱动的微服务组合平台,缺少对Web资源如XML文档的比较算法或变更检测方法,Web资源如XML文档的变更可作为微服务组合的触发事件。现有的XML文档比较算法多集中于对XML文档内容或结构单一层面的比较,缺少对多种因素的综合考虑[11]。本文提出一种新的XML文档比较算法,能在内容和结构2个层面来比较XML文档。具体而言,结合了内容和结构的相似度以实现综合考虑的目的。对内容层面的XML文档比较,用更先进的算法替换了原来的算法,提高了时间效率。
3.1 基于相似度的XML文档内容和结构比较
研究人员已经提出了多种用于比较XML文档的算法[12 - 15],然而这些算法主要依赖于XML文档中的主键,但主键并非在所有情况下都能定义,也并非在文档变化中一成不变,算法也欠缺一定的灵活性,无法针对XML文档中某个特定的元素进行相似度权重的配置。因此,Oliveira等人[16]提出了一种基于内容相似度的XML文档比较算法。但是,该算法没有考虑到XML文档结构的相似度,算法的时间效率仍然可以提高。
对于结构层面的XML比较,本文将XML文档结构转化为时间序列,进而通过离散时间傅里叶变换得到的频谱来进行比较[18],最后得到结构相似度。
良好的相似度函数应考虑到内容和结构的综合,因此本文提出的相似度函数为内容相似度和结构相似度的加权平均值,如式(1)所示,内容和结构因素的权重均是可定制的,保证了一定的灵活性。
Similarity(d1,d2)=
(1)
其中,Similarity为总体相似度,contsim和strusim分别代表内容相似度和结构相似度,Wc和Ws分别代表内容和结构相似度的权重,d1和d2分别代表2个XML。
3.2 内容层面相似度的衡量
在2个XML文档中,对于有子element的父element之间的相似度的衡量,本文采用动态规划的思想来处理,同样构造一个针对父element的相似度矩阵,用于衡量子element之间的相似度,这个过程一直持续到没有更深层次结构的子element为止。对于2个(子)element之间的相似度,分为4种分量:
(1)element名称相似度分量NS。
此处的名称指表示一个element“是什么类型”的字符串。若此项不相同,则无法进行比较,因此假定XML文档随时间的演变,此项不会发生更改。
(2)内容相似度分量CS。
“内容”指element所包含的内容,可能是以下4种格式:布尔值、数字、日期/时间和字符串。对于布尔值内容,若相同,则该分量为1,不同则为0。若为数字内容,则用式(2)来衡量数字之间的相似度:
CS(e1,e2)=
(2)
其中,CS代表数字之间的相似度,abs为绝对值函数。
若为日期/时间内容,则将时间转化为Unix时间戳,再利用式(2)计算相似度分量。若为字符串内容,则利用最长公共子序列LCS(Longest Common Subsequence)算法计算2个字符串之间的相似度。
(3)属性相似度分量AS。
首先提取出2个element所包含的属性名集合,若2个属性名相同,则使用(2)中的方法比较2个属性中内容的值。若属性名不同,则将不同的属性视为一个新属性。若另一个element中没有属性,则将其相似度设为0。最后,将所有得到的属性相似度除以开始得到的属性数量,得到总体的属性相似度分量。
(4)子element相似度分量SES。
分量的计算也使用动态规划思想,递归地计算2个子element的相似度,得到相似度矩阵,由优化算法得到总体相似度,作为子element相似度分量。
最后,2个XML element的总体相似度由上述4个分量进行加权平均得到。每个分量的权值都是可定制的,默认为25%。如式(3)所示:
Contsim(e1,e2)=(Wn*NS(e1,e2)+
Wc*CS(e1,e2)+Wa*AS(e1,e2)+
Ws*SES(e1,e2))/(Wc+Ws+Wa+Wn)
(3)
其中Contsim为内容相似度;NS,CS,AS,SES分别为计算以上4个相似度分量的函数;Wn,Wc,Wa,Ws分别为其相应的权值。
3.3 优化算法
本文采用的优化算法为一种改进的用于最大权二部图匹配的分解算法,可以以更高的时间效率找出二部图最大权匹配。该算法可以找出矩阵中满足一定条件的元素值的最大和。在本文的背景下,即找出相似度矩阵中相似度最大的element的匹配,作为2个XML文档的总体相似度的衡量。
找出相似度矩阵(无向带权二部图)中的最大element匹配的步骤如下所示:
(1)设矩阵中最大元素值为N。找出矩阵G中最大的2个元素值N和H2,并求出它们之间的差h=N-H2。记mm(G)为一个矩阵中满足两两元素均不在同一行/列的元素值个数的最大值。MWBM(G)为一个矩阵中满足两两元素均不在同一行/列,且元素之和最大的所有元素。Wt-MWBM(G)为MWBM(G)中所有元素之和。
(2)根据原矩阵G生成新矩阵Gh。新建一个矩阵Gh,其行列数与原矩阵相同。筛选出原矩阵中元素值在区间[N-h+1,N]内的元素,并重新赋值,赋值规则为:原矩阵元素值-h。其余元素赋以0值。
(3)为了便于表达下文的含义,如表1所示是一个矩阵以表格形式表示的例子,表格中数字即为原矩阵中的值。A和B分别代表矩阵的行和列的标号集合,Wt(A,B)表示矩阵中的元素值。定义一个函数C:A∪B→N0,满足C(A)+C(B)≥Wt(A,B)。设Wt(C)= ∑x∈A∪BC(x)。当Wt(C)最小时,记C为一个最小覆盖。求出矩阵Gh的最小覆盖,记为Ch。
Table 1 Table representing matrix elements
用(5)得到的最终结果除以矩阵的秩,即可得到2个XML element的最大总体相似度。
算法伪代码如算法1所示:
算法1最大权二部图匹配
输入:带权无向二部图G。
输出:二部图最大权匹配的权值和(Wt-MWBM(G))。
步骤1public static intWt-MWBM(G)= 0;/*初始值为0;*/
步骤2publicfindh(GraphG)/*计算出图中最大2个权值之差h;*/
步骤3public classBipartiteMatching{
publicBipartiteMatching(GraphG) {}};//找出最大基数匹配mm(G)
步骤4publicformationGh(GraphG){};/*从图G生成新图Gh*/
步骤5public class MinimumWeightedVertexCover{
public static voidfindMinimumWeighted-VertexCoverApprox(ArrayList〈Edge〉graph,int[]weights){ };/*找出图Gh的最小顶点覆盖*/
步骤7BipartiteMatching(Gh);//计算出mm(Gh)
returnh*|mm(Gh)|;/*得到最终结果Wt-MWBM(G)*/
}else{
4 微服务组合平台实现
4.1 微服务组合平台
第2节所述的微服务组合平台可用如下DSL代码实现运行:
composition {
process weather=require("getweather");
getweather.init({"latitude":"51.8498698","longitude":"-0.6637842"});
⋮
stream weather=getweather.invoke({"latitude":"51.8498698","longitude":"-0.6637842"});
on(weather:out as data) do {
weather-forecast.invoke("cityname":"beijing");}
DSL代码作为高级代码将被DSL编译器编译为可在运行时系统中运行的目标代码(JavaScript代码)。运行时系统Node.js的底层API被修改以适合微服务组合需求。如Node.js中的child_process.exec()函数应作为启动Node.js微服务应用程序进程的函数,其子进程调用结束后的callback改为data或error,Node.js事件机制中的emitter.on(eventName,listener)修改为其函数参数与微服务组合代码中事件驱动的参数相匹配。对于驱动微服务组合的事件形式,例如多个事件的叠加,采用基于事件订阅/发布模式的EventProxy[19]中的after方法,可以侦听多次相同事件或多次不同事件,待全部事件都发生后执行相应操作。
以上示例代码将实现若接收到用户查询当前天气状况的请求,作为事件驱动条件,则给出未来24 h的白天、夜晚的平均温度(开式度)和平均气压数据(帕)。如图3所示,用户查询到当前天气之后,将自动启动查询未来天气的微服务,以及Spring Cloud组件中的Zuul Gateway网关、Eureka服务注册中心、Ribbon负载均衡组件和Hystrix服务熔断组件,并在浏览器中查看运行结果。
Figure 3 Microservices after the composition of microservices and running results图3 运行微服务组合后的各项微服务和运行结果
4.2 微服务组合平台中的细粒度数据访问控制
在微服务组合平台中的细粒度数据访问控制方面,本文采用的是基于标签的访问控制方法。基于标签的访问控制的实现借助PolicyLine[20],它是一个基于属性的Node.js访问控制库,涉及对4种实体(user、env、action和resource,分别代表用户、访问环境、操作和所访问资源)的操作。访问控制策略policy用于检查访问权限并计算condition的对象。new Policy (rules)将根据规则rules创建一个新的policy对象。规则rules代码如下所示:
let rules={
effect:"permit",
algorithm:"all",
condition:[ "resource.label=confidential"]};
let policy=new Policy (rules);
其中user.label、resource.label和action.name分别代表基于标签的访问控制的3种标签。target 表示用于计算policy的一组逻辑条件,可以包含任何逻辑表达式。algorithm表示根据规则计算策略的算法,可以取值为all或any。取值为all表示所有规则rules都应返回true,取值为any表示至少应有一个规则返回true。effect表示对policy计算结果所施加的影响,可取permit或deny值。当规则rules和算法algorithm综合计算的结果为true时,则由effect值决定返回policy为true或false值。若允许则policy返回true,若拒绝则policy返回false。condition表示用于访问资源的一组条件,可由user、env、action和resource这4种参数组成逻辑表达式。
以以下描述微服务组合的DSL代码为例:
⋮
stream issues=getNewIssues.invoke({"repository":"medley/repo" });
on (issues:out as issue) do {
sendEmail.invoke({
"to":"john@doe.com","body":"New issue:{{$.issue.url}}" })};
⋮
为了避免issues中的数据泄漏到服务sendEmail中,需要根据基于标签的访问控制设定policy,核心代码如前文规则rules所示,根据是否允许读写将effect的值改为permit或deny。利用Policy.check()方法验证实例sendEmail是否可以根据所定义的访问控制策略访问到目标资源,结果将返回true或false。将代码部署至CodeSandbox平台上的运行结果如图4所示。
Figure 4 Different access results obtained by two different access strategies图4 2种不同的访问策略得到的不同访问结果
4.3 XML文档比较
本文利用JavaFX技术构建了XML文档比较工具,并选取不同文件大小的XML文档组,进行10次5组XML文档比较,每组比较分别使用匈牙利算法和3.1.2节中的优化算法,在Eclipse控制台中记录得到的执行时间(ms),结果如图5,从图5中可看出算法执行耗时确实有所下降。
Figure 5 Time-consuming comparison of the two algorithms图5 2种算法耗时比较
以图6a中的2个XML文档为例,根据3.1节中的公式可得内容相似度和结构相似度分别为0.75和0.58。
Figure 6 Overall similarity changes of two XML documents with different structures and different contents图6 2个结构、内容不同的XML文档及其总体相似度变化的曲线
考虑XML文档内容和结构的综合特征,通过改变权值Wc和Ws,可得到总体相似度变化的曲线为一次函数s=0.17w+0.58,如图6b所示,w表示内容层面相似度。
5 结束语
本文设计了一种基于变更事件驱动的微服务组合平台,其在领域特定语言层面上支持细粒度数据的访问控制,防止敏感数据泄露给不受信任的服务,丰富了事件驱动的形式,并引入Spring Cloud Netflix生态系统,支持微服务的动态部署,提高了平台的健壮性。对于触发编排微服务组合事件的条件,增加了对Web资源(XML文档)的变更检测,即对XML文档从内容和结构2个层面综合比较,并对现有的XML文档内容层面的比较算法进行了优化。未来的工作着眼于利用机器学习技术,在执行微服务组合作业的多节点集群的调度器中考虑每个组合的资源使用情况,以改进作业分配策略,提高集群整体资源利用率。