whmcs对接cdn系统_搭建系统奇美拉的同构渲染服务

50e9bf2f915b341f7a334677c6026ea6.png

背景

我们的搭建系统从积木盒子到奇美拉,PC和H5页面都是静态的,发布时将页面静态化发布到cdn,浏览器访问时,从cdn获取页面进行异步渲染。由于页面是静态和异步的,对于首屏和SEO有诉求的场景无法很好的支持;另外对于像组件AB、动态区块等动态能力也没有办法实现。基于上诉原因,同构渲染是我们搭建系统所需要的。

技术方案

出于性能、稳定性和可扩展性的考虑,奇美拉的同构渲染服务分为3个部分:搭建(页面来源)-> 预处理 -> 同构渲染服务,下面是整体架构图,接下来将详细介绍每个部分。

7c22fecb9ee4f19acb1e4c3cf42b84d6.png

搭建(页面来源)

搭建就是将组件拼凑成页面,类似搭积木,输出是描述页面结构的schema,供预处理消费。由于页面是通过schema表示的,所以可以方便的对接其他平台。那么schema里面有什么呢?

组件

第一个肯定是组件,组件分为”布局“”组件“”布局“是用来控制”组件“放在什么地方的,比如下面的”布局“含有三个位置,可以放不同的”组件“

96728c4fb9c46980fdfc52ec98cb73d4.png

数据

一般情况下仅有组件是无法渲染的,还需要数据,除非是静态组件,为此schema还需要包括数据获取方式的描述。虽然奇美拉和积木盒子通过jdata能够很好的获取数据,但是一些业务无法走jdata,所以需要一种通用的方式,在服务器端HSF就是一种很好的方式。为此获取数据的方式分为jdata和HSF两种,如下:

jdata(通过value就可以获取数据):

{
   "type": "jdata",
   "value": 123456
} 

HSF(value是用来描述调用HSF的数据):

{
   "type": "hsf",
   "value": {
      "appName": "widget-router-hsf",
      "id": "com.alibaba.widgetpt.service.WidgetService:2.0.0",
      "group": "HSF",
      "method": "getJsonComponent",
      "args": [
         "adPcWidget:adPcWidget",
         "execute"
      ],
      "argsTypes": [
         "java.lang.String",
         "java.lang.String"
      ]
   }
}

除了jdata和HSF,上面的格式能够容易的扩展到其他方式。

组件组件的其他描述

为了能够支持一张页面同时存在同构动态组件(服务器端渲染,数据会不断变化,比如千人千面的组件)、同构静态组件(服务器端渲染,数据不会变化,比如热区组件)、异步组件(在浏览器端获取数据并渲染的组件),schema需要有字段区分它们;同时为了支持不同技术体系的组件,比如react和jquery,也需要有字段区分;此外schema中还有一些组件其他描述,比如spm(c位)。

页面渲染描述

上面已经了解了渲染组件所需要的内容,将组件拼装成页面,还需要一些页面级别的描述。比如承载页面的模板,我们成为原型prototype;页面的访问地址;页面的SPM ab位;下线时间和下线地址;页面的头部和尾部等。

schema

下面是一份简单的页面schema:

{
  "attributes": {
    "page-url": "https://cms.1688.com/ssr/ssr/ssr/hhd2n7pi.html", // 访问地址
    "page-spm": "a2632k.12847700", // spm ab位置
    "offline-time": null, // 下线时间
    "offline-url": "", // 下线地址
    "page-prototype": "1688/pc/fusion.html", // 页面原型
    "page-header": "", // 页面头部
    "page-footer": "", // 页面尾部
  },
  "children": [
    {
      ...
      "attributes": {
        "component-name": "@alife/ocms-layout-1688-wap-layout-common", // 包名
        "component-version": "1.0.4", // 版本
        "component-spm": "jxolqgpw", // spm c
        "component-type": "layout", // 表示布局类型
        "component-tech": "pure", // 技术体系,pure表示jquery组件
        "component-runat": "server" // 表示同构静态组件
      },
      "children": [
        {
          "attributes": {
            ...
            "component-name": "@alife/ocms-fusion-1688-pc-brand-pc-gather",
            "component-spm": "jyh2ezk1",
            "component-version": "1.0.17",
            "component-type": "component",
            "component-tech": "fusion",
            "component-runat": "web",
            "component-isomorphic": true,
            // 描述数据获取的方式
            "component-isomorphic-data": "{"type":"hsf","value":{"appName":"widget-router-hsf","id":"com.alibaba.widgetpt.service.WidgetService:2.0.0","group":"HSF","method":"getJsonComponent","args":["adPcWidget:adPcWidget","execute"],"argsTypes":["java.lang.String","java.lang.String"]}}"
          }
        }
      ]
    }
  ]
}

预处理

出于性能和稳定性的考虑,我们没有把描述页面的schema直接交给同构渲染服务,而且进行一次预处理,主要包括资源分析和处理预渲染资源预取兜底页面几个部分。

0dca1707f34c870e93f050e91866eefd.png

资源分析处理

由于我们的组件都在cdn上,schema只给出了组件的包名和版本,组件渲染还需要其依赖,为此要将每个组件的依赖、js列表和css列表都分析出来。由于同构渲染服务是通过积木盒子的渲染引擎croco来实现的,它的渲染是根据描述页面的DSL来实现的,所以需要将描述页面schema转化成DSL,DSL中包含预先渲染好的同构静态组件。下面是一个croco DSL的例子:

<div page-id="4286" data-cms="chimera" class="ocms-container">
  <div component-uuid="4" component-name="@alife/ocms-layout-1688-wap-layout-common"
       component-version="1.0.4" component-type="layout" component-tech="pure" data-spm="jxolqgpw"
       component-async="false" component-stage="render">
    <div class="ocms-layout-1688-wap-layout-common-1-0-4">
      <div>
        <div name="main" class="croco slot">
          <component slot="main" component-uuid="5" component-name="@alife/ocms-fusion-1688-pc-brand-pc-gather"
                    component-version="1.0.17" component-type="component" 
                    component-tech="fusion" component-smart="false" component-isomorphic="true" 
                    component-isomorphic-data="{&quot;type&quot;:&quot;hsf&quot;,&quot;hsf&quot;:{&quot;appName&quot;:&quot;widget-router-hsf&quot;,&quot;id&quot;:&quot;com.alibaba.widgetpt.service.WidgetService:2.0.0&quot;,&quot;group&quot;:&quot;HSF&quot;,&quot;method&quot;:&quot;getJsonComponent&quot;,&quot;args&quot;:[&quot;adPcWidget:adPcWidget&quot;,&quot;execute&quot;],&quot;argsTypes&quot;:[&quot;java.lang.String&quot;,&quot;java.lang.String&quot;]}}"
                    data-spm="jyh2ezk1" component-async="true">
            <div style=";overflow:hidden; width:auto;height:150px" class="ocms-loading"></div>
          </component>
        </div>
      </div>
    </div>
  </div>
</div>

预渲染

根据上文,一个页面可能具有同构静态组件,它在服务器端渲染的结果是不会变,因为组件版本和数据都不会变,所以没必要每次访问页面时都渲染一次,提前渲染好是一种不错的选择,这样访问时只需要将渲染好的html拼接到页面里就好了。

另外搭建系统中页面的头部和尾部是通过esi实现的,几乎很少变动,所以也可以提前处理好。

资源预取

拼接页面需要模板,也就是原型prototype,它在cdn上,可以提前预取。资源分析出的css也可以提前预取,在拼接页面的时候打到html,可以避免reflow和repaint。

兜底页面

出于稳定性的考虑,预处理的时候会cdn发布一张浏览器可以直接跑的页面,同构动态组件会在浏览器端进行异步渲染,这样即使同构渲染服务挂掉了,也可以通过兜底页面展示内容。

输出

预处理的结果会保存在oss上供同构渲染服务使用,具体结构如下:

b914bcfd443a09f2b4fbb7b0873adf8c.png

同构渲染服务

当用户访问时,同构渲染服务会返回渲染好的html,从下到上分为3层:纯渲染服务 -> 拼接服务 -> 上层服务,它们是基于Fass实现的,架构图如下:

fd89d70fe6280986565eadec6c31216b.png

纯渲染服务

纯渲染服务只做渲染,输入croco的DSL、组件列表和数据列表,就能得到渲染好的html。由于只做渲染,所以该服务可以开放出去给其他团队使用,比如和我们密切合作的广告团队。

拼接服务

获取渲染好的html,接下来就是拼接页面了,核心就是将预取的头尾、js列表、css和渲染好的html填充到原型prototype中,原型prototype本质上是一个art-template。

上层服务

实际上拼接服务返回的内容就可以直接返回给用户了,出于性能、稳定性和扩展性的考虑,我们抽象出了这一层。该服务目前做了兜底和缓存。如果下层拼接服务或者纯渲染服务挂掉了,会将预处理的兜底页面返回给用户;一些业务没有实时性的要求,可以为页面配置不同的缓存策略。除了兜底和缓存,这层还可以扩展其他功能,比如页面AB、页面下线等。

最近看了一下CDN团队开发的边缘Serverless计算环境EdgeRoutine,感觉是非常适合做这些事情的,后期可能会将该服务迁移到EdgeRoutine上。

性能优化

上面我们已经提及了一些优化,主要在预处理阶段,比如同构静态组件的预渲染,原型prototype的预取、css的预取等。此外我们在同构渲染服务那层也做了一定优化,主要在于组件上。

  1. 因为组件资源在cdn上,如果每次渲染都从cdn上拉取,会非常耗时;另外组件从文件变成类也会消耗大量的CPU计算。为此我们对组件进行了3级缓存:内存、本地文件和oss,具体如下:

d39aaacd94425d9410baec3532c26dd5.png

2. 第一次访问页面时,由于各级缓存都没有,所以还会走cdn,为此我们做了组件预热。在diamond中配置重要页面的组件,服务器在重启或修改diamond时会将组件提前加载并缓存起来。

3. 如果一个页面有多个组件,所以组件和数据都是批量并发请求的。

兜底

处于稳定性的考虑,我们做了一定兜底处理,分为组件兜底和页面兜底。

组件兜底

组件渲染需要模块和数据,两者都获取成功时才会在服务器端进行同构渲染;如果有一个获取失败,组件将会到前端进行异步渲染。

c071b841540d0e5038c97bcbe8d95743.png

页面兜底

页面渲染时,如果出现错误或超时,会返回预处理阶段生成的兜底页面。

0f3a56057664bbc14cb75c90a53347ee.png

案例展示

ok,原理讲了这么多,让我们看一下效果吧。广告pc品专组件,在接入同构渲染服务之前,首屏时间大概需要1.52s,接入同构渲染后首屏变成了0.3s,降低了1.22s。查看地址:链接

图中红框框柱的部分是广告pc品专组件:

10cc1b868a4d6ae5daa3b75abbf28059.png

接入奇美拉同构渲染服务的结果:

3296fa6ca6d3ed85d90da43de139b90d.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值