专栏名称: 程序人生
十年漫漫程序人生,打过各种杂,也做过让我骄傲的软件;管理过数十人的团队,还带领一班兄弟姐妹创过业,目前在硅谷一家创业公司担任 VP。关注程序人生,了解程序猿,学做程序猿,做好程序猿,让我们的程序人生精彩满满。
目录
相关文章推荐
码农翻身  ·  今年后端这薪资是认真的吗? ·  11 小时前  
老刘说NLP  ·  RAG&KG&LLM&文档智能四大领域技术前 ... ·  昨天  
程序猿  ·  Python有史以来最强大的挑战者终于出现 ·  2 天前  
OSC开源社区  ·  Gitee ... ·  5 天前  
51好读  ›  专栏  ›  程序人生

透过 Rust 探索系统的本原:网络篇

程序人生  · 公众号  · 程序员  · 2021-03-29 08:05

正文

请到「今天看啥」查看全文


(图片来自 Explained: How does async work in Rust? [3])

如果你做过多核 CPU 下的(非 ASIC)网络设备相关的开发,会发现这个过程似曾相识。我觉得未来 Rust 会在高性能网络设备领域占据一席之地,这得益于其高效强大的易步处理库。

Rust 下主流的异步库有 Tokio 和 async-std。下图做了不错的总结,大家可以就着原文的讨论一起看:

(图片来自 reddit 讨论:Diagram of Async Architectures [4],有些旧,async-std 现在已经基于 smol 了,但整个讨论值得一读)

异步开发的好处是:尽管底层的处理相当复杂,各种实现但对开发者来说,接口非常简单,只需要很少的代价就可以把一个同步的处理变成异步的处理。

但是,我们要小心其中的一些陷阱:

  • 锁的使用。如果对 I/O 相关的数据结构加锁,需要使用支持异步的锁。比如你要对 TCP stream 的 writer 加锁(不建议这么做,下文会讲),那么要用异步锁。异步锁和同步锁的区别是,异步锁只是让异步任务状态变为 Poll::Pending ,不会锁线程,等锁 ready 后异步任务重新被唤醒;而同步锁会锁线程,导致性能问题甚至死锁。

  • 异步代码和高延时同步代码的混用。如果在两个异步操作之间有高延时同步代码,要么把同步代码放在前面或者后面执行,要么把同步代码异步化(分阶段 poll)。

对于后者,我这两天在做一个类似 Phoenix Channel[5] 的 Websocket 应用(以下简称 WS channel)时遇到了这个问题:我的 WebSocket 在 wss 连接时,每个连接要花大概 300-400ms,很奇怪。后来我用 jaeger 追踪了一下 tracing,发现客户端代码在连接时时间都耗在了 new_with_rustls_cert 上,真正花在 TLS handshake 上的时间只有 1.5ms:

由于我客户端做的操作是:

  1. TCP connect(异步)

  1. 准备 TLS connector(这里会做 new_with_rustls_cert ),这是同步操作,耗时 ~300ms

  1. TLS connect(异步)

这直接导致服务端 tls_accept 每个新建连接的延迟奇高(因为客户端 TCP 连上了迟迟不做 handshake):

解决办法:客户端准备 TLS connector 的步骤提前到第一步。之后,服务器的延迟正常了(~1ms):

这是个有趣的 bug。 new_with_rustls_cert 看上去是个人畜无害的纯内存操作,但因为里面有读取操作系统的受信证书的操作,所以延时高一些。其实它应该做成异步操作。

队列

在网络开发中,最快能提升性能的工具就是队列。虽然操作系统层面,已经使用了发送队列和接收队列来提升性能,在应用层面,我们最好也构建相应的队列,来让整个服务的处理变得更顺滑,更健壮,更高效。除此之外,队列还能帮助我们解耦,让应用本身的逻辑和 I/O 分离。







请到「今天看啥」查看全文