云原生微信小程序开发实战读书笔记-6

小程序的资源可以笼统地分为前端和后端资源:前端资源也可以被称为端侧资源(包括脚本、样式文件等),后端资源指的是小程序的一些服务接口。

端侧更新策略

开发小程序的技术栈虽然和传统前端非常相似,但在端侧资源的更新策略上却有明显的差异,其根源在于小程序特殊的端侧资源管理机制,所以要理解更新策略就要先了解这些知识。

网站的前端资源可以分为动态资源和静态资源, 静态的资源包括 js、css、图片等文件,为了提高性能通常会将这些文件尽量缓存到本地。动态的资源只有 HTML 文件。

网站的HTML 文件最初是由服务端通过模板引擎渲染出来的,比如 freemarker、smarty 等,现在仍然有很多网站使用这种方式,不过更流行的是用 React/Vue SSR 以及 SPA 的静态 HTML。

虽然在 SPA 架构中,HTML 文件与 js 文件、css文件一样作为静态资源部署,但跟 js 和 css 不同的是,我们并不会让浏览器缓存 HTML 文件,而是通过服务器配置将 HTML 文件的 HTTP 请求的 Cache-Control Header 设置为 no-cache 。这是为了保证用户每次打开网站都会得到最新版的 HTML 文件,而其他静态资源都要通过 HTML 文件才会被引入,这保证了HTML 文件的实时性,也保证了网站所有静态资源的实时性。

跟网站不同的是,小程序的“所有”端侧资源都是静态的(“所有”我加了引号是因为它指的是小程序代码包中的所有文件,至于代码中引用的外部文件不在我们的讨论范围之内)。

小程序的资源是托管在微信服务器上的,跟网站不同,微信不会在用户每次打开小程序时,从服务器拉取最新的小程序资源,而是尽可能地发挥缓存的优势(触发拉取新版本资源的时机有很多种,稍后我会一条条地讲)。先来看下面这张图:Drawing 0.png

当用户打开小程序时,微信客户端会先从缓存中拉取小程序的端侧资源,有的话就展示给用户,没有的话会从微信服务器拉取,这时,拉取的肯定是最新版本,然后放入缓存并展示给用户。

从这套流程里你会发现一个问题:既然优先使用缓存中的资源,那么当我发布了小程序新版本之后,怎么保证用户尽可能快地更新为新版本呢?这就是我们要讨论的重点:小程序的端侧资源更新机制。

触发拉取新版本资源的时机有很多种:

  • 本地没有缓存会触发是最简单的一种时机
  • 未启动时: 指的是小程序处于非活跃状态时(比如处于后台),但是请注意,这种状态是用户已经用过小程序后才会产生的,如果用户从来都没有用过你的小程序,就不存在状态的概念了,因为对于这个用户来说,你的小程序是无状态的。
  • 冷启动时: 小程序被销毁重新打开后会进入冷启动状态

如果小程序处于未启动状态, 微信客户端会在“若干个时机”去检查缓存中的小程序有没有新版本,如果有会默默把新版本资源拉取到本地缓存中。请注意,“若干时机”并不是我瞎说的,而是官方说明,而这部分信息对于开发者来说是不透明的,但是有一点可以确定,那就是当你发布了新版本小程序后,无法立刻让所有用户体验最新版(至于多久能覆盖所有用户,官方说明最晚24小时)。整个流程请看下图

Drawing 2.png

如果小程序处于冷启动状态,微信客户端会主动检查是否有新版本,同时会向用户展示缓存中的旧版本。有新版本的话会默默地拉取到本地,然后在用户再次触发小程序冷启动时展示给用户。也就是说,需要两次冷启动才能将最新版本的小程序展示给用户。整个流程如下图所示:

Drawing 4.png

当你发布一个新版本后,用户并不能“立即”获得更新。

在传统前端领域,当网站发布新版本之后,用户下次打开或刷新之后就会“立即”体验到新版本,没有延迟。但是在小程序场景下,更新之后并不是“立即”让用户体验到新版,而是“尽可能快”。

从官方描述中,小程序未启动时最慢 24 小时可以覆盖全部用户,或者需要经历两次冷启动,这对一些紧急的版本更新来说太慢了,所以在现实工作中往往要将小程序的更新提速,让用户尽可能快地获取到新版本。具体实施方法是通过小程序的UpdateManager对象,在代码里主动检查并应用更新信息。

Drawing 6.png

const axios = require('axios')
const updateManager = wx.getUpdateManager()
updateManager.onCheckForUpdate(function (res) {
  // 将是否有新版本信息挂载到全局对象上
  this.globalData.hasUpdate = res.hasUpdate
})
updateManager.onUpdateReady(function () {
  if(!this.globalData.hasUpdate){
    return
  }
  const { miniProgram } = wx.getAccountInfoSync()
  // 获取当前小程序的版本号
  const currVersion = miniProgram.version
  // 从你的开发者服务器接口中获取是否有紧急版本需要更新
  axios.get(`${<your-url?}?currVersion=${currVersion}`).then(res=>{
    if(res.needUpdate){
      // 紧急版本立即重启小程序应用更新
      updateManager.applyUpdate()
    }
  })
})

后端服务灰度发布策略

后端服务的发布流程中有一个非常重要且通用的策略:灰度发布。所谓的灰度发布简单理解就是将新版本的服务只向一定比例的用户开放,而另一部分用户仍然使用旧版本的服务,然后观察新版本的状态,如果一切正常则慢慢扩大新版本的用户比例,直到全部用户都切入新版本,便完成了灰度发布的全流程。

灰度发布需要提前制定用户请求的转发策略,一般有两种:

  • 按照新旧服务所占用的服务器比例随机转发;
  • 按照用户的 ID 转发。

第一种简单粗暴,比如你有 10 台服务器,其中 2 台部署了新版本的服务,负载均衡器会在接收到用户请求时按照 20% 的概率随机转发到新版本服务器上,剩余的转发到旧版本服务器。

第二种需要进行一定的编码工作,比如 Nginx 配置 Lua 脚本,当接收到用户请求时,从请求中获取到用户的 ID ,在小程序场景下就是用户的 OpenId ,然后匹配转发策略中是否这个 ID 在新版本服务的白名单中,如果是的话便转发到新版本服务,否则转发到旧版本服务。如下图所示:

Drawing 8.png