”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > Encore.ts — 比 ElysiaJS 和 Hono 更快

Encore.ts — 比 ElysiaJS 和 Hono 更快

发布于2024-11-05
浏览:161

几个月前,我们发布了 Encore.ts — TypeScript 的开源后端框架。

由于已经有很多框架,我们想分享我们做出的一些不常见的设计决策以及它们如何带来卓越的性能数据。

性能基准

我们之前发布的基准测试显示 Encore.ts 比 Express 快 9 倍,比 Fastify 快 2 倍。

这次我们将 Encore.ts 与 ElysiaJS 和 Hono 这两个现代高性能 TypeScript 框架进行了基准测试。

我们对带有和不带有模式验证的每个框架进行了基准测试,使用 TypeBox 与 ElsyiaJS 和 Hono 进行验证,因为它是这些框架原生支持的验证库。 (Encore.ts 有自己的内置类型验证,可以端到端工作。)

对于每个基准测试,我们选取​​五次运行中的最佳结果。每次运行都是通过 150 个并发工作线程发出尽可能多的请求来执行,时间超过 10 秒。负载生成是使用 oha 执行的,oha 是一个基于 Rust 和 Tokio 的 HTTP 负载测试工具。

废话不多说,让我们看看数字!

每秒请求数:使用类型验证时,Encore.ts 比 ElysiaJS 和 Hono 快 3 倍

Encore.ts — faster than ElysiaJS & Hono

(查看 GitHub 上的基准代码。)

除了性能之外,Encore.ts 还实现了这一点,同时保持了与 Node.js 100% 的兼容性

工作原理:异常设计决策

这怎么可能?通过我们的测试,我们确定了性能的三个主要来源,所有这些都与 Encore.ts 的工作原理有关。

1. Encore.ts 是多线程的,由 Rust 运行时提供支持

Node.js 使用单线程事件循环运行 JavaScript 代码。尽管其具有单线程性质,但实际上它具有相当大的可扩展性,因为它使用非阻塞 I/O 操作,并且底层 V8 JavaScript 引擎(也为 Chrome 提供支持)经过了极其优化。

但是您知道什么比单线程事件循环更快吗?多线程。

Encore.ts由两部分组成:

  1. 使用 Encore.ts 编写后端时使用的 TypeScript SDK。

  2. 高性能运行时,具有用 Rust 编写的多线程异步事件循环(使用 Tokio 和 Hyper)。

Encore Runtime 处理所有 I/O,例如接受和处理传入的 HTTP 请求。它作为一个完全独立的事件循环运行,利用底层硬件支持的尽可能多的线程。

一旦请求被完全处理和解码,它就会被移交给 Node.js 事件循环,然后从 API 处理程序获取响应并将其写回客户端。

(在你说之前:是的,我们在你的事件循环中放置了一个事件循环,这样你就可以在事件循环时进行事件循环。)

Encore.ts — faster than ElysiaJS & Hono

2. Encore.ts 预先计算请求模式

Encore.ts,顾名思义,是专为 TypeScript 设计的。但你实际上无法运行 TypeScript:它首先必须通过剥离所有类型信息来编译为 JavaScript。这意味着运行时类型安全更难实现,这使得验证传入请求之类的事情变得困难,导致像 Zod 这样的解决方案在运行时定义 API 模式变得流行。

Encore.ts 的工作方式有所不同。借助 Encore,您可以使用本机 TypeScript 类型定义类型安全的 API:

import { api } from "encore.dev/api";

interface BlogPost {
    id:    number;
    title: string;
    body:  string;
    likes: number;
}

export const getBlogPost = api(
    { method: "GET", path: "/blog/:id", expose: true },
    async ({ id }: { id: number }) => Promise {
        // ...
    },
);

Encore.ts 然后解析源代码以了解每个 API 端点期望的请求和响应架构,包括 HTTP 标头、查询参数等。然后对模式进行处理、优化并存储为 Protobuf 文件。

当 Encore Runtime 启动时,它会读取此 Protobuf 文件并预先计算请求解码器和响应编码器,并使用每个 API 端点期望的确切类型定义,针对每个 API 端点进行优化。事实上,Encore.ts 甚至直接在 Rust 中处理请求验证,确保无效请求永远不必接触 JS 层,从而减轻许多拒绝服务攻击。

Encore 对请求模式的理解从性能角度来看也证明是有益的。像 Deno 和 Bun 这样的 JavaScript 运行时使用与 Encore 基于 Rust 的运行时类似的架构(事实上,Deno 也使用 Rust Tokio Hyper),但缺乏 Encore 对请求模式的理解。因此,他们需要将未处理的 HTTP 请求交给单线程 JavaScript 引擎执行。

另一方面,

Encore.ts 在 Rust 内部处理更多的请求处理,并且只移交解码后的请求对象。通过在多线程 Rust 中处理更多的请求生命周期,JavaScript 事件循环可以专注于执行应用程序业务逻辑,而不是解析 HTTP 请求,从而产生更大的性能提升。

3. Encore.ts 与基础设施集成

细心的读者可能已经注意到了一个趋势:性能的关键是尽可能多地从单线程 JavaScript 事件循环中卸载工作。

我们已经了解了 Encore.ts 如何将大部分请求/响应生命周期卸载给 Rust。那么还有什么可做的呢?

嗯,后端应用程序就像三明治。您有硬壳顶层,您可以在其中处理传入的请求。中间有美味的配料(当然,也就是你的业务逻辑)。在底部有硬壳数据访问层,您可以在其中查询数据库、调用其他 API 端点等等。

我们对业务逻辑无能为力——毕竟我们想用 TypeScript 编写! — 但是让所有数据访问操作占用我们的 JS 事件循环并没有多大意义。如果我们将它们移至 Rust,我们将进一步释放事件循环,以便能够专注于执行我们的应用程序代码。

这就是我们所做的。

使用 Encore.ts,您可以直接在源代码中声明基础设施资源。

例如,定义一个 Pub/Sub 主题:

import { Topic } from "encore.dev/pubsub";

interface UserSignupEvent {
    userID: string;
    email:  string;
}

export const UserSignups = new Topic("user-signups", {
    deliveryGuarantee: "at-least-once",
});

// To publish:
await UserSignups.publish({ userID: "123", email: "[email protected]" });

“那么它使用哪种 Pub/Sub 技术?”
— 他们所有人!

Encore Rust 运行时包括最常见的 Pub/Sub 技术的实现,包括 AWS SQS SNS、GCP Pub/Sub 和 NSQ,以及更多计划中的技术(Kafka、NATS、Azure 服务总线等)。您可以在应用程序启动时在运行时配置中按资源指定实现,或者让 Encore 的 Cloud DevOps 自动化为您处理。

除了 Pub/Sub 之外,Encore.ts 还包括 PostgreSQL 数据库、Secrets、Cron Jobs 等的基础设施集成。

所有这些基础设施集成都在 Encore.ts Rust 运行时中实现。

这意味着,一旦您调用 .publish(),有效负载就会被移交给 Rust,Rust 负责发布消息,并在必要时重试,等等。数据库查询、订阅 Pub/Sub 消息等也是如此。

最终结果是,使用 Encore.ts,几乎所有非业务逻辑都从 JS 事件循环中卸载。

Encore.ts — faster than ElysiaJS & Hono

本质上,通过 Encore.ts,您可以“免费”获得真正的多线程后端,同时仍然能够在 TypeScript 中编写所有业务逻辑。

结论

此性能是否重要取决于您的用例。如果您正在构建一个小型爱好项目,那么它主要是学术性的。但如果您将生产后端发送到云,它可能会产生相当大的影响。

较低的延迟对用户体验有直接影响。显而易见的是:更快的后端意味着更快的前端,这意味着更快乐的用户。

更高的吞吐量意味着您可以使用更少的服务器为相同数量的用户提供服务,这直接对应于更低的云费用。或者,相反,您可以使用相同数量的服务器为更多用户提供服务,确保您可以进一步扩展而不会遇到性能瓶颈。

虽然我们有偏见,但我们认为 Encore 为在 TypeScript 中构建高性能后端提供了一个非常优秀、最好的解决方案。它速度快、类型安全,并且与整个 Node.js 生态系统兼容。

而且它都是开源的,因此您可以查看代码并在 GitHub 上做出贡献。

或者尝试一下,让我们知道您的想法!

版本声明 本文转载于:https://dev.to/encore/encorets-3x-faster-than-elysiajs-hono-48hj?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 从零到 Web 开发人员:掌握 PHP 基础知识
    从零到 Web 开发人员:掌握 PHP 基础知识
    掌握PHP基础知识至关重要:安装PHP创建PHP文件运行代码理解变量和数据类型使用表达式和运算符创建实际项目以提高技能PHP开发入门:掌握PHP基础PHP是一种用途广泛、功能强大的脚本语言,用于创建动态且交互式Web应用程序。对于初学者来说,掌握PHP的基本知识至关重要。一、安装PHP在本地开发机器...
    编程 发布于2024-11-05
  • 缓冲区:Node.js
    缓冲区:Node.js
    Node.js 中缓冲区的简单指南 Node.js 中的 Buffer 用于处理原始二进制数据,这在处理流、文件或网络数据时非常有用。 如何创建缓冲区 来自字符串: const buf = Buffer.from('Hello'); 分配特定大小的Buffer...
    编程 发布于2024-11-05
  • 掌握 Node.js 中的版本管理
    掌握 Node.js 中的版本管理
    作为开发者,我们经常遇到需要不同 Node.js 版本的项目。对于可能不经常参与 Node.js 项目的新手和经验丰富的开发人员来说,这种情况都是一个陷阱:确保每个项目使用正确的 Node.js 版本。 在安装依赖项并运行项目之前,验证您的 Node.js 版本是否匹配或至少兼容项目的要求至关重要。...
    编程 发布于2024-11-05
  • 如何在 Go 二进制文件中嵌入 Git 修订信息以进行故障排除?
    如何在 Go 二进制文件中嵌入 Git 修订信息以进行故障排除?
    确定 Go 二进制文件中的 Git 修订版部署代码时,将二进制文件与构建它们的 git 修订版关联起来会很有帮助排除故障的目的。然而,直接使用修订号更新源代码是不可行的,因为它会改变源代码。解决方案:利用构建标志解决此挑战的方法包括利用构建标志。通过使用构建标志在主包中设置当前 git 修订版的版本...
    编程 发布于2024-11-05
  • 常见 HTML 标签:视角
    常见 HTML 标签:视角
    HTML(超文本标记语言)构成了 Web 开发的基础,是互联网上每个网页的结构。通过了解最常见的 HTML 标签及其高级用途,到 2024 年,开发人员可以创建更高效​​、更易于访问且更具视觉吸引力的网页。在这篇文章中,我们将探讨这些 HTML 标签及其最高级的用例,以帮助您提高 Web 开发技能。...
    编程 发布于2024-11-05
  • CSS 媒体查询
    CSS 媒体查询
    确保网站在各种设备上无缝运行比以往任何时候都更加重要。随着用户通过台式机、笔记本电脑、平板电脑和智能手机访问网站,响应式设计已成为必要。响应式设计的核心在于媒体查询,这是一项强大的 CSS 功能,允许开发人员根据用户设备的特征应用不同的样式。在本文中,我们将探讨什么是媒体查询、它们如何工作以及实现它...
    编程 发布于2024-11-05
  • 了解 JavaScript 中的提升:综合指南
    了解 JavaScript 中的提升:综合指南
    JavaScript 中的提升 提升是一种行为,其中变量和函数声明在之前被移动(或“提升”)到其包含范围(全局范围或函数范围)的顶部代码被执行。这意味着您可以在代码中实际声明变量和函数之前使用它们。 变量提升 变量 用 var 声明的变量被提升到其作...
    编程 发布于2024-11-05
  • 将 Stripe 集成到单一产品 Django Python 商店中
    将 Stripe 集成到单一产品 Django Python 商店中
    In the first part of this series, we created a Django online shop with htmx. In this second part, we'll handle orders using Stripe. What We'll...
    编程 发布于2024-11-05
  • 在 Laravel 中测试排队作业的技巧
    在 Laravel 中测试排队作业的技巧
    使用 Laravel 应用程序时,经常会遇到命令需要执行昂贵任务的情况。为了避免阻塞主进程,您可能决定将任务卸载到可以由队列处理的作业。 让我们看一个例子。想象一下命令 app:import-users 需要读取一个大的 CSV 文件并为每个条目创建一个用户。该命令可能如下所示: /* Import...
    编程 发布于2024-11-05
  • 如何创建人类水平的自然语言理解 (NLU) 系统
    如何创建人类水平的自然语言理解 (NLU) 系统
    Scope: Creating an NLU system that fully understands and processes human languages in a wide range of contexts, from conversations to literature. ...
    编程 发布于2024-11-05
  • 如何使用 JSTL 迭代 HashMap 中的 ArrayList?
    如何使用 JSTL 迭代 HashMap 中的 ArrayList?
    使用 JSTL 迭代 HashMap 中的 ArrayList在 Web 开发中,JSTL(JavaServer Pages 标准标记库)提供了一组标记来简化 JSP 中的常见任务( Java 服务器页面)。其中一项任务是迭代数据结构。要迭代 HashMap 及其中包含的 ArrayList,可以使...
    编程 发布于2024-11-05
  • Encore.ts — 比 ElysiaJS 和 Hono 更快
    Encore.ts — 比 ElysiaJS 和 Hono 更快
    几个月前,我们发布了 Encore.ts — TypeScript 的开源后端框架。 由于已经有很多框架,我们想分享我们做出的一些不常见的设计决策以及它们如何带来卓越的性能数据。 性能基准 我们之前发布的基准测试显示 Encore.ts 比 Express 快 9 倍,比 Fasti...
    编程 发布于2024-11-05
  • 为什么使用 + 对字符串文字进行字符串连接失败?
    为什么使用 + 对字符串文字进行字符串连接失败?
    连接字符串文字与字符串在 C 中,运算符可用于连接字符串和字符串文字。但是,此功能存在限制,可能会导致混乱。在问题中,作者尝试连接字符串文字“Hello”、“,world”和“!”以两种不同的方式。第一个例子:const string hello = "Hello"; const...
    编程 发布于2024-11-05
  • React 重新渲染:最佳性能的最佳实践
    React 重新渲染:最佳性能的最佳实践
    React高效的渲染机制是其受欢迎的关键原因之一。然而,随着应用程序复杂性的增加,管理组件重新渲染对于优化性能变得至关重要。让我们探索优化 React 渲染行为并避免不必要的重新渲染的最佳实践。 1. 使用 React.memo() 作为函数式组件 React.memo() 是一个高...
    编程 发布于2024-11-05
  • 如何实现条件列创建:探索 Pandas DataFrame 中的 If-Elif-Else?
    如何实现条件列创建:探索 Pandas DataFrame 中的 If-Elif-Else?
    Creating a Conditional Column: If-Elif-Else in Pandas给定的问题要求将新列添加到 DataFrame 中基于一系列条件标准。挑战在于在实现这些条件的同时保持代码效率和可读性。使用函数应用程序的解决方案一种方法涉及创建一个将每一行映射到所需结果的函数...
    编程 发布于2024-11-05

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3