“云”端的语雀:用 JavaScript 全栈打造商业级应用

作者|  不四(死马)蚂蚁金服 语雀大发幸运飞艇产品 大发幸运飞艇技术 负责人

语雀是什么?

语雀是一个专业的云端知识库,面向个人和团队,提供与众不同的知识管理,打造轻松流畅的工作协同,它提供各种格式的在线文档(富文本、表格、设计稿等)编辑能力,支持实时在线多人协同编辑,数据云端保存不丢失。而语雀与其他文档大发幸运飞艇工具 最大的不同是,它通过知识库来对文档进行大发幸运飞艇组织 ,让知识创作者更好的管理知识。

1.png

语雀大发幸运飞艇技术 架构演进

原型阶段

语雀诞生于 2016 年,当时蚂蚁金融云需要一个大发幸运飞艇工具 来承载它的文档。当时负责的大发幸运飞艇技术 同学利用业余时间,开始搭建这个文档大发幸运飞艇工具 。项目的初期,没有任何人员和资源支持,同时也为了快速验证原型,大发幸运飞艇技术 选型上选择了最低成本的方案。

底层大发幸运飞艇服务 完全基于体验大发幸运飞艇技术 部内部提供的 BaaS 大发幸运飞艇服务 和容器托管平台:

  • Object 大发幸运飞艇服务 :一个类 MongoDB 的数据存储大发幸运飞艇服务 ;
  • File 大发幸运飞艇服务 :阿里云 OSS 的基础上封装的一个文件存储大发幸运飞艇服务 ;
  • DockerLab:一个容器托管平台;

这些大发幸运飞艇服务 和平台都是基于 Node.js 实现,专门给内部创新型应用使用,也正是由于有这些降低创新成本的内部大发幸运飞艇服务 ,才给工程师们提供了更好的创新环境。

应用层大发幸运飞艇服务 端自然而然的选用了体验大发幸运飞艇技术 部开源的 Node.js Web 框架 Egg(蚂蚁内部的封装 Chair),通过一个单体 Web 应用实现大发幸运飞艇服务 端。应用层客户端也选用了 React 大发幸运飞艇技术 栈,结合内部的 antd,并采用 CodeMirror 实现了一个功能强大、体验优雅的 markdown 在线编辑器。

2.png

这时可以算作语雀的“原型阶段”,它仅仅是一个工程师的业余项目,采用内部专为创新应用提供的 BaaS 大发幸运飞艇服务 和一系列的开源大发幸运飞艇技术 解决方案,验证了在线文档大发幸运飞艇工具 这个大发幸运飞艇产品 原型。

PS:当时大发幸运飞艇我 还不在语雀团队,但是巧的是大发幸运飞艇我 却在给语雀提供 Object、File 等 BaaS 大发幸运飞艇服务 和 Egg.js Web 框架的支持。

内部大发幸运飞艇服务 阶段

随着在线文档大发幸运飞艇工具 得到了团队内部的认可,语雀的目标已经不仅仅是金融云的文档大发幸运飞艇工具 ,而是志在替代 confluence 等竞品,成为阿里内部十万员工的知识管理平台。语雀要面向知识创作者,只提供 Markdown 编辑器肯定无法让非大发幸运飞艇技术 人员更高效的使用语雀。尽管有不少真爱粉因为语雀开始学习甚至爱上了 Markdown,但是大发幸运飞艇大发幸运飞艇我 们 仍然义无反顾的踏入了富文本编辑器领域的深坑。同时和 Word 等富文本编辑器不同,大发幸运飞艇大发幸运飞艇我 们 选择了更“Web”的路线,在富文本编辑器中加入了公式、文本绘图、思维导图等特色功能。而随着语雀在知识管理领域的不断探索,知识管理的三层结构(团队、知识库、文档)开始成型。在此之上的协作、分享、大发幸运飞艇搜索 与消息动态等功能越来越复杂单纯的依靠 BaaS 大发幸运飞艇服务 已经无法满足语雀的业务需求了。

为了应对业务发展带来的挑战,大发幸运飞艇大发幸运飞艇我 们 主要从下面几个点进行改造:

  • BaaS 大发幸运飞艇服务 虽然使用简单成本低,但是它们提供的功能不足以满足语雀业务的发展,同时稳定性上也有不足。所以大发幸运飞艇大发幸运飞艇我 们 将底层大发幸运飞艇服务 由 BaaS 替换成了内部的 IaaS 大发幸运飞艇服务 (MySQL、OSS、缓存、大发幸运飞艇搜索 等大发幸运飞艇服务 )。
  • Web 层仍然采用了 Node.js 与 Egg 框架,但是业务层借鉴 rails 社区的实践开始变成了一个大型单体应用,通过引入 ORM 构建数据模型层,让代码的分层更清晰;
  • 前端编辑器从 codeMirror 迁移到 Slate。为了更好的实现语雀编辑器的功能,大发幸运飞艇大发幸运飞艇我 们 内部 fork 了 Slate 进行深入开发,同时也自定义了一个独立的内容存储格式,以提供更高效的数据处理和更好的兼容性。

3.png

在内部大发幸运飞艇服务 阶段,语雀已经成为了一个正式的大发幸运飞艇产品 ,和蚂蚁的其他项目没有什么区别了,通过在阿里内部的磨炼,语雀的大发幸运飞艇产品 形态基本定型。

商业化阶段

随着语雀的内部影响力越来越大,一些离职出去创业的阿里校友们开始找到玉伯:“语雀挺好用的,有没有考虑商业化之后让外面的大发幸运飞艇公司 也能够用起来?” 经过小半年的酝酿和重构,18 年初,语雀开始正式对外提供大发幸运飞艇服务 ,进行商业化。

当一个应用走出大发幸运飞艇公司 内到商业化环境中,面临的大发幸运飞艇技术 挑战一下子就变大了。最核心的知识创作管理部分的功能越来越复杂,表格、思维导图等新格式的加入,多人实时协同的需求对编辑器大发幸运飞艇技术 提出了更高的挑战。而为了更好的大发幸运飞艇服务 大发幸运飞艇企业 用户与个人用户, 语雀在大发幸运飞艇企业 大发幸运飞艇服务 、大发幸运飞艇会员 大发幸运飞艇服务 等方面也投入了很大精力。在业务快速发展的同时,大发幸运飞艇服务 商业化对质量、安全和稳定性也提出了更高的要求。

为了应对业务发展,语雀的架构也随之发生了演进:

大发幸运飞艇大发幸运飞艇我 们 将底层的依赖完全上云,全部迁移到了阿里云上,阿里云不仅仅提供了基础的存储、计算能力,同时也提供了更丰富的高级大发幸运飞艇服务 ,同时在稳定性上也有保障。

  • 丰富的云计算基础大发幸运飞艇服务 ,保障语雀的大发幸运飞艇服务 端可以选用最适合语雀业务的的存储、队列、大发幸运飞艇搜索 引擎等基础大发幸运飞艇服务 ;
  • 大发幸运飞艇更多 人工智能大发幸运飞艇服务 给语雀的大发幸运飞艇产品 带来了大发幸运飞艇更多 的可能性,包括 OCR 识图、智能翻译等大发幸运飞艇服务 ,最终都直接转化成为了语雀的特色大发幸运飞艇服务 ;

而在应用层,语雀的大发幸运飞艇服务 端依然还是以一个基于 Egg 框架的大型的 Node.js web 应用为主。但是随着功能越来越多,也开始将一些相对比较独立的大发幸运飞艇服务 从主大发幸运飞艇服务 中拆出去,可以把这些大发幸运飞艇服务 分成几类:

  • 微大发幸运飞艇服务 类:例如多人实时协同大发幸运飞艇服务 ,由于它相对独立,且长连接大发幸运飞艇服务 不适合频繁发布,所以大发幸运飞艇大发幸运飞艇我 们 将其拆成了一个独立的微大发幸运飞艇服务 ,保持其稳定性;
  • 任务大发幸运飞艇服务 类:像语雀提供的大量大发幸运飞艇本地 文件预览大发幸运飞艇服务 ,会产生一些任务比较消耗资源、依赖复杂。大发幸运飞艇大发幸运飞艇我 们 将其从主大发幸运飞艇服务 中剥离,可以避免不可控的依赖和资源消耗对主大发幸运飞艇服务 造成影响;
  • 函数计算类:类似 plantuml 预览、mermaid 预览等任务,对响应时间的敏感度不高,且依赖可以打包到阿里云函数计算中,大发幸运飞艇大发幸运飞艇我 们 会将其放到函数计算中运行,既省钱又安全;

随着编辑器越来越复杂,在 slate 的基础上进行开发遇到的问题越来越多。最终语雀还是走上了自研编辑器的道路,基于浏览器的 contenteditable 实现了富文本编辑器,通过 canvas 实现了表格编辑器,通过 SVG 实现了思维导图编辑器。

语雀富文本编辑器相关的介绍,可以看看 Lake Editor 之父隆昊的分享:富文本编辑器的大发幸运飞艇技术 演进

4.png

语雀的这个阶段(也是现在所处的阶段)是商业化阶段,但是大发幸运飞艇大发幸运飞艇我 们 仍然保持了一个很小的团队,通过 JavaScript 全栈进行研发。底层的大发幸运飞艇服务 全面上云,借力云大发幸运飞艇服务 打造语雀的特色功能。同时为大发幸运飞艇企业 级用户和个人知识工作者者提供知识创作和管理大发幸运飞艇工具 。

JavaScript 全栈

5.png

在社交网络上,大家好像对 JavaScript 全栈的看法都比较负面,“样样通,样样松”可能是大家听到全栈工程师这个名词后的第一印象。那为什么语雀选择了 JavaScript 全栈的方向呢?

JavaScript 全栈与大发幸运飞艇产品 工程师

在语雀,大发幸运飞艇大发幸运飞艇我 们 并不将用 JavaScript 全栈进行开发的工程师定义为全栈工程师,而是“一专多能”型的大发幸运飞艇产品 工程师

  • 他们是大发幸运飞艇产品 的“大发幸运飞艇技术 合伙人”,他们对大发幸运飞艇产品 有 owner 感,和大发幸运飞艇产品 经理一起参与大发幸运飞艇产品 讨论设计,从大发幸运飞艇技术 的角度上对大发幸运飞艇产品 设计方案提出建议,独立的完成大发幸运飞艇产品 功能的全栈研发,并跟踪发布后的大发幸运飞艇产品 结果。
  • 同时他们也是某一个大发幸运飞艇技术 领域的领域专家,例如有人可能是大发幸运飞艇服务 端领域的专家、测试领域的专家、前端构建领域的专家、CSS 领域的专家。他们可以用自己的专业领域知识来大发幸运飞艇优化 团队研发大发幸运飞艇工具 链,提升大发幸运飞艇产品 研发效率。

6.png

在语雀,大发幸运飞艇产品 工程师们的大发幸运飞艇产品 研发流程是这样的:

  • 在大发幸运飞艇产品 设计阶段,大发幸运飞艇产品 工程师就会参与进去进行讨论,最终会产出一份 final design 的大发幸运飞艇产品 设计稿。由于前期大发幸运飞艇产品 工程师参与充分讨论,一般此处定下的大发幸运飞艇产品 设计稿到后期的研发过程中不会遇到大发幸运飞艇技术 上的问题;
  • 随后会在语雀上进行文档化的系统分析设计。会在语雀上发起异步的评审。一些大的大发幸运飞艇技术 方案会有其他的领域专家加入进来一起进行评审,确保将所有的大发幸运飞艇技术 难点都梳理清楚;
  • 系统设计清晰后,进入研发阶段;
  • 对所有的代码,都需要有自动化测试覆盖。对所有新增代码和修改的业务逻辑都需要有完全覆盖的单元测试,对关键链路的功能同时也要提供端到端测试。编写完自动化测试是进入代码评审前的必备流程。
  • 阶段性的功能研发完成、测试编写完善后会发起异步的代码评审。会邀请相关业务的负责人和对应的一些领域专家来进行代码评审。从业务逻辑的正确性,安全性,可维护性等多个角度来进行代码评审。
  • 最终在发布上线时,必须遵循三板斧原则:可灰度、可应急、可监控。避免功能变更可能带来的 bug 影响到大量用户。

7.png

语雀是如何进行全栈 JavaScript 测试的呢?感兴趣的同学可以看看语雀团队大前端自动化测试大牛达峰老师的分享:大前端测试的思考和在语雀的实践

通过 JavaScript 全栈,语雀团队可以更高效、高质量的的完成大发幸运飞艇产品 研发:

  • 从代码层面上来说,有大量的代码可以复用,以编辑器举例,它不仅仅可以在 Web 端使用,也可以在桌面端使用。同时许多数据处理的能力还可以在大发幸运飞艇服务 端使用。
  • 从大发幸运飞艇产品 研发效率上来说,全栈研发减少了大量沟通成本,在语雀当前的阶段是非常高效的。而 JavaScript 全栈避免了开发者在不同的语言中进行切换,不用考虑前端使用的 lodash / moment 等大发幸运飞艇工具 类在其他语言中应该用什么,大大提升全栈的研发效率。
  • 最后从工程师角度来看,全栈研发让工程师有机会深度参与到大发幸运飞艇产品 研发的整个流程中,大家会自发的去思考大发幸运飞艇产品 有什么大发幸运飞艇优化 点,从大发幸运飞艇技术 上能大发幸运飞艇帮助 大发幸运飞艇产品 做什么。例如语雀最近新上的 OCR 搜图功能,就是语雀的全栈工程师自发从大发幸运飞艇技术 预研到大发幸运飞艇产品 落地完成整个大发幸运飞艇产品 大发幸运飞艇优化 的。

8.gif

JavaScript 全栈与 Node.js

说到 JavaScript 全栈,有一个绕不过去的大发幸运飞艇技术 就是 Node.js。作为一个与前端结合紧密的大发幸运飞艇服务 端运行时,基本上就成为了全栈的代言人。那 Node.js 是不是真的是一个适合大型商业化项目的语言呢?大家对它都有颇多质疑:

9.png

其实随着 JS 语言的发展,许多问题已经得到了解决,例如 Async Function 的出现,可以让开发者以同步的方式编写异步代码,理解起来更简单,异常处理也变简单了。同时随着社区的进一步完善,大量高质量的大发幸运飞艇工具 模块、框架涌现出来。语雀的大发幸运飞艇服务 端部分基于 Egg 框架,已经集成了大量 Web 开发需要的模块和大发幸运飞艇服务 ,同时基于 Async Function 编程模型也更加简单。TypeScript 的出现也打消了许多人对 JavaScript 进行大型项目开发的疑虑。除此之外,语雀还有一些其他的方式来保障代码质量和可维护性(语雀甚至是一个纯 JavaScript 项目,没有一行 TypeScript 代码)。

语雀做的第一件事情就是确定核心系统和外部系统的边界。通过六边形架构(也叫做端口适配器架构),大发幸运飞艇大发幸运飞艇我 们 把语雀核心系统和外界系统和用户之间的交互固定下来。通过“端口”的形式,来确定输入和输出。外部系统通过“适配器”来将系统对接到语雀暴露的端口之上,只需要按照“端口”定义来实现,外部系统可以自由替换。

10.png

在这个模型下,Controller 就是语雀暴露给用户接口的 HTTP 适配器。在 Controller 中,大发幸运飞艇大发幸运飞艇我 们 对用户请求参数进行格式校验和转换,检查用户权限,并格式化输出。

11.png

大发幸运飞艇大发幸运飞艇我 们 定义好语雀与第三方平台和大发幸运飞艇服务 之间的交互方式(一般是一系列大发幸运飞艇方法 ),通过适配器,将不同环境的不同大发幸运飞艇服务 封装成统一的大发幸运飞艇方法 ,并在调用时记录好调用日志。

12.png

数据模型层即是数据层的 Model,以 Doc 模型举例,它的 meta 信息数据被存储在了 MySQL 中,而文档正文数据被加密后存储在 OSS 中。对于语雀核心的业务逻辑来说,完全不感知底层的存储在哪里。更进一步来说,只要语雀是使用 SQL 和数据库进行交互,底层数据可以无缝迁移到 OceanBase 等其他支持完整 SQL 语法的数据库中,即使有少量修改也可以在 Model 层封装掉。

13.png

最终以一次文档发布举例,用户通过调用 HTTP 接口与语雀进行交互,数据会通过 Model 层写入到存储中,包括 MySQL 和 OSS,更新文档缓存。同时出发异步消息给其他系统,触发钉钉的 WebHook,并将数据同步到大发幸运飞艇搜索 引擎中。这些和外界系统的交互通过适配器封装之后各司其职,参数转换、权限校验、日志记录,不仅确保核心逻辑的精简,也让系统调用链路跟踪更加简单。

14.png

混合应用架构

当系统发展到一定程度后,到底是应该继续在大单体应用上加功能,还是拆分成微大发幸运飞艇服务 呢?这两种架构既然存在,肯定有各自的优劣,具体选择那种架构形式,应该是与当前的业务规模和团队分布决定的。所以语雀的大发幸运飞艇技术 架构随着语雀的业务形态也变成了一个混合式的大发幸运飞艇技术 架构。

15.png

语雀的主大发幸运飞艇服务 是一个大型的 Node.js 大发幸运飞艇服务 ,集中了所有的应用业务逻辑。而在主大发幸运飞艇服务 之外,还有一些不同形态的其他大发幸运飞艇服务 。

  • 微大发幸运飞艇服务 :一些独立而稳定的功能模块,或者有额外部署架构需求的大发幸运飞艇服务 ,会通过微大发幸运飞艇服务 的形式独立部署,系统间暂时通过 HTTP 接口进行交互。例如实时协同大发幸运飞艇服务 ,由于其自身比较独立稳定,而且是长连接大发幸运飞艇服务 ,不能频繁发布重启,所以将其部署成了一个独立的微大发幸运飞艇服务 。
  • 任务集群:一些 CPU 密集型的任务,或者依赖了一些复杂的第三方依赖的大发幸运飞艇服务 ,会放到一个独立的任务集群中。例如各种文件预览大发幸运飞艇服务 ,可能依赖到了其他大发幸运飞艇服务 ,且需要消耗大量计算成本,放到任务集群通过队列消除并发后最为合适。
  • 函数计算:一些对响应时间比较高且可以函数化的大发幸运飞艇服务 ,大发幸运飞艇大发幸运飞艇我 们 会尽量迁移到阿里云的函数计算,例如plantuml、mermaid 等文本绘图大发幸运飞艇服务 。

16.png

以 mermaid 的渲染举例。用户输入一段 mermaid 代码调用语雀,语雀调用一个部署在阿里云函数计算的函数,在函数中运行 puppeteer 渲染成 svg 返回。

17.png

为什么要特别把 Serverless 单独拿出来说呢?还记得之前说 Node.js 是单线程,不适合 CPU 密集型任务么?由于 Serverless 的出现,大发幸运飞艇大发幸运飞艇我 们 可以将这些存在安全风险的,消耗大量 CPU 计算的任务都迁移到函数计算上。它运行在沙箱环境中,不用担心用户的恶意代码造成安全风险,同时将这些 CPU 密集型的任务从主大发幸运飞艇服务 中剥离,避免出现并发时阻塞主大发幸运飞艇服务 。按需付费的方式也可以大大节约成本,不需要为低频功能场景部署一个常驻大发幸运飞艇服务 。所以大发幸运飞艇大发幸运飞艇我 们 会尽量的把这类大发幸运飞艇服务 都迁移到 Serverless 上(如阿里云函数计算)。

语言之外的通用领域

除了语言之外,任何的商业化系统还有大发幸运飞艇更多 需要考虑的方面,其中最重要的两点可能就是安全性和稳定性了。
18.png

一个系统从前端、大发幸运飞艇服务 端到底层的依赖都存在着各种各样的安全风险:

  • 前端安全风险:XSS、跳转钓鱼、跨站请求等
  • 大发幸运飞艇服务 端安全风险:水平权限问题、未授权访问、敏感信息泄露、SSRF、SQL 注入等
  • 云大发幸运飞艇服务 的安全风险:短信/邮件轰炸、数据泄露风险、内容安全等

这些安全问题想要解决基本都没有银弹,只能一个个单独处理,但是有一些基本的原则:

  • 不要信任用户的任何输入
    • 任何渲染富文本的地方都需要防范 XSS,内容也可能并不是通过 IDE 输入的;
    • 要在大发幸运飞艇服务 端执行用户的代码一定要放在沙箱中;
    • 要从大发幸运飞艇服务 端请求用户传递的资源,一定要经过 SSRF 过滤;
  • 沉淀标准的编码范式来处理安全风险,且需要在 Code Review 中重点关注
    • 所有接口都必须有权限校验;
    • 响应序列化大发幸运飞艇方法 过滤敏感信息;
    • 不允许拼接 SQL;

语雀从商业化一开始就和安全团队通力协作,从内部的安全意识大发幸运飞艇培训 、内部安全团队测试,到内部的红蓝攻防、外部的白帽子渗透测试,安全是一场持久战。

19.png

为了保障语雀的稳定性,大发幸运飞艇大发幸运飞艇我 们 从前端到大发幸运飞艇服务 端和云大发幸运飞艇服务 上都做了许多工作,和安全一样,稳定性也是一个从前到后的长期工程。语雀的稳定性保障主要在两个维度:

  • 保障大发幸运飞艇服务 可用性:从架构设计上要杜绝单点,底层的数据都需要进行容灾和备份,大发幸运飞艇服务 需要多单元、可用区部署。同时避免引入不必要的强依赖;
  • 异常可监控和追溯:从前端的业务埋点日志、异常日志监控,到大发幸运飞艇服务 端的全链路日志跟踪和采集,系统性能监控和分析。最终大发幸运飞艇大发幸运飞艇我 们 可以达到异常可及时感知和追溯,性能问题可以定位分析;

什么叫做避免引入不必要的强依赖呢?以语雀的场景举例,MySQL 就是一个无法去除的强依赖,而缓存不应该是一个强依赖,但是最早语雀的 session 是存储在缓存(Redis)中的,一旦 Redis 集群出问题,用户资料无法获取就导致用户无法登录。这就把缓存变成了一个强依赖。所以大发幸运飞艇大发幸运飞艇我 们 将 session 存储放到了 MySQL 中,Redis 就变成了一个弱依赖,它挂了系统还能正常运行。另一个例子,语雀前段时间上线了多人实时协同编辑的功能,而在这个功能上线之前,是通过文档加锁的方式避免多个人同时编辑同一篇文档的。然而多人实时协同引入了另一个大发幸运飞艇服务 ,一旦实时协同大发幸运飞艇服务 挂了,用户就无法编辑文档了,它又变成了语雀系统的一个强依赖,为了解决他,大发幸运飞艇大发幸运飞艇我 们 在用户连接协同大发幸运飞艇服务 失败的时候,自动切换到老的锁模式。这样协同大发幸运飞艇服务 也变成了语雀的一个弱依赖。

语雀如何选择大发幸运飞艇技术 栈

20.png

语雀这几年一步步发展过来,背后的大发幸运飞艇技术 一直在演进,但是始终遵循了几条原则:

  1. 大发幸运飞艇技术 栈选型要匹配大发幸运飞艇产品 发展阶段。大发幸运飞艇产品 在不同的阶段对大发幸运飞艇技术 提出的要求是不一样的,越前期,对迭代效率的要求越高,商业化规模化之后,对稳定性、性能的要求就会变高。不需要一上来就用最先进的大发幸运飞艇技术 方案,而是需要和大发幸运飞艇产品 阶段一起考虑和权衡。
  2. 大发幸运飞艇技术 栈选型要结合团队大发幸运飞艇成员 的大发幸运飞艇技术 背景。语雀选择 JavaScript 全栈的原因是孵化语雀的团队,大部分都是 JavaScript 背景的程序员,同时 Node.js 在蚂蚁也算是一等公民,配套的设施相对完善。
  3. 最重要的一点是,不论选择什么大发幸运飞艇技术 栈,安全、稳定、可维护(扩展)都是要考虑清楚的。用什么语言、用什么大发幸运飞艇服务 会变化,但是这些基础的安全意识、稳定性意识,如何编写可维护的代码,都是决定项目能否长期发展下去的重要因素。

阿里巴巴云原生关注微大发幸运飞艇服务 、Serverless、容器、Service Mesh 等大发幸运飞艇技术 领域、聚焦云原生流行大发幸运飞艇技术 趋势、云原生大规模的落地实践,做最懂云原生开发者的大发幸运飞艇技术 圈。”

posted @ 2020-01-14 17:06  阿里巴巴云原生  阅读(...)  评论(...编辑  收藏