如何避免JS内存泄漏?

简介: 很多开发者可能平时并不关心自己维护的页面是否存在内存泄漏,原因可能是刚开始简单的页面内存泄漏的速度很缓慢,在造成严重卡顿之前可能就被用户刷新了,问题也就被隐藏了,但是随着页面越来越复杂,尤其当你的页面是 SAP 方式交互时,内存泄漏的隐患便越来越严重,直到突然有一天用户反馈说:“操作一会儿页面就卡住不动了,也不知道为什么,以前不这样的呀”。这篇文章通过一些简单的例子介绍内存泄漏的调查方法、总结内存泄漏出现的原因和常见情况,并针对每种情况总结如何避免内存泄漏。希望能对大家有所帮助。

image.png

作者 | 木及
来源 | 阿里技术公众号

很多开发者可能平时并不关心自己维护的页面是否存在内存泄漏,原因可能是刚开始简单的页面内存泄漏的速度很缓慢,在造成严重卡顿之前可能就被用户刷新了,问题也就被隐藏了,但是随着页面越来越复杂,尤其当你的页面是 SAP 方式交互时,内存泄漏的隐患便越来越严重,直到突然有一天用户反馈说:“操作一会儿页面就卡住不动了,也不知道为什么,以前不这样的呀”。

这篇文章通过一些简单的例子介绍内存泄漏的调查方法、总结内存泄漏出现的原因和常见情况,并针对每种情况总结如何避免内存泄漏。希望能对大家有所帮助。

一 一个简单的例子

先看一个简单的例子,下面是这个例子对应的代码:

image.png

image.png

代码 1

代码 1 的逻辑很简单:点击“add date”按钮时会向 dateAry 数组中 push 3000 个 new Date 对象,点击“clear”按钮时将 dateAry 清空。很明显,“add date”操作会造成内存占用不断增长,如果将这个逻辑用在实际应用中便会造成内存泄漏(不考虑故意将代码逻辑设计成这样的情况),下面我们看一下如何调查这种内存增长出现的原因以及如何找出内存泄漏点。

1 heap snapshot

为了避免浏览器插件的干扰,我们在 chrome 中新建一个无痕窗口打开上述代码。然后在 chrome 的 devtools 中的 Memory 工具中找到 “Heap Snapshot”工具,点击左上角的录制按钮录制一个 Snapshot,然后点击“add date”按钮,在手动触发 GC(Garbage Collect)之后,再次录制一个 Snapshot,反复执行上述操作若干次,像图 1 中操作的那样,得到一系列的 Snapshot。

image.png

图 1 录制 Snapshot

图 2 是我们刚刚得到的 Snapshot 组,其中的第一个是页面初始加载的时候录制的,不难发现,从第二个开始,每个 Snapshot 相比于上一个其大小都增加了约 200KB,我们点击选择 Snapshot 2,在 class filter 输入框中处输入 date,可以得到 Snapshot 2 中所有被 Date 构造器构造出来的 JS 对象,也就是 Date 对象,这里看到的构造器跟浏览器内部的实现有关,不必跟 JS 的对象对应。

选中一个 Date 对象,在下面的面板中可以看到所选对象的持有链以及相关持有对象的内存的保留大小(Retained Size),从图中可以看出选中的 Date 对象是 Array 的第 1 个元素(index 从 0 开始),而这个 Array 的持有者是 system/Context 上下文中的 dateAry,system/Context 上下文就是代码中 script 标签的上下文,我们可以看到在这个 dataAry 的保留大小是 197KB,我们再切到 Snapshot 3,用相同的方式查看内存持有和大小,可以发现 Snapshot 3 中的 dataAry 的保留大小变成了 386KB,相比于 Snapshot 2 增涨了约 200KB!逐一比较后面的 Snapshot 4、5 后也能得到相同的对比结果,即下一个 Snapshot 中的 dateAry 比上一个的保留大小大约 200KB。

image.png

图 2 录制的 Snapshot 组

参考【代码 1】我们可以知道,“add date”按钮在被点击时,会向 dateAry 数组中 push 3000 个新的 Date 对象,而在图 2 中的 Date 构造器的右侧可以看到这 3000 个 Date 对象(Date x 3000),它对应的正式我们的循环创建的那 3000 个 Date 对象。综合上面的操作我们可以知道,chorome devtools 中的 Memroy 的 Heap Snapshot 工具可以录制某一个时刻的所有内存对象,也就是一个“快照”,快照中按“构造器”分组,展示了所有被记录下来的 JS 对象。

如果这个页面是一个实际服务于用户的网站的某个页面话(用户可能非常频繁的点击“add date”按钮,作者可能想记录用户点击的次数?也许吧,虽然我也不知道他什么要这么做)随着用户使用时间的增长,“add date”按钮的反应就会越来越慢,整体页面也随之越来越卡,原因除了系统的内存资源被占用之外,还有 GC 的频率和时长增长,如图 3 所示,因为 GC 执行的过程中 JS 的执行是被暂停的,所以页面就会呈现出越来越卡的样子。

image.png

图 3 Performance 录制的 GC 占比

image.png

图 4 chrome 的任务管理器

最终:

image.png

图 5 内存占用过高导致浏览器崩溃

那么,在这个“实际”的场景下,如何找出那“作祟”的 3000 个 Date 对象呢?我们首先想到的应该是就是:之前不是录制了好多个 Snapshot 吗?可不可以把它们做对比找到“差异”呢,从差异中找到增长的地方不就行了?思路非常正确,在此之前我们再分析一下这几个 Snapshot:每次点击“add date”按钮、手动触发 GC、得到的 Snapshot 的大小相比上一次都有所增加,如果这种内存的增长现象不符合“预期”的话(显然在这个“实际”的例子中是不符合预期的),那么这里就有很大的嫌疑存在内存泄漏。

这个时候我们选中 Snapshot 2,在图 2 所示的 " Summary" 处选择“Comparison”,在右侧的 "All objects" 处选择 Snapshot 1,这样一来,Constructor 里展示便是 Snapshot 1 和 Snapshot 2 的对比,通过观察不难发现,图中的 +144KB 最值得怀疑,于是我们选中它的构造器 Date,展开选中任意子项看详情,发现其是被 Array 构造器构造出来的 dateAry 持有的(即 dateAry 中的一员),并且 dateAry 被三个地方持有,其中系统内部的 array 我们不用理会,图 6 中写有 "context in ()" 地方给了我们持有 dateAry 的 context 所在的位置,点击便可以跳到代码所在的位置了,整个操作如图 6 所示:

image.png

图 6 定位代码位置

这里有一个值得注意的地方,图 6 中的 “context in () @449305” 中的 "()",这里之所以展示为了 "()" 是因为代码中用了“匿名函数”(代码 2 中第 2 行的箭头函数):

// 【写入 date】
pushDate.addEventListener("click", () => {dateCount.innerHTML = `${++dateNum}`;for (let j = 0; j < 3000; ++j) {dateAry.push(new Date());}
});

代码 2 匿名函数

但是如果我们给函数起一个名字,如下面的代码所示,也就是如果我们使用具名函数(代码3 第 2 行函数 add)或者将函数赋值给一个变量并使用这个变量(第 10 和 18 行的行为)的时候,devtools 中都可以看到相应的函数的名字,这也就可以帮助我们更好的定位代码,如图 7 所示。

// 【写入 date】
pushDate.addEventListener("click", function add() {dateCount.innerHTML = `${++dateNum}`;for (let j = 0; j < 3000; ++j) {dateAry.push(new Date());}
});const clear = document.querySelector(".clear");const doClear = function () {dateAry = [];dateCount.innerHTML = "0";
};// 【回收内存】
clear.addEventListener("click", doClear);

代码 3 具名函数

image.png

图 7 具名函数方便定位

这样我们便找到了代码可疑的地方,只需要将代码的作者抓过来对着他一顿“分析”这个内存泄漏的问题基本就水落石出了。

其实,Snapshot 除了“Comparison”之外还有一个更便捷的用于对比的入口,在这里直接可以看到在录制 Snapshot 1 和 Snapshot 2 两个时间点之间被分配出来的内存,用这种方式也可以定位到那个可疑的 Date x 3000:

image.png

图 8 Snapshot 比较器

上文件介绍的是用 Heap Snapshot 寻找内存泄漏点的方法,这个方法的优点:可以录制多个 Snapshot,然后方便的两两比较,并且能看到 Snapshot 中的全量内存,这一点是下文要讲的“Allocation instrumentation on timeline”方法不具备的,并且这种方法可以更加方便地查找后面会讲的因 Detached Dom 导致的内存泄漏。

2 Allocation instrumentation on timeline

但是,不知道你有没有觉得,这种高频率地录制 Snapshot、对比、再对比的方式有点儿麻烦?我需要不断的去点击“add date”,然后鼠标又要跑过去点击手动 GC、录制 Snapshot、等待录制完毕,再去操作,再去录制。有没有简单一些的方式来查找内存泄漏?这个时候我们回到 Memory 最初始的界面,你突然发现 “Heap snapshot”下面还有一个 radio:“Allocation instrumentation on timeline”,并且这个 radio 下面的介绍文案的最后写着:“Use this profile type to isolate memory leaks”,原来这是一个专门用于调查内存泄漏的工具!于是,我们选中这个 radio,点击开始录制按钮,然后将注意力放在页面上,然后你发现当点击“add date”按钮时,右面录制的 timeline 便会多出一个心跳:

image.png

图 9 Allocation instrumentation on timeline

如图 9 所示,每当我们点击“add date”按钮时,右面都有一个对应的心跳,当我们点击“clear”按钮时,刚才出现的所有心跳便全都“缩回”去了,于是我们得出结论:每一个“心跳”都是一次内存分配,其高度代表内存分配的量,在之后的时间推移过程中,如果刚才心跳对应的被分配的内存被 GC 回收了,“心跳”便会跟着变化为回收之后的高度。于是,我们便摆脱了在 Snapshot 中来回操作、录制的窘境,只需要将注意力集中在页面的操作上,并观察哪个操作在右边的时间线变化中是可疑的。

经过一系列操作,我们发现“add date”这个按钮的点击行为很可疑,因为它分配的内存不会自动被回收,也就是只要点击一次,内存就会增长一点,我们停止录制,得到了一个 timeline 的 Snapshot,这个时候如果我们点击某个心跳的话:

image.png

图 10 点击某个心跳

熟悉的 Date x 3000 又出现了(图 11),点击一个 Date 对象看持有链,接下来便跟上文 Snapshot 的持有链分析一样了:

image.png

图 11 通过 timeline 找到泄漏点

这个方法的优点上文已经说明,可以非常直观、方便的观察内存随可疑操作的分配与回收过程,可以方便的观察每次分配的内存。它的缺点:录制时间较长时 devtools 收集录制结果的时间会很长,甚至有时候会卡死浏览器;下文会讲到 detached DOM,这个工具不能比较出 detached DOM,而 heap snapshot 可以。

3 performance

devtools 中的 Performance 面版中也有一个 Memory 功能,下面看一下它如何使用。我们把 Memory 勾选上,并录制一个 performance 结果:

image.png

图 12 Performance 的录制过程

在图 12 中可以看到,在录制的过程中我们连续点击“add date”按钮 10 次,然后点击一次“clear”按钮,然后再次点击“add date” 10 次,得到的最终结果如图 13 所示:

image.png

图 13 Performance 的录制结果

在图 13 中我们可以得到下面的信息:

  • 整个操作过程中内存的走势:参见图 13 下方的位置,第一轮点击 10 次的过程中内存不断增长,点 clear 之后内存断崖式下跌,第二轮点击 10 次内存又不断增长。这也是这个工具的主要作用:得到可疑操作的内存走势图,如果内存持续走高则有理由怀疑此操作由内存泄漏的可能。
  • 内存的增长量:参见 JS Heap 位置,鼠标放上去可以看见每个阶梯上下位置的内存增长/下跌的量
  • 通过在 timeline 中定位某个“阶梯”,我们也能找到可疑的代码,如图 14 所示:

image.png

图 14 通过 Performance 定位问题代码

这种方法的优点:可以直观得看到内存的总体走势,并且同时得到所有操作过程中的函数调用栈和时间等信息。缺点:没有具体的内存分配的细节,录制的过程不能实时看到内存分配的过程。

二 内存泄漏出现的场景

1 全局

JS 采用标记清扫法去回收无法访问的内存对象,被挂载在全局对象(在浏览器中即指的是 window 对象,在垃圾回收的角度上称其为根节点,也叫 GC root)上的属性所占用内存是不会被回收的,因为其是始终可以访问的,这也符合“全局”的命名含义。

解决方案就是避免用全局对象存储大量的数据。

2 闭包(closure)

我们把【代码 1】稍加改动便可以得到一个闭包导致内存泄漏的版本:

image.png

image.png

代码 3 闭包导致内存泄漏

将上述代码加载到 chrome 中,并用 timeline 的方式录制一个 Snapshot,得到的结果如图 15 所示:

image.png

图 15 闭包的录制结果

我们选中 index = 2 的心跳,可以看到 Constructor 里面出现了一个 "(closure)",我们展开这个 closure,可以看到里面的 "inner()",inner() 后面的 "()" 表示 inner 是一个函数,这时候你可能会问:“图中的 Constructor 的 Retained Size 大小都差不多,为什么你要选 (closure)?”,正是因为没有明显占比较高的 Retained Size 我们才随便选一个调查,后面你会发现不管你选了哪一个最后的调查链路都是殊途同归的。

我们在下面的 Retainers 中看下 inner() 的持有细节:从下面的 Retainers 中可以看出 inner() 这个 closure 是某个 Array 的第 2 项(index 从 0 开始),而这个数组的持有者是 system/Context(即全局) 中的 ary,通过观察可以看到 ary 的持有大小(Retained Size)是 961KB 大约等于 192KB 的 5 倍,5 即是我们点击“add date”按钮的次数,而下面的 5 个 "previous in system/Context" 每个大小都是 192KB,而它们最终都是被某个 inner() 闭包持有,至此我们便可以得出结论:全局中有一个 ary 数组,它的主要内存是被 inner() 填充的,通过蓝色的 index.html:xx 处的代码入口定位到代码所在地看一下一切就都了然了,原来是 inner() 闭包内部持有了一个大对象,并且所有的 inner() 闭包及其持有的大对象都被 ary 对象持有,而 ary 对象是全局的不会被回收,导致了内存泄漏(如果这种行为不符合预期的话)。返回去,如果这个时候你选择上面提到的 system/Context 构造器,你会看到(见图 16,熟悉吧):

image.png

图 16 system/Context

也就是你选择的 system/Context 其实是 inner() 闭包的上下文对象(context),而此上下文持有了 192KB 内存,通过蓝色的 index.html:xx 又可以定位到问题代码了。如果你像图 17 一样选择了 Date 构造器进行查看的话也可以最终定位到问题,此处将分析过程留给读者自己进行:

image.png

图 17 选中 Date 构造器

3 Detached DOM

我们先看一下下面的代码,并用 chrome 载入它:

image.png

代码 4 Detached Dom

然后我们采用 Heap Snapshot 的方式将点击“del”按钮前后的两个 snapshot 录制下来,得到的结果如图 6 所示。我们选用和 snapshot 1 对比的方式并在 snapshot 2 的过滤器中输入 "detached"。我们观察得到的筛选结果的 "Delta" 列,其中不为 0 的列如下:

image.png

要解释上述表格需要先介绍一个知识点:DOM 对象被回收需要同时满足两个条件,1、DOM 在 DOM 树中被删掉;2、DOM 没有被 JS 对象引用。其中第二点还是比较容易被忽视的。正如上面的例子所示,Detached HTMLButtonElement +1 代表有一个 button DOM 被从组件树中删掉了,但是仍有 JS 引用之(我们不考虑有意为之的情况)。

相似的,Detached EventListener 也是因为 DOM 被删掉了,但是事件没有解绑,于是 Detached 了,解决方案也很简单:及时解绑事件即可。

于是解决的方法就很简单了:参见代码 5,回掉函数 del 在执行完毕时临时变量会被回收,于是两个条件就都同时满足了,DOM 对象就会被回收掉,事件解绑了,Detached EventListener 也就没有了。值得注意的是 table 元素,如果一个 td 元素发生了 detached,则由于其自身引用了自己所在的 table,于是整个 table 就也不会被回收了。

image.png

代码 5 Detached DOM 的解决方法

image.png

图 18 Detached DOM 的 Snapshot

Performance monitor 工具

DOM/event listener 泄漏在编写轮播图、弹窗、toast 提示这种工具的时候还是很容易出现的,chrome 的 devtools 中有一个 Performance monitor 工具可以用来帮助我们调查内存中是否有 DOM/event listener 泄漏。首先看一下代码 6:

image.png

代码 6 不断增加 DOM NODE

按照我们图 19 的方式打开 Performance monitor 面版:

image.png

图 19 打开 Performance monitor 工具

DOM Nodes 右侧的数量是当前内存中的所有 DOM 节点的数量,包括当前 document 中存在的和 detached 的以及计算过程中临时创建的,每当我们点击一次“add date”按钮,并手动触发 GC 之后 DOM Nodes 的数量就 + 2,这是因为我们向 document 中增加了一个 button 节点和一个 button 的文字节点,就像图 20 中所示。如果你写的 toast 组件在临时插入到 document 并过一会儿执行了 remove 之后处于了 detached 状态的话,Performance monitor 面版中的 DOM Nodes 数量就会不断增加,结合 snapshot 工具你便可以定位到问题所在了。值得一提的是,有的第三方的库的 toast 便存在这个问题,不知道你被坑过没有。

image.png

图 20 不断增加的 DOM Nodes

4 console

这一点可能有人不会留意到,控制台打印的内容是需要始终保持引用的存在的,这一点也是值得注意的,因为打印过多过大对象的话也是会造成内存泄漏的,如图 21 所示(配合代码 7)。解决方法便是不要肆意打印对象到控制台中,只打印必要的信息出来。

image.png

代码 7 console 导致内存泄漏

image.png

图 21 console 导致的内存泄漏

三 总结

本文用了几个简单的小例子介绍了内存泄漏出现的时机、寻找泄漏点的方法并将各种方法的优缺点进行了对比,总结了避免出现内存泄漏的注意点。希望能对读者有所帮助。文中如果有本人理解错误或书写错误的地方欢迎留言指正。

原文链接
本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512752.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java传递实例_Java方法的参数传递机制实例详解

本文实例讲述了Java方法的参数传递机制。分享给大家供大家参考&#xff0c;具体如下&#xff1a;参数传递机制对于程序设计语言来说&#xff0c;一般方法(函数)的参数传递有两种&#xff1a;按值传递和按引用传递。按值传递意味着当将一个参数传递给一个方法时&#xff0c;方法…

低代码发展专访系列之六:低代码平台能解决业务重构的问题么?

编辑 | 曹芊芊 话题&#xff1a;低代码发展系列专访 前言&#xff1a;2019年开始&#xff0c;低代码爆火。有人认为它是第四代编程语言&#xff0c;有人认为它是开发模式的颠覆&#xff0c;也有人认为是企业管理模式的变革……有很多声音&#xff0c;社区讨论很热烈。CSDN随后展…

在 Dubbo3.0 上服务治理的实践

简介&#xff1a; Dubbo 3.0 是在云原生背景下诞生的&#xff0c;使用 Dubbo 构建的微服务遵循云原生思想&#xff0c;能更好的复用底层云原生基础设施、贴合云原生微服务架构。 Dubbo3.0 介绍 作者 | 十眠 自从 Apache Dubbo 在 2011 年开源以来&#xff0c;经过多年一众大…

redis 可视化工具_自荐一个有情怀的跨平台Redis可视化客户端工具——RedisViewer...

介绍在以往的文章中曾经介绍过几款Redis的可视化工具&#xff0c;在笔者的印象中&#xff0c;Redis至今没有一款非常专业的可视化管理客户端&#xff0c;就算之前介绍过的几款也是差强人意&#xff0c;有些时候满足不了我们的需求&#xff0c;而今天本文要介绍的是另一款值得推…

内核热补丁,真的安全么?

简介&#xff1a; Linux 内核函数的热替换“撞上”函数调用约定还靠谱吗&#xff1f; Linux 内核热补丁可以修复正在运行的 linux 内核&#xff0c;是一种维持线上稳定性不可缺少的措施&#xff0c;现在比较常见的比如 kpatch 和 livepatch。内核热补丁可以修复内核中正在运行的…

谁是 2021「IT 圈」年度 C 位?快来报名,彰显你的影响力!

2021年&#xff0c;数字化转型正磅礴兴起&#xff0c;大批传统企业正在拥抱数字化&#xff0c;云计算、大数据、AI、5G应用能力正在变成企业的核心竞争力&#xff1b;核心技术正在崛起&#xff0c;在操作系统、数据库&#xff0c;依靠开源的力量&#xff0c;众多开发者背后的行…

当Java遇上机密计算,又一段奇幻之旅开始了!

简介&#xff1a; 汪少军&#xff1a;如何为Java业务提供机密计算保护&#xff1f; 写在前面 在信息世界里&#xff0c;数据存在三种状态&#xff1a; 存储态、传输态和计算态。存储在数据库或磁盘中的数据属于存储状态&#xff0c;在网络中传输的数据属于传输状态&#xff0c…

电脑桌面归纳小窗口_电脑一分钟小技巧:如何将电脑设置为定时关机?

关注公众号&#xff0c;发现好教程如何设置电脑设置定时关机呢&#xff1f;哈哈哈&#xff0c;可能这个教程用处不大&#xff0c;不过每个教程都有它的用处&#xff0c;这个也算是教程哈&#xff01;有些小伙伴可能需要这个教程&#xff01;至于什么用处&#xff0c;我就不多说…

时序数据库永远的难关 — 时间线膨胀(高基数 Cardinality)问题的解决方案

简介&#xff1a; 本文主要讨论 influxdb 在遇到写入的数据出现高基数 Cardinality 问题时&#xff0c;一些可行的解决方案。 作者 | 徐建伟 &#xff08;竹影&#xff09; 前序 随着移动端发展走向饱和&#xff0c;现在整个 IT 行业都期待着“万物互联”的物联网时代。在物…

中文巨量模型“源1.0”:模型结构与生成效果解析

浪潮人工智能研究院 “源 1.0”自 2021 年 9 月底发布以来收获了广泛的关注。其参数量达 2457 亿&#xff0c;超越美国 OpenAI 组织研发的 GPT-3。“源 1.0”在语言智能方面表现优异&#xff0c;获得中文语言理解评测基准 CLUE 榜单的零样本学习&#xff08;zero-shot&#xff…

python中gmtime的hour错误_python中gmtime的hour错误_在Python中操作日期和时间之gmtime()方法的使用...

python中datetime怎么用广告总是在最精彩的时候出现&#xff0c;你总是在小编爱的最深的时候离开。 日期相关的操作 from datetime import datetime from datetime import timedelta DATE_FMT %Y-%m-%d DATETIME_FMT %Y-%m-%d %H:%M:%S DATE_US_FMT %d/%m/%Y 格式化常用的…

webview键盘自适应_黑爵毛茸茸机械键盘:感受来自治愈系的暖萌

随着近几年电竞业的火爆&#xff0c;特别是女性玩家的增多&#xff0c;越来越多的外设厂商推出了个性化定制的产品&#xff0c;比如符合女生群体的鼠标、键盘、显卡甚至主板等。今天为大家介绍的这款黑爵毛茸茸机械键盘&#xff0c;是黑爵Project C系列的第三款主题键盘&#x…

阿里云云效技术专家分享:云原生开发、调测及可靠发布解决方案

简介&#xff1a; 高效开发、稳健发布。 在云原生环境中&#xff0c;基于Kubernetes的工具链一方面简化了开发者的许多日常琐碎&#xff0c;另一方面也带来了许多新的概念和工作方式的改变。本篇文章将聚焦于云原生基础设施&#xff0c;谈谈如何在面向云原生的开发流程中&…

代码质量第 5 层 - 只是实现了功能

产品实现的功能是产品价值的体现形式。功能实现是基础。功能没有实现&#xff0c;其他方面做得再好也没有意义。那么&#xff0c;如何保证实现的功能覆盖了需求呢&#xff1f; 产品实现的功能是产品价值的体现形式。功能实现是基础。功能没有实现&#xff0c;其他方面做得再好也…

阿里巴巴 DevOps 工具体系

简介&#xff1a; 随着阿里巴巴多元化业务 20 多年的高速发展&#xff0c;技术体系经历了 web 时代、移动化时代、数据智能时代、云计算时代等多个重大变革。在这些变革中&#xff0c;开发者面对的技术体系、工具体系、知识体系也在不断进化。研发工具在其中起到了技术规模化和…

云原生引领全云开发时代

简介&#xff1a; 云原生是近几年最火爆的技术热词之一&#xff0c;几乎所有的云计算产品都会或多或少跟云原生发生关联&#xff0c;云原生正在重塑整个软件的生命周期。但到底什么是云原生&#xff1f;云原生带来的最大的技术创新和未来机会是什么&#xff1f;以及&#xff0c…

中国首部智能交通微纪录片正式发布 探讨交通强国高质量发展路径

12月23日&#xff0c;由央视财经出品&#xff0c;中国首部智能交通题材微纪录片《大国交通-车路智行》正式发布。该微纪录片全景式呈现出中国交通领域智能化转型历程&#xff0c;探索了交通强国高质量发展的中国路径。据悉&#xff0c;《大国交通-车路智行》微纪录片共五集&…

Kettle on MaxCompute使用指南

简介&#xff1a; Kettle是一款开源的ETL工具&#xff0c;纯java实现&#xff0c;可以运行于Windows, Unix, Linux上运行&#xff0c;提供图形化的操作界面&#xff0c;可以通过拖拽控件的方式&#xff0c;方便地定义数据传输的拓扑。Kettle支持丰富的数据输入输出源&#xff0…

使用AirFlow调度MaxCompute

简介&#xff1a; airflow是Airbnb开源的一个用python编写的调度工具&#xff0c;基于有向无环图(DAG)&#xff0c;airflow可以定义一组有依赖的任务&#xff0c;按照依赖依次执行&#xff0c;通过python代码定义子任务&#xff0c;并支持各种Operate操作器&#xff0c;灵活性大…

css让背景图片显示透明遮罩_CSS项目测试(支持深色模式)

*事先声明&#xff1a;本文章教程教学、文章封面来源自[CSS]聚光灯项目 by CodingStartup起码课&#xff0c;且已经CodingStartup起码课授权允许转载&#xff01;为了保持原有风格&#xff0c;本文始终保持与CodingStartup起码课的视频风格一致*在出现同样的代码块时&#xff0…