人人都是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 架构模式
目录
相关文章
|
16小时前
|
存储 SQL 网络协议
C语言C/S架构PACS影像归档和通信系统源码 医院PACS系统源码
医院影像科PACS系统,意为影像归档和通信系统。它是应用在医院影像科室的系统,主要的任务是把日常产生的各种医学影像(包括核磁、CT、超声、各种X光机、各种红外仪、显微仪等设备产生的图像)通过各种接口(模拟、DICOM、网络)以数字化的方式海量保存起来,并在需要的时候在一定授权下能够快速地调回使用。同时,PACS系统还增加了一些辅助诊断管理功能。
40 11
|
16小时前
|
传感器 存储 数据采集
04 深度解析物联网架构与技术应用于农业大棚系统
本文将深入探讨物联网架构在农业大棚系统中的应用,从设备接入、边缘网关、数据传输到云平台和应用平台,逐层解析其技术应用与通信协议,为读者全面呈现物联网在农业领域的实际运用场景。
|
16小时前
|
存储
嵌入式微处理器的系统架构中指令系统
嵌入式微处理器的系统架构中指令系统
19 0
|
16小时前
|
存储 前端开发 BI
基于云计算技术的B/S架构智能云HIS系统源码 集挂号、处方、收费、取药、病历于一体
云HIS是针对中小医院机构、乡镇卫生室推出的一套基于云端的云HIS服务平台,借助云HIS,将医院业务流程化,大大提高医院的服务效率和服务质量,为客户提供医院一体化的信息解决方案。云HIS主要功能:包含门诊收费管理,住院收费管理,门诊医生工作站,住院医生工作站,住院护士工作站,辅助检查科室管理,药房药品管理,药库药品管理,报表查询。满足诊所、中小医院业务中看诊、收费、发药、药库管理、经营分析等多环节的工作需要。
48 4
|
16小时前
|
安全 数据管理 中间件
云LIS系统源码JavaScript+B/S架构MVC+SQLSugar医院版检验科云LIS系统源码 可提供演示
检验科云LIS系统源码是医疗机构信息化发展的重要趋势。通过云计算技术实现数据的集中管理和共享可以提高数据利用效率和安全性;通过高效灵活的系统设计和可扩展性可以满足不同医疗机构的需求;通过移动性和智能化可以提高医疗服务的精准度和效率;通过集成性可以实现医疗服务的协同性和效率。因此,多医院版检验科云LIS系统源码将成为未来医疗机构信息化发展的重要方向之一。
27 2
|
16小时前
|
人工智能 运维 监控
构建高性能微服务架构:现代后端开发的挑战与策略构建高效自动化运维系统的关键策略
【2月更文挑战第30天】 随着企业应用的复杂性增加,传统的单体应用架构已经难以满足快速迭代和高可用性的需求。微服务架构作为解决方案,以其服务的细粒度、独立性和弹性而受到青睐。本文将深入探讨如何构建一个高性能的微服务系统,包括关键的设计原则、常用的技术栈选择以及性能优化的最佳实践。我们将分析微服务在处理分布式事务、数据一致性以及服务发现等方面的挑战,并提出相应的解决策略。通过实例分析和案例研究,我们的目标是为后端开发人员提供一套实用的指南,帮助他们构建出既能快速响应市场变化,又能保持高效率和稳定性的微服务系统。 【2月更文挑战第30天】随着信息技术的飞速发展,企业对于信息系统的稳定性和效率要求
|
16小时前
|
前端开发 Java 关系型数据库
Java医院绩效考核系统源码B/S架构+springboot三级公立医院绩效考核系统源码 医院综合绩效核算系统源码
作为医院用综合绩效核算系统,系统需要和his系统进行对接,按照设定周期,从his系统获取医院科室和医生、护士、其他人员工作量,对没有录入信息化系统的工作量,绩效考核系统设有手工录入功能(可以批量导入),对获取的数据系统按照设定的公式进行汇算,且设置审核机制,可以退回修正,系统功能强大,完全模拟医院实际绩效核算过程,且每步核算都可以进行调整和参数设置,能适应医院多种绩效核算方式。
30 2
|
16小时前
|
API 开发者 UED
构建高效微服务架构:后端开发的新趋势移动应用与系统:开发与优化的艺术
【4月更文挑战第30天】 随着现代软件系统对可伸缩性、灵活性和敏捷性的日益需求,传统的单体应用架构正逐渐向微服务架构转变。本文将探讨微服务架构的核心概念,分析其优势,并着重讨论如何利用最新的后端技术栈实现一个高效的微服务系统。我们将涵盖设计模式、服务划分、数据一致性、服务发现与注册、API网关以及容器化等关键技术点,为后端开发者提供一份实操指南。 【4月更文挑战第30天】 在数字化时代的浪潮中,移动应用和操作系统的紧密交织已成为日常生活和商业活动的基石。本文将深入探讨移动应用开发的关键技术、跨平台开发工具的选择以及移动操作系统的架构和性能优化策略。通过分析当前移动应用开发的挑战与机遇,我们将
|
16小时前
|
消息中间件 监控 中间件
探索微服务架构下的系统弹性设计
【4月更文挑战第26天】 在当今快速迭代和持续部署的软件发展环境中,系统的弹性设计成为维护高可用性和稳定性的关键因素。本文将深入探讨在微服务架构下如何实现系统弹性,包括识别潜在的故障点、设计容错机制、以及通过实践案例分析提升系统整体的韧性。我们将讨论一系列策略,如服务降级、超时管理、重试策略、断路器模式等,旨在为开发者提供一套实用的系统弹性设计方案。
|
16小时前
|
缓存 监控 算法
Python性能优化面试:代码级、架构级与系统级优化
【4月更文挑战第19天】本文探讨了Python性能优化面试的重点,包括代码级、架构级和系统级优化。代码级优化涉及时间复杂度、空间复杂度分析,使用内置数据结构和性能分析工具。易错点包括过度优化和滥用全局变量。架构级优化关注异步编程、缓存策略和分布式系统,强调合理利用异步和缓存。系统级优化则涵盖操作系统原理、Python虚拟机优化和服务器调优,需注意监控系统资源和使用编译器加速。面试者应全面理解这些层面,以提高程序性能和面试竞争力。
18 1
Python性能优化面试:代码级、架构级与系统级优化

热门文章

最新文章

相关产品

  • 函数计算