Rust 最明显的优势是解决 CPU 密集型任务的速度和非常有效的内存处理。后者不需要任何垃圾收集器。
尽管这些功能很好,但它们也有一点缺点——它要求我们坚持非常严格的所有权模型。虽然,并不总是有人认为这是一个缺点,因为坚持所有权模型会产生非常稳定和可维护的代码。
但是,有时开发速度是项目中的一个重要因素。尤其是将应用程序分成几个部分并为每个部分使用最合适的语言时尤其理想。实现这个概念的一种解决方案是使用微服务架构,而这正是我们将在这个故事中看到的。
理解本文内容的先决条件是对 Rust 和 TypeScript 有基本的了解。两者都可以从这里获得。在这里。
总体目标
为了有一个简单的例子,我们将一共实现三个微服务:
main-server
:这提供了一个公共 API 并托管了一个基于 Vue 的小型客户端。语言是 TypeScript,我们将使用非常流行的框架NestJS。calc-engine
:这是一个 Rust 服务器,它提供了执行一些 CPU 密集型计算的方法。rabbitmq
:这被视为上述服务之间的消息代理,并由RabbitMQ支持。
最后,所有这些都将部署在一个 docker-compose 中,以使其易于共享。
在下文中,我将只对实现的最重要部分进行描述,以提供尽可能愉快的阅读体验。整个代码可以在这个仅用于教育目的的存储库中查看。
计算引擎 (Rust)
这部分是一个典型的基于 cargo 的二进制应用。依赖关系如下:
amiquip = { version = "0.4.2", default-features = false } serde_json = { version = "1.0.81" } rayon = { version = "1.5.3" } num_cpus = { version = "1.13.1" } dotenv = { version = "0.15.0" }
重要的是amiquip
启用与 RabbitMQ 的通信。板条箱rayon
用于使应用程序使用尽可能多的并行计算。特别是,我们正在建立一个线程池,如下所示:
fn setup_pool() -> rayon::ThreadPool { rayon::ThreadPoolBuilder::new() .num_threads(num_cpus::get() ) .build( ) .unwrap() }
对于 CPU 密集型任务,并行运行的 CPU 数通常不超过可用 CPU 数。
与 RabbitMQ 的连接是这样获得的:
fn setup_connection() -> Connection { if let Ok(c) = Connection::insecure_open(&format!( "amqp://{}:{}@{}:{}", env::var("RABBITMQ_USER") .unwrap(), env::var("RABBITMQ_PWD"). unwrap(), env::var("RABBITMQ_HOST"). unwrap(), env::var("RABBITMQ_PORT").unwrap() )) { println !("已连接到 rabbitmq!"); c } else { println!("未能连接到 rabbitmq。将在 2s 后重试。"); std::thread::sleep(std::time::Duration::from_secs(2)); setup_connection() } }
除了采用从文件中获取某些值的传统方法外.env
,我们还通过重试每个2s
. 这种方法特别适合微服务设置,因为每个涉及的服务都应该能够从其他服务的临时故障中恢复。
作为我们将要使用的服务之间的通信方式RPC
(远程过程调用)。这并不总是最合适的,但在这里它足以达到目的。我们的“繁重”CPU 任务将是计算Tower of Hanoi的求解步骤。这将解释下面的命名。
我们在这里做的第一件事是确保命名的队列hanoi
存在于 RabbitMQ 上。这是通过channel
在 the 上打开一个connection
然后通过声明queue
.
这queue
用作跨服务的消息生产/消费。queue
我们可以通过调用来收听被放到 上的消息queue.receiver()
。这类似于监听 Rust 的内部通道并确保块内的代码正在逐条消息地运行。
每个新的都message
必须与其类型匹配,原则上是ConsumerMessage::Delivery
我们在other
. 除了body
消息之外,还有另外两个字段,即reply_to
和correlation_id
。body
代表实际内容(这里是 Hanoi 游戏中的元素数量),一个reply_to
用于发回结果并correlation_id
用于将请求与响应匹配的“独占”队列。
因此,客户端向队列发送消息时hanoi
,会附加一个唯一correlation_id
值,以便稍后选择服务器写入reply_to
队列的结果。为了尽可能多地并行处理 CPU 任务,我们只是将当前请求放入线程池的闭包中。reply_to
每个这样的闭包都包含通过队列发回结果的逻辑。
发送是通过使用通道的实例完成Exchange::direct
的。仅通过重新尝试建立此侦听器构造每个. 同样,这使我们的服务免受连接失败或 RabbitMQ 重启的影响。channel_for_msg
other
2s
主服务器(TypeScript)
首先,主服务器提供了一个公共 API,让客户端请求 Hanoi 游戏的解决步骤:
@Get ('/hanoi') getHello( @Query ('n', ParseIntPipe) n: number): Promise<string> { ... return this.appService.makeHanoi(n); }
如果您从未接触过 NestJS,没问题,它是最简单易学的 Web 框架,在这个易于阅读的概述中介绍了其主要概念。
如您所见,端点正在委托一个makeHanoi
最终在 method 处结束的方法
本站声明:
1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/294478.html