揭密首个面向IaaS的查问言语 ZStack Language Query ZQL

为了简化UI上班并为运维人员提供一种愈加灵敏的资源查问模式,ZStack在2.6版本中推出了***面向IaaS的查问言语 —— ZStack Query Language,简称ZQL。

背景

IaaS治理着海量的数据中心资源,如何对这些资源启动灵敏极速的查问是运维人员面临的一个难题。在以往的IaaS软件中,往往只对单个资源的某些字段提供有限的API查问支持,例如可以经过虚构机的IP字段查问,这无余够也不灵敏。运维人员在做复杂查问时往往得绕开IaaS软件间接查问其后端数据库,这既要求运维人员要了解IaaS资源的外部相关,又带来了数据库误操作的危险。

从ZStack正式颁布的***个版本ZStack0.6开局,咱们就努力在API层面提供跟数据库级别的查问性能,ZStack的每个资源都蕴含一个Query API,能够经过资源的自身字段以及资源的关联资源字段启动查问。例如

QueryVmInstance name~=web-vm state=Running

这里查问一切名字蕴含web-vm字符串,正在运转中的VM。又例如

QueryVmInstance vmNics.eip.vip.ip='22.22.22.22'

EIP是虚构机的关联资源,这里查问网卡绑定了EIP为22.22.22.22的虚构机。

Query API性能弱小:

用户可以经过count参数前往满足查问条件资源数量,相似SQL的count();

经过fields参数指定要前往的字段,相似SQL的uuid,name from;

经过sortBy、sortDirection参数指定排序的字段和方向,相似SQL的order by;

经过start、limit参数成功分页查问,相似SQL的limit和offset。

Query API除了经常使用繁难外,定义也很繁难。程序员在ZStack中参与了一种新资源后,只有要在代码中定义如下class:

@AutoQuery(replyClass = APIQueryVmInstanceReply.class, inventoryClass = VmInstanceInventory.class)

public class APIQueryVmInstanceMsg extends APIQueryMessage {

不须要写任何成功,对应资源就具有了Query API。

ZStack外部蕴含一个Query Service担任处置一切资源的Query API,将他们翻译成相应的SQL语句,在查问条件中蕴含关联资源条件时会生成对应的Join子句。

基于Query API, ZStack0.6版本就蕴含了超越万个单项查问条件,组合查问条件数为万的阶乘。极大的繁难了运维和复杂UI的设计。但Query API依然蕴含一些毛病:

查问条件之间只能是AND逻辑,不可口头OR逻辑,条件之间也不可加括号成功复杂逻辑组合

不支持相似SQL中的sub query子句

单个API只能查问一种资源,查问多种资源时须要调用多个API

不支持跟监控系统的查问言语整合

随着ZStack UI的场景越来越丰盛,Query API的限度使得UI端的上班越来越多,很多场景须要屡次调用Query API启动数据组合。例如在监控Top 5页面(用于检测系统中CPU、内存、磁盘、网络等资源经常使用率***5个资源的页面),须要先驳回Query API将虚构机、物理机等资源信息查问回来,再调用监控系统ZWatch的API查问对应的监控数据。

Query API在未来的ZStack版本中会不时保管并保养,其后端成功曾经从原来的Query Service交流成ZQL。

ZStack Query Language

经常使用过驰名issue治理系统JIRA的开发者都知道JIRA在启动初级搜查的时刻提供一种查问言语JQL (JIRA Query Language),能够经常使用一种相似SQL的DSL(Domain Specific Language)对JIRA中ticket的各个字段启动高效的查问。ZQL跟JQL相似,也是一种相似SQL的DSL,先来看一个例子:

query vminstance where or vmnics.ip='192.168.0.10' or (vmnics.eip = '172.20.100.100' (cpuNum >= 8 or clusterUuid in ('fe13b725c80e45709f0414c266a80239','73ca1ca7603d454f8fa7f3bb57097f80')))

在这个繁难例子中,可以看到很多相熟的SQL元素,例如and/or条件、括号、>=/in操作符等。ZQL可以看作SQL的一个子集外加ZStack依据自身需求启动的增强的查问言语。它的基本结构如下:

QUERY queryTarget (WHERE condition+)? restrictBy? returnWith? groupBy? orderBy? limit? offset? filterBy? namedAs?

query关键词

一条ZQL语句通常以query关键字扫尾,queryTarget示意要查问的资源或资源字段的汇合。前面的例子中vminstance代表虚构机,例如host代表物理机、zone代表区域,一切可被查问的资源都有自己的称号。假设不宿愿前往资源的一切字段,只宿愿取得资源的一个或多个字段,成功相似SQL的uuid,name from ...的性能,可以在资源名后指定字段名,多个字段名用逗号隔离,例如:

query vminstance.uuid,name,cpuNum

该查问前往一切虚构机的UUID、称号以及CPU数量。

除了query关键字,查问也能以count和sum关键字扫尾,前者前往满足查问条件资源的总数,后者可以对资源的某个字段启动求和。例如:

vminstance where cpuNum > 8

前往系统中CPU数量超越8核的虚构机的总数。

sum vminstance.memorySize by name where cpuNum > 8

用虚构机名字对CPU核数超越8个的虚构机启动分组,对它们的memorySize字段启动求和。假设系统中有两个10CPU8G的虚构机都名为webvm,则求和后前往webvm虚构机总内存经常使用数为16G。翻译成SQL则为:

sum(memorySize) from vminstance where cpuNum > 8 group by name

WHERE从句

ZQL的WHERE从句跟SQL的WHERE从句相似,支持and/or逻辑操作符、括号组合,条件的比拟符支持=,!=,>,>=,<, <=, like, not like, is null, is not null, in, not in,查问条件名为资源的字段名。跟SQL不一样的中央在于,ZQL的查问条件可以是关联资源的字段,例如:

query vminstance where

vmNics.eip.vip.ip='22.22.22.22'

留意where从句前无需写相似SQL的from xx从句,由于query vminstance曾经限定了被查问的资源

这里vip跟eip关联,eip跟vmnic关联,vmnic又跟vminstance关联,则咱们可以指定vip的IP作为查问条件。这正是ZQL的弱小之处,关于多个关联资源的查问,无需调用屡次API在运行端组合数据,也无需像SQL一样写复杂的join从句,只有要像编程一样经过点号(.)援用另一个资源即可, ZQL的翻译器会智能将跨资源援用翻译成对应的SQL join从句。

WHERE从句可以蕴含子查问,相似于SQL的sub query性能,例如:

query vminstance where vmNics.l3NetworkUuid in (query l3network.uuid

where ipRanges.networkCidr='10.1.0.0/24')

这里找出一切CIRD为10.1.0.0/24的三层网络上运转的虚构机。

上方这个例子也可以用更繁难的方法成功:query vminstance where vmNics.l3network.ipRanges.networkCidr='10.1.0.0/24',这里只是为了演示子查问性能

GROUP BY、ORDER BY、LIMIT、OFFSET 子句

跟SQL一样,ZQL支持GROUP BY、ORDER BY、LIMIT、OFFSET关键字,以成功分组、排序、分页等性能。

经过虚构机的区域UUID和集群UUID分组,统计各区域中各集群中虚构机的数量。

vminstance group by zoneUuid,clusterUuid

查问一切虚构机,经常使用cpuNum字段降序排序。

1.query vminstance order by cpuNum desc

LIMIT、OFFSET:

经常使用limit和offset成功分页:

query vminstance limit 100 offset 10

多资源查问

关于多个资源的查问,可以经过多条query查问语句成功,语句之间经常使用分号分隔,例如:

query vminstance where name = 'my-vm';

query host where cpuNum > 10;

query zone;

则一次性调用即可前往三种资源的查问结果。由于前往的结果是一个map的JSON结构,为了繁难取得对应语句的查问结果,可以经常使用named as关键字对查问语句命名,例如:

query vminstance where name = 'my-vm' named as 'vm';

query host where cpuNum > 10 named as 'host';

query zone named as 'zone';

则在前往的JSON map中,可以经过vm、host、zone作为key取得对应语句的查问结果。

兼并监控查问 (return with从句)

在ZStack中经常使用了两种数据库:相关数据库寄存元数据,时序数据库寄存监控数据。由于不同数据库查问模式不一样,在ZQL之前,用户要查问一个资源的监控数据,须要先经过Query API取得该资源的元数据,再经过ZWatch的查问API取得其监控数据。例如要查问一个名为webvm虚构机的CPU经常使用率监控数据,要口头如下API:

QueryVmInstance fields=uuid name=webvm

GetMetricData namespace=ZStack/VM metricName=CPUUsedUtilization labels=VMUuid=QueryVmInstance前往的UUID offsetAheadOfCurrentTime=60

ZQL经过return with子句处置这个疑问。return with是一种插件机制,它准许子系统 经过插件将自身的查问条件注入ZQL中,ZQL会先口头相关数据库查问,将满足条件资源的原数据查问进去后,再将资源的主键(primary key)作为输入条件调用成功return with子句的插件,***将插件的查问结果一并前往给ZQL的调用者。

上述查问虚构机监控数据的需求可以经过一条ZQL语句成功:

query vminstance.hostUuid,uuid where name = 'webvm' return with (zwatch{resultName='webvm-cpu',metricName='CPUAllUsedUtilization',offsetAheadOfCurrentTime=60})

前往:

"results": [

"inventories": [

"hostUuid": "f8271f58468b4281a212a43e530b5535",

"uuid": "05781209d24341ac84fc055ae71820ac"

"returnWith": {

"webvm-cpu": [

"labels": {

"VMUuid": "05781209d24341ac84fc055ae71820ac"

"time": 1533280402,

"value": 0.8

"labels": {

"VMUuid": "05781209d24341ac84fc055ae71820ac"

"time": 1533280462,

"value": 0.8

"success": true

这里咱们用一条ZQL语句中即前往了咱们感兴味的元数据字段:uuid和hostUuid,也前往了该虚构机的监控数据。认真的读者曾经留意到咱们在ZWatch查问字段中指定了参数resultName='webvm-cpu',并且在前往的JSON map中监控数据的key也是webvm-cpu。跟named as关键字一样,这是为了口头多条ZWatch查问子句时繁难检索前往结果预备的。 ZStack UI经常使用十分复杂的ZQL查问语句,例如在TOP 5页面,一条ZQL查问蕴含多达13个ZWatch查问:

ZQLQuery zql="query vmInstance.uuid,name where zoneUuid='89e148fb667c404dbc5309a2e956fa28' hypervisorType='KVM' type='UserVm' state='Running' return with (zwatch{resultName='CPUAllUsedUtilization',metricName='CPUAllUsedUtilization',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid\")',functions='top(num=5)'},zwatch{resultName='MemoryUsedInPercent',metricName='MemoryUsedInPercent',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid\")',functions='top(num=5)'},zwatch{resultName='MemoryFreeInPercent',metricName='MemoryFreeInPercent',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid\")',functions='top(num=5)'},zwatch{resultName='DiskAllReadOps',metricName='DiskAllReadOps',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid\")',functions='top(num=5)'},zwatch{resultName='DiskAllWriteOps',metricName='DiskAllWriteOps',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid\")',functions='top(num=5)'},zwatch{resultName='DiskAllReadBytes',metricName='DiskAllReadBytes',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid\")',functions='top(num=5)'},zwatch{resultName='DiskAllWriteBytes',metricName='DiskAllWriteBytes',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid\")',functions='top(num=5)'},zwatch{resultName='NetworkOutBytes',metricName='NetworkOutBytes',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid,NetworkDeviceLetter\")',functions='top(num=5)'},zwatch{resultName='NetworkInBytes',metricName='NetworkInBytes',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid,NetworkDeviceLetter\")',functions='top(num=5)'},zwatch{resultName='NetworkOutPackets',metricName='NetworkOutPackets',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid,NetworkDeviceLetter\")',functions='top(num=5)'},zwatch{resultName='NetworkInPackets',metricName='NetworkInPackets',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid,NetworkDeviceLetter\")',functions='top(num=5)'},zwatch{resultName='NetworkOutErrors',metricName='NetworkOutErrors',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid,NetworkDeviceLetter\")',functions='top(num=5)'},zwatch{resultName='NetworkInErrors',metricName='NetworkInErrors',offsetAheadOfCurrentTime=60,period=6,functions='average(groupBy=\"VMUuid,NetworkDeviceLetter\")',functions='top(num=5)'})"

上例是在ZStack CLI中口头时的例子,经常使用\对引号转义

当资源特意多时,时序数据库查问性能或者成为多条ZWatch查问的性能瓶颈,故return with会经过并发的模式口头插件,自动并发度为10。例如上述例子中的13条ZWatch查问会在10个线程中并发口头。用户可以经过全局性能zql.returnWith.concurrency更改并发度,例如

UpdateGlobalConfig category=query name=zql.returnWith.concurrency value=15

限度查问 (restrict by从句)

ZStack的企业治理模块蕴含一特性能,可以对治理绑定某个区域,使得该治理员只能治理该区域内的资源,这就要求咱们的ZQL对该治理员的查问恳求只前往与其绑定区区中的资源。

关于虚构机这样的资源,其元数据自身就带zoneUuid字段用于标识所在区域。但关于eip这样的资源,其元数据并无任何字段示意区域属性,区域属性是由其所在的三层网络或绑定的虚构机确定的。例如要查问某个区域内的eip,可以经常使用:

# 经过与虚构机的绑定相关查问

query eip where vmNic.vmInstance.zoneUuid = '52fdad0a2c0d4131a6c0fc6c1b7141a6'

# 经过所在三层网络确定

query eip where vip.l3Network.zoneUuid = '52fdad0a2c0d4131a6c0fc6c1b7141a6'

无论那种模式,都须要调用者了解知道eip跟zone之间的关联相关,这对API的经常使用者提出了十分厚道的要求。ZQL经过restrict by从句处置这个疑问。跟return with从句相似,restrict by也是个插件框架,它准许其它服务经过插件解读restrict by从句中指定的条件,向生成的SQL中注入额外条件。例如上方的eip例子经过restrict by从句可以写成:

query eip restrict by (zone.uuid='52fdad0a2c0d4131a6c0fc6c1b7141a6')

这里调用者无需知道eip跟zone之间的逻辑相关,restrict by的门路插件会智能计算两者的逻辑相关,并生成对应的SQL join从句。这里eip既可以经过所在三层网络,也可以经过绑定的虚构机确定和区域的相关,插件会智能计算门路权重,经常使用权重***的门路生成SQL语句。

关于eip这个例子,插件会选取经过三层网络的相关生成SQL语句。由于eip或者没有跟虚构机绑定,但其必定处于某个三层网络,故三层网络这条门路的权重更高。

restrict by支持多个条件,经过逗号分隔,多个条件之间是AND相关。

除了给ZQL调用者经常使用外,restrict by插件在ZStack外部也被其它服务宽泛经常使用。例如账号系统会经过插件在个别账户调用ZQL的时刻注入跟账号关联的SQL语句,使得个别账号只能查问到属于该账号的资源;又例如SNS服务会经过插件注入语句让ZQL只能查问到非系统类型的接纳端。

未来

ZQL为ZStack提供了一种相似SQL的IaaS查问言语,并且能够经过return with插件框架跟其它非相关数据库系统启动查问整合。在未来的版本中咱们还会继续丰盛其性能,目前有两个方向:

filter by从句

只管return with的ZWatch插件能让咱们在查问资源元数据的同时查问其监控数据,但还不能将监控数据作为元数据的查问条件,例如不可经过一条ZQL成功查问某个集群中一切CPU经常使用率超越90%的虚构机。这在未来版本中会经过filter by从句成功,例如:

query vminstance where clusterUuid = '33e26bd547d149fbb190436cc9aca824' filter by (zwatch{metricName='CPUAllUsedUtilization', offsetAheadOfCurrentTime=60, threshold>90})

雷同,filter by从句会成功成相似return with的插件框架,用于整合非相关数据库的查问条件。

智能CLI

ZQL有少量的从句,每个ZStack又有少量的可查问字段,目前ZStack CLI可以对Query API的可查问字段启动补全,但ZQL还临时不可补全。未来版本中,咱们会对CLI启动在增强,使其对一切查问条件可以启动揭示和补全。

欢迎大家在ZStack官方下载页面

启动收费的下载装置和试用。

您可能还会对下面的文章感兴趣: