那些必须要了解的Serverless时代的并发神器-Rust语言Tokio框架基础

本文涉及的产品
函数计算FC,每月15万CU 3个月
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
简介: 今天我们继续高并发的话题,传统的云计算技术,本质上都是基于虚拟机的,云平台可以将一些性能强劲的物理服务器,拆分成若干个虚拟机,提供给用户使用,但在互联网发展到今天,虚拟机还是太重了。即使是飞天集群,新增部署虚拟机的时间也是以分钟来计的。但是对于互联网用户来讲20秒的等等就是就会千万50%以上的用户流失,不能忍受的煎熬,因此Docker秒级启动的速度也不是个完美的解决方案,最终还是要Serverless极速的伸缩才能满足客户需求。


今天我们继续高并发的话题,传统的云计算技术,本质上都是基于虚拟机的,云平台可以将一些性能强劲的物理服务器,拆分成若干个虚拟机,提供给用户使用,但在互联网发展到今天,虚拟机还是太重了。即使是飞天集群,新增部署虚拟机的时间也是以分钟来计的。但是对于互联网用户来讲20秒的等等就是就会千万50%以上的用户流失,不能忍受的煎熬,因此Docker秒级启动的速度也不是个完美的解决方案,最终还是要Serverless极速的伸缩才能满足客户需求。

通俗的讲,Serverless就是基建狂魔版的云平台,虽然传统的基建技术安全性更高,稳定性也更好,但是从头修路、盖房、装修成本太高时间也太长,而Serverless本质上是一个比容器还小的最小运行环境的镜像,只要给点阳光就能野灿烂,而且用完以后想拆也很方便,是应对云原生时代最新发展出的神器。

在我之前的博客中也不止一次提到,在Serverless时代,服务冷启动的速度与服务内存的消耗都是决定成败的关键。无GC更不依赖JVM的Rust无论在冷启速度还是在内存消耗上都比JAVA和GO更具优势,而且相比C语言Rust的生产效率也更高,很多储如从函数式语言借鉴而来的Future机制都非常先进,根据官方的测试结果,在性能方面Rust的网络编程框架比JAVA和GO要好得多

image.png

但是我意外的看到像Rust中Tokio这样优秀的高并发网络编程框架在中文技术社区却没有个完整的教程,因此笔者决定将这段时间探索Tokio的心得向大家分享一下,

初识Tokio

Tokio是基于Rust开发的异地网络编程框架,用于执行异步代码的多线程运行时。通过Future、async/await等机制,开发者可以让代码产生极高生产力的同时保持程序的性能基本与C语言一致,基于Tokio的开发在编写异步代码时,开发者不能使用Rust标准库提供的阻塞api,而必须使用由Tokio提供,镜像了Rust标准库的API。我们先来看一个Tokio的Helloworld程序

1.首先创建项目

cargo new my-tokio

命令创建一个my-tokio的项目

    1. 修改Cargo.toml

    vi Cargo.toml

    在依赖处添加以下内容

    [dependencies]
    tokio = { version = "1", features = ["full"] }

    image.gif

      1. 修改源代码

      vi src/main.rs

      并将代码替换为以下内容

      async fn say_word() {
          println!("my tokio");
      }
      #[tokio::main]
      async fn main() {
          let op = say_word();
          println!("hello");
          op.await;
      }

      image.gif

        1. 编译并执行

        cargo build

        cargo run

        结果如下:    

        Finished dev [unoptimized + debuginfo] target(s) in 0.02s
             Running `target/debug/my-tokio`
        hello
        my tokio

        image.gif

        这里我们先解释一下async和await的用法,我们看到async fn say_word()中,say_word()函数是被async关键词修饰的,那么也就是说这个函数在被调用时 let op = say_word();

        ,以上代码是被立即返回而没有被执行的,而这时op实际是一个Future,也就是一个现在为空,在未来才会产生的值(有关Future的机制我们接下来解释),而在调用op.await的时其实是在等到这个async异步操作执行完毕才返回,是一个阻塞操作,因此最终输出会是先打印hello,然后再打印my tokio

        程序 程序员如何理解更像自然语言的Future

           在以下这段代码中,网络连接socket、请求发送request、响应接收response三个对象全部都是future类型的,也就是在代码执行之后不会被执行也没有值仅有占位的意义,当未来执行后才会有值返回,and_then方法其实是在future对象执行成功后才会被调用的方法,比如read_to_end这行代码就是在request对象执行成功后,调用read_to_end方法对读取结果。

        use futures::Future;
        use tokio_core::reactor::Core;
        use tokio_core::net::TcpStream;fn main() {
            let mut core = Core::new().unwrap();
             let addr = "127.0.0.1:8080".to_socket_addrs().unwrap().next().unwrap();
             let socket = TcpStream::connect(&addr, &core.handle());
             let request = socket.and_then(|socket|{
                 tokio_core::io::write_all(socket, "Hello World".as_bytes())
             });
             let response = request.and_then(|(socket, _)| {
                 tokio_core::io::read_to_end(socket, Vec::new())
             });
             let (_, data) = core.run(response).unwrap();
             println!("{}", String::from_utf8_lossy(&data));
         }

        image.gif

        而想象一下如果是传统编程所采用的方式,需要在网络连接完成后调用请求发送的回调函数,然后再请求发送的响应处理方法中再注册接收请求的回调函数,复杂不说还容易出错。

        上面的代码就是建立Tcp连接,发送数据,最后取返回每个Future都是通过and_then建立关系,而future机制精髓之处在于,整个过程是通过core.run(response).unwrap();这行代码运行起来的,也就是说在Future的帮助下,程序员只需要关心最终的结果就可以了整个链条通过poll机制串联,从poll机制来看,这几个模块的传递机制如下:

        image.png


        从建立网络连接开始的调用链交给计算机去帮你完成,不但省去了回调所带来的复杂性,最终的效率反而还会更高。

        poll模到底是什么意思?

        笔者看到不少博主在介绍Rust的Future等异步编程框架时都提到了Rust的Future采用poll模式,不过到底什么是poll模式却大多语焉不详其实poll做的本质工作就是监测链条上前续Future的执行状态。

        image.png

        以上述情况为例,poll的方向是由response到request最后是socket,但是state和data的返回方向是完全返过来的,也就是说response通过poll来获取request的state,而request也同样通过poll来获取socket的state。

        笔者还是这样的观点,程序员群体之所以觉得future机制难以理解,其关键在于思维模式被计算机的各种回调机制给束缚住了,而忘记了最简单直接的方式。在解决这个问题之前我们先来问一个问题,假如让我们自己设计一个类似于tokio这样的异步Future管理器,应该如何入手?

        最直接也是最容易想到的方案就是事件循环,定期遍历整个事件队列,把状态是ready的事件通知给对应的处理程序,这也是我们常说的select方案;另外一种做法是在事件poll管理器中直接拿到处理程序的句柄,不再遍历整个事件队列,而是直接在中断处理响应中把通知发给对应处理进程,比如上述例子中实际是按照poll的链条传递的处理进程句柄的,这就是Poll模式。而基于poll设计的如tokio框架进行应用开发时,程序员根本不必关心整个消息传递,只需要用and_then、spawn等方法建立链条并让系统工作起来就可以了。

        而epoll(多路复用)是基于poll的另一种高并发机制,这种机制可以监视多个描述符,一旦某个描述符状态变为就绪,能够通知对应的handler进行后续操作。笔者在前文《这位创造了Github冠军项目的老男人,堪称10倍程序员本尊》中曾经介绍过Tdengine的定时器,其中就有这种多路复用的思想。由于操作系统timer的处理程序还不支持epoll的多路复用,因此每注册一个timer就必须要启动一个线程进行处理,资源浪费严重,因此Tdengine自己实现了一个多路复用的timer,可以做到一个线程同时处理多个timer,这些细节上的精巧设计也是Tdengine封神的原因之一。

        后记

        写到这突然发现tokio框架的介绍一篇文章根本就不可能完成,那么本文权当一个基础介绍,为入门tokio做准备,如果后面读者们再有强烈需求,我们再继续聊这个话题。

        相关实践学习
        【文生图】一键部署Stable Diffusion基于函数计算
        本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
        建立 Serverless 思维
        本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
        相关文章
        |
        1月前
        |
        Rust 安全 Java
        探索Rust语言的并发编程模型
        探索Rust语言的并发编程模型
        |
        1月前
        |
        Rust 安全 区块链
        探索Rust语言:系统编程的新选择
        【10月更文挑战第27天】Rust语言以其安全性、性能和并发性在系统编程领域受到广泛关注。本文介绍了Rust的核心特性,如内存安全、高性能和强大的并发模型,以及开发技巧和实用工具,展示了Rust如何改变系统编程的面貌,并展望了其在WebAssembly、区块链和嵌入式系统等领域的未来应用。
        |
        1月前
        |
        Rust 安全 Java
        编程语言新宠:Rust语言的特性、优势与实战入门
        【10月更文挑战第27天】Rust语言以其独特的特性和优势在编程领域迅速崛起。本文介绍Rust的核心特性,如所有权系统和强大的并发处理能力,以及其性能和安全性优势。通过实战示例,如“Hello, World!”和线程编程,帮助读者快速入门Rust。
        73 1
        |
        1月前
        |
        Rust 安全 编译器
        编程语言新宠:Rust语言的特性、优势与实战入门
        【10月更文挑战第26天】Rust语言诞生于2006年,由Mozilla公司的Graydon Hoare发起。作为一门系统编程语言,Rust专注于安全和高性能。通过所有权系统和生命周期管理,Rust在编译期就能消除内存泄漏等问题,适用于操作系统、嵌入式系统等高可靠性场景。
        97 2
        |
        1月前
        |
        Rust 安全
        深入理解Rust语言的所有权系统
        深入理解Rust语言的所有权系统
        36 0
        |
        1月前
        |
        Rust 安全 前端开发
        探索Rust语言的异步编程模型
        探索Rust语言的异步编程模型
        |
        1月前
        |
        Rust 自然语言处理 API
        |
        1月前
        |
        Rust 安全 云计算
        Rust语言入门:安全性与并发性的完美结合
        【10月更文挑战第25天】Rust 是一种系统级编程语言,以其独特的安全性和并发性保障而著称。它提供了与 C 和 C++ 相当的性能,同时确保内存安全,避免了常见的安全问题。Rust 的所有权系统通过编译时检查保证内存安全,其零成本抽象设计使得抽象不会带来额外的性能开销。Rust 还提供了强大的并发编程工具,如线程、消息传递和原子操作,确保了数据竞争的编译时检测。这些特性使 Rust 成为编写高效、安全并发代码的理想选择。
        31 0
        |
        2月前
        |
        Rust 安全 网络安全
        在 Rust 语言中,寻找企业上网行为管理软件的突破
        在数字化企业环境中,上网行为管理软件对于保障网络安全和提升工作效率至关重要。Rust 语言凭借其安全性、高性能和并发性,为开发此类软件提供了新机遇。本文通过几个 Rust 代码示例,展示了如何实现网址检查、访问频率统计及访问控制等功能,旨在探索 Rust 在企业上网行为管理中的应用潜力。
        43 0
        |
        4月前
        |
        Rust 安全 程序员
        Rust 语言的防错机制太惊人了!安全编码从此不再是难题,快来一探究竟!
        【8月更文挑战第31天】《安全编码原则:Rust 语言中的防错机制》探讨了代码安全的重要性,并详细介绍了Rust语言如何通过内存安全模型、所有权与借用规则等特性,在编译阶段检测并阻止潜在错误,如缓冲区溢出和悬空指针。文章还讨论了类型安全、边界检查等其他安全特性,并提出了遵循不可变引用、避免裸指针及充分测试等实用编码原则,以进一步提升代码质量和安全性。随着Rust在软件开发中的应用日益广泛,掌握其安全编码原则变得尤为重要。
        76 0

        相关产品

      1. 函数计算