第 72 届早早聊大会将于 2023 年 10 月 29 日(本周日)举办 - 前端跨端方案|跨端同构,方法框架,5 位讲师下午直播,关键词:跨端框架/跨端组件库/小程序/ Harmony /Electron 。 跟早早聊一起,码上多平台,上车链接:www.zaozao.run/conf/c72
正文如下
本文是 2023 年 4 月 8 日,第六十二届 - 前端早早聊【Node.js】专场,来自 58 同城的 Node 架构师 ------ 王澍的分享。感谢 AI 的发展,借助 AI 的能力,最近我们终于可以非常高效地将各位讲师的精彩分享文本化后,分享给大家。(完整版含演示请看录播视频和 PPT):www.zaozao.run/video/c62
大家好,我叫王澍,大家可以叫我大树,我今天分享的主题是《如何在创业公司建立 Node.js 生态》。
今天的主题是 Node。我想通过回顾我们历史上不同的时代来帮助大家理解,我是如何从零开始构建一个 Node 生态系统的。我们可以将这个过程划分为几个时代,包括石器时代、铁器时代、蒸汽时代、电器时代以及信息时代。
当大家看到我的 ppt 时,可能会注意到少了一个信息时代。这是因为我认为在信息时代方面,像阿里巴巴和腾讯这样的公司已经建立了更大、更成熟的生态系统。相比之下,58 同城的生态系统可能没有那么成熟。所以,我认为信息时代更适用于像阿里巴巴和腾讯这样的公司,它们拥有更庞大、更成熟的生态环境。
关于构建生态系统,我们需要考虑一系列步骤。首先,生态系统的建立与生存息息相关。
- 首要任务是确保生存。一旦你确保了生存,你可以着手提高效率。
- 提高效率之后,下一个目标就是提高质量。
- 当你成功提高了效率和质量,接下来需要考虑扩展生态系统的广度。
石器时代
我们现在进入主题,那么首先介绍一下什么叫"双石时代",这个术语的由来是因为在早期,我经历了两次创业公司的初期阶段,因此我将这个时期称为"双石时代"。在这个时期,从零到一的过程都是为了生存。
首先,回顾一下初期的经历。我加入的两家公司,其中一家是物流公司,另一家是二手交易平台。在那个时候,物流公司只有 8 名全栈工程师,而我们的业务相对简单,主要包括司机端的 API 接口以及相应的后台管理平台。
为什么当时选择采用 Node.js 呢?因为团队中有一些成员精通 PHP,还有一些擅长前端开发,但前端开发者通常不太擅长编写 PHP。但是 PHP 会写 JS,这就导致领导决定采用 Node.js,以充分发挥每个人的能力。当时,Node.js 的版本还比较早。
那个时候,我们的团队采用一种轮流开发需求的方式,涵盖了物流公司和二手平台的不同场景。尽管后者相对来说可能薪资更好,因为它建立在 58 同城的生态系统之上,但是 58 同城的 Node.js 生态系统相对较新,相对不那么成熟。Node.js 的生态系统在当时也相对有限,接入的项目也相对较少。
当时我们进行招聘时,我是一个全栈工程师,一人独当一面。我们的业务涉及一个独立的运营活动,与其他业务没有太多的交集,这个运营活动是我们的试点。
在最初的阶段,我们的物流公司非常简化,只包括服务器、守护进程、版本控制系统和数据库,我将这称为"最小单元"。一旦建立了这个最小单元,就可以开始处理一些业务。可能有人会疑惑,为什么没有提及 Nginx 层呢?缺少 Nginx 层可能无法访问你的服务,我认为它更多地涉及运维方面的工作,与前端开发可能存在一些差异。在转向二手平台后,我开始构建生态系统,包括一些包管理和 SSO 登录等功能。
下面我给大家展示一个思维导图。
在物流公司时,我们的团队相对较小,但快速开发。我们采用了垂直、领域专有的方法,一个人负责一个需求,从前端到后端再到数据层,一个人承担所有责任。当时我们也考虑了微服务的概念,将底层服务(如 4G 服务和订单服务)分割成不同的服务,并通过微服务来解耦。通信方面非常简单,我们主要使用 HTTP,因为我们当时的主要目标是迅速开发,因此在内网中,HTTP 性能是可以接受的。
至于版本控制,也非常简单,我们使用标签(tag)进行版本控制。现在我们基本上都使用 Git,但当时我们通过打标签来实现快速上线。服务器的上线也是手动的,我们在三台线上机器上部署代码,然后重启。如果你了解 Node,你会知道,代码的重新拉取其实没有关系,因为 Node 在服务启动时已经将所有的 JS 文件加载到内存中,所以这不会影响。如果需要回滚,我们也采用了一种简单而有效的方法,即通过标签来进行回滚。
对于 Node.js 服务,当时我们使用的版本非常低。那个时候,除了 NPM,我们没有太多选择,因为私服也没有很好的可供借鉴的模板。所以,我们干脆直接使用 GitLab 上的 git+ssh 来安装 NPM 包。在当时,我们主要使用的框架是 Express 4.6,它是一种模块化的框架。我们需要什么功能,就去找相应的包来使用,基本上都是根据我们的经验和当时的开发人员经验来选择,然后通过搜索和积累来构建我们的框架。
由于当时的版本较低,我们没有像 Promise 或 Generator 这样的现代特性,因此我们使用了"seq"这个包来解决异步问题,尽管它的维护频率较低。后来,我们切换到了"async"这个包,它现在也在维护中。对于某些场景,比如批量发送请求,我们使用了一个类库,以便更好地控制每次发送的请求数量,这比纯粹使用 Promise.all 等方法更有效。如果感兴趣的同学可以尝试一下。
对于守护进程,我们非常简单地使用了"PM2",我认为它是一个非常出色的库,而且是免费的。对于多个项目,每个项目都有多个入口。在我们的情况下,有 4G 端和后台这两个入口,但我们尽量避免在一个项目中重复引入相同的包,然后使用 GitLab 和 NPM 运行。因此,我们的一个项目中有两个入口,一个是 C 端的入口,另一个是后台的入口。这种方式使我们底层的服务层、模型层等代码可以共享使用。
我们的数据库相对较简单,使用了 MongoDB,因为 Mongo 不需要编写 SQL,而且更符合我们的需求。不需要创建表,省去了维护的复杂性。我们还使用了一些缓存,包括 Redis 等。
所以左边的部分相对来说比较简单,为什么我称之为"最小单元"呢?因为拥有最小单元后,你满足了最小需求,这时候你就可以开始处理一些业务了。对于右边的二手平台,相比左边要更好一些,因为它有独立的运维。所以,当我们转向二手平台时,我们的 Node 服务从最初的"express"升级到了"QA"。在初期,我们主要靠经验和逐渐构建来搭建系统。
基本上,我将物流公司的经验带到了二手平台,所以左边和右边的对比其实差别不大,只是多了一个 MySQL 数据库,基础框架从"express"变成了"QA"。当我到了二手平台后,我意识到需要一个地方来存储我们的包,所以我找到了淘宝的 CNPM,虽然现在已经改名了。
然后,我们将其推送到我们的语音服务器上。但遇到一个问题,我想将它部署在多台机器上,因为我们可能会频繁地下载包,一台机器可能难以承受这么大的负载。所以,包存储不能仅存在于服务器上,它必须存储在语音服务器上,而 58 同城正好拥有自己的语音服务器,所以我们将其存储在 WS 云上。这样的好处是,我们不仅可以在多台机器上部署,而且如果某台机器宕机,我们的包依然存在。如果没有云存储,它的默认机制是将包存储在磁盘的某个目录中,这存在一定风险。
另外,当你刚刚加入一个企业时,你需要登录体系的接入。我们当时接入了企业微信的登录体系,以及一些 CAS 协议的登录体系。这里有一个小技巧,对于一些企业微信、钉钉等,它们都有一些鉴权机制,其中一些可能需要我们自己来实现,如获取 token 等,但实际上我认为这些可以交给运维层去处理。运维可以通过拦截在运维层进行处理。例如,当你访问一个域名,它没有登录体系,会自动跳转到相应的二维码扫描或登录页面。如果已经登录,运维就会直接放行,然后请求我们的服务。
这样,我们每个服务的接入就变得非常简单,不需要担心接入 SSO 登录和处理多个鉴权体系。我认为这是一个很好的技巧。至于 CAS 协议部分,可能我没有太多好的建议,因为它是一种协议,我们必须遵守,所以只能将其封装成一个中间件来接入。
总的来说,这是石器时代,左边和右边的对比显示,石器时代并没有太多花哨的东西,大部分都是手动操作,所以我将其称为石器时代。
铁器时代
接下来我们谈一下铁器时代。在铁器时代,为了提高效率,二手平台的业务发生了一些变化。这时,我们开始试用 SSR 项目,并正式地融入我们的业务。由于 Node 的接入,前端团队也开始对此产生浓厚兴趣,开始开展一些官网等工作。在人员配置方面,之前只有我一人,但现在整个架构基建依然由我负责,同时有多名前端同事协助完成 SSR 项目和后台项目。虽然这个时候团队规模相对较小,但已经足够满足我们的需求。至于铁器时代,我们开始制定规范并使用工具来提高效率。
下一张图中,你可以看到右下角的规范,稍后我会详细讨论这五大规范。规范是非常重要的,它可以让你事半功倍。然后,左下角显示了一个最小的服务单元。在其上有一些服务架构,最顶部有一些脚手架和设计上报。一旦你有了规范,大家都必须按照这些规范来开发,因此脚手架就成为了集成各种规范,包括一些基础基建的产物。或者说,因为你生成的脚手架项目肯定需要包含这些基础服务的组件。
由于我们引入了 SSR 项目来侵入业务,所以我们需要一种简单的上报统计机制。因为当时接入公司的监控体系并不是很容易,所以为了能够迅速接入 SSR,我们自行开发了一个非常简单的上报统计系统。
规范篇
首先,我要强调一个非常重要的规范,那就是统一 Node.js 版本。为什么这一点如此重要呢?我曾经遇到过一些问题,有一些同事带着不同版本的 Node.js 来找我寻求帮助。举个例子,我的电脑上可能安装的是 Node.js 14 版本,但有的同事可能带着版本 16 或 10 过来,结果他们遇到的问题在我的电脑上可能无法重现,或者出现了奇怪的问题。
为了避免这种情况,我认为个人感兴趣的话可以随意切换不同的 Node.js 版本来使用,没有问题。但对于公司来说,统一 Node.js 版本可以降低沟通成本。我们不应该因为版本问题而频繁进行沟通,我认为这是在浪费时间。同样的原则也适用于服务器。
服务器上的 Node.js 版本应该与我们的开发环境保持一致,这样也是为了避免出现以下情况,在线上无法运行的服务却可以在本地正常运行,或者相反。统一版本的目的是减少沟通,避免一些不必要的问题。当时,我是唯一一个负责解决问题的人,如果每个人都来找我,时间是不够的。
因此,统一 Node.js 版本会放在第一位
第二个是日志规范。关于日志,我们需要定义日志的级别、日志格式,以及日志存放的目录,这些将在后面详细讨论。
第三个接口规范也非常重要,因为前端对于后端的一些概念可能没有那么深入了解,有时候定义接口返回的格式可能会有一些奇怪之处。他们可能会随意定义一些类似于后端的代码,比如返回一个 code 值为 100,200,0 之类的,其他人可能不知道其含义。因此,接口规范也至关重要。
一个简单的接口规范可以包括以下内容:如果接口调用成功,返回的 code 应该为 0,如果失败,则为 -1。此外,失败时必须返回一个 message 字段,用于提供错误信息。而如果调用成功,data 字段则应该包含实际数据。即使只返回一个空对象也是可以的。
这些只是一个示例,具体的规范可以根据你公司的实际情况来定义。特别要强调的是 code 字段的含义,有时候可以使用 code,也可以使用 HTTP 状态码,两者都可以。
第四个是文档规范,包括注释规范、开发文档、接口文档、以及 README 文档。注释规范非常重要,每个方法都应该有注释,但注释不必过于复杂。注释应该具有明确的目的,例如,如果你编写了一个加法函数,你不应该只写一个简单的"这是一个加法函数"的注释,而应该明确指出你需要完成的任务,可以使用 TODO 标记。README 文档通常包括项目的介绍、如何部署、如何上线等信息,也应该受到规范的约束。
开发文档和接口文档都是必需的,即使你是前端开发人员,也需要遵守后端的规范。如果后端没有提供接口文档和开发文档,合作将会非常困难。
单元测试也是重要的规范之一,尤其在创业公司。创业公司通常缺乏 QA 测试团队,大部分测试由开发人员自己完成。因此,单元测试可以帮助你确保代码的质量。规范包括确保每个单元测试只测试一个方法,避免依赖其他单元测试的数据,以及设置合理的指标,例如正确率和覆盖率,根据实际情况进行调整。
最后一个规范涉及数据回滚,即在单元测试结束后,必须清除创建的数据。这是一项重要的规范,以确保单元测试不会影响数据库的状态。
接下来讲一下日志,在日志规范方面,包括以下信息:logid、远程 IP 地址、调用的方法、IP 版本、请求的状态、用户代理、以及接口的响应时间。这些信息对于记录和跟踪应用程序的运行情况以及在需要时进行故障排除非常重要。
首先解释一下什么是 logid。logid 是一个非常重要的标识,用于串联整个系统中不同服务之间的调用关系。例如,当服务 A 调用服务 B 时,logid 需要在请求中传递,以帮助追踪请求的源头和目标。此外,还需要引入一个称为 logstr 的概念,它用于定义调用关系的标识。这个 logstr 包含了关于请求的信息,例如 from、logid、CookieHeader、action、pathname,以及请求的方法类型。如果可能,还可以将 query 和 body 包含在 logstr 中,以便于后续的日志分析。
这里需要说一下,对于包含二进制数据的日志,需要进行单独处理,否则日志文件可能会变得非常庞大。你可以考虑删除这些二进制数据,只保留一个标识,表明这是一个二进制流数据。此外,关于日志的回滚策略,通常会保留最近的 7 天日志,但确切的保留时间可以根据你的应用需求和服务器存储能力来确定。在后续部分,我将继续讨论如何通过日志来进行故障排查。
在选择如何生成 logid 时,你可能会面临选择使用随机数还是唯一标识(UID)的困扰。我当时也曾思考这个问题。对于时间戳来说,如果在极短时间内产生多个请求,它们的时间戳可能相同,这可能导致问题。因此,我考虑使用时间戳并添加 5 个唯一随机数,以降低重复概率。然而,使用 UID 可以确保每个 ID 都是唯一的。但我觉得使用时间戳带有一定的意义,因为它在分析日志时可以帮助了解请求的时间段。因此,我推荐使用 logid 来避免使用 UID,但这只是一些建议。
在日志查询方面,我们采用了一种相对简单的方法。由于资源有限,我们没有构建复杂的日志系统,尤其对于 Node.js,相比 Java 等其他语言,它的生态系统相对较小。我们将日志存储在服务器上,并保留 7 天的日志数据,这个 7 天的时间段是有意义的,因为它可以满足我们的需求。
我们实现了一个简单版本的日志上报系统,称之为"agent",它运行在 PM2 上并监听特定的端口。我们可以通过向这个端口发送命令来执行日志查询操作,例如执行"find"命令。这种方式使我们无需构建复杂的日志存储和查询系统,只需一个简单的命令来执行查询并将结果返回。
"agent"不仅能够查询日志,还能够监听 PM2 的状态和服务状态。我们还可以通过调用 PM2 的 API 与"agent"进行交互。我们建议不要使用过于通用的命令,因为这可能存在安全风险。相反,我们建议使用一个枚举值,将其发送给"agent",由"agent"匹配执行相应的命令。这样可以提高安全性,如果没有匹配到命令,"agent"将不执行操作。
通信方面,我建议使用 Socket 进行双向通信,这样可以实现快速的数据传输。"agent"的存在使我们能够更好地监控服务状态、执行重启等操作。关于这方面的信息将在后续内容中介绍。
服务架构、工具篇
接下来,我们将讨论服务架构与工具部分。在经过前面的阶段之后,为了快速接入 Node.js 项目,我们不能仅仅依赖 QA 或 express,以积木的方式来构建项目。这个时候,我们需要使用企业级的框架,一个开箱即用且非常易用的框架。
在进行了多次调研后,我认为 Egg.js 是非常适合我们公司的框架,也符合创业公司的需求。它具有强大的生态系统,国内生态支持良好,是一种企业级框架,提供了全面的文档。最重要的是,它支持渐进式开发,这对于初期规模尚不明确的功能开发非常重要。我们可以通过插件的方式引入新功能,如果某个插件表现出色,我们可以将其单独提取并封装成包供其他项目使用。
在部署方面,你可能遇到过这样的情况,即将服务部署到服务器上,然后与其他服务的端口发生冲突。例如,你的服务使用端口 8000,而另一个服务也使用了同样的端口,这可能会带来很多麻烦。解决这个问题的诀窍是创建一个端口管理平台。
在部署服务之前,你可以通过这个管理平台申请一个可用的端口,它会自动分配一个端口给你。你无需手动在项目中指定端口,而是在部署时,运维人员可以通过更改配置文件中的端口号来进行设置。这可以让运维团队管理端口,避免冲突,从而让部署变得更加顺畅。这个管理平台可以以 json 文件或其他形式来实现,只要简化了端口管理流程即可。
随意在本地开发的时候,你可以随意使用端口 8000、8001 或其他数字,而不用担心冲突的问题。这种方法可以减轻对端口号的疑虑,因为有些人可能不理解端口的概念,可能会随机选择一个端口号,这会带来麻烦。由于前端开发人员的后端能力通常较弱,所以验证服务是否正常运行会相对简单。
验证活动服务的方法有两种,一种是通过查看服务的日志。例如,Console.log 出一个东西,如果服务成功启动,你可以判定为成功。另一种方法是使用中间件或者心跳机制。
另外脚手架是非常重要的,因为当你的 Node.js 服务达到一定规模后,为了让前端团队能够快速接入这些服务,你需要一个脚手架,以便他们能够快速下载、启动服务,并进行业务开发。这将大大提高工作效率。
关于简单上报通常包括 4 种数据:累积值、平均值、最小值和最大值,我们将在后面进行更详细的讨论。
下面说一下脚手架,关于脚手架,我们实际上在创建项目的同时使用 Sentry 的 API 来生成项目,并通过 API 返回的密钥将其添加到相应的项目中。这个方法的优点是省去了手动将项目添加到 Sentry 项目的麻烦,但缺点是可能会导致冗余项目。
解决这个问题的方法相对简单,可以定期清理那些在一段时间内没有报告错误的项目,使用 Sentry 的 API 来执行清理操作。
脚手架是为公司内部使用开发的框架,其中包括一些企业级框架和其他服务,如日志异常上报、数据库、SSO(单点登录)、监控等。这些服务和技术能力都需要集成到脚手架中,以便开发团队能够轻松地接入这些技术能力,而不需要了解或处理这些技术的复杂性。这样,开发人员可以更加专注于业务逻辑的开发,而不必关心底层技术的集成。
脚手架不仅用于项目的生成,还可作为工具和本地密码管理工具。此外还可以用于文档发布和文件上传等操作。在脚手架管理平台中,有权限控制,包括开发者和管理人员。
项目管理主要分为开发分支和线上分支。开发分支可以根据登录人的信息提取其账号信息,以拉取对应的开发者模板,并具有自动更新机制,通过调用 API 来检查版本并进行必要的更新。主营应用通常存放在 NPM 库中,而脚手架库项目建议存放在 GitLab 上,以便在生成项目时从 GitLab 上获取所需内容。
业务层主要是命令行工具,服务层包括服务平台,用于发布线上版本和维护工具的列表。数据层涉及 GitLab 和 MongoDB,用于存储模板和数据。
接下来是关于简单的指标上报工具,它非常简单。每个指标都有一个唯一的 ID,你可以使用不同的函数来查询特定的数据,比如使用"sum"来获取 QPS,或者使用"均值"来获取均值等。如果对此感兴趣,可以访问相关网址查看工具的效果。
就拿某个 Node 服务的 QPS 作为例子,我们不断使用 SUM 方法进行上报。这里不需要在本地进行数据总和或一分钟的总和等操作。每次上报后,我们的数据库都会记录当前的时间戳。我们可以稍后离线处理,计算出所需的统计信息。
这种方法是一种简单的上报方式,也适用于业务上报,如订单数量、按钮点击次数等等。甚至对于服务的错误量监控,如 500 错误,也可以通过这些简单的指标来衡量服务的质量。虽然这可能看起来有点基础,但实际上它可以解决许多问题。它非常简单易行,无需复杂的系统架构,是提高效率的一种有效方法。
这就是铁器时代,就是在不断提升效率。
蒸汽时代
接下来我们进入蒸汽时代,那么蒸汽时代我们的主要重点是提高质量。在这个时代,我们充分利用了之前建立的规范体系和监控平台,以进一步提高质量。
在业务方面也发生了一些进展,特别是我们的校园业务。校园业务成为了主要的业务,与其他业务同等对待。因此,我们专门为校园业务配备了两到三名专职的 Node 前端开发人员。这使校园业务成为了一个主要的关注点。最大的区别在于业务的角色和重要性。由于我们已经建立了一套脚手架体系,包括简单的监控工具、日志系统以及规范,现在,整个前端团队都可以轻松使用它们,无需过多关注服务接入等细节,因为这些细节都被封装在了脚手架中,使一切都变得非常简单。
在这个阶段,我们引入了更多层次的管理,就像构建环境和生态一样,一层一层地构建。这时,我们开始关注一些监控,特别是与 Node 相关的监控。这些监控工具相对来说不是非常高级,而是相当简单的。
监控篇
首先,我们进入了监控篇。最初,我们使用了一个名为 PM2.5 的工具,可能有些同学早些时候听说过,这是美团开发的用于监控 Node 进程的工具。随后,我基于 PM2.5 开发了一个名为 PM2 Manager 的工具,用于管理 PM2 进程。PM2 Manager 提供了一组 API,允许进行进程的复制、重启、停止等操作,以及监控状态、内存和进程维护。
例如,如果你希望在某个服务中动态增加进程,这也是可以实现的。此外,我们还设置了一些告警机制,用于监控频繁的重启和内存超限等情况。PM2 本身也提供了一些参数来满足我们的需求。
第二个是性能监控,我们使用了类似 Moment More 的性能监控工具,以及 More Monitor。在异常监控方面,早期我们使用了腾讯开发的 BYTE.js。后来,我们接入了 Sentry,并与他们打通,发现了一种周报系统,通过与 GitLab 打通,我们可以获取活跃项目的错误量等信息,用于生成周报。除此之外,我们建立了一个庞大的监控体系,这边不深入讨论。
接下来,我解释一下 PM2 Manager,因为我找不到原始图表了。下面这张图实际上是 PM2 自己的管理平台,与我当时开发的 PM2 Manager 类似。因为现在 PM2 的 API 已经开放,所以我们将其分为服务器端和客户端。如我之前提到的,我们运行一个代理在 PM2 上,它可以监听请求,查找日志,并且还可以调用 PM2 的 API 来执行命令、获取指标等,以供上报和管理。
服务器端主要用于日志查看、警报、命令下达、各种趋势图以及一些大屏监控等功能。我认为这些任务都可以相对简单地实现,尤其是在指标方面,一旦你从 PM2 获取了指标,扩展功能变得相当容易。
这个就是早期的异常监控,就是 BYTE js,其实它的页面其实还是相对来说比较简约的。那么后期我们经过调研,然后去改到 Sentry。感兴趣的可以看一下我们之前产出一个 Sentry 的文章。
性能监控的话就是 Easy Monitor,这个 Easy Monitor 不同于我之前提到的"简单监控",尽管名字相似,但是它实际上是一款非常强大的性能监控工具。当然你也可以使用 ALiNode 来进行分析,但 ALiNode 存在一些限制,最显著的是,对于一些企业级公司,他们可能不希望将敏感的数据上传到外部公司的服务器上。Easy Monitor 是支持私有化部署。
Easy Monitor 的工作原理非常简单,它在你的 Node 服务端运行一个进程,用于采集日志数据。然后,通过 WebSocket,这些日志数据被推送到我们的服务端,服务端进行告警、数据存储等处理。当然,还有其他监控工具,如 Prometheus,它是一个更庞大、更成熟的监控系统。你可以通过 NPM 包来轻松集成这些工具,这个我是非常推荐的。
上线平台
那么上线平台是相对简洁的,因为我们公司拥有自己的上线平台,它基于 Jenkins,这是一个非常强大的自动化工具。我只是简要地提到了我们的商业上线平台包括以下内容:
- 部署: 这部分包括打包和编译。
- 发布: 我们有测试环境和生产环境,并在线上发布时支持蓝绿升级和滚动升级。蓝绿升级是指逐渐将流量从旧服务切换到新服务,例如,初始时可能是 20%流量进入新服务,80%进入旧服务。如果一切正常,然后全量流量逐渐切换到新服务。滚动升级相对简单,它是一个时间线上的操作,首先下线旧服务,再上线新服务。
- 回滚:我们还支持回滚,这意味着可以将代码回滚到之前的版本。
- 部署日志: 我们记录部署的时间和日志,以便在需要时进行审计。
- 权限管理: 这是确保只有授权人员可以访问上线平台的重要一部分。
- 测试环境平台: 这些环境平台可以根据我们的 Agent 上报的信息进行配置。例如,我们可以上报服务之间的调用关系和流量限制等信息,然后在测试环境平台上进行配置。
实际上 Agent 它可以用于执行多种任务和操作。
服务器部署优化
随着上线平台的建立,我们不仅可以部署到物理机,还可以使用 Docker。我们的 Docker 部署建立在之前的机制之上,其中包括动态端口管理。这意味着我们不再需要担心端口冲突,因为我们可以直接从端口管理平台获取可用的端口。
此外,我们还实施了心跳机制,通常使用 ping-pong 来实现。这个机制不断监听服务的活跃性。如果服务出现问题,例如它在规定时间内没有响应 ping 请求,系统将认为服务不再活跃。在这种情况下,系统会终止该服务并重新启动一个新的实例。
关于 Agent,对于 Docker 和前面提到的"Easy Monitor",你可以与公司的运维团队商讨,看是否可以将 Agent 优先集成到 Docker 容器中,以避免额外的安装步骤。对于物理机部署,只需联系运维团队,并在服务上提前运行 Agent,即可实现相似的功能。
CR 助手
那么接下来就是 CR 助手,为什么会有一个 CR 助手呢在代码提交时,每个人的代码质量各不相同,因此需要一种方法来确保代码质量。我们利用 GitLab 上的草稿机制来实现这一目标。例如,当某人提交了 MR 时,系统会触发一个事件评论,通知 MR 的创建者。如果有人在 MR 上进行了评论,MR 的创建者也会收到评论的通知。此外,我们还有一些统计模块来跟踪 CR 的发起和接收,以便更好地管理这个流程。
这里有一张示意图来解释 CR 的流程。首先,将分支合并到 Developer 分支上,然后将其标记为草稿。接下来,指定一个负责 CR 的人,通知他进行审查。如果有评论,他也会收到通知。CR 的负责人可以将草稿标记为 Ready,这意味着 CR 已经完成,然后可以转为待上线状态。组长的任务很简单,只需检查是否有 MR 从草稿状态转为就绪状态,如果是,就表示 CR 已完成。
CR 的负责人通常需要被@提及,但其他人可以随机挑选组内的成员来轮流进行 CR。这个系统使代码审查变得更加有组织和高效,同时减少了手动管理的工作量。
最后,我们还创建了一个 Node Topic 虚拟组。这个组对整个生态系统有着重要作用,允许有兴趣的同学加入,以便共建和协作项目。这个虚拟组为我们的 Node 生态系统做出了重要贡献。
电器时代
下面进入电器时代,在电器时代,我们注重提高广度而不是深度。这意味着我们广泛应用了许多开源工具和平台,来提高效率。例如,使用开源的 icon 平台、UI 接口管理平台等。在这个时期,我们不再亲自开发这些工具,因为在创业时,老板更关心产品能够解决什么问题,而不是关心如何开发这些工具。因此,我们采用开源社区提供的工具,或者自己构建一个简单的版本,以满足最基本的需求。这种方法有助于快速推出产品的 MVP(最小可行产品),然后逐步迭代改进。
平台、工具
在这个广度提高的阶段,我们关注了以下方面:
- 接口管理平台: 我们使用 Yapi 来管理接口。
- 图标平台: 我们使用 i icon 平台来处理图标相关的工作。
- 代码质量: 我们关注了代码质量,并采用了相应的工具来帮助监测和维护代码质量。
- 前端性能监控: 我们引入了前端性能监控工具,以确保应用程序的性能。
- 离线包
- 配置平台
- Fe 的内部文档: 我们整合了一些技术文档、前端业务文档和 SDK 文档,以便团队成员方便查阅。
下面讲一下代码质量这一块,代码质量的话其实我们也用的是飞冰的一个插件,它可以帮我们去做,去分析我们代码质量。有兴趣的同学也可以去访问一下,下载这个插件,我觉得是非常棒的,它能定义到你的文件的哪一行等等。
配置平台
谈到配置平台,有一个常见场景是前端上线时,需要进行一些简单的修改,如文案或图片的更改。这种情况下,每次上线都需要经历相对较长的生命周期,可能需要 20 到 40 分钟,即使只是进行了小的文案修改。为了解决这个问题,我们可以考虑使用开源的阿波罗配置平台。
阿波罗配置平台允许开发者定义键值对,其中键用于获取对应的值。这个平台可以轻松部署,虽然它的部署环境是基于 Java 的,但你可以与 Java 开发人员协作,以便快速部署配置平台。一旦配置平台就绪,你可以通过发布来更新配置,然后你的前端应用程序可以获取并使用最新的配置信息。这个方法可以显著减少前端上线所需的时间,特别是在需要频繁进行小规模修改的情况下,非常实用。
下面说一下是怎么做的,在我们阿波罗目录中,每个 APP 都可以申请一个特定的配置项,这个配置项具有一个 APP ID,而在 APP ID 下,你可以定义多个命名空间,每个命名空间下又可以包含多个 key。
在使用这个配置平台时,你可以通过访问特定的路由,使用 APP ID、命名空间、以及键来获取相应的配置信息。有时候,我们需要基于服务器时间而不是本地时间进行一些活动或操作,因此,我们支持传递一个时间戳参数,以确保获得服务器的时间戳。此外,缓存是非常关键的,所以在实现时,我个人建议添加了缓存机制,以提高性能和响应速度。
当我们从阿波罗配置平台获取配置后,为了应对该平台宕机的潜在问题,同时也出于不想将这些信息存储在我们的库或 Redis 中的考虑,我们采用了一种本地缓存的策略。具体做法是将配置信息存储在本地磁盘上,我们实现了一个每 30 秒轮询一次的接口,以确保获取最新的配置信息并将其优先加载到本地磁盘上。
这个方法的好处在于,即使服务出现问题或需要重启,我们也能保证服务不受影响,因为我们可以在本地磁盘上加载最新的 JSON 文件。当然,如果公司有更好的资源,比如 Mongo 或 Redis,也可以选择将配置信息存储在这些资源中。
此外,为了保障系统的安全性,我们还加入了跨域白名单的措施,以防止遭受强制刷新攻击。下面是一个案例,展示了如何使用版本号和时间戳来获取图片信息,从而使前端无需重新上线即可更新图片内容。
性能平台
此外,关于性能平台,具体的实现方法可能不同于一般的做法。我们通过在云平台上上报数据,然后定期从大型数据库中提取和分析这些数据来实现性能监控。
感兴趣的同学,可以参考我们的架构图。
离线包
另外关于离线包,我们的经验在之前的专案中已有详细的文献记录,其中介绍了如何操作和实施。主要要点包括全量包和拆分包的管理,比如当你的包大小为两兆时,而下次迭代可能只需要更新几十 k 的内容。在这种情况下,我们可以采用查重包的方式,与客户端包进行合并,从而避免每次都要重新下载完整的包。具体的操作方法,可以参考相关文献以获取更多信息。
总结
到这里,我已经基本讲解了整体内容。在结束之前,我们回顾一下整个架构。我将它分为几个主要部分:基础建设、监控、工程化以及提效和提质。需要强调的是,这一切不是我一个人的工作,而是通过与团队中其他成员一起合作完成的。
此外,要谈到生态系统,有时候并不是一味地说:"我现在觉得我立刻需要一个图标平台"或者"我立刻需要复杂的监控系统"。首先,你需要有业务场景,需要解决业务问题,然后才能回馈生态系统。这一点非常重要,因为如果没有应用场景,无论你做多少东西都是白费力气。你当然需要一个地方来存储这一切,这就需要有一个平台来发布和存储你的工具、文档等。内部门户可以用于存储这些文档,以帮助团队成员更容易地获取所需信息,而不必收藏大量书签,这将更加方便。
最后,我为了方便大家整理了一个时间轴,它记录了各个时间点产出的主要成果。这就是我今天的分享。