Apache IoTDB(11):分段聚合深度解析——从原理到实战的完整指南

Apache IoTDB(11):分段聚合深度解析——从原理到实战的完整指南

引言

在工业物联网时代,时序数据的高效处理成为企业数字化转型的核心挑战。Apache IoTDB作为专为物联网设计的时序数据库,其分段分组聚合能力堪称数据处理的"瑞士军刀"。

在这里插入图片描述

Apache IoTDB 时序数据库【系列篇章】

No.文章地址(点击进入)
1Apache IoTDB(1):时序数据库介绍与单机版安装部署指南
2Apache IoTDB(2):时序数据库 IoTDB 集群安装部署的技术优势与适用场景分析
3Apache IoTDB(3):时序数据库 IoTDB Docker部署从单机到集群的全场景部署与实践指南
4Apache IoTDB(4):深度解析时序数据库 IoTDB 在Kubernetes 集群中的部署与实践指南
5Apache IoTDB(5):深度解析时序数据库 IoTDB 中 AINode 工具的部署与实践
6Apache IoTDB(6):深入解析数据库管理操作——增删改查与异构数据库实战指南
7Apache IoTDB(7):设备模板管理——工业物联网元数据标准化的破局之道
8Apache IoTDB(8):时间序列管理——从创建到分析的实战指南
9Apache IoTDB(9):数据库操作——数据写入从CLI到集群部署的六种实战
10Apache IoTDB(10):数据库操作——从查询到优化的全链路实践指南

本文将深入剖析 IoTDB 分段聚合——五大分段,结合案例揭示性能优化密码,构建高吞吐、低延迟的时序数据处理架构。

一、时间区间分段聚合

时间区间分段聚合是一种时序数据典型的查询方式,数据以高频进行采集,需要按照一定的时间间隔进行聚合计算,如计算每天的平均气温,需要将气温的序列按天进行分段,然后计算平均值。

在 IoTDB 中,聚合查询可以通过 GROUP BY 子句指定按照时间区间分段聚合。用户可以指定聚合的时间间隔和滑动步长,相关参数如下:

参数 1:时间轴显示时间窗口大小
参数 2:聚合窗口的大小(必须为正数)
参数 3:聚合窗口的滑动步长(可选,默认与聚合窗口大小相同)

参数具体含义见下图:

在这里插入图片描述

1.1 未指定滑动步长的时间区间分段聚合查询

selectcount(status), max_value(temperature)from root.ln.wf01.wt01 groupby([2017-11-01T00:00:00,2017-11-07T23:00:00),1d);

这条查询的含义是:

由于用户没有指定滑动步长,滑动步长将会被默认设置为跟时间间隔参数相同,也就是1d。

上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是 [2017-11-01T00:00:00, 2017-11-07T23:00:00)。

上面这个例子的第二个参数是划分时间轴的时间间隔参数,将1d当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[0,1d), [1d, 2d), [2d, 3d) 等等。

然后系统将会用 WHERE 子句中的时间和值过滤条件以及 GROUP BY 语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在 [2017-11-01T00:00:00, 2017-11-07 T23:00:00) 这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从 2017-11-01T00:00:00 到 2017-11-07T23:00:00:00 的每一天)

每个时间间隔窗口内都有数据,SQL 执行后的结果如下:

在这里插入图片描述

1.2 指定滑动步长的时间区间分段聚合查询

selectcount(status), max_value(temperature)from root.ln.wf01.wt01 groupby([2017-11-0100:00:00,2017-11-0723:00:00),3h,1d);

这条查询的含义是:

由于用户指定了滑动步长为1d,GROUP BY 语句执行时将会每次把时间间隔往后移动一天的步长,而不是默认的 3 小时。

也就意味着,我们想要取从 2017-11-01 到 2017-11-07 每一天的凌晨 0 点到凌晨 3 点的数据。

上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是 [2017-11-01T00:00:00, 2017-11-07T23:00:00)。

上面这个例子的第二个参数是划分时间轴的时间间隔参数,将3h当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[2017-11-01T00:00:00, 2017-11-01T03:00:00), [2017-11-02T00:00:00, 2017-11-02T03:00:00), [2017-11-03T00:00:00, 2017-11-03T03:00:00) 等等。

上面这个例子的第三个参数是每次时间间隔的滑动步长。

然后系统将会用 WHERE 子句中的时间和值过滤条件以及 GROUP BY 语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在 [2017-11-01T00:00:00, 2017-11-07 T23:00:00) 这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从 2017-11-01T00:00:00 到 2017-11-07T23:00:00:00 的每一天的凌晨 0 点到凌晨 3 点)

每个时间间隔窗口内都有数据,SQL 执行后的结果如下:

在这里插入图片描述

滑动步长可以小于聚合窗口,此时聚合窗口之间有重叠时间

selectcount(status), max_value(temperature)from root.ln.wf01.wt01 groupby([2017-11-0100:00:00,2017-11-0110:00:00),4h,2h);

SQL 执行后的结果如下:

在这里插入图片描述

1.3 按照自然月份的时间区间分段聚合查询

selectcount(status)from root.ln.wf01.wt01 wheretime>2017-11-01T01:00:00groupby([2017-11-01T00:00:00,2019-11-07T23:00:00),1mo,2mo);

这条查询的含义是:

由于用户指定了滑动步长为2mo,GROUP BY 语句执行时将会每次把时间间隔往后移动 2 个自然月的步长,而不是默认的 1 个自然月。

也就意味着,我们想要取从 2017-11-01 到 2019-11-07 每 2 个自然月的第一个月的数据。

上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是 [2017-11-01T00:00:00, 2019-11-07T23:00:00)。

起始时间为 2017-11-01T00:00:00,滑动步长将会以起始时间作为标准按月递增,取当月的 1 号作为时间间隔的起始时间。

上面这个例子的第二个参数是划分时间轴的时间间隔参数,将1mo当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[2017-11-01T00:00:00, 2017-12-01T00:00:00), [2018-02-01T00:00:00, 2018-03-01T00:00:00), [2018-05-03T00:00:00, 2018-06-01T00:00:00) 等等。

上面这个例子的第三个参数是每次时间间隔的滑动步长。

然后系统将会用 WHERE 子句中的时间和值过滤条件以及 GROUP BY 语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在 [2017-11-01T00:00:00, 2019-11-07T23:00:00) 这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从 2017-11-01T00:00:00 到 2019-11-07T23:00:00:00 的每两个自然月的第一个月)

每个时间间隔窗口内都有数据,SQL 执行后的结果如下:

在这里插入图片描述
selectcount(status)from root.ln.wf01.wt01 groupby([2017-10-31T00:00:00,2019-11-07T23:00:00),1mo,2mo);

这条查询的含义是:

由于用户指定了滑动步长为2mo,GROUP BY 语句执行时将会每次把时间间隔往后移动 2 个自然月的步长,而不是默认的 1 个自然月。

也就意味着,我们想要取从 2017-10-31 到 2019-11-07 每 2 个自然月的第一个月的数据。

与上述示例不同的是起始时间为 2017-10-31T00:00:00,滑动步长将会以起始时间作为标准按月递增,取当月的 31 号(即最后一天)作为时间间隔的起始时间。若起始时间设置为 30 号,滑动步长会将时间间隔的起始时间设置为当月 30 号,若不存在则为最后一天。

上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是 [2017-10-31T00:00:00, 2019-11-07T23:00:00)。

上面这个例子的第二个参数是划分时间轴的时间间隔参数,将1mo当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[2017-10-31T00:00:00, 2017-11-31T00:00:00), [2018-02-31T00:00:00, 2018-03-31T00:00:00), [2018-05-31T00:00:00, 2018-06-31T00:00:00) 等等。

上面这个例子的第三个参数是每次时间间隔的滑动步长。

然后系统将会用 WHERE 子句中的时间和值过滤条件以及 GROUP BY 语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在 [2017-10-31T00:00:00, 2019-11-07T23:00:00) 这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从 2017-10-31T00:00:00 到 2019-11-07T23:00:00:00 的每两个自然月的第一个月)

每个时间间隔窗口内都有数据,SQL 执行后的结果如下:

在这里插入图片描述

1.4 左开右闭区间

每个区间的结果时间戳为区间右端点

selectcount(status)from root.ln.wf01.wt01 groupby((2017-11-01T00:00:00,2017-11-07T23:00:00],1d);

这条查询语句的时间区间是左开右闭的,结果中不会包含时间点 2017-11-01 的数据,但是会包含时间点 2017-11-07 的数据。

结果:

在这里插入图片描述

二、差值分段聚合

IoTDB支持通过GROUP BY VARIATION语句来根据差值进行分组。GROUP BY VARIATION会将第一个点作为一个组的基准点,每个新的数据在按照给定规则与基准点进行差值运算后,
如果差值小于给定的阈值则将该新点归于同一组,否则结束当前分组,以这个新的数据为新的基准点开启新的分组。
该分组方式不会重叠,且没有固定的开始结束时间。其子句语法如下:

groupby variation(controlExpression[,delta][,ignoreNull=true/false])

不同的参数含义如下

controlExpression
分组所参照的值,可以是查询数据中的某一列或是多列的表达式
(多列表达式计算后仍为一个值,使用多列表达式时指定的列必须都为数值列), 差值便是根据数据的controlExpression的差值运算。

delta
分组所使用的阈值,同一分组中每个点的controlExpression对应的值与该组中基准点对应值的差值都小于delta。当delta=0时,相当于一个等值分组,所有连续且expression值相同的数据将被分到一组。

ignoreNull
用于指定controlExpression的值为null时对数据的处理方式,当ignoreNull为false时,该null值会被视为新的值,ignoreNull为true时,则直接跳过对应的点。

在delta取不同值时,controlExpression支持的返回数据类型以及当ignoreNull为false时对于null值的处理方式可以见下表:

在这里插入图片描述

下图为差值分段的一个分段方式示意图,与组中第一个数据的控制列值的差值在delta内的控制列对应的点属于相同的分组。

在这里插入图片描述

2.1 注意事项

controlExpression的结果应该为唯一值,如果使用通配符拼接后出现多列,则报错。
对于一个分组,默认Time列输出分组的开始时间,查询时可以使用select __endTime的方式来使得结果输出分组的结束时间。
与ALIGN BY DEVICE搭配使用时会对每个device进行单独的分组操作。
当没有指定delta和ignoreNull时,delta默认为0,ignoreNull默认为true。
当前暂不支持与GROUP BY LEVEL搭配使用。

使用如下的原始数据,后面会给出几个事件分段查询的使用样例

在这里插入图片描述

2.2 delta=0时的等值事件分段

select __endTime,avg(s1),count(s2),sum(s3)from root.sg.d groupby variation(s6)

得到如下的查询结果,这里忽略了s6为null的行

在这里插入图片描述

当指定ignoreNull为false时,会将s6为null的数据也考虑进来

select __endTime,avg(s1),count(s2),sum(s3)from root.sg.d groupby variation(s6, ignoreNull=false)

结果:

在这里插入图片描述

2.3 delta!=0时的差值事件分段

select __endTime,avg(s1),count(s2),sum(s3)from root.sg.d groupby variation(s6,4)

结果:

在这里插入图片描述

group by子句中的controlExpression同样支持列的表达式

select __endTime,avg(s1),count(s2),sum(s3)from root.sg.d groupby variation(s6+s5,10)

结果:

在这里插入图片描述

三、条件分段聚合

当需要根据指定条件对数据进行筛选,并将连续的符合条件的行分为一组进行聚合运算时,可以使用GROUP BY CONDITION的分段方式;不满足给定条件的行因为不属于任何分组会被直接简单忽略。
其语法定义如下:

groupby condition(predict,[keep>/>=/=/<=/<]threshold,[,ignoreNull=true/false])

predict
返回boolean数据类型的合法表达式,用于分组的筛选。

keep[>/>=/=/<=/<]threshold
keep表达式用来指定形成分组所需要连续满足predict条件的数据行数,只有行数满足keep表达式的分组才会被输出。keep表达式由一个’keep’字符串和long类型的threshold组合或者是单独的long类型数据构成。

ignoreNull=true/false
用于指定遇到predict为null的数据行时的处理方式,为true则跳过该行,为false则结束当前分组。

3.1 使用注意事项

keep条件在查询中是必需的,但可以省略掉keep字符串给出一个long类型常数,默认为keep=该long型常数的等于条件。
ignoreNull默认为true。
对于一个分组,默认Time列输出分组的开始时间,查询时可以使用select __endTime的方式来使得结果输出分组的结束时间。
与ALIGN BY DEVICE搭配使用时会对每个device进行单独的分组操作。
当前暂不支持与GROUP BY LEVEL搭配使用。
对于如下原始数据,下面会给出几个查询样例:
+-----------------------------+-------------------------+-------------------------------------+------------------------------------+ | Time|root.sg.beijing.car01.soc|root.sg.beijing.car01.charging_status|root.sg.beijing.car01.vehicle_status| +-----------------------------+-------------------------+-------------------------------------+------------------------------------+ |1970-01-01T08:00:00.001+08:00| 14.0| 1| 1| |1970-01-01T08:00:00.002+08:00| 16.0| 1| 1| |1970-01-01T08:00:00.003+08:00| 16.0| 0| 1| |1970-01-01T08:00:00.004+08:00| 16.0| 0| 1| |1970-01-01T08:00:00.005+08:00| 18.0| 1| 1| |1970-01-01T08:00:00.006+08:00| 24.0| 1| 1| |1970-01-01T08:00:00.007+08:00| 36.0| 1| 1| |1970-01-01T08:00:00.008+08:00| 36.0| null| 1| |1970-01-01T08:00:00.009+08:00| 45.0| 1| 1| |1970-01-01T08:00:00.010+08:00| 60.0| 1| 1| +-----------------------------+-------------------------+-------------------------------------+------------------------------------+ 

查询至少连续两行以上的charging_status=1的数据,sql语句如下:

select max_time(charging_status),count(vehicle_status),last_value(soc)from root.**groupby condition(charging_status=1,KEEP>=2,ignoreNull=true)

结果:

+-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+ | Time|max_time(root.sg.beijing.car01.charging_status)|count(root.sg.beijing.car01.vehicle_status)|last_value(root.sg.beijing.car01.soc)| +-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+ |1970-01-01T08:00:00.001+08:00| 2| 2| 16.0| |1970-01-01T08:00:00.005+08:00| 10| 5| 60.0| +-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+ 

当设置ignoreNull为false时,遇到null值为将其视为一个不满足条件的行,会结束正在计算的分组。

select max_time(charging_status),count(vehicle_status),last_value(soc)from root.**groupby condition(charging_status=1,KEEP>=2,ignoreNull=false)

结果:

+-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+ | Time|max_time(root.sg.beijing.car01.charging_status)|count(root.sg.beijing.car01.vehicle_status)|last_value(root.sg.beijing.car01.soc)| +-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+ |1970-01-01T08:00:00.001+08:00| 2| 2| 16.0| |1970-01-01T08:00:00.005+08:00| 7| 3| 36.0| |1970-01-01T08:00:00.009+08:00| 10| 2| 60.0| +-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+ 

四、会话分段聚合

GROUP BY SESSION可以根据时间列的间隔进行分组,在结果集的时间列中,时间间隔小于等于设定阈值的数据会被分为一组。例如在工业场景中,设备并不总是连续运行,GROUP BY SESSION会将设备每次接入会话所产生的数据分为一组。
其语法定义如下:

groupbysession(timeInterval)

timeInterval
设定的时间差阈值,当两条数据时间列的差值大于该阈值,则会给数据创建一个新的分组。

下图为group by session下的一个分组示意图

在这里插入图片描述

4.1 注意事项

对于一个分组,默认Time列输出分组的开始时间,查询时可以使用select __endTime的方式来使得结果输出分组的结束时间。
与ALIGN BY DEVICE搭配使用时会对每个device进行单独的分组操作。
当前暂不支持与GROUP BY LEVEL搭配使用。

对于下面的原始数据,给出几个查询样例

在这里插入图片描述


可以按照不同的时间单位设定时间间隔,sql语句如下:

select __endTime,count(*)from root.**groupbysession(1d)

结果:

+-----------------------------+-----------------------------+------------------------------------+---------------------------------+-------------------------------+ | Time| __endTime|count(root.ln.wf02.wt01.temperature)|count(root.ln.wf02.wt01.hardware)|count(root.ln.wf02.wt01.status)| +-----------------------------+-----------------------------+------------------------------------+---------------------------------+-------------------------------+ |1970-01-01T08:00:01.000+08:00|1970-01-01T08:08:00.000+08:00| 15| 18| 15| |1970-01-02T08:08:01.000+08:00|1970-01-02T08:08:05.000+08:00| 5| 5| 5| +-----------------------------+-----------------------------+------------------------------------+---------------------------------+-------------------------------+ 

也可以和HAVING、ALIGN BY DEVICE共同使用

select __endTime,sum(hardware)from root.ln.wf02.wt01 groupbysession(50s)havingsum(hardware)>0 align by device 

得到如下结果,其中排除了sum(hardware)为0的部分

在这里插入图片描述

五、点数分段聚合

GROUP BY COUNT可以根据点数分组进行聚合运算,将连续的指定数量数据点分为一组,即按照固定的点数进行分组。
其语法定义如下:

groupbycount(controlExpression, size[,ignoreNull=true/false])

controlExpression
计数参照的对象,可以是结果集的任意列或是列的表达式

size
一个组中数据点的数量,每size个数据点会被分到同一个组

ignoreNull=true/false
是否忽略controlExpression为null的数据点,当ignoreNull为true时,在计数时会跳过controlExpression结果为null的数据点

5.1 注意事项

对于一个分组,默认Time列输出分组的开始时间,查询时可以使用select __endTime的方式来使得结果输出分组的结束时间。
与ALIGN BY DEVICE搭配使用时会对每个device进行单独的分组操作。
当前暂不支持与GROUP BY LEVEL搭配使用。
当一个分组内最终的点数不满足size的数量时,不会输出该分组的结果

对于下面的原始数据,给出几个查询样例

在这里插入图片描述
selectcount(charging_stauts), first_value(soc)from root.sg groupbycount(charging_status,5)

得到如下结果,其中由于第二个1970-01-01T08:00:00.006+08:00到1970-01-01T08:00:00.010+08:00的窗口中包含四个点,不符合size = 5的条件,因此不被输出

在这里插入图片描述

而当使用ignoreNull将null值也考虑进来时,可以得到两个点计数为5的窗口,sql如下

selectcount(charging_stauts), first_value(soc)from root.sg groupbycount(charging_status,5,ignoreNull=false)

结果:

在这里插入图片描述

六、结语

Apache IoTDB的分段聚合能力构建了从边缘到云端的完整数据价值链。通过五大分段策略实现时间维度的智能切片实现业务维度的深度挖掘,形成了高吞吐、低延迟的时序数据处理闭环。掌握这些核心技术,企业可构建自主可控的工业物联网数据中台,在数字化转型的浪潮中抢占先机。

Read more

Apache HertzBeat部署安装

前段时间项目上需要部署一个实时监控服务,选择了HertzBeat,第一次接触这个组件,所以上网搜安装教程,结果网上的教程少的可怜,甚至官网也搜索不到,,,        经过不限努力查找,终于找打了官网,里面有详细的安装步骤,,官网如下,安装步骤我只是简单总结了下,需要的伙伴可以参考,但还是要看官网上的介绍等等。        原理就是将主体服务部署在一个服务器,然后再将采集器服务部署到需要监听数据的服务即可。 https://hertzbeat.apache.org/zh-cn/docs/   目录 1、环境 2、上传解压hertzbeat安装包 3、根据文档修改数据存储配置,改为使用postgersql 4、账号设置 5、启动 6、启动时报错解决 7、采集器部署 7.1、上传安装包 7.2、jdk版本问题 7.3、配置采集服务 7.4、启动 1、

By Ne0inhk
ARM Linux 驱动开发篇--- pinctrl 子系统详解-- Ubuntu20.04

ARM Linux 驱动开发篇--- pinctrl 子系统详解-- Ubuntu20.04

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》   《嵌入式linux驱动开发》《freertos专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 文章目录 * 前言 * 一、pandas是什么? * 二、使用步骤 * 1.引入库 * 2.读入数据 * 总结 前言 在上一期的实战中,我们完成了基于设备树的 LED 驱动开发,但从底层逻辑来看,核心依旧是直接配置 LED 对应的 GPIO 寄存器 —— 这种开发方式,本质上和裸机驱动开发并无二致。 Linux 作为一套成熟、庞大的操作系统,其驱动框架的设计核心就是复用与简化。对于 GPIO 这类最基础、

By Ne0inhk
Linux 磁盘基础:从物理结构到 CHS/LBA 寻址,吃透数据存储底层逻辑

Linux 磁盘基础:从物理结构到 CHS/LBA 寻址,吃透数据存储底层逻辑

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 磁盘硬件基础:机械结构与存储单元 * 1.1 磁盘物理组成 * 1.2 磁盘容量计算 * 1.3 核心概念辨析:磁道、柱面、扇区 * 二. 磁盘逻辑结构:系统对物理硬件的抽象 * 2.1 多维度理解和理清磁盘逻辑结构 * 2.2 逻辑结构的本质 * 2.3 逻辑结构的核心优势 * 三. CHS 寻址:早期的物理坐标定位 * 3.1 CHS 寻址原理 * 3.2

By Ne0inhk
Flutter 组件 dart_dev 适配鸿蒙 HarmonyOS 实战:效能基座方案,构建全生命周期自动化开发流水线与研发套件治理架构

Flutter 组件 dart_dev 适配鸿蒙 HarmonyOS 实战:效能基座方案,构建全生命周期自动化开发流水线与研发套件治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 dart_dev 适配鸿蒙 HarmonyOS 实战:效能基座方案,构建全生命周期自动化开发流水线与研发套件治理架构 前言 在鸿蒙(OpenHarmony)生态迈向大规模工业化协同、涉及海量跨端功能并发验证及严苛代码交付质量标准的背景下,如何实现研发流程的“机器化”约束,已成为决定团队产出稳定性与效能上限的关键。在鸿蒙设备这类强调 AOT 极致性能与多包(HAP/HSP)协同部署的环境下,如果研发环节依然依赖分散的散装脚本或非标的 Git 工作流,由于由于环境配置的微差异,极易由于由于“本地通过,远端爆炸”导致集成交付效率的高频损耗。 我们需要一种能够统一任务调度(Task Runner)、支持全量规范校验且具备“一站式”研发脚本治理能力的基座方案。 dart_dev 为 Flutter 开发者引入了“研发即代码(Dev-as-Code)

By Ne0inhk