在电商网站中,单页Web是非常常见的一种形式,比如首页、频道页、广告页等都属于单页应用。而这种页面是由模板+数据组成。传统的构建方式一般通过静态化实现。而这种方式的灵活性并不是很好,比如页面模板部分变更了需要重新全部生成。因此最好能有一种实现方式是可以实时动态渲染,以支持模板的多变性。另外也要考虑好如下几个问题:
1、动态化模板渲染支持;
2、数据和模板的多版本化:生产版本、灰度版本和预发布版本;
3、版本回滚问题,假设当前发布的生产版本出问题了如何快速的回滚到上一个版本;
4、异常问题,假设渲染模板时遇到了异常情况(比如获取Redis出问题了),如何处理;
5、灰度发布问题,比如切20%量给灰度版本;
6、预发布问题,目的是在正式环境测试数据和模板的正确性。
整体架构
静态化页面的方案如下图所示:
直接将生成的静态页推送到相关服务器即可。使用这种方式要考虑文件操作的原子化问题(即从老版本切换到新版本如何做到文件操作原子化)。
而动态化方案的整体架构如下图所示,分为三大系统:CMS系统、控制系统和前端展示系统。
CMS系统
1、在CMS系统可以配置页面的模板和数据;
1.1、模板动态在CMS系统中维护,即模板不是一个静态文件,而是存储在CMS中的一条数据,最终发布到“发布数据存储Redis”中,前端展示系统从Redis中获取该模板进行渲染,从而前端展示系统更换了模板也不需要重启,纯动态维护模板数据;
2、原始数据存储到“元数据存储Mysql”中即可,比如频道页一般需要:前端访问的URL、分类、轮播图、商品楼层数据等;这些数据按照相应的维度存储在CMS系统中;
3、提供发布到“发布数据存储Redis”的控制,将CMS系统中的原始数据和模板数据组装成聚合数据(JSON存储)同步到“发布数据存储Redis”,以便前端展示系统获取进行展示;此处提供三个发布按钮:正式版本、灰度版本和预发布版本。
目前存在如下几个问题:
1、用户如访问http://channel.jd.com/fashion.html怎么定位到对应的聚合数据呢? 我们可以在CMS元数据中定义URL作为KEY,如果没有URL,则使用ID作为KEY,或者自动生成一个URL。
2、多版本如何存储呢? 使用Redis的Hash结构存储即可,KEY为URL(比如http://channel.jd.com/fashion.html),字段按照维度存储:正式版本使用当前时间戳存储(这样前端系统可以根据时间戳排序然后获取最新的版本)、预发布版本使用“predeploy”作为字段,灰度版本使用“abVersion”作为字段即可,这样就区分开了多版本。
3、灰度版本如何控制呢?这个通过控制系统的开关来控制如何灰度;
4、如何访问预发布版本呢?比如在URL参数总带上predeploy=true,另外可以限定只有内网可以访问或者访问时带上访问密码,比如pwd=absdfedwqdqw。
5、模板变更的历史数据校验问题?比如模板变更了,但是使用历史数据渲染该模板会出现问题,即模板要兼容历史数据的;此处的方案不存在这个问题,因为每次存储时是当时的模板快照,即数据快照和模板快照推送到“发布数据存储Redis”中。
前端展示系统
1、获取当前URL,使用URL作为KEY首先从本机“发布数据存储Redis”获取数据;
2、如果没有数据或者异常则从主“发布数据存储Redis”获取;
3、如果主“发布数据存储Redis”也发生了异常,那么会直接调用CMS系统暴露的API直接从元数据存储Mysql中获取数据进行处理。
展示系统的伪代码
Java代码
--1、加载Lua模块库
local template = require("resty.template")
template.load = function(s)returns end
--2、动态获取模板
local myTemplate ="{* title *}"
--3、动态获取数据
local data = {title ="iphone6s"}
--4、渲染模板
local func = template.compile(myTemplate)
local content = func(data)
--5、通过ngx API输出内容
ngx.say(content)
即模板和数据都是动态获取的,然后使用动态获取的模板和数据进行渲染。
此处假设最新版本的模板或数据有问题怎么办?这个可以从流程上避免:1、首先进行预发布版本发布,测试人员验证没问题后;2、接着发布灰度版本,在灰度时自动去掉CDN功能(即不设置页面的缓存时间),发布验证OK;3、发布正式版本即可;正式版本发布的5分钟内是不设置页面缓存的,这样就可以防止发版时遇到问题,但是问题版本已经在CDN上给全部用户造成问题。当然这个流程很麻烦,可以按照自己的场景进行简化。
控制系统
控制系统用于版本降级和灰度发布的,当然可以把这个功能放在CMS系统中实现。
版本降级:假设当前线上的版本遇到问题了,想要快速切换回上一个版本,可以使用控制系统实现,选中其中一个历史版本然后通知给前端展示系统即可,使用URL和当前版本的字段即可,这样前端展示系统就可以自动切换到选中的那个版本;当问题修复后,再删除该降级配置即切换回最新版本。
灰度发布:在控制系统控制哪些URL需要灰度发布和灰度发布的比例,同版本降级类似将相关的数据推送到前端展示系统即可,当不想灰度发布时删除相关数据即可。
数据和模板动态化
我们将数据和模板都进行动态化存储,这样可以在CMS进行数据和模板的变更;实现了前端和后端开发人员的分离;前端开发人员进行CMS数据配置和模板开发,而后端开发人员只进行系统的维护。另外因为模板的动态化存储,每次发布新的模板不需要老重启前端展示系统,后端开发人员更好地得到了解放。
模板和数据可以是一对多的关系,即一个模板可以被多个数据使用。假设模板发生变更后,我们可以批量推送模板关联的数据,首先进行预发布版本的发布,测试人员进行验证,验证没问题即可发布正式版本。
多版本机制
我们将数据和模板分为多版本后,可以实现:
预发布版本:更容易让测试人员在实际环境进行验证;
灰度版本:只需要简单的开关控制,就可以进行A/B测试;
正式版本:存储多个历史正式版本,假设最新的正式版本出现问题,可以非常快速的切换回之前的版本。
异常问题
其中一个担心就是本机从“发布数据存储Redis”和主“发布数据存储Redis”都挂了,那么我们直接调用CMS系统暴露的HTTP服务直接从元数据存储Mysql获取数据。
另外一个担心是数据和模板获取到了,但是渲染模板出错了,比如遇到500、503;解决方案是:使用上一个版本的数据进行渲染。
另外还一种问题是数据和模板都没问题,但是因为一些疏忽,渲染出来的页面错乱了或者有些区域出现了空白;对于这种问题没有很好的解决方案;可以根据自己的场景定义异常扫描库,扫描当前版本有异常就发警告给相关人员,并自动降级到上一个版本。