人人都是Serverless架构师之传统内容管理系统改造实战三[性能优化]

本文涉及的产品
简介: 内容管理系统是很常见的一种web应用场景,可以用到个人独立站,企业官网展示等场景,具有很高的实用价值,一个标准的内容管理系统主要由三个部分组成 主站展示部分、后台管理系统、API接口服务,本系列文章会以一个已有内容管理系统的Serverless架构重构展开,介绍改造的基本思路,改造细节,以及性能优化业务可观测设计等。涉及大家关心的Serverless生产遇到的一些问题,比如数据库、日志、动静态分离、调试、维护、灰度方案等。最真实的展现Serverless架构的实施落地细节。

本次改造的项目题材取自GiiBee CMS
Serverless架构改造的仓库地址

项目改造后预览地址 SSR方案地址

1. 应用性能瓶颈分析

用户获取网站内容并且在本地预览受到网络性能,资源量,接口速度,页面渲染性能等诸多因素的影响,本篇主要讨论方向是如何通过改进架构以及利用云服务的基础设施能力来提升访问性能。接下来我们需要对这个架构的各个组成部分展开分析。

1.1 入口网关

本次使用的是API网关的共享实例,他的好处是只会收取调用次数+网络流量的费用,成本非常低,坏处就是无法保证后端延时及性能,下图是跟专享版本的对比(截取部分)

专享版本有各种优势但是价格偏高,对中小型用户不推荐,此外网关本身不具备CDN能力,所以做不到就近访问的能力,对于网络请求有极致要求的用户也不能够满足要求。

1.2 后端函数服务

这里包含两个部分,一个是SSR渲染的服务,一个是后端API服务,他们都部署在函数计算上,我们都知道Serverless的一大特征是会有冷启动的现象,一旦触发冷启动会耗时较长,严重影响用户访问体验,尽管产品侧已经在冷启动方面做了一些优化,但是业务侧的冷启动耗时依然不能不忽视。

关于冷启动优化的建议可以参考这里。除了建议,本次也提供了直观的配置优化方案,接下来会给大家做详细介绍。

1.3 前端静态资源

静态资源如果是大的文件,网络请求会花费比较长的时间,可以根据需要对相应的图片进行压缩处理,此外可以考虑静态资源跟后端服务的分离,目前前端的静态资源是放到nas,通过api服务响应给用户,后续可以将请求直接转给OSS的静态服务,也就是说后端函数服务不再影响前端静态资源。

2. 优化方案

2.1 网关优化

2.1.1 边缘应用程序介绍

在[开篇]中介绍了以CDN边缘程序作为网关入口的架构方案。其最大的好处就是充分利用了内容分发网络的优势,根据用户访问地域找到最近的服务节点快速给予响应。CDN也是在基础架构层面提升访问性能的有效工具

阿里云的边缘程序EdgeRoutine(ER)可以让开发者在阿里云全球边缘节点上编写Js业务代码,比如处理请求路径并进行后端服务的调用返回,可以用来替代API网关作为应用服务的分流入口。其原理如下

利用ER我们还可以做很多事情,比如缓存请求进一步加速用户获取网站内容的速度、根据请求标识做A/B测试验证产品功能的好坏、改写返回内容满足特异性需求以及身份验证等等。我们本次主要实现路由分发的编写,以及添加缓存能力。

2.1.2 边缘应用程序开通使用

参考官网

2.1.3 边缘应用程序网关代码示例

const staticUrl = 'http://xxxx.oss-cn-hangzhou.aliyuncs.com';  //静态资源的oss地址,包含管理后台地址 (CSR)以及 portal (SSG)
const apiUrl = 'http://modern-app-new.modern-app-new.xxxxxx.cn-hangzhou.fc.devsapp.net'; // api接口地址 
const portalUrl = 'http://modern-app-portal.xxxxxxx.cn-hangzhou.fc.devsapp.net'; // 官网主页地址 SSR
const init = {
    headers: {
        "content-type": "text/html;charset=UTF-8",
    },
}

const API_ROUTER_REG = /^\/(prod-api|api)/g;
const UPLOAD_ROUTER_REG = /^\/(uploads)\/?/g;
const ADMIN_OR_PORTAL_ROUTER_REG = /^\/(admin|portal)\/?/g;

const API_PATH = '/api';
async function gatherResponse(response) {
    const headers = response.headers
    const contentType = headers.get("content-type") || ""
    if (contentType.includes("application/json")) {
        return JSON.stringify(await response.json())
    } else if (contentType.includes("application/text")) {
        return response.text()
    } else if (contentType.includes("text/html;charset=UTF-8")) {
        return response.text()
    } else {
        return response.blob()
    }
}

async function handleRequest(request) {
    try {
        const requestHeaders = request.headers || init;
        const body = request.body;
        const method = request.method;
        const url = new URL(request.url)
        const { pathname, search } = url;
        const cacheResponse = await cache.get(request.url);
        if(cacheResponse) {
             return cacheResponse;
        }
        let response = {};
        if (pathname.match(API_ROUTER_REG)) {
            const matchedData = pathname.match(API_ROUTER_REG)[0];
            let finalurl = (apiUrl + pathname + search).replace(matchedData, API_PATH);
            response = await fetch(finalurl, { headers: requestHeaders, method,body });
        } else if (pathname.match(UPLOAD_ROUTER_REG)) {
            let finalurl = apiUrl + pathname + search;
            response = await fetch(finalurl, { headers: requestHeaders, method ,body})
        }  else if(pathname.match(ADMIN_OR_PORTAL_ROUTER_REG)){
            let finalurl = staticUrl + pathname + search;
            response = await fetch(finalurl, { headers: requestHeaders, method });
        } else {
            let finalurl = portalUrl + pathname + search;
            response = await fetch(finalurl, init);
        }
  
        const results = await gatherResponse(response)
        const finalResponse =  new Response(results, response.headers);
        try {
             await cache.put(request.url,finalResponse);
        } catch(e) {}
       
        return finalResponse;
    } catch (e) {
        return new Response(JSON.stringify({ message: e.message }), {
            headers: {
                "content-type": "application/json;charset=UTF-8"
            }
        })
    }

}

addEventListener("fetch", event => {
    return event.respondWith(handleRequest(event.request))
})


代码比较简单,对到来的请求做正则区分,然后分别代理给相应的后端服务,比如静态资源,后端函数服务等


2.2 函数冷启动优化

增加对服务的心跳触发,避免服务关闭。改造方案非常简单,只需要在 s.yaml中配置一个插件即可,url需要指定自己后端函数服务

api-server:
  component: fc
  actions:
  post-deploy: # 在deploy之后运行
    - plugin: keep-warm-fc
      args:
        url: http://modern-app-new.modern-app-new.xxxxxx.cn-hangzhou.fc.devsapp.net

该插件是部署后置的,当你的函数部署完之后执行,它会帮助你自动创建一个新的后端函数,用以定时向服务发起检测请求,从而避免函数的冷起动。注意该函数可能会引起一定的资费,如觉得不需要可以直接在函数计算控制台删除

2.2.1 静态渲染(SSG)

SSG说明

我们前面的实现方案是利用了SSR技术,并且以独立的函数服务部署到函数计算上,SSR本身是需要先从后端接口服务拉取数据做好服务端渲染然后再返回给客户端的,受接口服务的响应性能影响较大。 为了进一步的提高用户获取网站内容的效率,我们可以采用预渲染技术即SSG, 利用Nuxtjs的特性,提前拿到后台数据形成静态界面,这样我们可以不依赖于后端服务,可以直接将静态资源存储到OSS上,并且结合边缘程序以及内置的缓存能力,极大的提升站点的访问性能。最终实现下面的效果

SSG VS SSR

关于二者访问性能对比,这里有两点可以说明一下:

  1. 大家可以直接访问笔者制作好的项目地址 SSR方案SSG方案, 这二者都是在相对平等的条件下产出的,没有做专门的体验优化。大家可以直观感受一下效果
  2. PageSpeed Insights 测试结果对比:

SSR方案移动及桌面性能测试结果展示:

SSG方案移动及桌面性能测试结果展示

可以看到不管是PC还是H5,SSG方案都是优于SSR

3. 问题总结与未来拓展

关于Serverless架构整体系统性能优化本篇也只是揭开了冰山一角,而且已经展示出来的部分也还存在着一些不足,比如SSG方案固然是好,但也存在着缺点,比如我们如何通过自动化机制保障更新内容后会自动触发SSG并且更新站点缓存,针对大规模的网站SSG方案的执行效率也会影响业务迭代本身。

不过值得庆幸的是,这个方案在基础设施以上都是对前端可见的,意味着我们可以更好的掌控我们的web服务的访问性能,从边缘节点到静态资源再到后端的服务,他也为广大前端工程师提供了一个走向更加专业全栈的可能性。

接下来我还会继续对项目进行深入改造,会解决上面提到的持续集成中保障SSG的正常运行,以及继续扩展系统的稳定性保障,深入可观测体系跟大家一起探讨Serverless架构的可观测方案,敬请期待。

相关实践学习
基于函数计算一键部署掌上游戏机
本场景介绍如何使用阿里云计算服务命令快速搭建一个掌上游戏机。
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
5天前
|
存储 运维 Serverless
Serverless架构在图像处理领域展现出了强大的优势
【4月更文挑战第22天】Serverless架构在图像处理中表现出显著优势:弹性伸缩自动适应负载变化,节省成本;按需付费减少费用,适合需求波动场景;简化运维让开发者专注应用创新;快速迭代部署提升市场响应速度;高可用性和容错性保证服务稳定性;跨平台支持增强兼容性;丰富生态加速开发进程。因此,Serverless是图像处理的理想选择。
20 1
|
5天前
|
存储 弹性计算 Serverless
Serverless架构在图像处理的优势
Serverless架构在图像处理的优势
22 2
|
5天前
|
存储 SQL 网络协议
C语言C/S架构PACS影像归档和通信系统源码 医院PACS系统源码
医院影像科PACS系统,意为影像归档和通信系统。它是应用在医院影像科室的系统,主要的任务是把日常产生的各种医学影像(包括核磁、CT、超声、各种X光机、各种红外仪、显微仪等设备产生的图像)通过各种接口(模拟、DICOM、网络)以数字化的方式海量保存起来,并在需要的时候在一定授权下能够快速地调回使用。同时,PACS系统还增加了一些辅助诊断管理功能。
41 11
|
3天前
|
运维 Oracle 容灾
Oracle dataguard 容灾技术实战(笔记),教你一种更清晰的Linux运维架构
Oracle dataguard 容灾技术实战(笔记),教你一种更清晰的Linux运维架构
|
5天前
|
安全 数据管理 中间件
云LIS系统源码JavaScript+B/S架构MVC+SQLSugar医院版检验科云LIS系统源码 可提供演示
检验科云LIS系统源码是医疗机构信息化发展的重要趋势。通过云计算技术实现数据的集中管理和共享可以提高数据利用效率和安全性;通过高效灵活的系统设计和可扩展性可以满足不同医疗机构的需求;通过移动性和智能化可以提高医疗服务的精准度和效率;通过集成性可以实现医疗服务的协同性和效率。因此,多医院版检验科云LIS系统源码将成为未来医疗机构信息化发展的重要方向之一。
27 2
|
2天前
|
缓存 监控 安全
Django框架在大型Web应用中的架构设计与实战
【5月更文挑战第18天】Django框架在构建大型Web应用中扮演重要角色,采用分层架构(数据、业务逻辑、表示层)和多应用组织模式,结合缓存策略(如Memcached、Redis)提升性能。通过异步处理、分布式部署提高响应速度和扩展性。关注数据分区、安全设计及监控日志,确保系统高效、稳定。Django为复杂业务提供坚实基础,助力打造卓越Web系统。
19 7
|
3天前
|
前端开发 Android开发
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
|
3天前
|
Android开发
Android Jetpack架构开发组件化应用实战,字节跳动+阿里+华为+腾讯等大厂Android面试题
Android Jetpack架构开发组件化应用实战,字节跳动+阿里+华为+腾讯等大厂Android面试题
|
4天前
|
边缘计算 负载均衡 网络协议
B站千万级长连接实时消息系统的架构设计与实践
本文将介绍B站基于golang实现的千万级长连接实时消息系统的架构设计与实践,包括长连接服务的框架设计,以及针对稳定性与高吞吐做的相关优化。
23 9
|
4天前
|
安全 Serverless API
Serverless架构在图像处理中展现出高成本效益,按需付费降低费用,动态调整资源避免浪费
【5月更文挑战第16天】Serverless架构在图像处理中展现出高成本效益,按需付费降低费用,动态调整资源避免浪费。其出色的并发处理能力和自动扩展确保高并发场景的顺利执行。简化开发流程,让开发者专注业务逻辑,同时提供丰富API和集成服务。安全方面,Serverless通过云服务商管理基础架构和多种安全机制保障任务安全。因此,Serverless是处理高并发、动态需求的理想选择,尤其适合图像处理领域。随着技术发展,其应用前景广阔。
14 4

热门文章

最新文章

相关产品

  • 函数计算