electron应用更新方案大调研

lxf2023-05-05 06:17:01

前言

应用更新一直是桌面应用开发者乐于探讨的一个问题。不同于一般的web开发,桌面应用的源代码存储于用户端,想要更新软件只能通过远程更新来实现。

在众多的更新软件的手段中,主要可以分为全量更新和非全量更新。接下来我将会分享我调研的全量更新和非全量更新的经验,如有错误请谅解。

本片文章主要为调研性文章。罗列了大部分的应用更新方案,简单比较了他们的优缺点。希望对大家的开发有所帮助。

下一篇文章中,我将会分享一个简单的electron小程序的方案。

全量更新

全量更新的基本原理

对于刚刚入手electron开发的前端来说,比如我。一开始对全量更新的原理其实是好奇的,但是通过阅读成熟开源的容器更新框架后我发现其实是挺简单的。

全量更新的基本流程

  1. 客户端将通过某种方式查询云端发布的版本
  2. 客户端获取到版本信息后决定是否更新应用
  3. 客户端下载安装包和其他源信息,启动安装包后,立马杀死自己,避免冲突
  4. 安装程序安装完后,重新启动应用

解释

  1. 客户端可以主动轮询云端发布地版本,假如客户端使用了类似mqtt的消息服务的话,可以等待云端通知客户端发起版本查询
  2. 安装包是一个可以内嵌逻辑的程序,一般可以执行校验安装内容,清除上一版本的文件,安装完后启动应用等功能,还可以配置静默安装。

社区中成熟的全量更新方案

社区中的更新方案一般和打包方案集成在一起,由前面的基本流程我们也可以知道更新和打包一般是集成在一起比较好。下面我简单介绍一下我熟悉的更新方案

electron-updater

electron-updater 是 electron-builder里面的一个包。里面有着成熟的electron更新方案。这个插件提供了全量和增量更新的electron的能力。并且支持比较多的云服务商, 发布和推送新的安装包。‘

专有的云服务商

  1. Amazon S3
  2. DigitalOcean Spaces
  3. Keygen

开源的选择

  1. GitHub
  2. Generic(通用的方式,必须自己架设自己的静态资源服务器)

假如需要构建自己的http更新服务器的话,可以查看下面的例子。服务器例子

自己实现的全量更新方案

但是,假如我们将完全定制好自己的更新方案的话,仅仅使用 Generic 的方案是不够的,因为其有诸多限制,比如说其规定了

  1. 更新查询接口,包地址,blockMap地址的URL的格式
  2. 无法配置公司自带的鉴权系统
  3. 无法使用自己的Restful api, 相关issue

在经过不少代码调研后,我发现了,我们可以导入代码中的几个关键类,然后重写其关键的几个方法,就能实现定制化的升级方案(主要是将其对更新地址的要求给去除)。这种类似于打补丁的方式,利用了js的类没有成员修饰符的限制来重现方法,需要将ts的类型检查禁用。

我的方案写在gist上了,里面有详细的注释,我知道写得可能不太全,有什么可以留言来问我。

非全量更新

前言

在上古时代,流量往往十分宝贵,网速比较慢。假如进行全量更新的话,往往会有显而易见的两个缺点。

  1. 高额的流量费用
  2. 糟糕的更新体验 因此在增量更新作为一种节省流量,提高网络传输的速度,一直得到广泛的应用。

增量更新原理

增量更新的原理其实大同小异。主要分为diff和patch

  1. diff是一个比较两个文件差异的过程
  2. patch是合成出新文件的过程

增量更新的原理就是先diff出差异,然后用差异和老文件合成出新文件来。没有什么特别的

diff的细粒度的不同,是文件级别的还是二进制级别的,diff发生的在服务端还是客户端,diff能否预先计算造成了这些方案的不同。

增量更新的技术方案

diff算法

diff算法主要是针对的是二进制文件的diff,具体原理我就不表述了。这种方法主要的问题是需要服务端拥有客户端中要更新的旧版本的文件,然后将旧版本的文件和新版本的文件做diff,然后将差异下载到客户端,客户端再做patch。假设客户端有10个版本,那么我们就要做新版本和旧版本的10次diff

不同的diff算法性能也有所不同,我贴出下面,并简单介绍:

bsdiff

这个算法比较有名,并且配有论文食用,有兴趣的可以看看。 如果你想使用这个算法,可以直接再npm库中搜索bsdiff

HDiffPatch

下面这个算法是国人发明的算法,大家可以直接到它的仓库中查看,里面有详细的性能比较。 我这里贴出一个作者对其原理说明的博客

其他算法

接下来我将会介绍比较有名的算法,并且分析一下各中的优缺点。

rsync
简介

rsync算法解决的问题就是计算diff的时候,必须有新旧两个版本的文件处于同一个存储地点。之前的diff算法需要服务器保存客户端所有的版本,进而计算diff。

流程

假设我们有两个电脑A,B。A电脑上有文件a,B电脑上有文件b。

  1. 对于电脑B来说,它想获取A电脑中的文件。那么电脑B需要计算将本地的文件b,分成固定大小的不重叠的小块文件。对于每个这样的不重叠的小块文件,它需要计算这个文件的两种hash,一个是128位的md4,一种是滚动hash。 并且将这些信息传给A electron应用更新方案大调研

  2. 对于A电脑来说,它将会对自己的文件a做如下操作,它将使用一种滚动hash的算法,对于任意起点不断计算固定长度块的rolling hash。对于b中和a中都有的hash,那么我们就不需要传输数据了。假如b中没有,a中有的数据,就需要传输到b中

electron应用更新方案大调研

  1. B自己合成出a来。大功告成
分析

这里面最关键的其实是这个算法提出的rolling checksum(计算hash很快) 和 multi-alternate search mechanism。 这里的关键细节我就不详细解释了。我贴一下别人的paper

zsync
简介

注意到再rsync中我们的服务器A承载了大量的计算rolling checksum的工作,zsync算法试图将这些工作转移到客户端,并且提供对压缩文件的支持。

将计算转移到客户端
  1. 服务器将会按照某种固定的长度计算为每一小块文件计算checksum。因为这个操作是不对针对特定客户端的,因此这些计算可以计算一次就能缓存下来。
  2. 客户端将会下载这些服务端的checksum文件,再对本地的文件做rsync rolling checksum。遇到缺失的文件就使用range request获取自己缺失的部分。然后本地合成。

electron应用更新方案大调研

解释

这种方式的最大的好处是将服务器的计算量缓存,并且将性能操作放在客户端。

electron-updater的增量更新技术方案分析
简介

假如你看过electron-updater增量更新的源代码,你就会发现它也尝试将hash的计算缓存到称之为blockmap的文件中。

主要流程
  1. 7z压缩安装包,然后获取安装包的7zheader,得到相应的hash,生产blockmap
    1. 读取7z header,手写解析程序
    2. 生产blockmap的过程就是算出每个file的offset和end
    3. 然后自己算hash
  2. 下载时候,下载不同的blockmap,比较不同的本地文件的blockmap和云端文件blockMap的差异,然后使用range request请求差异的部分
解释

我知道我这里给出的解释有点不足够,我补充一点额外的信息

  1. electron-updater更新的mr地址大家有兴趣可以扎进去看看。但是原理就是着一些了。
  2. electron-updater更新的更新方式存在性能问题。 有个印度佬使用了HDiffPatch搞了一个服务号称可以提高性能问题。
定制自己的增量更新方案

我的方案写在gist上了,里面有详细的注释,我知道写得可能不太全,有什么可以留言来问我。

总结

本片文章主要为调研性文章。罗列了大部分的应用更新方案,简单比较了他们的优缺点。主要包括如下几个方案。

  1. 全量更新
    1. electron-updater全量更新
    2. 魔改electron-updater全量更新
  2. 非全量更新
    1. diff算法方案
      1. bsdiff
      2. HDiffPatch
    2. 其他算法方案
      1. rsync
      2. zsync
      3. electron-updater增量更新

希望对大家有所帮助。

其他

参加工作一年了,在开发electron应用才3个月,做了许多调研,但由于中间缺乏指导,有些地方可能没有讲明白或者讲错了,各位可以留言问我或者给我指正,希望我能帮助到你。假如有必要,我会定时更新这篇文章,希望大家多多点赞,评论,感谢了。

我正在参与AdminJS技术社区创作者签约计划招募活动,点击链接报名投稿。