Web开发编程网
分享Web开发相关技术

浏览器技术架构的演进过程和背景

作者介绍

Topqiang(灵士),专有钉钉前端成员,深度使用低代码平台搭建职业生涯中业务复杂度最高的后台管理系统,目前负责专有钉部分协同产品研发。

背景

当下的前端工程师可能接触最多的就是chrome浏览器了。但是可能大部分同学不清楚其内部运行的机制,以及这些年浏览器到底进行了哪些优化才得以支撑互联网世界庞大的业务量以及给广大网民一个网上冲浪的极致体验。本文不会去写深入底层的实现,而是想通过描述浏览器的技术演进来知道他的背景,以及明白在开发和使用浏览器应用时遇到问题的背后原因。

  • 为什么现代浏览器相对来说比较稳定,而早期的浏览器极易容易崩溃?
  • 为什么现代web应用比较流畅可以做混合应用堪比原生,而早期的web页面会高频卡顿?
  • 为什么早在2009年前web网站上各种插件横行,且经常爆出安全问题?

在2007年前后就已经上网的同学应该玩过QQ空间、百度贴吧或者校内网(人人网)等,那个时候的PC浏览器打开页面需要等待好久,手机打开网页速度慢的体感更强。当然在一定程度上也取决于当时的移动网络为2g网络,而PC网络多为有限宽带。网络因素、硬件设备配置等因素我们今天先不做深究,今天我们主要的主题是探讨浏览器本身的技术架构演进过程。

单进程浏览器

在了解单进程浏览器之前,大家需要先了解下什么是进程,什么是线程?这里就不在赘述了大家可以自行了解下进程和线程之后,我们再来一起看下单进程浏览器的架构。

单进程浏览器是指浏览器的所有功能都在同一个进程里运行,这其中包括了网络、插件、JavaScript 运行环境、渲染引擎和页面等。其实在2007年前后,市面上浏览器大部分都是单进程的。单进程浏览器的架构如下图所示:

image.png

如此多的功能运行在一个进程里,是导致单进程浏览器不稳定、不流畅和不安全的一个主要因素。下面我就来一一分析下出现这些问题的原因。

不稳定

早期浏览器的视频、游戏需要借助于插件来实现诸如 Flash 播放器等各种强大的功能,但是插件是最容易出问题的模块,并且还运行在浏览器进程之中,所以一个插件的意外崩溃会引起整个浏览器的崩溃。除了插件之外,渲染引擎模块也是不稳定的,通常一些复杂的 JavaScript 代码就有可能引起渲染引擎模块的崩溃。和插件一样,渲染引擎的崩溃也会导致整个浏览器的崩溃。

不流畅

从上面的“单进程浏览器架构示意图”可以看出,所有页面的渲染模块、JavaScript 执行环境以及插件都是运行在同一个线程中的,这就意味着同一时刻只能有一个模块可以执行。比如,下面这个无限循环的脚本:

   function freeze() { 
     while (1) { 
       console.log("freeze"); 
     }
   }
   freeze();
复制代码

如果让这个脚本运行在一个单进程浏览器的页面里,你感觉会发生什么?因为这个脚本是无限循环的,所以当其执行时,它会独占整个线程,这样导致其他运行在该线程中的模块就没有机会被执行。因为浏览器中所有的页面都运行在该线程中,所以这些页面都没有机会去执行任务,这样就会导致整个浏览器失去响应,变卡顿。这块内容要继续往深的地方讲就到页面的事件循环系统了。除了上述脚本或者插件会让单进程浏览器变卡顿外,页面的内存泄漏也是单进程变慢的一个重要原因。通常浏览器的内核都是非常复杂的,运行一个复杂点的页面再关闭页面,会存在内存不能完全回收的情况,这样导致的问题是使用时间越长,内存占用越高,浏览器会变得越慢。

不安全

这里依然可以从插件和页面脚本两个方面来解释该原因。插件可以使用 C/C++ 等代码编写,通过插件可以获取到操作系统的任意资源,当你在页面运行一个插件时也就意味着这个插件能完全操作你的电脑。如果是个恶意插件,那么它就可以释放病毒、窃取你的账号密码,引发安全性问题。至于页面脚本,它可以通过浏览器的漏洞来获取系统权限,这些脚本获取系统权限之后也可以对你的电脑做一些恶意的事情,同样也会引发安全问题。以上这些就是当时浏览器的特点,不稳定,不流畅,而且不安全。

多进程浏览器

你可以先看看下面这张图,这是 2008 年 Chrome 发布时的进程架构。

image.png

从图中可以看出,Chrome 的页面是运行在单独的渲染进程中的,同时页面里的插件也是运行在单独的插件进程之中,而进程之间是通过 IPC 机制进行通信(如图中虚线部分)。

我们先看看如何解决不稳定的问题。由于进程是相互隔离的,所以当一个页面或者插件崩溃时,影响到的仅仅是当前的页面进程或者插件进程,并不会影响到浏览器和其他页面,这就完美地解决了页面或者插件的崩溃会导致整个浏览器崩溃,也就是不稳定的问题。

我提到 Chrome 使用多个渲染器进程。在最简单的情况下,您可以想象每个选项卡都有自己的渲染器进程。假设您打开了 3 个选项卡,每个选项卡都由一个独立的渲染器进程运行。如果一个选项卡变得无响应,那么您可以关闭无响应的选项卡并继续使用其他选项卡的服务,如果所有选项卡都在一个进程上运行,当一个选项卡变得无响应时,所有选项卡都无响应。

image.png

对于内存泄漏的解决方法那就更简单了,因为当关闭一个页面时,整个渲染进程也会被关闭,之后该进程所占用的内存都会被系统回收,这样就轻松解决了浏览器页面的内存泄漏问题。最后我们再来看看上面的两个安全问题是怎么解决的。采用多进程架构的额外好处是可以使用安全沙箱,你可以把沙箱看成是操作系统给进程上了一把锁,沙箱里面的程序可以运行,但是不能在你的硬盘上写入任何数据,也不能在敏感位置读取任何数据,例如你的文档和桌面。Chrome 把插件进程和渲染进程锁在沙箱里面,这样即使在渲染进程或者插件进程里面执行了恶意程序,恶意程序也无法突破沙箱去获取系统权限。

有哪些进程存在

不过 Chrome 的发展是滚滚向前的,相较之前,目前的架构又有了很多新的变化。我们先看看最新的 Chrome 进程架构,你可以参考下图:

image.png

图中是我从 Chrome/94 浏览器中截图出来的,大家可以看到最新的 Chrome 浏览器包括:1 个 GPU 进程、1 个 V8代理解析工具(v8代码解释器)、1 个NetWork进程、1 个浏览器主进程、多个插件进程(我插件装的太多)、多个渲染进程和备用渲染进程。
下面我们来逐个分析下这几个进程的功能。

  • GPU 进程。Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了GPU 进程。
  • V8代理解析工具(v8代码解释器)Google的开源高性能JavaScript和WebAssembly引擎,用C编写,它实现ECMAScript和WebAssembly,可独立运行或嵌入到任何C应用程序中,如Chrome和Node.js。
  • NetWork进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
  • 浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
  • 扩展程序(插件进程)。主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
  • 渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
    • GUI渲染线程:负责渲染页面,布局和绘制、页面需要重绘和回流时,该线程就会执行、与js引擎线程互斥,防止渲染结果不可预期
    • JS引擎线程:负责处理解析和执行javascript脚本程序、只有一个JS引擎线程(单线程)、与GUI渲染线程互斥,防止渲染结果不可预期
    • 事件触发线程:用来控制事件循环(鼠标点击、setTimeout、ajax等)当处理一些不能立即执行的代码时,会将对应的任务在其可以触发的时机,添加到事件队列的末端事件循环机制会在JS引擎线程空闲时,循环访问事件队列的头部,如果有函数,则会将该函数推到执行栈中并立即执行
    • 定时触发器线程:setInterval与setTimeout所在的线程、定时任务并不是由JS引擎计时的,是由定时触发线程来计时的、计时完毕后,将回调事件放入到事件队列中
    • 异步http请求线程:浏览器有一个单独的线程用于处理AJAX请求、当请求完成时,若有回调函数,将回调事件放入到事件队列中。

渲染进程的分配

其时浏览器的渲染进程划分,还会多一个维度,那就是按照站点维度进行划分:

image.png

站点隔离是 Chrome 中最近引入的一项功能,它为每个跨站点 iframe 运行单独的渲染器进程。我们一直在讨论每个选项卡模型一个渲染器进程,它允许跨站点 iframe 在单个渲染器进程中运行,并在不同站点之间共享内存空间。在同一个渲染器进程中运行 a.com 和 b.com 似乎没问题。同源策略是网络的核心安全模型;它确保一个站点未经同意无法访问其他站点的数据。绕过此策略是安全攻击的主要目标。进程隔离是最有效的站点分离方式。使用 Meltdown 和 Spectre,我们需要使用流程来分离站点变得更加明显。自 Chrome 67 以来,桌面上默认启用站点隔离,选项卡中的每个跨站点 iframe 都有一个单独的渲染器进程。

启用站点隔离是一项多年的工程努力。站点隔离并不像分配不同的渲染器进程那么简单;它从根本上改变了 iframe 相互通信的方式。在不同进程上运行 iframe 的页面上打开 devtools 意味着 devtools 必须实施幕后工作以使其看起来无缝。即使运行简单的 Ctrl+F 来查找页面中的单词也意味着在不同的渲染器进程中进行搜索。您可以看到浏览器工程师将 Site Isolation 的发布称为重大里程碑的原因。

但也不完全是相同站点就一定会在同一个渲染进程中,不同的tab就一定不在同一个渲染进程中。这个具体的进程分配逻辑取决于Chromium的处理模型(process-models)决定,而Chrome的默认处理模式就是Process-per-site-instance。 Process-per-site-instance :  当你打开一个 tab 访问 a.baidu.com,然后再打开一个 tab 访问 b.baidu.com,这两个 tab 会使用两个进程。如果 b.baidu.com 是通过 a.baidu.com 页面的 JavaScript 代码打开的,这两个 tab 会使用同一个进程,比如下图的例子,可以看到两个 tab 的 processId 是相同的。

image.png

除此之外还有三种处理模型【Process-per-site、Process-per-tab、Single process】,大家感兴趣可以查看chromium官方相关介绍

现阶段存在的缺陷

凡事都有两面性,虽然面向服务的多进程架构模型提升了浏览器的稳定性、流畅性和安全性,但同样不可避免地带来了一些问题:

  • 更高的资源占用。因为每个进程都会包含公共基础结构的副本(如 JavaScript 运行环境),这就意味着浏览器会消耗更多的内存资源。
  • 更复杂的体系架构。浏览器各模块之间耦合性高、扩展性差等问题,会导致现在的架构已经很难适应新的需求了。

为了更好的均衡各方面诉求,我们来看下 Chrome 为了节省更多内存,所做的服务化。

image.png

Chrome 正在经历架构更改以将浏览器程序的每个部分作为服务运行,从而可以轻松拆分为不同的进程或聚合为一个。一般的想法是,当 Chrome 在强大的硬件上运行时,它可能会将每个服务拆分为不同的进程,从而提供更高的稳定性,但如果是在资源受限的设备上,Chrome 会将服务整合到一个进程中,从而节省内存占用。在此更改之前,已在 Android 等平台上使用了类似的方法来合并进程以减少内存使用量。

当然上面所讲的这些并不是问题的全部原因,这些年发展的也并不只是浏览器架构,当然还有网络协议,基站,芯片,cpu,带宽承载能力等科技领域的发展,才得以让PC/移动互联网体验更好的应用服务我们的生活。我们才得以感受到数字化生活的便利。

参考文档

作者:专有钉钉前端团队
链接:https://juejin.cn/post/7035791029814951950
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

未经允许不得转载:WEB开发编程网 » 浏览器技术架构的演进过程和背景

WEB开发编程网

谢谢支持,我们一直在努力

安全提示:您正在对WEB开发编程网进行赞赏操作,一但支付,不可返还。