【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

构建高可伸缩性的 WEB 交互式系统(中)

  • 2014-09-17
  • 本文字数:3686 字

    阅读完需:约 12 分钟

《构建高可伸缩性的WEB 交互式系统》的第一篇,我们介绍了Web 交互式系统中平台的可伸缩性。本文将描述模块的可伸缩性。

模块的可伸缩性

WEB 交互式系统对模块的可伸缩性同样表现为:

  • 可扩展性:对于系统新增的功能需求能够快速响应支持
  • 可缩减性:对于系统退化的模块能够以最小的修改方式剔除

这里我们提供一套模块调度的系统架构模式,用于支持单页富应用系统的设计架构、模块拆分、模块重组、调度管理等功能。

模块

我们定义的模块是指:从系统中拆分出来的、可与用户进行交互完成一部分完整功能的独立单元。

模块组成

因为这里描述的模块可独立与用户完成交互功能,因此模块会包含以下元素:

  • 样式:定义模块的效果
  • 结构:定义模块的结构
  • 逻辑:实现模块的功能

以上元素对于一个 WEB 系统开发者来说并不陌生,而我们只需要寻求一种形式将这些内容封装起来即可。

模块封装

从模块的组成我们可以看到系统中分离出来的模块可能会长成这个样子,比如 module.html 就是我们分离出来的一个模块。

当然这里也可以用脚本文件封装,样式和结构采用注入形式。下面以 html 文件封装举例:

复制代码
<!-- 模块样式 -->
<style>
.m-mdl-1 .a{color:#aaa;}
.m-mdl-1 .b{color:#bbb;}
/* 此处省略若干内容 */
</style>
<!-- 模块结构 -->
<div class="m-mdl-1">
<p class="a">aaaaaaaaaaaaaaaaaaa</p>
<p class="b">bbbbbbbbbbbbbbbbbbb</p>
<!-- 此处省略若干内容 -->
</div>
<!-- 模块逻辑 -->
<script>
(function(){
var a = 'aaa';
var b = 'bbb';
// 此处省略若干内容
})();
</script>

这个模块在用户需要时加载到客户端,并展现出来跟用户进行交互,完成功能。但是我们会发现,如果系统预加载了此模块或者模块在 parse 时,这些内容会被直接执行,而这个结果并不是我们需要的,因此我们需要将模块的各元素文本化处理。文本化处理有多种方式,如作为文本 script、textarea 等标签内容,因此 module.html 里的模块我们可以封装成如下样子,以 textarea 举例:

复制代码
<!-- 模块样式 -->
<textarea name="css">
.m-mdl-1 .a{color:#aaa;}
.m-mdl-1 .b{color:#bbb;}
/* 此处省略若干内容 */
</textarea>
<!-- 模块结构 -->
<textarea name="html">
<div class="m-mdl-1">
<p class="a">aaaaaaaaaaaaaaaaaaa</p>
<p class="b">bbbbbbbbbbbbbbbbbbb</p>
<!-- 此处省略若干内容 -->
</div>
</textarea>
<!-- 模块逻辑 -->
<textarea name="js">
(function(){
var a = 'aaa';
var b = 'bbb';
// 此处省略若干内容
})();
</textarea>

管理依赖

从系统中拆分出来的模块之间是存在有一定关系的,如一个模块的呈现必须依赖另外一个模块的呈现。下面我们会以一个简单的例子来讲解模块之间的依赖管理,如下图是我们的一个单页应用系统:

从上图不难看出整个系统包含以下几部分内容:

  • 日志管理

    • 日志:日志列表,可切换收件箱 / 草稿箱 / 回收站 / 标签
    • 标签:标签列表,可转至日志按标签查看列表
  • 博客设置

    • 账号管理

      • 基本资料:用户基本资料设置表单
      • 个人经历:个人经历填写表单
    • 权限设置:权限设置表单

而这些模块之间的层级关系则如下所示:

针对交互式系统的这种层级架构典型的模式可以参阅:

然而在 WEB 交互式系统的实践过程中我们发现这种模式会存在一些缺陷:

  • 由于每个父模块自己维护了所有的子模块,因此父子模块之间耦合性过强,父模块必须耦合所有子模块
  • 由于模块之间不能直接越级调用,因此子模块需要其他模块协助时必须层层向上传递事件,如果层级过深则会影响到系统效率
  • 模块的增删等变化导致的变更涉及的影响较大,删除中间节点上的模块可能导致相邻的若干模块的变更
  • 多人协作开发系统时存在依赖关系的模块会导致开发人员之间的紧密耦合

在这里,我们给出了一种基于模块标识的依赖管理配置方案,可以彻底的将模块进行解耦,每个模块可以独立完整的完成自己的交互功能,而系统的整合则可以通过配置的方式灵活的重组各模块,模块的增删操作只需修改配置即可完成,而无需影响到具体业务逻辑。

下文我们会通过以上例子来讲解此方案的原理和实际操作方式。

模块标识

因为本方案会基于模块标识做配置,因此在介绍方案之前我们先介绍一下模块标识,这里我们给模块标识取名为UMI(Uniform Module Identifier)统一模块标识,下文简称 UMI,遵循以下规则约定:

  • 格式同 URI 的 Path 部分,如 /m/m0/
  • 必须以“/”符开始
  • 私有模块必须以“/?”开始
  • 承载模块的依赖关系,如 /m/m0/ 和 /m/m1/ 表明这两个标识对应模块的父模块标识均为 /m

每个 UMI 均可唯一标识一个模块及模块在系统中的依赖关系,在模块章节我们介绍了一个模块可以用一个 html 进行封装,因此我们可以得到以下结果:

每个 UMI 均可映射到一个模块实现文件,这样我们就可以将模块从具体实现中解耦出来,对模块的增删修改操作只需调整 UMI 和模块文件的映射关系即可,而无需涉及具体业务逻辑的修改。

模块依赖

在解决了模块与实现分离的问题后,我们接下来需要将层级式的模块扁平化来解耦模块之间的依赖关系。回到前面的例子,模块之间的层级关系如下图所示:

如果我们将图中的依赖关系进行抽象分离后,可以发现所有的模块即可呈现扁平的状态:

而对于模块之前的依赖关系的管理,在所有系统中都是一致的,但是每个模块的具体功能实现是由系统来决定的,不同的系统是截然不同的,因此本方案提供的解决方案主要是用来维护模块之间的依赖关系的。

从上图我们可以比较清楚的看到模块之间的依赖关系呈现树状结构,因此我们会以树的结构来组织维护模块之间的依赖关系,我们称之为依赖关系树。而当我们将这棵树上的任意节点与根节点之间的路径用“/”分隔序列化后,发现刚好与我们提供的 UMI 是匹配的,因此组成系统的模块的 UMI 可以跟依赖关系树的节点一一对应起来,如下图所示:

在模块标识章节我们介绍了 UMI 与模块封装文件可以相互映射,因此依赖关系树上的节点可以直接与模块的实现文件做一一对应,如下图所示:

至此,我们将垂直层级依赖的模块通过依赖关系树分解成了无任何关系的扁平模块结构。

模块组合

模块只需要有个呈现容器即可渲染出来,因此模块如果需要能够做任意组合,只需将模块分成两种类型:提供容器的模块,和使用容器的模块即可。当然,一个模块可同时兼具提供容器和使用容器的功能,提供容器的模块和使用容器的模块可任意组合。

对于模块组合的配置代码范例:

复制代码
'/m/blog/list/':{
module:'module/layout/blog.list/index.html',
composite:{
box:'/?/blog/box/',
tag:'/?/blog/tag/',
list:'/?/blog/list/',
clazz:'/?/blog/class/'
}
}

调度策略

在将模块扁平化后,各模块就可以安排给不同的开发人员进行功能实现和测试了,各模块完成后根据依赖关系树进行系统整合,系统整合后各模块会遵循一定的调度策略进行调度。

模块状态

根据模块调度的阶段划分,模块的状态可以分为以下四种:

  • 模块构建:构建模块结构
  • 模块显示:将模块渲染到指定的容器中
  • 模块刷新:根据外界输入的参数信息获取数据并展示(这里主要做数据处理)
  • 模块隐藏:模块放至内存中,回收由显示和刷新阶段产生的额外数据及结构

调度策略主要控制模块在这几个阶段之间的转换规则。

模块显示

当用户请求显示一个模块时各模块会遵循以下步骤进行调度,假设请求显示 /m/blog/list/ 模块:

  1. 检查目标节点到根节点路径上注册的模块,如果注册的是模块的实现文件地址,则请求载入模块实现文件
  2. 如果节点所在的模块的所有祖先节点已显示,则当前模块可被显示出来,否则等待祖先模块的显示调度
  3. 模块载入后根据第二步骤原则尝试调度目标模块的显示

模块切换

当用户从一个模块切换到另外一个模块时各模块遵循以下步骤调度,假设从 /m/blog/list/ 切换到 /m/setting/account/edu/ 模块:

  1. 查找源模块与目标模块的公共父节点


2. 从源节点到公共节点之间的模块调度隐藏操作


3. 从根节点到公共节点之间的模块调度刷新操作


4. 从公共节点到目标节点之间的模块调度显示操作

消息通道

大部分时候我们不建议使用模块之前的消息通信,实践中也存在一些特殊情况会需要模块之前的消息通信,这里提供两种方式的消息通讯:

  • 点对点的消息:一个模块发送消息时明确指定目标模块的 UMI
  • 观察订阅消息:一个模块可以对外申明发布了什么样的消息,有需要的模块可以订阅该模块 UMI 上的消息

上面介绍了模块可伸缩性的一些原理。在本系列的最后一篇文章中,我们将以网易的 NEJ 框架为例,对上述原则进行说明。敬请期待!

本作品采用知识共享署名 4.0 国际许可协议进行许可。

作者介绍

蔡剑飞,网易杭州研究院前端高级技术专家,2005 年加入网易,先后参与过网易邮箱、网易博客、网易相册、网易云音乐等产品的开发,从2010 年开始开发NEJ 框架,现在负责NEJ 框架的维护及培训。他的邮箱是 genify@163.com ,微博是 genify ,欢迎大家来信交流!


感谢张云龙对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014-09-17 17:056687

评论

发布
暂无评论
发现更多内容

博睿学院 | 本周四,博睿学院数据集成系列公开课正式开讲

博睿数据

数据中台 智能运维 博睿数据 博睿学院

GPT-4 可以开始测试了

HoneyMoose

pulsar 报错源码排查:"Not enough non-faulty bookies available"

李code

源码 pulsar 故障恢复 参数调优 白话源码

机器学习算法(八):基于BP神经网络的乳腺癌的分类预测

汀丶人工智能

数据挖掘 机器学习 BP神经网络

对前端性能优化的一些小看法

Yestodorrow

全网最新架构实战文档:高并发+分布式+微服务+SpringBoot+Nginx

三十而立

Java 分布式 java面试

30s 就可以掌握的 Nginx 片段

Java你猿哥

Java nginx ssm Java工程师 nginx 开源版

《云原生架构容器&微服务优秀案例集》惊喜来袭

阿里巴巴云原生

阿里云 容器 微服务 云原生

Three.js 进阶之旅:全景漫游-初阶移动相机版

dragonir

JavaScript 前端 three.js

Spring竟然可以创建“重复”名称的bean?—一次项目中存在多个bean名称重复问题的排查

京东科技开发者

spring bean @Bean 企业号 3 月 PK 榜

解决90%面试问题!GitHub顶级"Java面试手册"了解下八股文天花板

Java你猿哥

Java 面经 校招 Java工程师 春招

CNStack 虚拟化服务:实现虚拟机和容器资源的共池管理

阿里巴巴云原生

阿里云 云原生 虚拟化 CNStack

软件工程高效学 | 软件工程基础

TiAmo

软件工程

阿里架构组分布式架构技术使用心得:全在这一份文档里面了

三十而立

Java java面试

【共创共赢】AntDB数据库合作伙伴交流会(北京站)顺利举办

亚信AntDB数据库

数据库 AntDB AntDB数据库 企业号 3 月 PK 榜

Dubbo + ZooKeeper丨如何解决线上故障排查链路长的难题

阿里巴巴云原生

阿里云 开源 云原生 dubbo Zookeepe

如何使用责任链默认优雅地进行参数校验?

JAVA旭阳

Java spring

滴滴 一面总结

Java你猿哥

Java 滴滴 java面试 面经

聊聊前端性能指标那些事儿

京东科技开发者

性能优化 前端 性能 前端性能 企业号 3 月 PK 榜

cookie时效无限延长方案

京东科技开发者

自动化测试 Cookie 接口自动化 UI自动化 企业号 3 月 PK 榜

49天含泪苦学这些分布式技术文档,一不小心,吊打了字节跳动面试官

三十而立

Java 分布式 java面试

Redis高频40问

程序员大彬

Java Redis redis 底层原理

扒站软件:SiteSucker汉化激活

真大的脸盆

Mac Mac 软件 网站下载 下载网站工具

工信部数据库适配验证中心项目验收通过 柏睿数据参与共建

科技热闻

OpenHarmony社区运营报告(2023年2月)

OpenHarmony开发者

OpenHarmony

Z世代新母婴人群消费洞察2023

易观分析

母婴 新消费 Z世代 消费

微前端框架single-spa子应用加载解析

京东科技开发者

生命周期 微前端 VUE 3.0 源码 企业号 3 月 PK 榜 single-spa

ShareSDK常见问题

MobTech袤博科技

AI大模型加速升级,数据和隐私何以为安?

博文视点Broadview

openEuler开源新项目,嵌入式实时虚拟机ZVM介绍

openEuler

Linux 操作系统 虚拟机 嵌入式 openEuler

Spring依赖注入Bean类型的8种情况,你学废了吗?

Java永远的神

spring 源码 程序员 后端 java面试

构建高可伸缩性的WEB交互式系统(中)_语言 & 开发_蔡剑飞_InfoQ精选文章